agent-relay-server 0.126.0 → 0.127.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +1 -1
- package/package.json +3 -3
- package/public/assets/{MessageBubble-B6YgulnX.js → MessageBubble-DnXnkt4a.js} +2 -2
- package/public/assets/{MessageBubble-B6YgulnX.js.map → MessageBubble-DnXnkt4a.js.map} +1 -1
- package/public/assets/{PlanGraphPanel--DNxHn1T.js → PlanGraphPanel-D6CPEpQD.js} +2 -2
- package/public/assets/{PlanGraphPanel--DNxHn1T.js.map → PlanGraphPanel-D6CPEpQD.js.map} +1 -1
- package/public/assets/{activity-Fo5aLYD6.js → activity-ZPwHcPLK.js} +2 -2
- package/public/assets/{activity-Fo5aLYD6.js.map → activity-ZPwHcPLK.js.map} +1 -1
- package/public/assets/{agent-profiles-C3p0xclG.js → agent-profiles-CT0E9LV1.js} +2 -2
- package/public/assets/{agent-profiles-C3p0xclG.js.map → agent-profiles-CT0E9LV1.js.map} +1 -1
- package/public/assets/{agents-_GBQyF5b.js → agents-Dt5TncjC.js} +2 -2
- package/public/assets/{agents-_GBQyF5b.js.map → agents-Dt5TncjC.js.map} +1 -1
- package/public/assets/{automation-Cf_Ewi9f.js → automation-yzUJbvT2.js} +2 -2
- package/public/assets/{automation-Cf_Ewi9f.js.map → automation-yzUJbvT2.js.map} +1 -1
- package/public/assets/chat-DhYX9BDG.js +2 -0
- package/public/assets/chat-DhYX9BDG.js.map +1 -0
- package/public/assets/{formatted-body-jjhFXTap.js → formatted-body-D5-1aiyB.js} +3 -3
- package/public/assets/{formatted-body-jjhFXTap.js.map → formatted-body-D5-1aiyB.js.map} +1 -1
- package/public/assets/{formatted-body-impl-gOLBKeyW.js → formatted-body-impl-Do5NbJUA.js} +2 -2
- package/public/assets/{formatted-body-impl-gOLBKeyW.js.map → formatted-body-impl-Do5NbJUA.js.map} +1 -1
- package/public/assets/{index-CgH1BZwq.js → index-BdNafiPz.js} +6 -6
- package/public/assets/{index-CgH1BZwq.js.map → index-BdNafiPz.js.map} +1 -1
- package/public/assets/{insights-CTW85An9.js → insights-i6AEZT_a.js} +2 -2
- package/public/assets/{insights-CTW85An9.js.map → insights-i6AEZT_a.js.map} +1 -1
- package/public/assets/{maintenance-D5PEygZz.js → maintenance-CJ6wikpg.js} +2 -2
- package/public/assets/{maintenance-D5PEygZz.js.map → maintenance-CJ6wikpg.js.map} +1 -1
- package/public/assets/{managed-agents-pkpAzZy8.js → managed-agents-JZn0xNJR.js} +3 -3
- package/public/assets/managed-agents-JZn0xNJR.js.map +1 -0
- package/public/assets/{markdown-preview-impl-DMzFE-Lo.js → markdown-preview-impl-BsvAkpR4.js} +2 -2
- package/public/assets/{markdown-preview-impl-DMzFE-Lo.js.map → markdown-preview-impl-BsvAkpR4.js.map} +1 -1
- package/public/assets/{memory-BnquVYQz.js → memory-BE9IwCdC.js} +2 -2
- package/public/assets/{memory-BnquVYQz.js.map → memory-BE9IwCdC.js.map} +1 -1
- package/public/assets/{messages-DgxLlndC.js → messages-vXgdc3H4.js} +2 -2
- package/public/assets/{messages-DgxLlndC.js.map → messages-vXgdc3H4.js.map} +1 -1
- package/public/assets/{orchestrators-BaWxeAlK.js → orchestrators-CribhLg-.js} +2 -2
- package/public/assets/{orchestrators-BaWxeAlK.js.map → orchestrators-CribhLg-.js.map} +1 -1
- package/public/assets/{overview-Kvs-j2gg.js → overview-q3O9K9VK.js} +2 -2
- package/public/assets/{overview-Kvs-j2gg.js.map → overview-q3O9K9VK.js.map} +1 -1
- package/public/assets/{pairs-eDzbqcER.js → pairs-BNOnd0IE.js} +2 -2
- package/public/assets/{pairs-eDzbqcER.js.map → pairs-BNOnd0IE.js.map} +1 -1
- package/public/assets/{prompt-settings-CjcqBG_r.js → prompt-settings-Bdz_xCgD.js} +2 -2
- package/public/assets/{prompt-settings-CjcqBG_r.js.map → prompt-settings-Bdz_xCgD.js.map} +1 -1
- package/public/assets/{registry-C1DSnjjg.js → registry-DPcyGypi.js} +2 -2
- package/public/assets/{registry-C1DSnjjg.js.map → registry-DPcyGypi.js.map} +1 -1
- package/public/assets/{security-VbEe_jwm.js → security-Z3l3u-Pl.js} +2 -2
- package/public/assets/{security-VbEe_jwm.js.map → security-Z3l3u-Pl.js.map} +1 -1
- package/public/assets/{select-CVfwgfKl.js → select-BX_lW49D.js} +2 -2
- package/public/assets/{select-CVfwgfKl.js.map → select-BX_lW49D.js.map} +1 -1
- package/public/assets/{settings-ujA9hZW1.js → settings-Y381jZt7.js} +2 -2
- package/public/assets/{settings-ujA9hZW1.js.map → settings-Y381jZt7.js.map} +1 -1
- package/public/assets/{tasks-BtbKaa-b.js → tasks-Di6d0_Ch.js} +2 -2
- package/public/assets/{tasks-BtbKaa-b.js.map → tasks-Di6d0_Ch.js.map} +1 -1
- package/public/assets/{teams-CrVDQ99v.js → teams-DJv4l-_l.js} +2 -2
- package/public/assets/{teams-CrVDQ99v.js.map → teams-DJv4l-_l.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-Aays_e7-.js → terminal-viewer-impl-3zBLpRel.js} +2 -2
- package/public/assets/{terminal-viewer-impl-Aays_e7-.js.map → terminal-viewer-impl-3zBLpRel.js.map} +1 -1
- package/public/assets/types-BqeSkWrv.js.map +1 -1
- package/public/assets/{work-queue-DDSGZO4Z.js → work-queue-D73-hP0c.js} +2 -2
- package/public/assets/{work-queue-DDSGZO4Z.js.map → work-queue-D73-hP0c.js.map} +1 -1
- package/public/index.html +2 -2
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +2 -2
- package/runner/src/provisioning.ts +121 -0
- package/runner/src/relay-mcp-proxy.ts +1 -3
- package/src/db/index.ts +1 -0
- package/src/db/task-deliverables.ts +206 -0
- package/src/db/task-entity.ts +4 -8
- package/src/mcp/index.ts +4 -4
- package/src/mcp/tools-workspace.ts +3 -36
- package/src/mcp-task-deliver-tools.ts +143 -0
- package/src/notification-rules.ts +1 -0
- package/src/notification-types.ts +2 -0
- package/src/prompt-resolver.ts +6 -6
- package/src/routes/agent-sessions.ts +4 -1
- package/src/routes/agents.ts +3 -0
- package/src/services/task-delivery.ts +275 -0
- package/src/services/task-entity.ts +32 -1
- package/src/workspace-actions.ts +4 -2
- package/src/workspace-phase.ts +5 -5
- package/public/assets/chat-BdbIWZpQ.js +0 -2
- package/public/assets/chat-BdbIWZpQ.js.map +0 -1
- package/public/assets/managed-agents-pkpAzZy8.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,gt as t,jn as n,rr as r,yn as i}from"./lucide-react-NXHPIMsX.js";import{p as a,t as o}from"./store-D-Frk_EC.js";import{K as s,ct as c,d as l}from"./display-aTnwsqQh.js";import{t as u}from"./badge-BCvp7LK3.js";import{t as d}from"./button-DyHjsHY3.js";import{At as f,G as p,wt as m}from"./index-
|
|
2
|
-
//# sourceMappingURL=work-queue-
|
|
1
|
+
import{a as e,gt as t,jn as n,rr as r,yn as i}from"./lucide-react-NXHPIMsX.js";import{p as a,t as o}from"./store-D-Frk_EC.js";import{K as s,ct as c,d as l}from"./display-aTnwsqQh.js";import{t as u}from"./badge-BCvp7LK3.js";import{t as d}from"./button-DyHjsHY3.js";import{At as f,G as p,wt as m}from"./index-BdNafiPz.js";import{r as h,t as g}from"./card-BoqYxpVW.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(),n=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=m(),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)(t,{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(t=>(0,_.jsx)(b,{item:t,now:e,agentName:e=>n[e]?l(n[e]):e.slice(-10),onClaim:()=>w(t),onCancel:()=>T(t),onEvents:t.task?()=>E(t):void 0},t.id))]})})]})}function b({item:t,now:r,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[t.severity]||c.info,m=v[t.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:t.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:t.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:t.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:t.status}),t.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`})]}),t.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:t.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[t.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(i,{className:`w-3 h-3 inline mr-1`}),a(t.owner)]}),t.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:t.source}),(0,_.jsxs)(`span`,{title:String(t.updatedAt),children:[(0,_.jsx)(n,{className:`w-3 h-3 inline mr-1`}),s(r,t.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[t.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)(e,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
|
|
2
|
+
//# sourceMappingURL=work-queue-D73-hP0c.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-DDSGZO4Z.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":"wXAWM,EAAwC,CAC5C,KAAM,kDACN,QAAS,wDACT,QAAS,2DACT,YAAa,wDACd,CAED,SAAgB,GAAgB,CAC9B,IAAM,EAAM,GAAQ,CACd,EAAa,EAAe,GAAM,EAAE,WAAW,CAC/C,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAM,EAAe,GAAM,EAAE,IAAI,CACjC,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAiB,EAAe,GAAM,EAAE,eAAe,CACvD,EAAY,EAAe,GAAM,EAAE,UAAU,CAC7C,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAkB,EAAe,GAAM,EAAE,gBAAgB,CACzD,EAAqB,EAAe,GAAM,EAAE,mBAAmB,CAE/D,EAAgB,GAAkB,CAClC,EAAQ,GAAmB,CAE3B,EAAiB,EAAM,OAAQ,GAAM,EAAE,UAAU,CAAC,OAExD,SAAS,EAAY,EAAqB,CACxC,GAAI,CAAC,EAAQ,KAAM,CAAE,EAAU,aAAc,mCAAmC,CAAE,OAC9E,EAAK,aAAe,QAAU,EAAK,KAAM,EAAY,EAAK,KAAK,GAAG,CAC7D,EAAK,aAAe,WAAa,EAAK,SAAS,EAAQ,EAAK,QAAQ,GAAG,CAGlF,SAAS,EAAa,EAAqB,CAEzC,EAAY,cAAe,WADb,EAAK,QAAU,EAAK,aAAe,OAAS,IAAI,EAAK,KAAO,YAAY,EAAK,MAC/C,+BAAkC,CACxE,EAAK,aAAe,QAAU,EAAK,KAAM,EAAmB,EAAK,KAAM,WAAW,CAC7E,EAAK,aAAe,WAAa,EAAK,SAAS,EAAgB,EAAK,QAAQ,GAAG,EACxF,CAGJ,SAAS,EAAa,EAAqB,CACrC,EAAK,MAAM,EAAe,EAAK,KAAK,CAG1C,OACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qBAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,6DAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,gCAAkC,CAAA,EACxD,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,iCAAwB,aAAe,CAAA,CACpD,EAAiB,IAChB,EAAA,EAAA,MAAC,EAAD,CAAO,UAAU,wEAAjB,CACG,EAAe,aACV,GAEN,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,QAAD,CAAO,UAAU,kDAAyC,WAAgB,CAAA,EAC1E,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,mEACV,MAAO,EAAQ,KACf,SAAW,GAAM,EAAI,CAAE,QAAS,CAAE,GAAG,EAAS,KAAM,EAAE,OAAO,MAAO,CAAE,CAAC,UAHzE,EAKE,EAAA,EAAA,KAAC,SAAD,CAAQ,MAAM,YAAG,kBAAwB,CAAA,CACxC,EAAc,IAAK,IAAM,EAAA,EAAA,KAAC,SAAD,CAAmB,MAAO,EAAE,YAAK,EAAY,EAAE,CAAU,CAA5C,EAAE,GAA0C,CAAC,CAC7E,GACL,GACF,IAEN,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,mCACpB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,CACG,EAAM,SAAW,IAChB,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,2DAAkD,sBAAyB,CAAA,CAE3F,EAAM,IAAK,IACV,EAAA,EAAA,KAAC,EAAD,CAEQ,OACD,MACL,UAAY,GAAO,EAAW,GAAM,EAAY,EAAW,GAAI,CAAG,EAAG,MAAM,IAAI,CAC/E,YAAe,EAAY,EAAK,CAChC,aAAgB,EAAa,EAAK,CAClC,SAAU,EAAK,SAAa,EAAa,EAAK,CAAG,IAAA,GACjD,CAPK,EAAK,GAOV,CACF,CACE,GACK,CAAA,CACT,GAIV,SAAS,EAAc,CACrB,OAAM,MAAK,YAAW,UAAS,WAAU,YAQxC,CACD,IAAM,EAAgB,EAAgB,EAAK,WAAa,EAAgB,KAClE,EAAc,EAAc,EAAK,SAAW,kDAElD,OACE,EAAA,EAAA,KAAC,EAAD,CAAM,UAAW,EAAK,UAAY,4BAA8B,aAC9D,EAAA,EAAA,KAAC,EAAD,CAAa,UAAU,gBACrB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kCAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kDAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,wCAAgC,EAAK,MAAa,CAAA,EAClE,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAkB,EAAK,SAAiB,CAAA,EAC1G,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAgB,EAAK,OAAe,CAAA,CACrG,EAAK,aAAe,YACnB,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAU,uEAA8D,MAAW,CAAA,CAE1G,GACL,EAAK,OACJ,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,2DAAmD,EAAK,KAAS,CAAA,EAEhF,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2EAAf,CACG,EAAK,QACJ,EAAA,EAAA,MAAC,OAAD,CAAM,MAAM,iBAAZ,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,sBAAwB,CAAA,CAAC,EAAU,EAAK,MAAM,CACjE,GAER,EAAK,SACJ,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,qBAAa,EAAK,OAAc,CAAA,EAElD,EAAA,EAAA,MAAC,OAAD,CAAM,MAAO,OAAO,EAAK,UAAU,UAAnC,EACE,EAAA,EAAA,KAAC,EAAD,CAAU,UAAU,sBAAwB,CAAA,CAAC,EAAQ,EAAK,EAAK,UAAU,CACpE,GACH,GACF,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,wCAAf,CACG,EAAK,YACJ,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,UAAU,2DAA2D,QAAS,WAAhG,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,mBAAqB,CAAA,CAAA,SACtC,GAEV,IACC,EAAA,EAAA,KAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,cAAc,QAAS,WAAU,SAEtE,CAAA,EAEX,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,oFAAoF,QAAS,WAA3I,EACE,EAAA,EAAA,KAAC,EAAD,CAAG,UAAU,mBAAqB,CAAA,CAAA,UAC3B,GACL,GACF,GACM,CAAA,CACT,CAAA"}
|
|
1
|
+
{"version":3,"file":"work-queue-D73-hP0c.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":"wXAWM,EAAwC,CAC5C,KAAM,kDACN,QAAS,wDACT,QAAS,2DACT,YAAa,wDACd,CAED,SAAgB,GAAgB,CAC9B,IAAM,EAAM,GAAQ,CACd,EAAa,EAAe,GAAM,EAAE,WAAW,CAC/C,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAM,EAAe,GAAM,EAAE,IAAI,CACjC,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAiB,EAAe,GAAM,EAAE,eAAe,CACvD,EAAY,EAAe,GAAM,EAAE,UAAU,CAC7C,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAkB,EAAe,GAAM,EAAE,gBAAgB,CACzD,EAAqB,EAAe,GAAM,EAAE,mBAAmB,CAE/D,EAAgB,GAAkB,CAClC,EAAQ,GAAmB,CAE3B,EAAiB,EAAM,OAAQ,GAAM,EAAE,UAAU,CAAC,OAExD,SAAS,EAAY,EAAqB,CACxC,GAAI,CAAC,EAAQ,KAAM,CAAE,EAAU,aAAc,mCAAmC,CAAE,OAC9E,EAAK,aAAe,QAAU,EAAK,KAAM,EAAY,EAAK,KAAK,GAAG,CAC7D,EAAK,aAAe,WAAa,EAAK,SAAS,EAAQ,EAAK,QAAQ,GAAG,CAGlF,SAAS,EAAa,EAAqB,CAEzC,EAAY,cAAe,WADb,EAAK,QAAU,EAAK,aAAe,OAAS,IAAI,EAAK,KAAO,YAAY,EAAK,MAC/C,+BAAkC,CACxE,EAAK,aAAe,QAAU,EAAK,KAAM,EAAmB,EAAK,KAAM,WAAW,CAC7E,EAAK,aAAe,WAAa,EAAK,SAAS,EAAgB,EAAK,QAAQ,GAAG,EACxF,CAGJ,SAAS,EAAa,EAAqB,CACrC,EAAK,MAAM,EAAe,EAAK,KAAK,CAG1C,OACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qBAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,6DAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,gCAAkC,CAAA,EACxD,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,iCAAwB,aAAe,CAAA,CACpD,EAAiB,IAChB,EAAA,EAAA,MAAC,EAAD,CAAO,UAAU,wEAAjB,CACG,EAAe,aACV,GAEN,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,QAAD,CAAO,UAAU,kDAAyC,WAAgB,CAAA,EAC1E,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,mEACV,MAAO,EAAQ,KACf,SAAW,GAAM,EAAI,CAAE,QAAS,CAAE,GAAG,EAAS,KAAM,EAAE,OAAO,MAAO,CAAE,CAAC,UAHzE,EAKE,EAAA,EAAA,KAAC,SAAD,CAAQ,MAAM,YAAG,kBAAwB,CAAA,CACxC,EAAc,IAAK,IAAM,EAAA,EAAA,KAAC,SAAD,CAAmB,MAAO,EAAE,YAAK,EAAY,EAAE,CAAU,CAA5C,EAAE,GAA0C,CAAC,CAC7E,GACL,GACF,IAEN,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,mCACpB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,CACG,EAAM,SAAW,IAChB,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,2DAAkD,sBAAyB,CAAA,CAE3F,EAAM,IAAK,IACV,EAAA,EAAA,KAAC,EAAD,CAEQ,OACD,MACL,UAAY,GAAO,EAAW,GAAM,EAAY,EAAW,GAAI,CAAG,EAAG,MAAM,IAAI,CAC/E,YAAe,EAAY,EAAK,CAChC,aAAgB,EAAa,EAAK,CAClC,SAAU,EAAK,SAAa,EAAa,EAAK,CAAG,IAAA,GACjD,CAPK,EAAK,GAOV,CACF,CACE,GACK,CAAA,CACT,GAIV,SAAS,EAAc,CACrB,OAAM,MAAK,YAAW,UAAS,WAAU,YAQxC,CACD,IAAM,EAAgB,EAAgB,EAAK,WAAa,EAAgB,KAClE,EAAc,EAAc,EAAK,SAAW,kDAElD,OACE,EAAA,EAAA,KAAC,EAAD,CAAM,UAAW,EAAK,UAAY,4BAA8B,aAC9D,EAAA,EAAA,KAAC,EAAD,CAAa,UAAU,gBACrB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kCAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kDAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,wCAAgC,EAAK,MAAa,CAAA,EAClE,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAkB,EAAK,SAAiB,CAAA,EAC1G,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAgB,EAAK,OAAe,CAAA,CACrG,EAAK,aAAe,YACnB,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAU,uEAA8D,MAAW,CAAA,CAE1G,GACL,EAAK,OACJ,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,2DAAmD,EAAK,KAAS,CAAA,EAEhF,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2EAAf,CACG,EAAK,QACJ,EAAA,EAAA,MAAC,OAAD,CAAM,MAAM,iBAAZ,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,sBAAwB,CAAA,CAAC,EAAU,EAAK,MAAM,CACjE,GAER,EAAK,SACJ,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,qBAAa,EAAK,OAAc,CAAA,EAElD,EAAA,EAAA,MAAC,OAAD,CAAM,MAAO,OAAO,EAAK,UAAU,UAAnC,EACE,EAAA,EAAA,KAAC,EAAD,CAAU,UAAU,sBAAwB,CAAA,CAAC,EAAQ,EAAK,EAAK,UAAU,CACpE,GACH,GACF,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,wCAAf,CACG,EAAK,YACJ,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,UAAU,2DAA2D,QAAS,WAAhG,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,mBAAqB,CAAA,CAAA,SACtC,GAEV,IACC,EAAA,EAAA,KAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,cAAc,QAAS,WAAU,SAEtE,CAAA,EAEX,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,oFAAoF,QAAS,WAA3I,EACE,EAAA,EAAA,KAAC,EAAD,CAAG,UAAU,mBAAqB,CAAA,CAAA,UAC3B,GACL,GACF,GACM,CAAA,CACT,CAAA"}
|
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-BdNafiPz.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-NXHPIMsX.js">
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<link rel="modulepreload" crossorigin href="./assets/agent-type-icon-BmBR-9CH.js">
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/button-DyHjsHY3.js">
|
|
38
38
|
<link rel="modulepreload" crossorigin href="./assets/copy-button-C67FUG3E.js">
|
|
39
|
-
<link rel="modulepreload" crossorigin href="./assets/formatted-body-
|
|
39
|
+
<link rel="modulepreload" crossorigin href="./assets/formatted-body-D5-1aiyB.js">
|
|
40
40
|
<link rel="modulepreload" crossorigin href="./assets/task-chip-CeujtXYT.js">
|
|
41
41
|
<link rel="modulepreload" crossorigin href="./assets/badge-BCvp7LK3.js">
|
|
42
42
|
<link rel="modulepreload" crossorigin href="./assets/input-Rc4oWv4q.js">
|
|
@@ -56,8 +56,8 @@ var RUNNER_PROMPT_TEMPLATE_DEFAULTS = [
|
|
|
56
56
|
category: "runner",
|
|
57
57
|
title: "Runner Workspace Lifecycle Note",
|
|
58
58
|
description: "Lifecycle note injected by the runner for isolated workspaces.",
|
|
59
|
-
template: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done,
|
|
60
|
-
defaultTemplate: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done,
|
|
59
|
+
template: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, call `relay_task_deliver` with disposition `complete`, a summary, commits, and passing `typecheck` + `test` gates. Relay records the typed delivery on the Task, then rebases onto the latest {{baseRef}}, lands your work, and pushes for you.", "After `relay_task_deliver` accepts a complete delivery, the workspace status becomes `ready` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after delivery and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
|
|
60
|
+
defaultTemplate: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, call `relay_task_deliver` with disposition `complete`, a summary, commits, and passing `typecheck` + `test` gates. Relay records the typed delivery on the Task, then rebases onto the latest {{baseRef}}, lands your work, and pushes for you.", "After `relay_task_deliver` accepts a complete delivery, the workspace status becomes `ready` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after delivery and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
|
|
61
61
|
variables: ["branch", "baseRef"]
|
|
62
62
|
},
|
|
63
63
|
{
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
4
|
import { sanitizeFsName } from "agent-relay-sdk/fs-name";
|
|
@@ -355,6 +356,126 @@ export function writeCodexHooksJson(codexHome: string, hooks: ProvisioningHookSe
|
|
|
355
356
|
return;
|
|
356
357
|
}
|
|
357
358
|
writeFileSync(join(codexHome, "hooks.json"), JSON.stringify({ hooks }, null, 2), { mode: 0o600 });
|
|
359
|
+
writeCodexHookTrustState(codexHome, hooks);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const CODEX_HOOK_EVENT_STATE_KEYS: Record<string, string> = {
|
|
363
|
+
PreToolUse: "pre_tool_use",
|
|
364
|
+
PermissionRequest: "permission_request",
|
|
365
|
+
PostToolUse: "post_tool_use",
|
|
366
|
+
PreCompact: "pre_compact",
|
|
367
|
+
PostCompact: "post_compact",
|
|
368
|
+
SessionStart: "session_start",
|
|
369
|
+
UserPromptSubmit: "user_prompt_submit",
|
|
370
|
+
SubagentStart: "subagent_start",
|
|
371
|
+
SubagentStop: "subagent_stop",
|
|
372
|
+
Stop: "stop",
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
interface CodexHookTrustEntry {
|
|
376
|
+
key: string;
|
|
377
|
+
trustedHash: string;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function codexHookTrustEntries(codexHome: string, hooks: ProvisioningHookSet): CodexHookTrustEntry[] {
|
|
381
|
+
const sourcePath = join(codexHome, "hooks.json");
|
|
382
|
+
const entries: CodexHookTrustEntry[] = [];
|
|
383
|
+
for (const [eventName, groups] of Object.entries(hooks)) {
|
|
384
|
+
const eventKey = CODEX_HOOK_EVENT_STATE_KEYS[eventName];
|
|
385
|
+
if (!eventKey) continue;
|
|
386
|
+
groups.forEach((group, groupIndex) => {
|
|
387
|
+
group.hooks.forEach((handler, handlerIndex) => {
|
|
388
|
+
if (handler.type !== "command") return;
|
|
389
|
+
entries.push({
|
|
390
|
+
key: `${sourcePath}:${eventKey}:${groupIndex}:${handlerIndex}`,
|
|
391
|
+
trustedHash: codexCommandHookHash(eventKey, group.matcher, group, handler),
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return entries;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function writeCodexHookTrustState(codexHome: string, hooks: ProvisioningHookSet): void {
|
|
400
|
+
const entries = codexHookTrustEntries(codexHome, hooks);
|
|
401
|
+
if (!entries.length) return;
|
|
402
|
+
const configPath = join(codexHome, "config.toml");
|
|
403
|
+
let config = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
404
|
+
for (const entry of entries) {
|
|
405
|
+
config = upsertCodexHookTrustEntry(config, entry);
|
|
406
|
+
}
|
|
407
|
+
writeFileSync(configPath, config, { mode: 0o600 });
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function upsertCodexHookTrustEntry(config: string, entry: CodexHookTrustEntry): string {
|
|
411
|
+
const header = `[hooks.state.${tomlQuotedString(entry.key)}]`;
|
|
412
|
+
const lines = config.length ? config.split(/\n/) : [];
|
|
413
|
+
const headerIndex = lines.findIndex((line) => line.trim() === header);
|
|
414
|
+
if (headerIndex < 0) {
|
|
415
|
+
const prefix = config.endsWith("\n") || config.length === 0 ? config : `${config}\n`;
|
|
416
|
+
return `${prefix}\n${header}\ntrusted_hash = ${tomlQuotedString(entry.trustedHash)}\n`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
let nextHeaderIndex = lines.length;
|
|
420
|
+
for (let i = headerIndex + 1; i < lines.length; i += 1) {
|
|
421
|
+
if (/^\s*\[.*\]\s*$/.test(lines[i] ?? "")) {
|
|
422
|
+
nextHeaderIndex = i;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const trustedHashLine = `trusted_hash = ${tomlQuotedString(entry.trustedHash)}`;
|
|
428
|
+
const existingIndex = lines
|
|
429
|
+
.slice(headerIndex + 1, nextHeaderIndex)
|
|
430
|
+
.findIndex((line) => /^\s*trusted_hash\s*=/.test(line));
|
|
431
|
+
if (existingIndex >= 0) {
|
|
432
|
+
lines[headerIndex + 1 + existingIndex] = trustedHashLine;
|
|
433
|
+
} else {
|
|
434
|
+
lines.splice(nextHeaderIndex, 0, trustedHashLine);
|
|
435
|
+
}
|
|
436
|
+
return lines.join("\n");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function codexCommandHookHash(
|
|
440
|
+
eventKey: string,
|
|
441
|
+
matcher: string | undefined,
|
|
442
|
+
group: ProvisioningHookSet[string][number],
|
|
443
|
+
handler: ProvisioningHookSet[string][number]["hooks"][number],
|
|
444
|
+
): string {
|
|
445
|
+
// Mirrors Codex's command_hook_hash: hash the normalized hook identity, not the source file.
|
|
446
|
+
const normalizedHandler: Record<string, unknown> = {
|
|
447
|
+
type: "command",
|
|
448
|
+
command: handler.command,
|
|
449
|
+
async: false,
|
|
450
|
+
timeout: Math.max(1, handler.timeout ?? 600),
|
|
451
|
+
};
|
|
452
|
+
if (handler.statusMessage !== undefined) normalizedHandler.statusMessage = handler.statusMessage;
|
|
453
|
+
|
|
454
|
+
const identity: Record<string, unknown> = {
|
|
455
|
+
event_name: eventKey,
|
|
456
|
+
hooks: [normalizedHandler],
|
|
457
|
+
};
|
|
458
|
+
if (matcher !== undefined) identity.matcher = matcher;
|
|
459
|
+
|
|
460
|
+
const canonical = canonicalJson(identity);
|
|
461
|
+
const hash = createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
462
|
+
return `sha256:${hash}`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function canonicalJson(value: unknown): unknown {
|
|
466
|
+
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
467
|
+
if (value && typeof value === "object") {
|
|
468
|
+
return Object.fromEntries(
|
|
469
|
+
Object.entries(value as Record<string, unknown>)
|
|
470
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
471
|
+
.map(([key, val]) => [key, canonicalJson(val)]),
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return value;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function tomlQuotedString(value: string): string {
|
|
478
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
358
479
|
}
|
|
359
480
|
|
|
360
481
|
// #1096 — write relay-owned settings into an ISOLATED Claude managed home's `settings.json`
|
|
@@ -43,7 +43,7 @@ const SSE_KEEPALIVE_MS = 25_000;
|
|
|
43
43
|
const DEFAULT_BUFFERABLE_TOOLS = new Set<string>([
|
|
44
44
|
"relay_send_message",
|
|
45
45
|
"relay_reply",
|
|
46
|
-
"
|
|
46
|
+
"relay_task_deliver",
|
|
47
47
|
]);
|
|
48
48
|
|
|
49
49
|
// Tools surfaced ONLY to an agent that owns a live isolated worktree. For any other agent the
|
|
@@ -51,12 +51,10 @@ const DEFAULT_BUFFERABLE_TOOLS = new Set<string>([
|
|
|
51
51
|
// would return them). This is the context the coarse token scope can't express (#214/#215).
|
|
52
52
|
const WORKTREE_ONLY_TOOLS = new Set<string>([
|
|
53
53
|
"relay_workspace_status",
|
|
54
|
-
"relay_workspace_ready",
|
|
55
54
|
"relay_workspace_deps",
|
|
56
55
|
"relay_workspace_list",
|
|
57
56
|
"relay_workspace_claim",
|
|
58
57
|
"relay_workspace_release",
|
|
59
|
-
"relay_workspace_land",
|
|
60
58
|
]);
|
|
61
59
|
|
|
62
60
|
interface ProxyContext {
|
package/src/db/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./agents.ts";
|
|
|
10
10
|
export * from "./agent-search.ts";
|
|
11
11
|
export * from "./tasks.ts";
|
|
12
12
|
export * from "./task-entity.ts";
|
|
13
|
+
export * from "./task-deliverables.ts";
|
|
13
14
|
export * from "./task-memory.ts";
|
|
14
15
|
export * from "./task-comment-through.ts";
|
|
15
16
|
export * from "./integrations.ts";
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TASK_DELIVERY_DISPOSITIONS,
|
|
3
|
+
TASK_GATE_STATUSES,
|
|
4
|
+
type TaskCommitRef,
|
|
5
|
+
type TaskBlocker,
|
|
6
|
+
type TaskDeliverableEventMetadata,
|
|
7
|
+
type TaskDeliveryDisposition,
|
|
8
|
+
type TaskDeliveryGateReports,
|
|
9
|
+
type TaskDeliveryResult,
|
|
10
|
+
type TaskDeliverySelfAcceptance,
|
|
11
|
+
type TaskEvent,
|
|
12
|
+
type TaskGateStatus,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import { parseJson } from "../utils";
|
|
15
|
+
import { getDb, ValidationError } from "./connection.ts";
|
|
16
|
+
import { rowToTaskEvent } from "./mappers.ts";
|
|
17
|
+
import { DELIVERABLE_TASK_KIND, insertTaskEvent } from "./task-core.ts";
|
|
18
|
+
|
|
19
|
+
function requireText(value: unknown, field: string, max: number): string {
|
|
20
|
+
if (typeof value !== "string" || !value.trim()) throw new ValidationError(`${field} required`);
|
|
21
|
+
const cleaned = value.trim();
|
|
22
|
+
if (cleaned.length > max) throw new ValidationError(`${field} must be ${max} characters or fewer`);
|
|
23
|
+
return cleaned;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function optionalText(value: unknown, field: string, max: number): string | undefined {
|
|
27
|
+
if (value === undefined || value === null) return undefined;
|
|
28
|
+
if (typeof value !== "string") throw new ValidationError(`${field} must be a string`);
|
|
29
|
+
const cleaned = value.trim();
|
|
30
|
+
if (!cleaned) return undefined;
|
|
31
|
+
if (cleaned.length > max) throw new ValidationError(`${field} must be ${max} characters or fewer`);
|
|
32
|
+
return cleaned;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function taskCommitRows(id: number): TaskCommitRef[] {
|
|
40
|
+
const row = getDb().query("SELECT kind, commits FROM tasks WHERE id = ?").get(id) as { kind: string; commits: string | null } | undefined;
|
|
41
|
+
if (!row) throw new ValidationError(`task ${id} not found`);
|
|
42
|
+
if (row.kind !== DELIVERABLE_TASK_KIND) throw new ValidationError(`task ${id} is not a deliverable task`);
|
|
43
|
+
return parseJson<TaskCommitRef[]>(row.commits, []);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function appendCommitRow(id: number, commit: TaskCommitRef): void {
|
|
47
|
+
const sha = requireText(commit.sha, "commit.sha", 80);
|
|
48
|
+
const subject = optionalText(commit.subject, "commit.subject", 1000);
|
|
49
|
+
const commits = taskCommitRows(id);
|
|
50
|
+
if (commits.some((c) => c.sha === sha)) return;
|
|
51
|
+
commits.push({ sha, ...(subject ? { subject } : {}) });
|
|
52
|
+
getDb().query("UPDATE tasks SET commits = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(commits), Date.now(), id);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function cleanCommitRefs(value: unknown): TaskCommitRef[] | undefined {
|
|
56
|
+
if (value === undefined || value === null) return undefined;
|
|
57
|
+
if (!Array.isArray(value)) throw new ValidationError("commits must be an array");
|
|
58
|
+
const commits = value.map((entry, index) => {
|
|
59
|
+
if (!isObject(entry)) throw new ValidationError(`commits[${index}] must be an object`);
|
|
60
|
+
return {
|
|
61
|
+
sha: requireText(entry.sha, `commits[${index}].sha`, 80),
|
|
62
|
+
...(optionalText(entry.subject, `commits[${index}].subject`, 1000) ? { subject: optionalText(entry.subject, `commits[${index}].subject`, 1000)! } : {}),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
return commits.length > 0 ? commits : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cleanGateReport(value: unknown, field: string): TaskDeliveryGateReports["typecheck"] {
|
|
69
|
+
if (value === undefined || value === null) return undefined;
|
|
70
|
+
if (!isObject(value)) throw new ValidationError(`${field} must be an object`);
|
|
71
|
+
if (!TASK_GATE_STATUSES.includes(value.status as TaskGateStatus)) throw new ValidationError(`${field}.status must be one of: ${TASK_GATE_STATUSES.join(", ")}`);
|
|
72
|
+
const count = value.count;
|
|
73
|
+
if (count !== undefined && (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0)) {
|
|
74
|
+
throw new ValidationError(`${field}.count must be a non-negative integer`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
status: value.status as TaskGateStatus,
|
|
78
|
+
...(optionalText(value.detail, `${field}.detail`, 4000) ? { detail: optionalText(value.detail, `${field}.detail`, 4000)! } : {}),
|
|
79
|
+
...(typeof count === "number" ? { count } : {}),
|
|
80
|
+
...(optionalText(value.runId, `${field}.runId`, 240) ? { runId: optionalText(value.runId, `${field}.runId`, 240)! } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cleanGateReports(value: unknown): TaskDeliveryGateReports | undefined {
|
|
85
|
+
if (value === undefined || value === null) return undefined;
|
|
86
|
+
if (!isObject(value)) throw new ValidationError("gates must be an object");
|
|
87
|
+
const out: TaskDeliveryGateReports = {};
|
|
88
|
+
const typecheck = cleanGateReport(value.typecheck, "gates.typecheck");
|
|
89
|
+
const test = cleanGateReport(value.test, "gates.test");
|
|
90
|
+
if (typecheck) out.typecheck = typecheck;
|
|
91
|
+
if (test) out.test = test;
|
|
92
|
+
if (value.other !== undefined) {
|
|
93
|
+
if (!Array.isArray(value.other)) throw new ValidationError("gates.other must be an array");
|
|
94
|
+
const other = value.other.map((entry, index) => {
|
|
95
|
+
if (!isObject(entry)) throw new ValidationError(`gates.other[${index}] must be an object`);
|
|
96
|
+
const gate = cleanGateReport(entry, `gates.other[${index}]`)!;
|
|
97
|
+
return { name: requireText(entry.name, `gates.other[${index}].name`, 120), ...gate };
|
|
98
|
+
});
|
|
99
|
+
if (other.length > 0) out.other = other;
|
|
100
|
+
}
|
|
101
|
+
return out.typecheck || out.test || out.other?.length ? out : undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function cleanRisks(value: unknown): string[] | undefined {
|
|
105
|
+
if (value === undefined || value === null) return undefined;
|
|
106
|
+
if (!Array.isArray(value)) throw new ValidationError("risks must be an array");
|
|
107
|
+
const risks = value.map((entry, index) => requireText(entry, `risks[${index}]`, 4000));
|
|
108
|
+
return risks.length > 0 ? risks : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function cleanBlockedBy(value: unknown): TaskBlocker[] | undefined {
|
|
112
|
+
if (value === undefined || value === null) return undefined;
|
|
113
|
+
if (!Array.isArray(value)) throw new ValidationError("blockedBy must be an array");
|
|
114
|
+
const entries = value.map((entry, index) => {
|
|
115
|
+
if (!isObject(entry)) throw new ValidationError(`blockedBy[${index}] must be an object`);
|
|
116
|
+
const type = entry.type as TaskBlocker["type"];
|
|
117
|
+
if (type !== "dep" && type !== "gate" && type !== "decision" && type !== "external") {
|
|
118
|
+
throw new ValidationError(`blockedBy[${index}].type must be one of: dep, gate, decision, external`);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
type,
|
|
122
|
+
...(optionalText(entry.ref, `blockedBy[${index}].ref`, 240) ? { ref: optionalText(entry.ref, `blockedBy[${index}].ref`, 240)! } : {}),
|
|
123
|
+
...(optionalText(entry.note, `blockedBy[${index}].note`, 2000) ? { note: optionalText(entry.note, `blockedBy[${index}].note`, 2000)! } : {}),
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
return entries.length > 0 ? entries : undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function cleanSelfAcceptance(value: unknown): TaskDeliverySelfAcceptance[] | undefined {
|
|
130
|
+
if (value === undefined || value === null) return undefined;
|
|
131
|
+
if (!Array.isArray(value)) throw new ValidationError("selfAcceptance must be an array");
|
|
132
|
+
const entries = value.map((entry, index) => {
|
|
133
|
+
if (!isObject(entry)) throw new ValidationError(`selfAcceptance[${index}] must be an object`);
|
|
134
|
+
if (entry.status !== "met" && entry.status !== "unmet") throw new ValidationError(`selfAcceptance[${index}].status must be met or unmet`);
|
|
135
|
+
return {
|
|
136
|
+
criterion: requireText(entry.criterion, `selfAcceptance[${index}].criterion`, 1000),
|
|
137
|
+
status: entry.status as TaskDeliverySelfAcceptance["status"],
|
|
138
|
+
...(optionalText(entry.note, `selfAcceptance[${index}].note`, 4000) ? { note: optionalText(entry.note, `selfAcceptance[${index}].note`, 4000)! } : {}),
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
return entries.length > 0 ? entries : undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface RecordTaskDeliverableInput extends TaskDeliveryResult {
|
|
145
|
+
actor: string;
|
|
146
|
+
workspaceId?: string;
|
|
147
|
+
idempotencyKey?: string;
|
|
148
|
+
land?: TaskDeliverableEventMetadata["land"];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function recordTaskDeliverable(id: number, input: RecordTaskDeliverableInput): { event: TaskEvent; created: boolean } {
|
|
152
|
+
taskCommitRows(id);
|
|
153
|
+
const actor = requireText(input.actor, "actor", 200);
|
|
154
|
+
const idempotencyKey = optionalText(input.idempotencyKey, "idempotencyKey", 240);
|
|
155
|
+
if (idempotencyKey) {
|
|
156
|
+
const existing = getDb()
|
|
157
|
+
.query("SELECT * FROM task_events WHERE task_id = ? AND type = 'deliverable' AND json_extract(metadata, '$.idempotencyKey') = ? LIMIT 1")
|
|
158
|
+
.get(id, idempotencyKey) as any;
|
|
159
|
+
if (existing) return { event: rowToTaskEvent(existing), created: false };
|
|
160
|
+
}
|
|
161
|
+
if (!TASK_DELIVERY_DISPOSITIONS.includes(input.disposition as TaskDeliveryDisposition)) {
|
|
162
|
+
throw new ValidationError(`disposition must be one of: ${TASK_DELIVERY_DISPOSITIONS.join(", ")}`);
|
|
163
|
+
}
|
|
164
|
+
const commits = cleanCommitRefs(input.commits);
|
|
165
|
+
const gates = cleanGateReports(input.gates);
|
|
166
|
+
const reason = optionalText(input.reason, "reason", 64_000);
|
|
167
|
+
const blockedBy = cleanBlockedBy(input.blockedBy);
|
|
168
|
+
const filesChanged = input.filesChanged;
|
|
169
|
+
if (filesChanged !== undefined && (typeof filesChanged !== "number" || !Number.isSafeInteger(filesChanged) || filesChanged < 0)) {
|
|
170
|
+
throw new ValidationError("filesChanged must be a non-negative integer");
|
|
171
|
+
}
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
const metadata: TaskDeliverableEventMetadata = {
|
|
174
|
+
v: 1,
|
|
175
|
+
disposition: input.disposition,
|
|
176
|
+
summary: requireText(input.summary, "summary", 1_000_000),
|
|
177
|
+
actor,
|
|
178
|
+
...(reason ? { reason } : {}),
|
|
179
|
+
...(blockedBy ? { blockedBy } : {}),
|
|
180
|
+
...(commits ? { commits } : {}),
|
|
181
|
+
...(gates ? { gates } : {}),
|
|
182
|
+
...(typeof filesChanged === "number" ? { filesChanged } : {}),
|
|
183
|
+
...(optionalText(input.rca, "rca", 64_000) ? { rca: optionalText(input.rca, "rca", 64_000)! } : {}),
|
|
184
|
+
...(optionalText(input.testCoverage, "testCoverage", 64_000) ? { testCoverage: optionalText(input.testCoverage, "testCoverage", 64_000)! } : {}),
|
|
185
|
+
...(cleanRisks(input.risks) ? { risks: cleanRisks(input.risks)! } : {}),
|
|
186
|
+
...(cleanSelfAcceptance(input.selfAcceptance) ? { selfAcceptance: cleanSelfAcceptance(input.selfAcceptance)! } : {}),
|
|
187
|
+
...(optionalText(input.workspaceId, "workspaceId", 240) ? { workspaceId: optionalText(input.workspaceId, "workspaceId", 240)! } : {}),
|
|
188
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
189
|
+
...(input.land ? { land: input.land } : {}),
|
|
190
|
+
};
|
|
191
|
+
const event = getDb().transaction(() => {
|
|
192
|
+
if (commits) {
|
|
193
|
+
for (const commit of commits) appendCommitRow(id, commit);
|
|
194
|
+
}
|
|
195
|
+
getDb().query("UPDATE tasks SET updated_at = ?, last_seen_at = ? WHERE id = ?").run(now, now, id);
|
|
196
|
+
return insertTaskEvent(id, {
|
|
197
|
+
source: actor,
|
|
198
|
+
type: "deliverable",
|
|
199
|
+
severity: input.disposition === "failed" || input.disposition === "blocked" ? "warning" : "info",
|
|
200
|
+
title: `Delivery result: ${input.disposition}`,
|
|
201
|
+
body: metadata.summary,
|
|
202
|
+
metadata: metadata as unknown as Record<string, unknown>,
|
|
203
|
+
}, now);
|
|
204
|
+
})();
|
|
205
|
+
return { event, created: true };
|
|
206
|
+
}
|
package/src/db/task-entity.ts
CHANGED
|
@@ -322,14 +322,7 @@ export function setTaskStage(id: number, input: SetTaskStageInput): TaskDetail {
|
|
|
322
322
|
return getTaskDetail(id)!;
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
export interface RecordGateInput {
|
|
326
|
-
gate: string;
|
|
327
|
-
status: TaskGateStatus;
|
|
328
|
-
detail?: string;
|
|
329
|
-
commitSha?: string;
|
|
330
|
-
runId?: string;
|
|
331
|
-
actor?: string;
|
|
332
|
-
}
|
|
325
|
+
export interface RecordGateInput { gate: string; status: TaskGateStatus; detail?: string; count?: number; commitSha?: string; runId?: string; actor?: string; }
|
|
333
326
|
|
|
334
327
|
// Quality-gate ledger write (#834). The append-only `task_events` IS the ledger (no second
|
|
335
328
|
// system of record). Idempotent by runId: the SAME run reported twice does not double-append,
|
|
@@ -341,6 +334,7 @@ export function recordGate(id: number, input: RecordGateInput): TaskGateState {
|
|
|
341
334
|
if (!TASK_GATE_STATUSES.includes(input.status)) {
|
|
342
335
|
throw new ValidationError(`gate status must be one of: ${TASK_GATE_STATUSES.join(", ")}`);
|
|
343
336
|
}
|
|
337
|
+
if (input.count !== undefined && (!Number.isSafeInteger(input.count) || input.count < 0)) throw new ValidationError("count must be a non-negative integer");
|
|
344
338
|
const type = `${GATE_EVENT_PREFIX}${gate}`;
|
|
345
339
|
const runId = optionalText(input.runId, "runId", 240);
|
|
346
340
|
if (runId) {
|
|
@@ -360,6 +354,7 @@ export function recordGate(id: number, input: RecordGateInput): TaskGateState {
|
|
|
360
354
|
gate,
|
|
361
355
|
status: input.status,
|
|
362
356
|
...(input.detail ? { detail: input.detail } : {}),
|
|
357
|
+
...(typeof input.count === "number" ? { count: input.count } : {}),
|
|
363
358
|
...(input.commitSha ? { commitSha: input.commitSha } : {}),
|
|
364
359
|
...(runId ? { runId } : {}),
|
|
365
360
|
},
|
|
@@ -373,6 +368,7 @@ function gateStateFromEvent(row: { type: string; created_at: number; metadata: s
|
|
|
373
368
|
gate: typeof meta.gate === "string" ? meta.gate : row.type.slice(GATE_EVENT_PREFIX.length),
|
|
374
369
|
status: (meta.status as TaskGateStatus) ?? "fail",
|
|
375
370
|
...(typeof meta.detail === "string" ? { detail: meta.detail } : {}),
|
|
371
|
+
...(typeof meta.count === "number" ? { count: meta.count } : {}),
|
|
376
372
|
...(typeof meta.commitSha === "string" ? { commitSha: meta.commitSha } : {}),
|
|
377
373
|
...(typeof meta.runId === "string" ? { runId: meta.runId } : {}),
|
|
378
374
|
ranAt: row.created_at,
|
package/src/mcp/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { McpAuthError, McpNotFoundError } from "../mcp-errors";
|
|
|
6
6
|
import { createActivityEvent, ValidationError } from "../db";
|
|
7
7
|
import { PLAN_TOOLS, relayPlanTool } from "../mcp-plan-tools";
|
|
8
8
|
import { TASK_TOOLS, relayTaskTool } from "../mcp-task-tools";
|
|
9
|
+
import { TASK_DELIVER_TOOLS, relayTaskDeliverTool } from "../mcp-task-deliver-tools";
|
|
9
10
|
import { TASK_MEMORY_TOOLS, relayTaskMemoryTool } from "../mcp-task-memory-tools";
|
|
10
11
|
import { DISPATCH_TOOLS, relayDispatchTool } from "../mcp-dispatch-tools";
|
|
11
12
|
import { TASK_WEAVE_TOOLS, relayTaskWeaveTool } from "../mcp-task-weave-tools";
|
|
@@ -37,7 +38,7 @@ const TOOLS: ToolDefinition[] = [
|
|
|
37
38
|
AGENT_ACTION_TOOL,
|
|
38
39
|
...WORKSPACE_TOOLS,
|
|
39
40
|
...MERGE_TOOLS, ...TEAM_TOOLS,
|
|
40
|
-
...PLAN_TOOLS, ...TASK_TOOLS, ...TASK_MEMORY_TOOLS, ...DISPATCH_TOOLS, ...TASK_WEAVE_TOOLS, ...RELEASE_TOOLS, ...ISSUE_REF_TOOLS, ...ISSUE_ASSOCIATION_TOOLS, ...ISSUE_ACTIVITY_TOOLS, ...ISSUE_LIFECYCLE_TOOLS,
|
|
41
|
+
...PLAN_TOOLS, ...TASK_TOOLS, ...TASK_DELIVER_TOOLS, ...TASK_MEMORY_TOOLS, ...DISPATCH_TOOLS, ...TASK_WEAVE_TOOLS, ...RELEASE_TOOLS, ...ISSUE_REF_TOOLS, ...ISSUE_ASSOCIATION_TOOLS, ...ISSUE_ACTIVITY_TOOLS, ...ISSUE_LIFECYCLE_TOOLS,
|
|
41
42
|
...BLACKBOARD_TOOLS,
|
|
42
43
|
...PROJECT_TOOLS,
|
|
43
44
|
...MEMORY_TOOLS,
|
|
@@ -52,7 +53,7 @@ export const RELAY_TOOL_GROUPS: Record<string, readonly string[]> = {
|
|
|
52
53
|
core: ["relay_whoami", "relay_send_message", "relay_reply", "relay_get_message", "relay_get_thread", "relay_find_agents"],
|
|
53
54
|
memory: ["relay_recall", "relay_compact_and_resume", "relay_memory_write", "relay_memory_promote", "relay_memory_retract", "relay_memory_read", "relay_memory_search"],
|
|
54
55
|
artifacts: ["relay_upload_artifact", "relay_attach_artifact"],
|
|
55
|
-
workspace: ["relay_workspace_status", "
|
|
56
|
+
workspace: ["relay_workspace_status", "relay_workspace_deps", "relay_workspace_list", "relay_workspace_claim", "relay_workspace_release", "relay_task_deliver", "relay_merge_pause", "relay_merge_pauses", "relay_merge_renew", "relay_merge_resume"],
|
|
56
57
|
"spawn-lifecycle": ["relay_spawn_agent", "relay_spawn_targets", "relay_my_agents", "relay_shutdown_agent", "relay_agent_action", "relay_agent_status", "relay_agent_crash_report"],
|
|
57
58
|
schedule: ["relay_schedule_timer", "relay_list_timers", "relay_cancel_timer"],
|
|
58
59
|
plans: ["relay_plan_create", "relay_plan_add_node", "relay_plan_update_node", "relay_plan_add_edge", "relay_plan_remove_node", "relay_plan_remove_edge", "relay_plan_archive", "relay_plan_delete", "relay_plan_get"],
|
|
@@ -167,13 +168,12 @@ async function callTool(auth: McpAuthContext, params: unknown): Promise<Record<s
|
|
|
167
168
|
else if (name === "relay_agent_action") result = relayAgentAction(auth, args);
|
|
168
169
|
else if (name === "relay_workspace_status") result = await relayWorkspaceStatus(auth, args);
|
|
169
170
|
else if (name === "relay_workspace_list") result = relayWorkspaceList(auth, args);
|
|
170
|
-
else if (name === "relay_workspace_ready") result = await relayWorkspaceMutation(auth, "ready", args);
|
|
171
171
|
else if (name === "relay_workspace_deps") result = await relayWorkspaceMutation(auth, "deps-refresh", args);
|
|
172
172
|
else if (name === "relay_workspace_claim") result = await relayWorkspaceMutation(auth, "claim", args);
|
|
173
173
|
else if (name === "relay_workspace_release") result = await relayWorkspaceMutation(auth, "release-claim", args);
|
|
174
|
-
else if (name === "relay_workspace_land") result = await relayWorkspaceMutation(auth, "merge", args);
|
|
175
174
|
else if (name.startsWith("relay_merge_")) result = relayMergeTool(auth, name, args); else if (name.startsWith("relay_team_")) result = await relayTeamTool(auth, name, args);
|
|
176
175
|
else if (name.startsWith("relay_plan_")) result = relayPlanTool(auth, name, args);
|
|
176
|
+
else if (name === "relay_task_deliver") result = await relayTaskDeliverTool(auth, name, args);
|
|
177
177
|
else if (name.startsWith("relay_task_memory_")) result = relayTaskMemoryTool(auth, name, args);
|
|
178
178
|
else if (name === "relay_task_dispatch") result = relayDispatchTool(auth, name, args);
|
|
179
179
|
else if (name === "relay_task_comment" || name === "relay_task_close_bridge") result = await relayTaskWeaveTool(auth, name, args);
|