agent-relay-server 0.119.6 → 0.119.9
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 +3 -3
- package/public/assets/{MessageBubble-Azda2GIf.js → MessageBubble-U5sX3SNZ.js} +2 -2
- package/public/assets/{MessageBubble-Azda2GIf.js.map → MessageBubble-U5sX3SNZ.js.map} +1 -1
- package/public/assets/PlanGraphPanel-Bi2gvXeE.js +2 -0
- package/public/assets/PlanGraphPanel-Bi2gvXeE.js.map +1 -0
- package/public/assets/{activity-B0hz4XPy.js → activity-Cwkz51pG.js} +2 -2
- package/public/assets/{activity-B0hz4XPy.js.map → activity-Cwkz51pG.js.map} +1 -1
- package/public/assets/{agent-profiles-BF0oSOTq.js → agent-profiles-BgddVqRp.js} +2 -2
- package/public/assets/{agent-profiles-BF0oSOTq.js.map → agent-profiles-BgddVqRp.js.map} +1 -1
- package/public/assets/{agents-CW4--j3Q.js → agents-CiGYBw0h.js} +2 -2
- package/public/assets/{agents-CW4--j3Q.js.map → agents-CiGYBw0h.js.map} +1 -1
- package/public/assets/{automation-S6_bQ2t-.js → automation-BBJB4RaE.js} +2 -2
- package/public/assets/{automation-S6_bQ2t-.js.map → automation-BBJB4RaE.js.map} +1 -1
- package/public/assets/{chat-hoFhhvgy.js → chat-DS578gZy.js} +2 -2
- package/public/assets/{chat-hoFhhvgy.js.map → chat-DS578gZy.js.map} +1 -1
- package/public/assets/{formatted-body-BHm145gP.js → formatted-body-DYUVqWym.js} +3 -3
- package/public/assets/{formatted-body-BHm145gP.js.map → formatted-body-DYUVqWym.js.map} +1 -1
- package/public/assets/{formatted-body-impl-CvE31j9F.js → formatted-body-impl-DdcyxVO0.js} +2 -2
- package/public/assets/{formatted-body-impl-CvE31j9F.js.map → formatted-body-impl-DdcyxVO0.js.map} +1 -1
- package/public/assets/{index-TamtghSr.js → index-CqcAqQKW.js} +6 -6
- package/public/assets/{index-TamtghSr.js.map → index-CqcAqQKW.js.map} +1 -1
- package/public/assets/{insights-Ci9ftVfl.js → insights-BunHbsTA.js} +2 -2
- package/public/assets/{insights-Ci9ftVfl.js.map → insights-BunHbsTA.js.map} +1 -1
- package/public/assets/{maintenance-bVhNbr33.js → maintenance-cICBVcpJ.js} +2 -2
- package/public/assets/{maintenance-bVhNbr33.js.map → maintenance-cICBVcpJ.js.map} +1 -1
- package/public/assets/{managed-agents-D5HO4dhI.js → managed-agents-BK9_Gi-M.js} +2 -2
- package/public/assets/{managed-agents-D5HO4dhI.js.map → managed-agents-BK9_Gi-M.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DO-p83dz.js → markdown-preview-impl-EZfcTHG9.js} +2 -2
- package/public/assets/{markdown-preview-impl-DO-p83dz.js.map → markdown-preview-impl-EZfcTHG9.js.map} +1 -1
- package/public/assets/{memory-D72o8umQ.js → memory-Vi-8HvK3.js} +2 -2
- package/public/assets/{memory-D72o8umQ.js.map → memory-Vi-8HvK3.js.map} +1 -1
- package/public/assets/{messages-5L_Bc_9w.js → messages-DScIiBMI.js} +2 -2
- package/public/assets/{messages-5L_Bc_9w.js.map → messages-DScIiBMI.js.map} +1 -1
- package/public/assets/{orchestrators-CnfgkQpl.js → orchestrators-DLogldXx.js} +2 -2
- package/public/assets/{orchestrators-CnfgkQpl.js.map → orchestrators-DLogldXx.js.map} +1 -1
- package/public/assets/{overview-Bu39NIyR.js → overview-B_RQAQR8.js} +2 -2
- package/public/assets/{overview-Bu39NIyR.js.map → overview-B_RQAQR8.js.map} +1 -1
- package/public/assets/{pairs-WuamP5W5.js → pairs-CSiPQ7E2.js} +2 -2
- package/public/assets/{pairs-WuamP5W5.js.map → pairs-CSiPQ7E2.js.map} +1 -1
- package/public/assets/{prompt-settings-BCrQreU6.js → prompt-settings-DsDi_Xpd.js} +2 -2
- package/public/assets/{prompt-settings-BCrQreU6.js.map → prompt-settings-DsDi_Xpd.js.map} +1 -1
- package/public/assets/{registry-kHKn0xwg.js → registry-DHFUWIdb.js} +2 -2
- package/public/assets/{registry-kHKn0xwg.js.map → registry-DHFUWIdb.js.map} +1 -1
- package/public/assets/{security-Dk5t3r_K.js → security-D2nHkJH6.js} +2 -2
- package/public/assets/{security-Dk5t3r_K.js.map → security-D2nHkJH6.js.map} +1 -1
- package/public/assets/{select-BdsZQfU2.js → select-BjNcommA.js} +2 -2
- package/public/assets/{select-BdsZQfU2.js.map → select-BjNcommA.js.map} +1 -1
- package/public/assets/{settings-EpjzQHnZ.js → settings-DiUEq54n.js} +2 -2
- package/public/assets/{settings-EpjzQHnZ.js.map → settings-DiUEq54n.js.map} +1 -1
- package/public/assets/{tasks-CedMQHbg.js → tasks-Dx8GVQiR.js} +2 -2
- package/public/assets/{tasks-CedMQHbg.js.map → tasks-Dx8GVQiR.js.map} +1 -1
- package/public/assets/{teams-BlHBo5Qt.js → teams-DQtf7bha.js} +2 -2
- package/public/assets/{teams-BlHBo5Qt.js.map → teams-DQtf7bha.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-gGqYpsjq.js → terminal-viewer-impl-D5rhXV4u.js} +2 -2
- package/public/assets/{terminal-viewer-impl-gGqYpsjq.js.map → terminal-viewer-impl-D5rhXV4u.js.map} +1 -1
- package/public/assets/types-uVOXgvMT.js.map +1 -1
- package/public/assets/{work-queue-D7opMqtU.js → work-queue-BfWNDp7U.js} +2 -2
- package/public/assets/{work-queue-D7opMqtU.js.map → work-queue-BfWNDp7U.js.map} +1 -1
- package/public/index.html +2 -2
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/commands-db.ts +72 -20
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +7 -0
- package/src/db/orchestrators.ts +1 -1
- package/src/db/provider-quotas.ts +2 -34
- package/src/db/schema.ts +2 -0
- package/src/db/work-evidence.ts +38 -0
- package/src/db/workspaces.ts +48 -3
- package/src/lifecycle-manager.ts +2 -4
- package/src/maintenance/jobs.ts +10 -1
- package/src/maintenance/workspaces.ts +71 -13
- package/src/routes/commands.ts +20 -4
- package/src/routes/workspaces.ts +6 -11
- package/src/services/shutdown-agent.ts +6 -1
- package/src/services/spawn-agent.ts +50 -7
- package/src/services/transient-agent-reaper.ts +82 -23
- package/src/workspace-actions.ts +26 -16
- package/src/workspace-auto-merge.ts +95 -13
- package/src/workspace-host.ts +55 -0
- package/src/workspace-merge.ts +3 -4
- package/src/workspace-orphans.ts +78 -55
- package/src/workspace-probe.ts +2 -3
- package/public/assets/PlanGraphPanel-B9TN_KQr.js +0 -2
- package/public/assets/PlanGraphPanel-B9TN_KQr.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{An as e,a as t,ht as n,nr as r,vn as i}from"./lucide-react-Dc3ZwCBi.js";import{f as a,t as o}from"./store-OLe8gFh0.js";import{K as s,ct as c,d as l}from"./display-exQDWum3.js";import{t as u}from"./badge-BhUZ1cAb.js";import{t as d}from"./button-C-V4oij1.js";import{Ot as f,St as p,U as m}from"./index-
|
|
2
|
-
//# sourceMappingURL=work-queue-
|
|
1
|
+
import{An as e,a as t,ht as n,nr as r,vn as i}from"./lucide-react-Dc3ZwCBi.js";import{f as a,t as o}from"./store-OLe8gFh0.js";import{K as s,ct as c,d as l}from"./display-exQDWum3.js";import{t as u}from"./badge-BhUZ1cAb.js";import{t as d}from"./button-C-V4oij1.js";import{Ot as f,St as p,U as m}from"./index-CqcAqQKW.js";import{r as h,t as g}from"./card-Br1JLjTH.js";var _=r(),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),c=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=p(),S=f(),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&&c(e.message.id)}function T(e){g(`Cancel Item`,`Cancel "${e.title||(e.sourceType===`task`?`T${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:l(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]?l(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:n,now:r,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[n.severity]||c.info,m=v[n.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:n.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:n.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:n.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:n.status}),n.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`})]}),n.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:n.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[n.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(i,{className:`w-3 h-3 inline mr-1`}),a(n.owner)]}),n.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:n.source}),(0,_.jsxs)(`span`,{title:String(n.updatedAt),children:[(0,_.jsx)(e,{className:`w-3 h-3 inline mr-1`}),s(r,n.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[n.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:l,children:[(0,_.jsx)(t,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
|
|
2
|
+
//# sourceMappingURL=work-queue-BfWNDp7U.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-D7opMqtU.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' ? `T${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-BfWNDp7U.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' ? `T${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-CqcAqQKW.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-C_JMBXql.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/lucide-react-Dc3ZwCBi.js">
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<link rel="modulepreload" crossorigin href="./assets/agent-type-icon-BEz393ra.js">
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/button-C-V4oij1.js">
|
|
38
38
|
<link rel="modulepreload" crossorigin href="./assets/copy-button-BStzXgtd.js">
|
|
39
|
-
<link rel="modulepreload" crossorigin href="./assets/formatted-body-
|
|
39
|
+
<link rel="modulepreload" crossorigin href="./assets/formatted-body-DYUVqWym.js">
|
|
40
40
|
<link rel="modulepreload" crossorigin href="./assets/task-chip-kxk4eNm1.js">
|
|
41
41
|
<link rel="modulepreload" crossorigin href="./assets/badge-BhUZ1cAb.js">
|
|
42
42
|
<link rel="modulepreload" crossorigin href="./assets/input-CtLT7WXi.js">
|
package/src/commands-db.ts
CHANGED
|
@@ -26,19 +26,25 @@ interface CommandFilters {
|
|
|
26
26
|
limit?: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const CLAIMED_ACTIVE_STATUSES: CommandStatus[] = ["accepted", "running"];
|
|
30
|
+
const SPAWN_IN_FLIGHT_STATUSES: CommandStatus[] = ["pending", "accepted", "running"];
|
|
30
31
|
const TERMINAL_STATUSES: CommandStatus[] = ["succeeded", "failed", "canceled", "timed_out"];
|
|
31
32
|
|
|
32
|
-
// #930 —
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// the spawn, not from enqueue. Scoped to `agent.spawn`; every other command type
|
|
39
|
-
// keeps create-time TTL semantics.
|
|
33
|
+
// #930/#953 — command TTLs are execution leases, not delivery deadlines. The #880
|
|
34
|
+
// spawn cap and long inline orchestrator commands both introduce legitimate
|
|
35
|
+
// queuing before dispatch, so pending commands must not be swept before the host
|
|
36
|
+
// can claim them. Refresh expiring command types at dispatch-intent (claim →
|
|
37
|
+
// accepted, or slot-acquire → running) so the window measures from when the host
|
|
38
|
+
// committed to doing the work, not from enqueue.
|
|
40
39
|
const SPAWN_TTL_MS = 180_000;
|
|
41
|
-
const
|
|
40
|
+
const LIFECYCLE_TTL_MS = 30_000;
|
|
41
|
+
const DISPATCH_STATUSES: CommandStatus[] = ["accepted", "running"];
|
|
42
|
+
|
|
43
|
+
function commandTtlMs(type: string): number | undefined {
|
|
44
|
+
if (type === "agent.spawn") return SPAWN_TTL_MS;
|
|
45
|
+
if (type === "agent.shutdown" || type === "agent.restart" || type === "agent.reconnect" || type === "agent.kill" || type === "agent.compact" || type === "agent.clearContext" || type === "agent.injectContext") return LIFECYCLE_TTL_MS;
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
function isTerminalStatus(status: CommandStatus): boolean {
|
|
44
50
|
return TERMINAL_STATUSES.includes(status);
|
|
@@ -123,6 +129,51 @@ export function listCommands(filters: CommandFilters = {}): Command[] {
|
|
|
123
129
|
return rows.map(rowToCommand);
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
export function findSpawnCommandByRequestId(spawnRequestId: string, statuses?: CommandStatus[]): Command | null {
|
|
133
|
+
const cleaned = spawnRequestId.trim();
|
|
134
|
+
if (!cleaned) return null;
|
|
135
|
+
const statusClause = statuses?.length
|
|
136
|
+
? `AND status IN (${statuses.map(() => "?").join(",")})`
|
|
137
|
+
: "";
|
|
138
|
+
const rows = getDb()
|
|
139
|
+
.query(`
|
|
140
|
+
SELECT * FROM commands
|
|
141
|
+
WHERE type = 'agent.spawn'
|
|
142
|
+
${statusClause}
|
|
143
|
+
AND (correlation_id = ? OR json_extract(params, '$.spawnRequestId') = ?)
|
|
144
|
+
ORDER BY created_at DESC
|
|
145
|
+
LIMIT 1
|
|
146
|
+
`)
|
|
147
|
+
.all(...(statuses ?? []), cleaned, cleaned) as CommandRow[];
|
|
148
|
+
return rows[0] ? rowToCommand(rows[0]) : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function countInFlightSpawnCommands(parentId: string): number {
|
|
152
|
+
const row = getDb()
|
|
153
|
+
.query(
|
|
154
|
+
`SELECT count(*) AS n
|
|
155
|
+
FROM commands
|
|
156
|
+
WHERE type = 'agent.spawn'
|
|
157
|
+
AND status IN (${SPAWN_IN_FLIGHT_STATUSES.map(() => "?").join(",")})
|
|
158
|
+
AND json_extract(params, '$.spawnedBy') = ?`,
|
|
159
|
+
)
|
|
160
|
+
.get(...SPAWN_IN_FLIGHT_STATUSES, parentId) as { n: number };
|
|
161
|
+
return row.n;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createSpawnCommandWithinQuota(
|
|
165
|
+
input: CreateCommandInput,
|
|
166
|
+
quota: { parentId: string; maxSpawnedAgents: number; liveChildren: () => number },
|
|
167
|
+
): { command: Command | null; occupiedSlots: number } {
|
|
168
|
+
return getDb().transaction(() => {
|
|
169
|
+
const live = quota.liveChildren();
|
|
170
|
+
const inFlight = countInFlightSpawnCommands(quota.parentId);
|
|
171
|
+
const occupied = live + inFlight;
|
|
172
|
+
if (occupied >= quota.maxSpawnedAgents) return { command: null, occupiedSlots: occupied };
|
|
173
|
+
return { command: createCommand(input), occupiedSlots: occupied + 1 };
|
|
174
|
+
})();
|
|
175
|
+
}
|
|
176
|
+
|
|
126
177
|
export function updateCommand(id: string, input: UpdateCommandInput): Command | null {
|
|
127
178
|
const existing = getCommand(id);
|
|
128
179
|
if (!existing) return null;
|
|
@@ -145,12 +196,14 @@ export function updateCommand(id: string, input: UpdateCommandInput): Command |
|
|
|
145
196
|
return existing;
|
|
146
197
|
}
|
|
147
198
|
const now = Date.now();
|
|
148
|
-
// #930 — refresh the
|
|
149
|
-
//
|
|
150
|
-
//
|
|
199
|
+
// #930/#953 — refresh the lease from dispatch-intent when an expiring command is
|
|
200
|
+
// claimed or starts running. Pending commands are not swept before delivery, so a
|
|
201
|
+
// busy orchestrator must not inherit an already-expired lease when it finally gets
|
|
202
|
+
// back to the command loop.
|
|
203
|
+
const ttlMs = input.status && DISPATCH_STATUSES.includes(input.status) ? commandTtlMs(existing.type) : undefined;
|
|
151
204
|
const nextExpiresAt =
|
|
152
|
-
|
|
153
|
-
? now +
|
|
205
|
+
ttlMs !== undefined
|
|
206
|
+
? now + ttlMs
|
|
154
207
|
: existing.expiresAt ?? null;
|
|
155
208
|
getDb().query(`
|
|
156
209
|
UPDATE commands
|
|
@@ -179,16 +232,15 @@ export function deleteCommand(id: string): boolean {
|
|
|
179
232
|
|
|
180
233
|
export function expireCommands(now: number = Date.now()): Command[] {
|
|
181
234
|
const rows = getDb()
|
|
182
|
-
.query(`SELECT * FROM commands WHERE expires_at IS NOT NULL AND expires_at <= ? AND status IN (${
|
|
183
|
-
.all(now, ...
|
|
235
|
+
.query(`SELECT * FROM commands WHERE expires_at IS NOT NULL AND expires_at <= ? AND status IN (${CLAIMED_ACTIVE_STATUSES.map(() => "?").join(",")})`)
|
|
236
|
+
.all(now, ...CLAIMED_ACTIVE_STATUSES) as CommandRow[];
|
|
184
237
|
for (const row of rows) updateCommand(row.id, { status: "timed_out", error: "command timed out" });
|
|
185
238
|
return rows.map((row) => getCommand(row.id)).filter((command): command is Command => Boolean(command));
|
|
186
239
|
}
|
|
187
240
|
|
|
188
241
|
function defaultExpiresAt(type: string, now: number): number | undefined {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return undefined;
|
|
242
|
+
const ttlMs = commandTtlMs(type);
|
|
243
|
+
return ttlMs === undefined ? undefined : now + ttlMs;
|
|
192
244
|
}
|
|
193
245
|
|
|
194
246
|
function recordShutdownTerminalEventOnSuccess(previousStatus: CommandStatus, command: Command): void {
|
package/src/db/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./integrations.ts";
|
|
|
16
16
|
export * from "./pairs.ts";
|
|
17
17
|
export * from "./artifacts.ts";
|
|
18
18
|
export * from "./messages.ts";
|
|
19
|
+
export * from "./work-evidence.ts";
|
|
19
20
|
export * from "./delivery.ts";
|
|
20
21
|
export * from "./message-reads.ts";
|
|
21
22
|
export * from "./inbox.ts";
|
package/src/db/migrations.ts
CHANGED
|
@@ -218,6 +218,13 @@ export function applyMigrations(): void {
|
|
|
218
218
|
`);
|
|
219
219
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_workspace_merge_pauses_expires ON workspace_merge_pauses(expires_at)");
|
|
220
220
|
|
|
221
|
+
const workspaceCols = getDb().query("PRAGMA table_info(workspaces)").all() as any[];
|
|
222
|
+
const workspaceColNames = workspaceCols.map((c: any) => c.name);
|
|
223
|
+
if (!workspaceColNames.includes("orchestrator_id")) {
|
|
224
|
+
getDb().run("ALTER TABLE workspaces ADD COLUMN orchestrator_id TEXT");
|
|
225
|
+
}
|
|
226
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_workspaces_orchestrator ON workspaces(orchestrator_id)");
|
|
227
|
+
|
|
221
228
|
const cols = getDb().query("PRAGMA table_info(messages)").all() as any[];
|
|
222
229
|
const colNames = cols.map((c: any) => c.name);
|
|
223
230
|
if (!colNames.includes("thread_id")) {
|
package/src/db/orchestrators.ts
CHANGED
|
@@ -309,7 +309,7 @@ export function orchestratorHeartbeat(id: string, runtime: OrchestratorRuntimeIn
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
export function updateManagedAgents(id: string, agents: ManagedAgent[]): Orchestrator | null {
|
|
312
|
-
for (const agent of agents) upsertWorkspaceFromManagedAgent(agent);
|
|
312
|
+
for (const agent of agents) upsertWorkspaceFromManagedAgent(agent, id);
|
|
313
313
|
electWorkspaceStewards();
|
|
314
314
|
const enriched = agents.map((agent) => {
|
|
315
315
|
const workspaceId = agent.workspace?.id;
|
|
@@ -8,9 +8,7 @@ import { parseStringArray } from "./shared.ts";
|
|
|
8
8
|
import type { AgentCard, CodexResetCredits, ProviderQuotaError, ProviderQuotaMatrix, ProviderQuotaRecord, ProviderQuotaSnapshot, ProviderQuotaUpdateInput, QuotaState } from "../types";
|
|
9
9
|
|
|
10
10
|
const PROVIDER_QUOTA_STALE_AFTER_MS = 10 * 60_000;
|
|
11
|
-
const QUOTA_UTILIZATION_EPSILON = 1e-9;
|
|
12
11
|
const QUOTA_RESET_BOUNDARY_ADVANCE_MS = 5_000;
|
|
13
|
-
const QUOTA_RESET_UTILIZATION_MAX = 0.01;
|
|
14
12
|
const FIVE_HOUR_MS = 5 * 60 * 60_000;
|
|
15
13
|
|
|
16
14
|
registerAccountKeyAliases("codex", (accountKey) => {
|
|
@@ -250,30 +248,6 @@ function quotaWindowKey(window: QuotaState["windows"][number]): string {
|
|
|
250
248
|
return `${window.name}:${window.unit}`;
|
|
251
249
|
}
|
|
252
250
|
|
|
253
|
-
function quotaResetBoundaryAdvanced(existing: QuotaState["windows"][number], incoming: QuotaState["windows"][number]): boolean {
|
|
254
|
-
return quotaResetBoundaryMoved(existing, incoming)
|
|
255
|
-
&& incoming.resetsAt! - existing.resetsAt! > QUOTA_RESET_BOUNDARY_ADVANCE_MS;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function quotaResetBoundaryMoved(existing: QuotaState["windows"][number], incoming: QuotaState["windows"][number]): boolean {
|
|
259
|
-
return typeof existing.resetsAt === "number"
|
|
260
|
-
&& Number.isFinite(existing.resetsAt)
|
|
261
|
-
&& typeof incoming.resetsAt === "number"
|
|
262
|
-
&& Number.isFinite(incoming.resetsAt)
|
|
263
|
-
&& incoming.resetsAt > existing.resetsAt;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function quotaWindowLooksReset(existing: QuotaState["windows"][number], incoming: QuotaState["windows"][number]): boolean {
|
|
267
|
-
return incoming.utilization <= QUOTA_RESET_UTILIZATION_MAX
|
|
268
|
-
&& quotaResetBoundaryMoved(existing, incoming);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function quotaWindowUtilizationRegressed(existing: QuotaState["windows"][number], incoming: QuotaState["windows"][number]): boolean {
|
|
272
|
-
return incoming.utilization + QUOTA_UTILIZATION_EPSILON < existing.utilization
|
|
273
|
-
&& !quotaResetBoundaryAdvanced(existing, incoming)
|
|
274
|
-
&& !quotaWindowLooksReset(existing, incoming);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
251
|
function publicQuotaState(quota: StoredQuotaState, now?: number): QuotaState {
|
|
278
252
|
let emulated = false;
|
|
279
253
|
const windows = quota.windows.map((window) => {
|
|
@@ -404,8 +378,7 @@ function mergeQuotaWindows(existing: StoredQuotaState | undefined, incoming: Quo
|
|
|
404
378
|
// Per-window source precedence: #712/#716 are handled by the symmetric three-way split below.
|
|
405
379
|
// Higher existing → keep (no regression check needed; lower-priority input simply loses).
|
|
406
380
|
// Higher incoming → accept (no regression check needed; higher-priority source is authoritative).
|
|
407
|
-
// Same priority →
|
|
408
|
-
// same-quality sources, e.g. a wedged probe self-report; #725 §4.3).
|
|
381
|
+
// Same priority → newest per-window sample wins; older/equal samples are stale.
|
|
409
382
|
const existingPriority = quotaSourcePriority(existingSource);
|
|
410
383
|
const incomingPriority = quotaSourcePriority(incoming.source);
|
|
411
384
|
if (existingPriority > incomingPriority) {
|
|
@@ -418,12 +391,7 @@ function mergeQuotaWindows(existing: StoredQuotaState | undefined, incoming: Quo
|
|
|
418
391
|
mergedTimes.set(key, incoming.updatedAt); mergedSources.set(key, incoming.source);
|
|
419
392
|
return incomingWindow;
|
|
420
393
|
}
|
|
421
|
-
|
|
422
|
-
// self-reports, but let authoritative API ingest correct a stale/maxed WHAM sample (#750).
|
|
423
|
-
if (incoming.updatedAt < existingUpdatedAt) {
|
|
424
|
-
mergedTimes.set(key, existingUpdatedAt); mergedSources.set(key, existingSource); return window;
|
|
425
|
-
}
|
|
426
|
-
if (incoming.source !== "ingest" && quotaWindowUtilizationRegressed(window, incomingWindow)) {
|
|
394
|
+
if (incoming.updatedAt <= existingUpdatedAt) {
|
|
427
395
|
mergedTimes.set(key, existingUpdatedAt); mergedSources.set(key, existingSource); return window;
|
|
428
396
|
}
|
|
429
397
|
acceptedIncomingWindow = true;
|
package/src/db/schema.ts
CHANGED
|
@@ -585,6 +585,7 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
585
585
|
mode TEXT NOT NULL,
|
|
586
586
|
requested_mode TEXT,
|
|
587
587
|
status TEXT NOT NULL DEFAULT 'active',
|
|
588
|
+
orchestrator_id TEXT,
|
|
588
589
|
owner_agent_id TEXT,
|
|
589
590
|
owner_policy_name TEXT,
|
|
590
591
|
owner_automation_run_id TEXT,
|
|
@@ -596,6 +597,7 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
596
597
|
cleaned_at INTEGER
|
|
597
598
|
);
|
|
598
599
|
CREATE INDEX IF NOT EXISTS idx_workspaces_repo_status ON workspaces(repo_root, status);
|
|
600
|
+
CREATE INDEX IF NOT EXISTS idx_workspaces_orchestrator ON workspaces(orchestrator_id);
|
|
599
601
|
CREATE INDEX IF NOT EXISTS idx_workspaces_owner_agent ON workspaces(owner_agent_id);
|
|
600
602
|
CREATE INDEX IF NOT EXISTS idx_workspaces_policy ON workspaces(owner_policy_name);
|
|
601
603
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getDb } from "./connection.ts";
|
|
2
|
+
import { MSG_SELECT, rowToMessage } from "./mappers.ts";
|
|
3
|
+
import type { Message } from "../types";
|
|
4
|
+
|
|
5
|
+
export function hasArtifactCreatedBy(createdBy: string, since: number = 0): boolean {
|
|
6
|
+
const row = getDb().query(`
|
|
7
|
+
SELECT 1
|
|
8
|
+
FROM artifacts a
|
|
9
|
+
JOIN artifact_blobs b ON b.digest = a.blob_digest
|
|
10
|
+
WHERE a.created_by = ?
|
|
11
|
+
AND a.created_at >= ?
|
|
12
|
+
AND a.size > 0
|
|
13
|
+
AND b.size > 0
|
|
14
|
+
AND EXISTS (
|
|
15
|
+
SELECT 1
|
|
16
|
+
FROM artifact_links l
|
|
17
|
+
WHERE l.artifact_id = a.id
|
|
18
|
+
AND l.entity_type IN ('message', 'task')
|
|
19
|
+
)
|
|
20
|
+
LIMIT 1
|
|
21
|
+
`).get(createdBy, since);
|
|
22
|
+
return Boolean(row);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function listFinalResponseCaptureMessages(from: string, since: number = 0, limit: number = 20): Message[] {
|
|
26
|
+
const safeLimit = Math.min(Math.max(Math.floor(limit), 1), 100);
|
|
27
|
+
const rows = getDb().query(`
|
|
28
|
+
${MSG_SELECT}
|
|
29
|
+
WHERE m.from_agent = ?
|
|
30
|
+
AND m.kind = 'chat'
|
|
31
|
+
AND m.created_at >= ?
|
|
32
|
+
AND json_extract(m.payload, '$.responseCapture.type') = 'response'
|
|
33
|
+
AND json_extract(m.payload, '$.responseCapture.final') = 1
|
|
34
|
+
ORDER BY m.id DESC
|
|
35
|
+
LIMIT ?
|
|
36
|
+
`).all(from, since, safeLimit) as any[];
|
|
37
|
+
return rows.map(rowToMessage);
|
|
38
|
+
}
|
package/src/db/workspaces.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_ME
|
|
|
16
16
|
import { matchAgents } from "../agent-ref";
|
|
17
17
|
import { getDb } from "./connection.ts";
|
|
18
18
|
import { resolveProjectsForCwds } from "./projects.ts";
|
|
19
|
+
import { workspaceActiveClaim } from "../workspace-claim";
|
|
19
20
|
import type {
|
|
20
21
|
AgentCard,
|
|
21
22
|
ActivityEvent,
|
|
@@ -96,6 +97,7 @@ function rowToWorkspace(row: any): WorkspaceRecord {
|
|
|
96
97
|
mode: row.mode,
|
|
97
98
|
requestedMode: row.requested_mode ?? undefined,
|
|
98
99
|
status: row.status,
|
|
100
|
+
orchestratorId: row.orchestrator_id ?? undefined,
|
|
99
101
|
ownerAgentId: row.owner_agent_id ?? undefined,
|
|
100
102
|
ownerPolicyName: row.owner_policy_name ?? undefined,
|
|
101
103
|
ownerAutomationRunId: row.owner_automation_run_id ?? undefined,
|
|
@@ -139,7 +141,7 @@ function spawnCommandLandMetadata(spawnRequestId: string | undefined): ReturnTyp
|
|
|
139
141
|
return workspaceLandMetadataFrom(parseJson<Record<string, unknown>>(row?.params, {}));
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
export function upsertWorkspaceFromManagedAgent(agent: ManagedAgent): WorkspaceRecord | null {
|
|
144
|
+
export function upsertWorkspaceFromManagedAgent(agent: ManagedAgent, orchestratorId?: string): WorkspaceRecord | null {
|
|
143
145
|
const workspace = agent.workspace;
|
|
144
146
|
if (!workspace) return null;
|
|
145
147
|
if (workspace.mode === "isolated" && workspace.id && workspace.repoRoot && workspace.worktreePath) {
|
|
@@ -173,6 +175,7 @@ export function upsertWorkspaceFromManagedAgent(agent: ManagedAgent): WorkspaceR
|
|
|
173
175
|
mode: workspace.mode,
|
|
174
176
|
requestedMode: workspace.requestedMode,
|
|
175
177
|
status: preserveStatus ? existing!.status : (workspace.status ?? "active"),
|
|
178
|
+
orchestratorId: existing?.orchestratorId ?? orchestratorId,
|
|
176
179
|
ownerAgentId: agent.agentId || undefined,
|
|
177
180
|
ownerPolicyName: agent.policyName,
|
|
178
181
|
ownerAutomationRunId: agent.automationRunId,
|
|
@@ -214,8 +217,8 @@ export function upsertWorkspaceFromManagedAgent(agent: ManagedAgent): WorkspaceR
|
|
|
214
217
|
export function upsertWorkspace(input: Omit<WorkspaceRecord, "createdAt" | "updatedAt"> & Partial<Pick<WorkspaceRecord, "createdAt" | "updatedAt">>): WorkspaceRecord {
|
|
215
218
|
const now = Date.now();
|
|
216
219
|
getDb().query(`
|
|
217
|
-
INSERT INTO workspaces (id, repo_root, source_cwd, worktree_path, branch, base_ref, base_sha, mode, requested_mode, status, owner_agent_id, owner_policy_name, owner_automation_run_id, steward_agent_id, metadata, created_at, updated_at, ready_at, cleaned_at)
|
|
218
|
-
VALUES ($id, $repoRoot, $sourceCwd, $worktreePath, $branch, $baseRef, $baseSha, $mode, $requestedMode, $status, $ownerAgentId, $ownerPolicyName, $ownerAutomationRunId, $stewardAgentId, $metadata, $createdAt, $updatedAt, $readyAt, $cleanedAt)
|
|
220
|
+
INSERT INTO workspaces (id, repo_root, source_cwd, worktree_path, branch, base_ref, base_sha, mode, requested_mode, status, orchestrator_id, owner_agent_id, owner_policy_name, owner_automation_run_id, steward_agent_id, metadata, created_at, updated_at, ready_at, cleaned_at)
|
|
221
|
+
VALUES ($id, $repoRoot, $sourceCwd, $worktreePath, $branch, $baseRef, $baseSha, $mode, $requestedMode, $status, $orchestratorId, $ownerAgentId, $ownerPolicyName, $ownerAutomationRunId, $stewardAgentId, $metadata, $createdAt, $updatedAt, $readyAt, $cleanedAt)
|
|
219
222
|
ON CONFLICT(id) DO UPDATE SET
|
|
220
223
|
repo_root = excluded.repo_root,
|
|
221
224
|
source_cwd = excluded.source_cwd,
|
|
@@ -226,6 +229,7 @@ export function upsertWorkspace(input: Omit<WorkspaceRecord, "createdAt" | "upda
|
|
|
226
229
|
mode = excluded.mode,
|
|
227
230
|
requested_mode = excluded.requested_mode,
|
|
228
231
|
status = excluded.status,
|
|
232
|
+
orchestrator_id = coalesce(excluded.orchestrator_id, workspaces.orchestrator_id),
|
|
229
233
|
owner_agent_id = coalesce(excluded.owner_agent_id, workspaces.owner_agent_id),
|
|
230
234
|
owner_policy_name = coalesce(excluded.owner_policy_name, workspaces.owner_policy_name),
|
|
231
235
|
owner_automation_run_id = coalesce(excluded.owner_automation_run_id, workspaces.owner_automation_run_id),
|
|
@@ -245,6 +249,7 @@ export function upsertWorkspace(input: Omit<WorkspaceRecord, "createdAt" | "upda
|
|
|
245
249
|
$mode: input.mode,
|
|
246
250
|
$requestedMode: input.requestedMode ?? null,
|
|
247
251
|
$status: input.status,
|
|
252
|
+
$orchestratorId: input.orchestratorId ?? null,
|
|
248
253
|
$ownerAgentId: input.ownerAgentId ?? null,
|
|
249
254
|
$ownerPolicyName: input.ownerPolicyName ?? null,
|
|
250
255
|
$ownerAutomationRunId: input.ownerAutomationRunId ?? null,
|
|
@@ -511,6 +516,46 @@ export function patchWorkspaceMetadata(id: string, patch: Record<string, unknown
|
|
|
511
516
|
return getWorkspace(id);
|
|
512
517
|
}
|
|
513
518
|
|
|
519
|
+
type WorkspaceClaimResult =
|
|
520
|
+
| { ok: true; workspace: WorkspaceRecord }
|
|
521
|
+
| { ok: false; reason: "not-found" | "claimed-by-other"; workspace?: WorkspaceRecord };
|
|
522
|
+
|
|
523
|
+
export function claimWorkspaceMetadata(
|
|
524
|
+
id: string,
|
|
525
|
+
claimant: string,
|
|
526
|
+
patch: Record<string, unknown>,
|
|
527
|
+
now: number = Date.now(),
|
|
528
|
+
): WorkspaceClaimResult {
|
|
529
|
+
const run = getDb().transaction((): WorkspaceClaimResult => {
|
|
530
|
+
const existing = getWorkspace(id);
|
|
531
|
+
if (!existing) return { ok: false, reason: "not-found" as const };
|
|
532
|
+
const active = workspaceActiveClaim(existing, now);
|
|
533
|
+
if (active?.by && active.by !== claimant) return { ok: false, reason: "claimed-by-other" as const, workspace: existing };
|
|
534
|
+
const next = { ...existing.metadata, ...patch };
|
|
535
|
+
getDb().query("UPDATE workspaces SET metadata = ? WHERE id = ?").run(JSON.stringify(next), id);
|
|
536
|
+
return { ok: true as const, workspace: getWorkspace(id)! };
|
|
537
|
+
});
|
|
538
|
+
return run();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function releaseWorkspaceClaimMetadata(
|
|
542
|
+
id: string,
|
|
543
|
+
claimant: string,
|
|
544
|
+
patch: Record<string, unknown>,
|
|
545
|
+
now: number = Date.now(),
|
|
546
|
+
): WorkspaceClaimResult {
|
|
547
|
+
const run = getDb().transaction((): WorkspaceClaimResult => {
|
|
548
|
+
const existing = getWorkspace(id);
|
|
549
|
+
if (!existing) return { ok: false, reason: "not-found" as const };
|
|
550
|
+
const active = workspaceActiveClaim(existing, now);
|
|
551
|
+
if (active?.by && active.by !== claimant) return { ok: false, reason: "claimed-by-other" as const, workspace: existing };
|
|
552
|
+
const next = { ...existing.metadata, ...patch };
|
|
553
|
+
getDb().query("UPDATE workspaces SET metadata = ? WHERE id = ?").run(JSON.stringify(next), id);
|
|
554
|
+
return { ok: true as const, workspace: getWorkspace(id)! };
|
|
555
|
+
});
|
|
556
|
+
return run();
|
|
557
|
+
}
|
|
558
|
+
|
|
514
559
|
// Re-elect stewards for every repo where an agent owns a live workspace. Called
|
|
515
560
|
// when an agent (re)registers so a dormant repo regains a steward on rejoin
|
|
516
561
|
// without a full unscoped sweep.
|
package/src/lifecycle-manager.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createCommand } from "./commands-db";
|
|
2
|
-
import {
|
|
2
|
+
import { workspaceOwnerOrchestrator } from "./workspace-host";
|
|
3
3
|
import { agentProviderState } from "./provider-state";
|
|
4
4
|
import { createActivityEvent, deleteWorkspace, getAgent, getDb, getOrchestrator, listOrchestrators, listWorkspaces } from "./db";
|
|
5
5
|
import {
|
|
@@ -584,9 +584,7 @@ export class LifecycleManager {
|
|
|
584
584
|
if (!candidates.length) return;
|
|
585
585
|
const orchestrators = listOrchestrators();
|
|
586
586
|
for (const ws of candidates) {
|
|
587
|
-
const orch = orchestrators
|
|
588
|
-
(candidate) => candidate.status === "online" && isPathWithinBase(ws.sourceCwd, candidate.baseDir),
|
|
589
|
-
);
|
|
587
|
+
const orch = workspaceOwnerOrchestrator(ws, orchestrators);
|
|
590
588
|
if (!orch) continue;
|
|
591
589
|
const command = createCommand({
|
|
592
590
|
type: "workspace.reconcile",
|
package/src/maintenance/jobs.ts
CHANGED
|
@@ -27,7 +27,7 @@ import { isAgentUnavailable } from "../db/agent-predicates";
|
|
|
27
27
|
import { flushQueuedDirectMessages } from "../services/managed-running";
|
|
28
28
|
import { notifyAgentOffline } from "../agent-lifecycle-events";
|
|
29
29
|
import { diagnoseAgentDeath } from "../agent-death-diagnosis";
|
|
30
|
-
import { autoMergeCleanFastForwards } from "../workspace-auto-merge";
|
|
30
|
+
import { autoMergeCleanFastForwards, runAutoMergeWatchdog } from "../workspace-auto-merge";
|
|
31
31
|
import { reapOrphanedWorktrees } from "../workspace-orphans";
|
|
32
32
|
import { getLifecycleManager } from "../lifecycle-manager";
|
|
33
33
|
import { applyCommandToRecipe } from "../recipe-runner";
|
|
@@ -485,6 +485,15 @@ export const definitions: MaintenanceJobDefinition[] = [
|
|
|
485
485
|
timeoutMs: 60 * 1000,
|
|
486
486
|
handler: autoMergeCleanFastForwards,
|
|
487
487
|
},
|
|
488
|
+
{
|
|
489
|
+
id: "workspace-auto-merge-watchdog",
|
|
490
|
+
title: "Workspace auto-merge watchdog",
|
|
491
|
+
description: "Restarts stale per-repo auto-merge loops when ready/review_requested work exists but no auto-merge cycle has completed recently.",
|
|
492
|
+
intervalMs: 60 * 1000,
|
|
493
|
+
runOnStart: false,
|
|
494
|
+
timeoutMs: 10 * 1000,
|
|
495
|
+
handler: runAutoMergeWatchdog,
|
|
496
|
+
},
|
|
488
497
|
{
|
|
489
498
|
id: "workspace-idle-refresh",
|
|
490
499
|
title: "Workspace idle refresh",
|