agent-relay-server 0.38.0 → 0.40.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 +1 -1
- package/package.json +2 -2
- package/public/assets/{activity-ClpDglG8.js → activity-DAz3DcDA.js} +2 -2
- package/public/assets/{activity-ClpDglG8.js.map → activity-DAz3DcDA.js.map} +1 -1
- package/public/assets/{agents-CHmEJvqV.js → agents-BJ7qRWxt.js} +2 -2
- package/public/assets/{agents-CHmEJvqV.js.map → agents-BJ7qRWxt.js.map} +1 -1
- package/public/assets/{analytics-2kTjXIj1.js → analytics-MNCS2qnT.js} +2 -2
- package/public/assets/{analytics-2kTjXIj1.js.map → analytics-MNCS2qnT.js.map} +1 -1
- package/public/assets/{automation-B5U_g-1P.js → automation-COp2Kb3h.js} +2 -2
- package/public/assets/{automation-B5U_g-1P.js.map → automation-COp2Kb3h.js.map} +1 -1
- package/public/assets/chat-D_O8VqMR.js +2 -0
- package/public/assets/chat-D_O8VqMR.js.map +1 -0
- package/public/assets/display-ConJ9cJB.js.map +1 -1
- package/public/assets/{formatted-body-impl-tmf8IBfr.js → formatted-body-impl-BbMHqkCy.js} +2 -2
- package/public/assets/{formatted-body-impl-tmf8IBfr.js.map → formatted-body-impl-BbMHqkCy.js.map} +1 -1
- package/public/assets/{index-B1QUkb_O.js → index-DgJOEApM.js} +5 -5
- package/public/assets/{index-B1QUkb_O.js.map → index-DgJOEApM.js.map} +1 -1
- package/public/assets/{index-Bins8N_5.css → index-Dop-uXiy.css} +1 -1
- package/public/assets/{maintenance-Tn23oWBF.js → maintenance-BHv90kXZ.js} +2 -2
- package/public/assets/{maintenance-Tn23oWBF.js.map → maintenance-BHv90kXZ.js.map} +1 -1
- package/public/assets/managed-agents-BAlvkxfo.js +2 -0
- package/public/assets/managed-agents-BAlvkxfo.js.map +1 -0
- package/public/assets/{markdown-preview-impl-D4UIjB3I.js → markdown-preview-impl-6k_FWjmd.js} +2 -2
- package/public/assets/{markdown-preview-impl-D4UIjB3I.js.map → markdown-preview-impl-6k_FWjmd.js.map} +1 -1
- package/public/assets/{memory-SVCob0fo.js → memory-DBjqdVpg.js} +2 -2
- package/public/assets/{memory-SVCob0fo.js.map → memory-DBjqdVpg.js.map} +1 -1
- package/public/assets/{messages-CHK24Uxx.js → messages-axoaJY2N.js} +2 -2
- package/public/assets/{messages-CHK24Uxx.js.map → messages-axoaJY2N.js.map} +1 -1
- package/public/assets/{orchestrators-CQcJb6VE.js → orchestrators-DMfHZ44H.js} +2 -2
- package/public/assets/{orchestrators-CQcJb6VE.js.map → orchestrators-DMfHZ44H.js.map} +1 -1
- package/public/assets/{overview-DbyX7k-7.js → overview-DqxYWpUT.js} +2 -2
- package/public/assets/{overview-DbyX7k-7.js.map → overview-DqxYWpUT.js.map} +1 -1
- package/public/assets/{pairs-CaL0_ZfW.js → pairs-D3wnzC1V.js} +2 -2
- package/public/assets/{pairs-CaL0_ZfW.js.map → pairs-D3wnzC1V.js.map} +1 -1
- package/public/assets/{security-BogsfkbT.js → security-KWrb7PKj.js} +2 -2
- package/public/assets/{security-BogsfkbT.js.map → security-KWrb7PKj.js.map} +1 -1
- package/public/assets/{settings-BOsnUh5f.js → settings-PXMEzNAm.js} +2 -2
- package/public/assets/{settings-BOsnUh5f.js.map → settings-PXMEzNAm.js.map} +1 -1
- package/public/assets/{tasks-CCxQovOv.js → tasks-CFYShBCz.js} +2 -2
- package/public/assets/{tasks-CCxQovOv.js.map → tasks-CFYShBCz.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BDikdsxs.js → terminal-viewer-impl-BwPYZlWI.js} +2 -2
- package/public/assets/{terminal-viewer-impl-BDikdsxs.js.map → terminal-viewer-impl-BwPYZlWI.js.map} +1 -1
- package/public/assets/{work-queue-fM-tu0iP.js → work-queue-VQ_6QDJc.js} +2 -2
- package/public/assets/{work-queue-fM-tu0iP.js.map → work-queue-VQ_6QDJc.js.map} +1 -1
- package/public/index.html +2 -2
- package/runner/src/adapter.ts +5 -1
- package/src/config-store.ts +1 -1
- package/src/db/messages.ts +36 -2
- package/src/db/migrations.ts +5 -0
- package/src/lifecycle-manager.ts +74 -6
- package/src/maintenance.ts +20 -27
- package/src/reviewer-pipeline.ts +197 -0
- package/src/routes/commands.ts +28 -0
- package/src/services/managed-running.ts +1 -0
- package/src/services/send-message.ts +4 -0
- package/src/workspace-pr-completion.ts +121 -0
- package/public/assets/chat-zPXWB-03.js +0 -2
- package/public/assets/chat-zPXWB-03.js.map +0 -1
- package/public/assets/managed-agents-CasacvJX.js +0 -2
- package/public/assets/managed-agents-CasacvJX.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{An as e,Zt as t,at as n,cn as r,i}from"./lucide-react-DLQFnqNm.js";import{i as a,t as o}from"./store-Bo72e9My.js";import{H as s,ct as c,d as l}from"./display-ConJ9cJB.js";import{t as u}from"./badge-JVybSpzR.js";import{t as d}from"./button-BsMqBNJb.js";import{N as f,s as p,z as m}from"./index-
|
|
2
|
-
//# sourceMappingURL=work-queue-
|
|
1
|
+
import{An as e,Zt as t,at as n,cn as r,i}from"./lucide-react-DLQFnqNm.js";import{i as a,t as o}from"./store-Bo72e9My.js";import{H as s,ct as c,d as l}from"./display-ConJ9cJB.js";import{t as u}from"./badge-JVybSpzR.js";import{t as d}from"./button-BsMqBNJb.js";import{N as f,s as p,z as m}from"./index-DgJOEApM.js";import{n as h,t as g}from"./card-I8w4U656.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),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=f(),S=m(),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`?`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:l(e)},e.id))]})]})]}),(0,_.jsx)(p,{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:e,now:n,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[e.severity]||c.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:l,children:[(0,_.jsx)(i,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
|
|
2
|
+
//# sourceMappingURL=work-queue-VQ_6QDJc.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-fM-tu0iP.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-VQ_6QDJc.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-DgJOEApM.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-DQVmg1Zk.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/lucide-react-DLQFnqNm.js">
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<link rel="modulepreload" crossorigin href="./assets/badge-JVybSpzR.js">
|
|
36
36
|
<link rel="modulepreload" crossorigin href="./assets/input-dPzf0luy.js">
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/themes-mW9zZP4O.js">
|
|
38
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
38
|
+
<link rel="stylesheet" crossorigin href="./assets/index-Dop-uXiy.css">
|
|
39
39
|
</head>
|
|
40
40
|
<body class="bg-background text-foreground antialiased">
|
|
41
41
|
<div id="root"></div>
|
package/runner/src/adapter.ts
CHANGED
|
@@ -36,13 +36,17 @@ export type ProviderStatusUpdate = SemanticStatus | ProviderStatusEvent;
|
|
|
36
36
|
* same lane Claude's transcript capture uses. Provider-independent boundary.
|
|
37
37
|
*/
|
|
38
38
|
export interface ProviderSessionEvent {
|
|
39
|
-
type: "prompt" | "response" | "reasoning" | "tool";
|
|
39
|
+
type: "prompt" | "response" | "narration" | "reasoning" | "tool";
|
|
40
40
|
body: string;
|
|
41
41
|
origin?: "chat" | "terminal" | "provider";
|
|
42
42
|
turnId?: string;
|
|
43
43
|
label?: string;
|
|
44
44
|
status?: "running" | "completed" | "failed";
|
|
45
45
|
streaming?: boolean;
|
|
46
|
+
/** Stable provider-side step id (Codex app-server item id). Carried into
|
|
47
|
+
* MessageSessionMeta.stepId so the server upserts the step's row in place instead of
|
|
48
|
+
* appending a duplicate (a tool's running→completed, a streamed reasoning row). */
|
|
49
|
+
stepId?: string;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export interface ProviderConfig {
|
package/src/config-store.ts
CHANGED
|
@@ -42,7 +42,7 @@ const VALID_PROFILE_ASSET_SOURCES = ["relay", "repo", "inline", "provider"] as c
|
|
|
42
42
|
const VALID_PROFILE_FILESYSTEM_SCOPES = ["repo", "workspace", "host"] as const;
|
|
43
43
|
const VALID_PERMISSION_MODES = ["open", "guarded", "read-only"] as const;
|
|
44
44
|
const VALID_POLICY_MODES = ["always-on", "on-demand"] as const;
|
|
45
|
-
const VALID_MANAGED_STATUSES = ["stopped", "starting", "running", "stopping", "backoff"] as const;
|
|
45
|
+
const VALID_MANAGED_STATUSES = ["stopped", "starting", "running", "stopping", "backoff", "failed"] as const;
|
|
46
46
|
const BUILT_IN_AGENT_PROFILE_NAMES = new Set(["default-relay", "minimal", "isolated-research"]);
|
|
47
47
|
|
|
48
48
|
const BUILT_IN_AGENT_PROFILES: AgentProfile[] = [
|
package/src/db/messages.ts
CHANGED
|
@@ -90,6 +90,34 @@ export function findMessageByIdempotencyKey(from: string, key: string): Message
|
|
|
90
90
|
return row ? rowToMessage(row) : null;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function sessionStepId(payload: Record<string, unknown> | undefined): string | undefined {
|
|
94
|
+
const session = isRecord(payload?.session) ? payload.session : undefined;
|
|
95
|
+
return stringValue(session?.stepId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Codex session-mirror upsert (#parity): a provider step that emits more than once — a
|
|
100
|
+
* tool's running→completed lifecycle, a reasoning/response row that streams — carries a
|
|
101
|
+
* stable `payload.session.stepId`. Instead of appending a second row (which renders as a
|
|
102
|
+
* duplicate on dashboard reload), update the EXISTING row in place: same DB id, so the
|
|
103
|
+
* dashboard's id-keyed merge replaces it. Gated on stepId presence + matching step, so the
|
|
104
|
+
* Claude transcript path (no stepId) is byte-identical. Newer-event-wins via occurredAt so a
|
|
105
|
+
* late retry of an earlier lifecycle phase can't clobber a later one.
|
|
106
|
+
*/
|
|
107
|
+
function upsertSessionStepInPlace(existing: Message, input: SendMessageInput, now: number): Message | null {
|
|
108
|
+
const incomingStep = sessionStepId(input.payload);
|
|
109
|
+
if (!incomingStep) return null;
|
|
110
|
+
if (existing.kind !== "session") return null;
|
|
111
|
+
if (sessionStepId(existing.payload) !== incomingStep) return null;
|
|
112
|
+
const incomingAt = sanitizeOccurredAt(input.occurredAt, now) ?? now;
|
|
113
|
+
const existingAt = existing.occurredAt ?? existing.createdAt;
|
|
114
|
+
if (incomingAt < existingAt) return null; // stale lifecycle phase — keep the newer row, no re-emit
|
|
115
|
+
getDb()
|
|
116
|
+
.query("UPDATE messages SET body = ?, payload = ?, occurred_at = ? WHERE id = ?")
|
|
117
|
+
.run(input.body, JSON.stringify(input.payload ?? {}), incomingAt, existing.id);
|
|
118
|
+
return getMessage(existing.id);
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
export function policyNameFromTarget(target: string): string | null {
|
|
94
122
|
if (!target.startsWith("policy:")) return null;
|
|
95
123
|
const name = target.slice("policy:".length).trim();
|
|
@@ -169,7 +197,7 @@ export function sanitizeOccurredAt(occurredAt: number | undefined, receivedAt: n
|
|
|
169
197
|
return Math.floor(occurredAt);
|
|
170
198
|
}
|
|
171
199
|
|
|
172
|
-
export function sendMessageWithResult(input: SendMessageInput): { message: Message; created: boolean } {
|
|
200
|
+
export function sendMessageWithResult(input: SendMessageInput): { message: Message; created: boolean; updated?: boolean } {
|
|
173
201
|
const now = Date.now();
|
|
174
202
|
const payload = input.payload ?? {};
|
|
175
203
|
const attachmentRefs = cleanAttachmentRefs(payload);
|
|
@@ -181,7 +209,13 @@ export function sendMessageWithResult(input: SendMessageInput): { message: Messa
|
|
|
181
209
|
|
|
182
210
|
if (input.idempotencyKey) {
|
|
183
211
|
const existing = findMessageByIdempotencyKey(input.from, input.idempotencyKey);
|
|
184
|
-
if (existing)
|
|
212
|
+
if (existing) {
|
|
213
|
+
// Session-step upsert: a re-emitted provider step (tool running→completed, streamed
|
|
214
|
+
// reasoning/response) updates its row in place instead of being dropped as a dup retry.
|
|
215
|
+
const upserted = upsertSessionStepInPlace(existing, input, now);
|
|
216
|
+
if (upserted) return { message: upserted, created: false, updated: true };
|
|
217
|
+
return { message: existing, created: false };
|
|
218
|
+
}
|
|
185
219
|
}
|
|
186
220
|
|
|
187
221
|
// Resolve thread: if replying, inherit from parent; reject unknown replyTo
|
package/src/db/migrations.ts
CHANGED
|
@@ -245,6 +245,11 @@ export function applyMigrations(): void {
|
|
|
245
245
|
}
|
|
246
246
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_msg_kind ON messages(kind)");
|
|
247
247
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_msg_from_kind_id ON messages(from_agent, kind, id)");
|
|
248
|
+
// (from_agent, idempotency_key) powers findMessageByIdempotencyKey — the retry-dedup lookup
|
|
249
|
+
// AND the session-step in-place upsert (Codex tool running→completed / streamed reasoning),
|
|
250
|
+
// which runs per persisted session step. Added here (not the base schema) because the column
|
|
251
|
+
// is migration-added for pre-hardening DBs. Without it that lookup full-scans messages.
|
|
252
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_msg_idempotency ON messages(from_agent, idempotency_key)");
|
|
248
253
|
|
|
249
254
|
// Backfill thread_id for pre-migration rows (self-threaded).
|
|
250
255
|
getDb().run("UPDATE messages SET thread_id = id WHERE thread_id IS NULL");
|
package/src/lifecycle-manager.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCommand } from "./commands-db";
|
|
2
|
+
import { extractClaudeModelUnavailableMessage } from "agent-relay-sdk";
|
|
2
3
|
import { isPathWithinBase } from "./utils";
|
|
3
4
|
import { createActivityEvent, deleteWorkspace, getAgent, getDb, getOrchestrator, listOrchestrators, listWorkspaces } from "./db";
|
|
4
5
|
import {
|
|
@@ -21,6 +22,8 @@ const DEFAULT_TICK_MS = 10_000;
|
|
|
21
22
|
// orchestrator never confirms must not strand it in "stopping" forever.
|
|
22
23
|
const START_TIMEOUT_MS = 120_000;
|
|
23
24
|
const STOP_TIMEOUT_MS = 60_000;
|
|
25
|
+
const FAST_FAIL_RUNTIME_MS = 30_000;
|
|
26
|
+
const MAX_FAST_FAILURES = 3;
|
|
24
27
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
25
28
|
const DEFAULT_COMPACT_THRESHOLD = 0.75;
|
|
26
29
|
const DEFAULT_COMPACT_TARGET = 0.3;
|
|
@@ -135,7 +138,7 @@ export class LifecycleManager {
|
|
|
135
138
|
(state.tmuxSession && agent.tmuxSession === state.tmuxSession) ||
|
|
136
139
|
(agent.policyName === policy.name && (!state.spawnRequestId || agent.spawnRequestId === state.spawnRequestId))
|
|
137
140
|
));
|
|
138
|
-
this.
|
|
141
|
+
this.markPolicyFailure(policy, state, exited?.lastError ?? "orchestrator session disappeared", exited);
|
|
139
142
|
} else if (state.status === "stopping" && !reported) {
|
|
140
143
|
const next = updateManagedAgentState(policy.name, {
|
|
141
144
|
status: "stopped",
|
|
@@ -166,7 +169,7 @@ export class LifecycleManager {
|
|
|
166
169
|
if (next) this.emitState(next);
|
|
167
170
|
return;
|
|
168
171
|
}
|
|
169
|
-
this.
|
|
172
|
+
this.markPolicyFailure(policy, state, "agent disappeared");
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
onConfigChanged(namespace: string, key: string): void {
|
|
@@ -318,7 +321,7 @@ export class LifecycleManager {
|
|
|
318
321
|
|
|
319
322
|
if (state.status === "starting") {
|
|
320
323
|
if (state.lastSpawnAt && this.now() - state.lastSpawnAt > START_TIMEOUT_MS) {
|
|
321
|
-
this.
|
|
324
|
+
this.markPolicyFailure(policy, state, "spawn timed out before registration");
|
|
322
325
|
}
|
|
323
326
|
return;
|
|
324
327
|
}
|
|
@@ -343,7 +346,7 @@ export class LifecycleManager {
|
|
|
343
346
|
return;
|
|
344
347
|
}
|
|
345
348
|
if (!agent || agent.status === "offline") {
|
|
346
|
-
this.
|
|
349
|
+
this.markPolicyFailure(policy, state, agentTerminalFailureMessage(agent) ?? "agent offline");
|
|
347
350
|
return;
|
|
348
351
|
}
|
|
349
352
|
this.resetBackoffAfterHealthyRun(policy, state);
|
|
@@ -373,6 +376,8 @@ export class LifecycleManager {
|
|
|
373
376
|
return;
|
|
374
377
|
}
|
|
375
378
|
|
|
379
|
+
if (state.status === "failed") return;
|
|
380
|
+
|
|
376
381
|
if (state.status === "stopped") {
|
|
377
382
|
if (enabled && (policy.mode === "always-on" || this.hasQueuedMessages(policy.name))) this.spawnAgent(policy, policy.mode === "always-on" ? "always-on" : "message-trigger");
|
|
378
383
|
}
|
|
@@ -408,9 +413,23 @@ export class LifecycleManager {
|
|
|
408
413
|
this.emitState(state);
|
|
409
414
|
}
|
|
410
415
|
|
|
411
|
-
private
|
|
416
|
+
private markPolicyFailure(policy: SpawnPolicy, state: ManagedAgentState, error: string, exited?: ManagedSessionExitDiagnostics): void {
|
|
412
417
|
if (state.status === "stopping") return;
|
|
418
|
+
const modelUnavailable = managedModelUnavailableMessage(policy, error, exited);
|
|
419
|
+
if (modelUnavailable) {
|
|
420
|
+
this.markFailed(policy, state, modelUnavailable);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
413
423
|
const failures = state.consecutiveFailures + 1;
|
|
424
|
+
if (isFastFailStorm(failures, exited)) {
|
|
425
|
+
this.markFailed(policy, state, error);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this.markBackoff(policy, state, error, failures);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private markBackoff(policy: SpawnPolicy, state: ManagedAgentState, error: string, failures = state.consecutiveFailures + 1): void {
|
|
432
|
+
if (state.status === "stopping") return;
|
|
414
433
|
const delay = this.backoffDelay(policy, state);
|
|
415
434
|
const next = upsertManagedAgentState({
|
|
416
435
|
policyName: policy.name,
|
|
@@ -419,6 +438,7 @@ export class LifecycleManager {
|
|
|
419
438
|
orchestratorId: policy.orchestratorId,
|
|
420
439
|
provider: policy.provider,
|
|
421
440
|
spawnRequestId: state.spawnRequestId,
|
|
441
|
+
tmuxSession: state.tmuxSession,
|
|
422
442
|
lastSpawnAt: state.lastSpawnAt,
|
|
423
443
|
restartCount: state.restartCount,
|
|
424
444
|
consecutiveFailures: failures,
|
|
@@ -428,6 +448,25 @@ export class LifecycleManager {
|
|
|
428
448
|
this.emitState(next);
|
|
429
449
|
}
|
|
430
450
|
|
|
451
|
+
private markFailed(policy: SpawnPolicy, state: ManagedAgentState, error: string): void {
|
|
452
|
+
const failures = state.consecutiveFailures + 1;
|
|
453
|
+
const next = upsertManagedAgentState({
|
|
454
|
+
policyName: policy.name,
|
|
455
|
+
status: "failed",
|
|
456
|
+
agentId: undefined,
|
|
457
|
+
orchestratorId: policy.orchestratorId,
|
|
458
|
+
provider: policy.provider,
|
|
459
|
+
spawnRequestId: state.spawnRequestId,
|
|
460
|
+
tmuxSession: state.tmuxSession,
|
|
461
|
+
lastSpawnAt: state.lastSpawnAt,
|
|
462
|
+
restartCount: state.restartCount,
|
|
463
|
+
consecutiveFailures: failures,
|
|
464
|
+
backoffUntil: undefined,
|
|
465
|
+
lastError: error,
|
|
466
|
+
});
|
|
467
|
+
this.emitState(next);
|
|
468
|
+
}
|
|
469
|
+
|
|
431
470
|
private backoffDelay(policy: SpawnPolicy, state: ManagedAgentState | null): number {
|
|
432
471
|
const schedule = policy.backoff.schedule.length ? policy.backoff.schedule : [30];
|
|
433
472
|
const index = Math.min(state?.consecutiveFailures ?? 0, schedule.length - 1);
|
|
@@ -586,10 +625,39 @@ export function __resetLifecycleManager(): void {
|
|
|
586
625
|
singleton = null;
|
|
587
626
|
}
|
|
588
627
|
|
|
628
|
+
function managedModelUnavailableMessage(policy: SpawnPolicy, error: string, exited?: ManagedSessionExitDiagnostics): string | null {
|
|
629
|
+
if (policy.provider !== "claude" && exited?.provider !== "claude") return null;
|
|
630
|
+
return extractClaudeModelUnavailableMessage([
|
|
631
|
+
error,
|
|
632
|
+
exited?.lastError,
|
|
633
|
+
...(exited?.logTail ?? []),
|
|
634
|
+
...(exited?.unavailable ?? []),
|
|
635
|
+
].join("\n"));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function isFastFailStorm(failures: number, exited?: ManagedSessionExitDiagnostics): boolean {
|
|
639
|
+
return failures >= MAX_FAST_FAILURES && exited !== undefined && exited.runtimeMs < FAST_FAIL_RUNTIME_MS;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function agentTerminalFailureMessage(agent: ReturnType<typeof getAgent> | null): string | null {
|
|
643
|
+
if (!agent?.meta) return null;
|
|
644
|
+
const reason = typeof agent.meta.terminalFailureReason === "string" ? agent.meta.terminalFailureReason : undefined;
|
|
645
|
+
const message = typeof agent.meta.terminalFailureMessage === "string" ? agent.meta.terminalFailureMessage : undefined;
|
|
646
|
+
if (reason === "model-unavailable" && message) return message;
|
|
647
|
+
const providerState = isPlainRecord(agent.meta.providerState) ? agent.meta.providerState : undefined;
|
|
648
|
+
const stateReason = typeof providerState?.reason === "string" ? providerState.reason : undefined;
|
|
649
|
+
const stateMessage = typeof providerState?.message === "string" ? providerState.message : undefined;
|
|
650
|
+
if (stateReason === "model-unavailable" && stateMessage) return stateMessage;
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
655
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
656
|
+
}
|
|
657
|
+
|
|
589
658
|
function alwaysReloadTags(tags: string[]): string[] {
|
|
590
659
|
return tags
|
|
591
660
|
.filter((tag) => tag.startsWith("memory-reload:"))
|
|
592
661
|
.map((tag) => tag.slice("memory-reload:".length).trim())
|
|
593
662
|
.filter(Boolean);
|
|
594
663
|
}
|
|
595
|
-
|
package/src/maintenance.ts
CHANGED
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
import type { WorkspaceMergePreview, WorkspaceRecord, WorkspaceStatus } from "./types";
|
|
35
35
|
import { requestWorkspaceMerge } from "./workspace-merge";
|
|
36
36
|
import { reconcileLandedWorkspace } from "./branch-landed";
|
|
37
|
+
import { isAwaitingReviewerApproval } from "./reviewer-pipeline";
|
|
38
|
+
import { preparePrCompletionScan } from "./workspace-pr-completion";
|
|
37
39
|
import { notifyAgentOffline } from "./agent-lifecycle-events";
|
|
38
40
|
import { workspaceActiveClaim } from "./workspace-claim";
|
|
39
41
|
import { reapOrphanedWorktrees } from "./workspace-orphans";
|
|
@@ -577,10 +579,8 @@ async function scanWorkspaceConflicts(): Promise<Record<string, unknown>> {
|
|
|
577
579
|
const candidates = listWorkspaces().filter(
|
|
578
580
|
(ws) => ws.mode === "isolated" && Boolean(ws.worktreePath) && CONFLICT_SCAN_STATUSES.has(ws.status),
|
|
579
581
|
);
|
|
580
|
-
const flagged: string[] = [];
|
|
581
|
-
const
|
|
582
|
-
const merged: string[] = [];
|
|
583
|
-
const notifiedStewards: string[] = [];
|
|
582
|
+
const flagged: string[] = [], cleared: string[] = [], merged: string[] = [], notifiedStewards: string[] = [];
|
|
583
|
+
const reviewerArmed: string[] = [], reviewerChangesRequested: string[] = [], relayPrMerged: string[] = [], relayPrWaiting: string[] = [];
|
|
584
584
|
|
|
585
585
|
for (const ws of candidates) {
|
|
586
586
|
const orch = orchestrators.find((candidate) => workspacePathWithinBase(ws.sourceCwd, candidate.baseDir));
|
|
@@ -608,19 +608,9 @@ async function scanWorkspaceConflicts(): Promise<Record<string, unknown>> {
|
|
|
608
608
|
if (patched && deriveBranchState(patched) !== before) emitWorkspaceChange(patched);
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
-
// Landing wins over
|
|
612
|
-
//
|
|
613
|
-
//
|
|
614
|
-
// conflict against the now-moved base (a PR-strategy row sits at
|
|
615
|
-
// merge_planned forever otherwise, and the conflict scan can even pin a
|
|
616
|
-
// landed branch to `conflict`). Reconcile to the terminal `merged` status so
|
|
617
|
-
// the dashboard stops showing it as unmerged and GC prunes it on schedule.
|
|
618
|
-
// This runs BEFORE the conflict-undefined skip below: a PR merged via a regular
|
|
619
|
-
// merge commit makes the branch an ancestor (ahead=0 → no-op → conflict comes
|
|
620
|
-
// back undefined), which the skip would otherwise drop, stranding the row at
|
|
621
|
-
// merge_planned — the exact #304 stall.
|
|
622
|
-
// Reconcile + finalize (record SHA, fire branch.landed) in branch-landed.ts so the
|
|
623
|
-
// giant doesn't grow (#291) and land-notify stays single-homed (#304).
|
|
611
|
+
// Landing wins over conflict checks. A merged PR can look like a no-op or conflict
|
|
612
|
+
// locally, so reconcile before the conflict-undefined skip and keep finalization in
|
|
613
|
+
// branch-landed.ts (#304).
|
|
624
614
|
const landed = p.landed === true || p.prMerged === true;
|
|
625
615
|
if (landed && LANDED_RECONCILE_STATUSES.has(ws.status)) {
|
|
626
616
|
reconcileLandedWorkspace(ws, p);
|
|
@@ -628,6 +618,13 @@ async function scanWorkspaceConflicts(): Promise<Record<string, unknown>> {
|
|
|
628
618
|
continue;
|
|
629
619
|
}
|
|
630
620
|
|
|
621
|
+
const prAction = preparePrCompletionScan(ws, p, orch.agentId);
|
|
622
|
+
if (prAction.armCommand) { emitCommand(prAction.armCommand); reviewerArmed.push(ws.id); continue; }
|
|
623
|
+
if (prAction.notifiedOwner) { reviewerChangesRequested.push(ws.id); continue; }
|
|
624
|
+
if (prAction.mergeCommand) { emitCommand(prAction.mergeCommand); relayPrMerged.push(ws.id); continue; }
|
|
625
|
+
if (prAction.waitReason) relayPrWaiting.push(`${ws.id}:${prAction.waitReason}`);
|
|
626
|
+
if (prAction.stop) continue;
|
|
627
|
+
|
|
631
628
|
// Past here we act on the conflict signal — skip when the host couldn't assess it
|
|
632
629
|
// (undefined): never flag/clear a conflict on incomplete data.
|
|
633
630
|
if (p.conflict === undefined) continue;
|
|
@@ -681,17 +678,11 @@ async function scanWorkspaceConflicts(): Promise<Record<string, unknown>> {
|
|
|
681
678
|
}
|
|
682
679
|
}
|
|
683
680
|
|
|
684
|
-
return { scanned: candidates.length, flagged, cleared, merged, notifiedStewards };
|
|
681
|
+
return { scanned: candidates.length, flagged, cleared, merged, notifiedStewards, reviewerArmed, reviewerChangesRequested, relayPrMerged, relayPrWaiting };
|
|
685
682
|
}
|
|
686
683
|
|
|
687
|
-
// Deterministic auto-land (Layer 0,
|
|
688
|
-
//
|
|
689
|
-
// `relay_workspace_ready`, or `review_requested` from a failed-merge retry) and
|
|
690
|
-
// land any whose merge is predicted conflict-free, via the shared lease-serialized
|
|
691
|
-
// merge helper — even
|
|
692
|
-
// when the base moved on (behind>0): mergeRebaseFf rebases onto the current base
|
|
693
|
-
// before fast-forwarding. Only a predicted conflict or an unknown merge state is
|
|
694
|
-
// left for the steward; clean parallel work lands with no agent in the loop.
|
|
684
|
+
// Deterministic auto-land (Layer 0, #167/#207/#242): land conflict-free ready
|
|
685
|
+
// work under the shared lease, rebasing when base moved; leave uncertainty to stewards.
|
|
695
686
|
async function autoMergeCleanFastForwards(): Promise<Record<string, unknown>> {
|
|
696
687
|
if (process.env.AGENT_RELAY_WORKSPACE_AUTO_MERGE === "0") return { skipped: "disabled" };
|
|
697
688
|
const orchestrators = listOrchestrators().filter((orch) => orch.status === "online" && orch.apiUrl);
|
|
@@ -704,10 +695,12 @@ async function autoMergeCleanFastForwards(): Promise<Record<string, unknown>> {
|
|
|
704
695
|
const merged: string[] = [];
|
|
705
696
|
const heldByLease: string[] = [];
|
|
706
697
|
const heldByClaim: string[] = [];
|
|
698
|
+
const heldAwaitingApproval: string[] = [];
|
|
707
699
|
const leftForSteward: string[] = [];
|
|
708
700
|
const wokeStewards: string[] = [];
|
|
709
701
|
|
|
710
702
|
for (const ws of candidates) {
|
|
703
|
+
if (isAwaitingReviewerApproval(ws)) { heldAwaitingApproval.push(ws.id); continue; }
|
|
711
704
|
// A claimed workspace is being validated by a steward — don't race it (#208).
|
|
712
705
|
if (workspaceActiveClaim(ws)) { heldByClaim.push(ws.id); continue; }
|
|
713
706
|
const orch = orchestrators.find((candidate) => workspacePathWithinBase(ws.sourceCwd, candidate.baseDir));
|
|
@@ -764,7 +757,7 @@ async function autoMergeCleanFastForwards(): Promise<Record<string, unknown>> {
|
|
|
764
757
|
});
|
|
765
758
|
}
|
|
766
759
|
|
|
767
|
-
return { scanned: candidates.length, merged, heldByLease, heldByClaim, leftForSteward, wokeStewards };
|
|
760
|
+
return { scanned: candidates.length, merged, heldByLease, heldByClaim, heldAwaitingApproval, leftForSteward, wokeStewards };
|
|
768
761
|
}
|
|
769
762
|
|
|
770
763
|
// Send a system DM, swallowing failures (a stale/missing/misconfigured target
|