agent-relay-server 0.123.2 → 0.124.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 +3 -3
- package/public/assets/{MessageBubble-NCggGKTA.js → MessageBubble-SQK8-Mzp.js} +2 -2
- package/public/assets/{MessageBubble-NCggGKTA.js.map → MessageBubble-SQK8-Mzp.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-CSdszmGk.js → PlanGraphPanel-CS-aow4S.js} +2 -2
- package/public/assets/{PlanGraphPanel-CSdszmGk.js.map → PlanGraphPanel-CS-aow4S.js.map} +1 -1
- package/public/assets/{activity-Cyp2nolX.js → activity-W31XvhC2.js} +2 -2
- package/public/assets/{activity-Cyp2nolX.js.map → activity-W31XvhC2.js.map} +1 -1
- package/public/assets/{agent-profiles-D5Izg0Vn.js → agent-profiles-DXysA9To.js} +2 -2
- package/public/assets/{agent-profiles-D5Izg0Vn.js.map → agent-profiles-DXysA9To.js.map} +1 -1
- package/public/assets/{agents-DIOxuJ6M.js → agents-B5GRNRnN.js} +2 -2
- package/public/assets/{agents-DIOxuJ6M.js.map → agents-B5GRNRnN.js.map} +1 -1
- package/public/assets/{automation-Bn6mOLAR.js → automation-DYlgHYcb.js} +2 -2
- package/public/assets/{automation-Bn6mOLAR.js.map → automation-DYlgHYcb.js.map} +1 -1
- package/public/assets/{chat-eUaPz399.js → chat-Clz6qOtn.js} +2 -2
- package/public/assets/{chat-eUaPz399.js.map → chat-Clz6qOtn.js.map} +1 -1
- package/public/assets/{formatted-body-CvrQ2D1J.js → formatted-body-BYQ-SPtj.js} +3 -3
- package/public/assets/{formatted-body-CvrQ2D1J.js.map → formatted-body-BYQ-SPtj.js.map} +1 -1
- package/public/assets/{formatted-body-impl-BJ7jdbpR.js → formatted-body-impl-L1EMnNW6.js} +2 -2
- package/public/assets/{formatted-body-impl-BJ7jdbpR.js.map → formatted-body-impl-L1EMnNW6.js.map} +1 -1
- package/public/assets/{index-DnUo0Zqg.js → index-0uIGXT14.js} +6 -6
- package/public/assets/{index-DnUo0Zqg.js.map → index-0uIGXT14.js.map} +1 -1
- package/public/assets/index-DOJ7KidA.css +2 -0
- package/public/assets/{insights-CQGH5LhD.js → insights-DwZxq1Dn.js} +2 -2
- package/public/assets/{insights-CQGH5LhD.js.map → insights-DwZxq1Dn.js.map} +1 -1
- package/public/assets/{maintenance-7c7UvpAt.js → maintenance-COcmUDnf.js} +2 -2
- package/public/assets/{maintenance-7c7UvpAt.js.map → maintenance-COcmUDnf.js.map} +1 -1
- package/public/assets/{managed-agents-yh4HPO5a.js → managed-agents-D9-dgMeX.js} +2 -2
- package/public/assets/{managed-agents-yh4HPO5a.js.map → managed-agents-D9-dgMeX.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-BuOn5-Uc.js → markdown-preview-impl-DH372bL-.js} +2 -2
- package/public/assets/{markdown-preview-impl-BuOn5-Uc.js.map → markdown-preview-impl-DH372bL-.js.map} +1 -1
- package/public/assets/{memory-Qv0JN8CE.js → memory-B9hRK8sk.js} +2 -2
- package/public/assets/{memory-Qv0JN8CE.js.map → memory-B9hRK8sk.js.map} +1 -1
- package/public/assets/{messages-CgjHJmc2.js → messages-BHkHlXzF.js} +2 -2
- package/public/assets/{messages-CgjHJmc2.js.map → messages-BHkHlXzF.js.map} +1 -1
- package/public/assets/{orchestrators-CgL4CFzb.js → orchestrators-CXy3SDSn.js} +2 -2
- package/public/assets/{orchestrators-CgL4CFzb.js.map → orchestrators-CXy3SDSn.js.map} +1 -1
- package/public/assets/{overview-Bri04eYE.js → overview-D2yRyCQq.js} +2 -2
- package/public/assets/{overview-Bri04eYE.js.map → overview-D2yRyCQq.js.map} +1 -1
- package/public/assets/{pairs-C78LwUH2.js → pairs-5tXdHylI.js} +2 -2
- package/public/assets/{pairs-C78LwUH2.js.map → pairs-5tXdHylI.js.map} +1 -1
- package/public/assets/{prompt-settings-DDEkMqPZ.js → prompt-settings-Bn5mmi9w.js} +2 -2
- package/public/assets/{prompt-settings-DDEkMqPZ.js.map → prompt-settings-Bn5mmi9w.js.map} +1 -1
- package/public/assets/registry-C3sB7qv5.js +4 -0
- package/public/assets/registry-C3sB7qv5.js.map +1 -0
- package/public/assets/{security-wvnRKFvm.js → security-CmD-YE-_.js} +2 -2
- package/public/assets/{security-wvnRKFvm.js.map → security-CmD-YE-_.js.map} +1 -1
- package/public/assets/{select-BIfbVmLf.js → select-C0UlhK2k.js} +2 -2
- package/public/assets/{select-BIfbVmLf.js.map → select-C0UlhK2k.js.map} +1 -1
- package/public/assets/{settings-B6Ntahx7.js → settings-DrqMG5nG.js} +2 -2
- package/public/assets/{settings-B6Ntahx7.js.map → settings-DrqMG5nG.js.map} +1 -1
- package/public/assets/{tasks-CUglafo5.js → tasks-ChQjtXHu.js} +2 -2
- package/public/assets/{tasks-CUglafo5.js.map → tasks-ChQjtXHu.js.map} +1 -1
- package/public/assets/{teams-EE8RHhHM.js → teams-itjNlDLp.js} +2 -2
- package/public/assets/{teams-EE8RHhHM.js.map → teams-itjNlDLp.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DBRkdyag.js → terminal-viewer-impl-B9M6s2vB.js} +2 -2
- package/public/assets/{terminal-viewer-impl-DBRkdyag.js.map → terminal-viewer-impl-B9M6s2vB.js.map} +1 -1
- package/public/assets/{work-queue-BFQVVL4T.js → work-queue-Kw0uMkEF.js} +2 -2
- package/public/assets/{work-queue-BFQVVL4T.js.map → work-queue-Kw0uMkEF.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/db/provisioning-registry.ts +174 -1
- package/src/routes/provisioning-registry.ts +3 -0
- package/src/routing-policy-store.ts +55 -0
- package/src/spawn-targets.ts +130 -28
- package/public/assets/index-DcmG5gLR.css +0 -2
- package/public/assets/registry-5KhP5FE5.js +0 -2
- package/public/assets/registry-5KhP5FE5.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-CoU_Xts2.js";import{p as a,t as o}from"./store-zFLzd0Jd.js";import{K as s,ct as c,d as l}from"./display-GFbGUG0O.js";import{t as u}from"./badge-XEru6wto.js";import{t as d}from"./button-C3CQSr4p.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-CoU_Xts2.js";import{p as a,t as o}from"./store-zFLzd0Jd.js";import{K as s,ct as c,d as l}from"./display-GFbGUG0O.js";import{t as u}from"./badge-XEru6wto.js";import{t as d}from"./button-C3CQSr4p.js";import{At as f,G as p,wt as m}from"./index-0uIGXT14.js";import{r as h,t as g}from"./card-C5XTIPvq.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-Kw0uMkEF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-BFQVVL4T.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `T${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"work-queue-Kw0uMkEF.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `T${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"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"}
|
package/public/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%2309090b'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='%2358a6ff'/%3E%3Ccircle cx='6' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='6' cy='24' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='24' r='2.5' fill='%233fb950'/%3E%3Cline x1='8' y1='9.5' x2='13' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='9.5' x2='19' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='8' y1='22.5' x2='13' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='22.5' x2='19' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3C/svg%3E">
|
|
13
13
|
<link rel="manifest" href="manifest.webmanifest">
|
|
14
14
|
<link rel="apple-touch-icon" href="icons/agent-relay-192.png">
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-0uIGXT14.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-CoU_Xts2.js">
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
<link rel="modulepreload" crossorigin href="./assets/agent-type-icon-BxUopALO.js">
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/button-C3CQSr4p.js">
|
|
38
38
|
<link rel="modulepreload" crossorigin href="./assets/copy-button-Bil2CjzT.js">
|
|
39
|
-
<link rel="modulepreload" crossorigin href="./assets/formatted-body-
|
|
39
|
+
<link rel="modulepreload" crossorigin href="./assets/formatted-body-BYQ-SPtj.js">
|
|
40
40
|
<link rel="modulepreload" crossorigin href="./assets/task-chip-BHM-e3I8.js">
|
|
41
41
|
<link rel="modulepreload" crossorigin href="./assets/badge-XEru6wto.js">
|
|
42
42
|
<link rel="modulepreload" crossorigin href="./assets/input-CXMbJ848.js">
|
|
43
43
|
<link rel="modulepreload" crossorigin href="./assets/themes-DjSwmAZN.js">
|
|
44
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
44
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DOJ7KidA.css">
|
|
45
45
|
</head>
|
|
46
46
|
<body class="bg-background text-foreground antialiased">
|
|
47
47
|
<div id="root"></div>
|
|
@@ -10,7 +10,12 @@ import { errMessage, isRecord, SPAWN_PROVIDERS } from "agent-relay-sdk";
|
|
|
10
10
|
import type {
|
|
11
11
|
AgentProfileProvider,
|
|
12
12
|
HookAssetDefinition,
|
|
13
|
+
McpApplicability,
|
|
13
14
|
McpAssetDefinition,
|
|
15
|
+
McpEnvValue,
|
|
16
|
+
McpHealthProbe,
|
|
17
|
+
McpRunConfig,
|
|
18
|
+
McpSetupRecipe,
|
|
14
19
|
PluginAssetDefinition,
|
|
15
20
|
ProvisioningCapability,
|
|
16
21
|
ProvisioningAsset,
|
|
@@ -41,6 +46,158 @@ const BUNDLED_SOURCE_TYPE = "bundled";
|
|
|
41
46
|
// (src/db/provisioning-callmux.ts) when splitting literals from placeholders.
|
|
42
47
|
export const SHARED_CALLMUX_CONFIG_ENV_ALLOWLIST: Readonly<Record<string, readonly string[]>> = { vent: ["VENT_REPO"] };
|
|
43
48
|
|
|
49
|
+
// #1030 slice 1 (#1110) — MCP setup recipe / run config validation.
|
|
50
|
+
const MCP_SETUP_KINDS = ["npm-global", "download", "bundled"] as const;
|
|
51
|
+
const MCP_TRANSPORTS = ["stdio", "ws", "http"] as const;
|
|
52
|
+
const MCP_HEALTH_PROBE_KINDS = ["tcp", "http", "none"] as const;
|
|
53
|
+
const MCP_LISTENER_MODES = ["shared", "per-agent"] as const;
|
|
54
|
+
// Exact pin only — no ranges ("^1.2.3"), tags ("latest", "next"), or partial versions
|
|
55
|
+
// ("1.2"). The same recipe must reproduce byte-for-byte on every host.
|
|
56
|
+
const EXACT_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
57
|
+
const SHA256_HEX = /^[a-f0-9]{64}$/i;
|
|
58
|
+
// callmux's own secret-ref grammar (`env:NAME`, `file:/path`), plus `secret:<name>` for
|
|
59
|
+
// the relay-owned secret store a follow-on issue (#1111) resolves. A ref is stored and
|
|
60
|
+
// validated for shape only in this slice — no resolution (env lookup / file read /
|
|
61
|
+
// secret-store fetch) happens here.
|
|
62
|
+
const MCP_ENV_REF_SCHEMES = ["secret", "env", "file"] as const;
|
|
63
|
+
// Content-shaped credential detection for literal runConfig.env values (#1030 review
|
|
64
|
+
// steer): a value that looks like a real token/key must be rejected in favor of a
|
|
65
|
+
// `secret:<name>` reference — the registry never carries a resolved secret.
|
|
66
|
+
const INLINE_SECRET_CONTENT_PATTERNS: RegExp[] = [
|
|
67
|
+
/-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/i,
|
|
68
|
+
/\bgh[pousr]_[A-Za-z0-9_]{20,}\b/,
|
|
69
|
+
/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/,
|
|
70
|
+
/\bsk-[A-Za-z0-9_-]{20,}\b/,
|
|
71
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
72
|
+
/^Bearer\s+\S{16,}$/i,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
function looksLikeInlineSecret(value: string): boolean {
|
|
76
|
+
if (INLINE_SECRET_CONTENT_PATTERNS.some((pattern) => pattern.test(value))) return true;
|
|
77
|
+
// Crude high-entropy heuristic: a long token-shaped string mixing upper/lower/digit.
|
|
78
|
+
return value.length >= 24 && /^[A-Za-z0-9_.-]+$/.test(value) && /[0-9]/.test(value) && /[A-Z]/.test(value) && /[a-z]/.test(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function assertMcpSetupRecipe(value: unknown): McpSetupRecipe {
|
|
82
|
+
if (!isRecord(value)) throw new ValidationError("mcp setup recipe must be an object");
|
|
83
|
+
const kind = value.kind;
|
|
84
|
+
if (kind === "npm-global") {
|
|
85
|
+
const pkg = value.pkg;
|
|
86
|
+
const version = value.version;
|
|
87
|
+
if (typeof pkg !== "string" || !pkg.trim()) throw new ValidationError("mcp setup npm-global requires pkg");
|
|
88
|
+
if (typeof version !== "string" || !EXACT_SEMVER.test(version)) {
|
|
89
|
+
throw new ValidationError("mcp setup npm-global requires an exact pinned version (e.g. 1.2.3), not a range or tag");
|
|
90
|
+
}
|
|
91
|
+
return { kind, pkg, version };
|
|
92
|
+
}
|
|
93
|
+
if (kind === "download") {
|
|
94
|
+
const url = value.url;
|
|
95
|
+
const sha256 = value.sha256;
|
|
96
|
+
const bin = value.bin;
|
|
97
|
+
if (typeof url !== "string" || !/^https:\/\//.test(url)) throw new ValidationError("mcp setup download requires an https url");
|
|
98
|
+
if (typeof sha256 !== "string" || !SHA256_HEX.test(sha256)) throw new ValidationError("mcp setup download requires a sha256 checksum (64 hex chars)");
|
|
99
|
+
if (typeof bin !== "string" || !bin.trim()) throw new ValidationError("mcp setup download requires bin");
|
|
100
|
+
return { kind, url, sha256: sha256.toLowerCase(), bin };
|
|
101
|
+
}
|
|
102
|
+
if (kind === "bundled") return { kind: "bundled" };
|
|
103
|
+
throw new ValidationError(`mcp setup recipe kind must be one of: ${MCP_SETUP_KINDS.join(", ")}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Splits a leading `<scheme>:<ref>` off an env value when scheme is one of the
|
|
107
|
+
// recognized MCP_ENV_REF_SCHEMES. Returns null for a bare literal (no colon, or a
|
|
108
|
+
// colon-containing literal like a Windows path whose prefix isn't a known scheme).
|
|
109
|
+
function parseMcpEnvRef(value: string): { scheme: (typeof MCP_ENV_REF_SCHEMES)[number]; ref: string } | null {
|
|
110
|
+
const colon = value.indexOf(":");
|
|
111
|
+
if (colon <= 0) return null;
|
|
112
|
+
const scheme = value.slice(0, colon);
|
|
113
|
+
if (!(MCP_ENV_REF_SCHEMES as readonly string[]).includes(scheme)) return null;
|
|
114
|
+
return { scheme: scheme as (typeof MCP_ENV_REF_SCHEMES)[number], ref: value.slice(colon + 1) };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function assertMcpRunConfigEnvValue(key: string, value: unknown): McpEnvValue {
|
|
118
|
+
if (!ENV_NAME.test(key)) throw new ValidationError(`mcp runConfig env key ${key} is not a valid env name`);
|
|
119
|
+
if (typeof value !== "string") throw new ValidationError(`mcp runConfig env ${key} must be a string (a literal, or a "secret:"/"env:"/"file:" reference)`);
|
|
120
|
+
const parsed = parseMcpEnvRef(value);
|
|
121
|
+
if (parsed) {
|
|
122
|
+
if (!parsed.ref.trim()) throw new ValidationError(`mcp runConfig env ${key} ${parsed.scheme}: reference must include a name/path`);
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
if (looksLikeInlineSecret(value) || (isSecretLikeEnvName(key) && !isEnvPlaceholder(value))) {
|
|
126
|
+
throw new ValidationError(`mcp runConfig env ${key} looks like an inline secret; use "secret:<name>" instead of a literal value`);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function assertMcpHealthProbe(value: unknown): McpHealthProbe {
|
|
132
|
+
if (!isRecord(value)) throw new ValidationError("mcp runConfig healthProbe must be an object");
|
|
133
|
+
const kind = value.kind;
|
|
134
|
+
if (kind !== "tcp" && kind !== "http" && kind !== "none") {
|
|
135
|
+
throw new ValidationError(`mcp runConfig healthProbe.kind must be one of: ${MCP_HEALTH_PROBE_KINDS.join(", ")}`);
|
|
136
|
+
}
|
|
137
|
+
const probe: McpHealthProbe = { kind };
|
|
138
|
+
if (value.path !== undefined) {
|
|
139
|
+
if (typeof value.path !== "string" || !value.path) throw new ValidationError("mcp runConfig healthProbe.path must be a non-empty string");
|
|
140
|
+
probe.path = value.path;
|
|
141
|
+
}
|
|
142
|
+
if (value.intervalMs !== undefined) {
|
|
143
|
+
if (typeof value.intervalMs !== "number" || !(value.intervalMs > 0)) throw new ValidationError("mcp runConfig healthProbe.intervalMs must be a positive number");
|
|
144
|
+
probe.intervalMs = value.intervalMs;
|
|
145
|
+
}
|
|
146
|
+
if (value.timeoutMs !== undefined) {
|
|
147
|
+
if (typeof value.timeoutMs !== "number" || !(value.timeoutMs > 0)) throw new ValidationError("mcp runConfig healthProbe.timeoutMs must be a positive number");
|
|
148
|
+
probe.timeoutMs = value.timeoutMs;
|
|
149
|
+
}
|
|
150
|
+
return probe;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function assertMcpRunConfig(value: unknown): McpRunConfig {
|
|
154
|
+
if (!isRecord(value)) throw new ValidationError("mcp runConfig must be an object");
|
|
155
|
+
const command = value.command;
|
|
156
|
+
if (typeof command !== "string" || !command.trim()) throw new ValidationError("mcp runConfig requires command");
|
|
157
|
+
const runConfig: McpRunConfig = { command, transport: "stdio", listenerMode: "shared" };
|
|
158
|
+
if (value.args !== undefined) {
|
|
159
|
+
if (!Array.isArray(value.args) || !value.args.every((arg) => typeof arg === "string")) throw new ValidationError("mcp runConfig args must be strings");
|
|
160
|
+
runConfig.args = value.args;
|
|
161
|
+
}
|
|
162
|
+
if (value.env !== undefined) {
|
|
163
|
+
if (!isRecord(value.env)) throw new ValidationError("mcp runConfig env must be an object");
|
|
164
|
+
const env: Record<string, McpEnvValue> = {};
|
|
165
|
+
for (const [key, raw] of Object.entries(value.env)) env[key] = assertMcpRunConfigEnvValue(key, raw);
|
|
166
|
+
runConfig.env = env;
|
|
167
|
+
}
|
|
168
|
+
const transport = value.transport;
|
|
169
|
+
if (transport !== "stdio" && transport !== "ws" && transport !== "http") {
|
|
170
|
+
throw new ValidationError(`mcp runConfig transport must be one of: ${MCP_TRANSPORTS.join(", ")}`);
|
|
171
|
+
}
|
|
172
|
+
runConfig.transport = transport;
|
|
173
|
+
if (value.healthProbe !== undefined) runConfig.healthProbe = assertMcpHealthProbe(value.healthProbe);
|
|
174
|
+
const listenerMode = value.listenerMode;
|
|
175
|
+
if (listenerMode !== "shared" && listenerMode !== "per-agent") {
|
|
176
|
+
throw new ValidationError(`mcp runConfig listenerMode must be one of: ${MCP_LISTENER_MODES.join(", ")}`);
|
|
177
|
+
}
|
|
178
|
+
runConfig.listenerMode = listenerMode;
|
|
179
|
+
return runConfig;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function assertMcpApplicability(value: unknown): McpApplicability {
|
|
183
|
+
if (!isRecord(value)) throw new ValidationError("mcp applicability must be an object");
|
|
184
|
+
const applicability: McpApplicability = {};
|
|
185
|
+
if (value.providers !== undefined) {
|
|
186
|
+
if (!Array.isArray(value.providers) || !value.providers.every((p) => typeof p === "string")) {
|
|
187
|
+
throw new ValidationError("mcp applicability.providers must be an array of strings");
|
|
188
|
+
}
|
|
189
|
+
for (const provider of value.providers) assertProvider(provider as AgentProfileProvider);
|
|
190
|
+
applicability.providers = value.providers as AgentProfileProvider[];
|
|
191
|
+
}
|
|
192
|
+
if (value.profiles !== undefined) {
|
|
193
|
+
if (!Array.isArray(value.profiles) || !value.profiles.every((p) => typeof p === "string" && p.trim())) {
|
|
194
|
+
throw new ValidationError("mcp applicability.profiles must be an array of non-empty strings");
|
|
195
|
+
}
|
|
196
|
+
applicability.profiles = value.profiles as string[];
|
|
197
|
+
}
|
|
198
|
+
return applicability;
|
|
199
|
+
}
|
|
200
|
+
|
|
44
201
|
interface ProvisioningAssetRow {
|
|
45
202
|
id: string;
|
|
46
203
|
capability_id?: string | null;
|
|
@@ -179,6 +336,9 @@ export interface PatchProvisioningAssetVariantInput {
|
|
|
179
336
|
|
|
180
337
|
export interface CreateMcpProvisioningVariantInput {
|
|
181
338
|
server: unknown;
|
|
339
|
+
setup?: unknown;
|
|
340
|
+
runConfig?: unknown;
|
|
341
|
+
applicability?: unknown;
|
|
182
342
|
enabled?: boolean;
|
|
183
343
|
approvalStatus?: "approved" | "pending";
|
|
184
344
|
versionConstraint?: string;
|
|
@@ -213,6 +373,9 @@ function assertDefinition(kind: ProvisioningAssetKind, definition: ResolvedAsset
|
|
|
213
373
|
assertMcpSecretPlaceholders(def.server, def.name);
|
|
214
374
|
const metadata = isRecord(def.metadata) ? def.metadata : {};
|
|
215
375
|
if (isRecord(metadata.callmuxServer)) assertCallmuxServerPlaceholders(metadata.callmuxServer as ProvisioningMcpServer, def.name);
|
|
376
|
+
if (def.setup !== undefined) assertMcpSetupRecipe(def.setup);
|
|
377
|
+
if (def.runConfig !== undefined) assertMcpRunConfig(def.runConfig);
|
|
378
|
+
if (def.applicability !== undefined) assertMcpApplicability(def.applicability);
|
|
216
379
|
}
|
|
217
380
|
if (kind === "hook") {
|
|
218
381
|
const def = definition as HookAssetDefinition;
|
|
@@ -873,11 +1036,21 @@ export function createMcpProvisioningVariant(
|
|
|
873
1036
|
const capability = getProvisioningCapabilityRowById(capabilityId);
|
|
874
1037
|
if (capability.kind !== "mcp") throw new ValidationError("MCP variants require an mcp capability");
|
|
875
1038
|
const server = coerceMcpServer(input.server);
|
|
1039
|
+
const setup = input.setup !== undefined ? assertMcpSetupRecipe(input.setup) : undefined;
|
|
1040
|
+
const runConfig = input.runConfig !== undefined ? assertMcpRunConfig(input.runConfig) : undefined;
|
|
1041
|
+
const applicability = input.applicability !== undefined ? assertMcpApplicability(input.applicability) : undefined;
|
|
876
1042
|
const asset = upsertProvisioningAsset({
|
|
877
1043
|
kind: "mcp",
|
|
878
1044
|
name: capability.slug,
|
|
879
1045
|
provider: "any",
|
|
880
|
-
definition: {
|
|
1046
|
+
definition: {
|
|
1047
|
+
kind: "mcp",
|
|
1048
|
+
name: capability.slug,
|
|
1049
|
+
server,
|
|
1050
|
+
...(setup ? { setup } : {}),
|
|
1051
|
+
...(runConfig ? { runConfig } : {}),
|
|
1052
|
+
...(applicability ? { applicability } : {}),
|
|
1053
|
+
},
|
|
881
1054
|
enabled: input.enabled,
|
|
882
1055
|
});
|
|
883
1056
|
const approvalStatus = input.approvalStatus ?? "approved";
|
|
@@ -134,6 +134,9 @@ export const postProvisioningVariantMcpRoute: Handler = async (req, params) => {
|
|
|
134
134
|
const body = expectObject(parsed.body);
|
|
135
135
|
const variant = createMcpProvisioningVariant(params.id!, {
|
|
136
136
|
server: body.server ?? body,
|
|
137
|
+
setup: body.setup,
|
|
138
|
+
runConfig: body.runConfig,
|
|
139
|
+
applicability: body.applicability,
|
|
137
140
|
enabled: typeof body.enabled === "boolean" ? body.enabled : undefined,
|
|
138
141
|
approvalStatus: optionalEnum(body.approvalStatus, "approvalStatus", APPROVAL_STATUSES, "approved"),
|
|
139
142
|
versionConstraint: cleanString(body.versionConstraint, "versionConstraint", { max: 120 }),
|
|
@@ -25,6 +25,8 @@ import type {
|
|
|
25
25
|
DispatchRule,
|
|
26
26
|
ProviderQuotaRoutingConfig,
|
|
27
27
|
QuotaBandThresholds,
|
|
28
|
+
QuotaPaceBandThresholds,
|
|
29
|
+
QuotaPacingConfig,
|
|
28
30
|
RoutingPolicy,
|
|
29
31
|
RoutingRule,
|
|
30
32
|
} from "agent-relay-sdk";
|
|
@@ -107,6 +109,7 @@ export function normalizeRoutingPolicy(value: unknown): RoutingPolicy {
|
|
|
107
109
|
rules: normalizeRules(input.rules),
|
|
108
110
|
...(input.quotaBands !== undefined ? { quotaBands: normalizeQuotaBands(input.quotaBands) } : {}),
|
|
109
111
|
...(input.providerQuota !== undefined ? { providerQuota: normalizeProviderQuota(input.providerQuota) } : {}),
|
|
112
|
+
...(input.quotaPacing !== undefined ? { quotaPacing: normalizeQuotaPacing(input.quotaPacing) } : {}),
|
|
110
113
|
...(input.dispatch !== undefined ? { dispatch: normalizeDispatchPolicy(input.dispatch) } : {}),
|
|
111
114
|
};
|
|
112
115
|
}
|
|
@@ -264,6 +267,58 @@ function normalizeQuotaBands(raw: unknown): Partial<QuotaBandThresholds> {
|
|
|
264
267
|
return result;
|
|
265
268
|
}
|
|
266
269
|
|
|
270
|
+
// #1098/#1105 — the budget-pacing controller's tunables. Same shape as normalizeQuotaBands: every
|
|
271
|
+
// field optional, so a partial PUT only overrides what it names and everything else keeps falling
|
|
272
|
+
// back to the provider-quota.ts defaults.
|
|
273
|
+
function normalizeQuotaPacing(raw: unknown): QuotaPacingConfig {
|
|
274
|
+
if (!isRecord(raw)) throw new ValidationError("quotaPacing must be an object");
|
|
275
|
+
const result: QuotaPacingConfig = {};
|
|
276
|
+
if (raw.delta !== undefined) {
|
|
277
|
+
if (typeof raw.delta !== "number" || !Number.isFinite(raw.delta) || raw.delta <= 0) {
|
|
278
|
+
throw new ValidationError("quotaPacing.delta must be a positive finite number");
|
|
279
|
+
}
|
|
280
|
+
result.delta = raw.delta;
|
|
281
|
+
}
|
|
282
|
+
if (raw.thresholds !== undefined) result.thresholds = normalizeQuotaPaceThresholds(raw.thresholds);
|
|
283
|
+
if (raw.windowLengthMs !== undefined) result.windowLengthMs = normalizeQuotaPaceWindowLengths(raw.windowLengthMs);
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function normalizeQuotaPaceThresholds(raw: unknown): Partial<QuotaPaceBandThresholds> {
|
|
288
|
+
if (!isRecord(raw)) throw new ValidationError("quotaPacing.thresholds must be an object");
|
|
289
|
+
const result: Partial<QuotaPaceBandThresholds> = {};
|
|
290
|
+
if (raw.preferAt !== undefined) result.preferAt = cleanFraction(raw.preferAt, "quotaPacing.thresholds.preferAt");
|
|
291
|
+
if (raw.normalAt !== undefined) result.normalAt = cleanFraction(raw.normalAt, "quotaPacing.thresholds.normalAt");
|
|
292
|
+
if (raw.cautionAt !== undefined) result.cautionAt = cleanFraction(raw.cautionAt, "quotaPacing.thresholds.cautionAt");
|
|
293
|
+
// Descending: preferAt > normalAt > cautionAt (weight 1 = best/promote, 0 = worst/avoid).
|
|
294
|
+
const orderedPairs: Array<[string, number]> = [];
|
|
295
|
+
if (result.preferAt !== undefined) orderedPairs.push(["preferAt", result.preferAt]);
|
|
296
|
+
if (result.normalAt !== undefined) orderedPairs.push(["normalAt", result.normalAt]);
|
|
297
|
+
if (result.cautionAt !== undefined) orderedPairs.push(["cautionAt", result.cautionAt]);
|
|
298
|
+
for (let i = 0; i < orderedPairs.length - 1; i++) {
|
|
299
|
+
const cur = orderedPairs[i]!;
|
|
300
|
+
const next = orderedPairs[i + 1]!;
|
|
301
|
+
if (cur[1] <= next[1]) {
|
|
302
|
+
throw new ValidationError(`quotaPacing.thresholds.${cur[0]} must be greater than quotaPacing.thresholds.${next[0]}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function normalizeQuotaPaceWindowLengths(raw: unknown): Partial<Record<"5h" | "7d", number>> {
|
|
309
|
+
if (!isRecord(raw)) throw new ValidationError("quotaPacing.windowLengthMs must be an object");
|
|
310
|
+
const result: Partial<Record<"5h" | "7d", number>> = {};
|
|
311
|
+
for (const role of ["5h", "7d"] as const) {
|
|
312
|
+
const value = raw[role];
|
|
313
|
+
if (value === undefined) continue;
|
|
314
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
315
|
+
throw new ValidationError(`quotaPacing.windowLengthMs.${role} must be a positive finite number`);
|
|
316
|
+
}
|
|
317
|
+
result[role] = value;
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
267
322
|
function normalizeProviderQuota(raw: unknown): Partial<Record<string, ProviderQuotaRoutingConfig>> {
|
|
268
323
|
if (!isRecord(raw)) throw new ValidationError("providerQuota must be an object");
|
|
269
324
|
const result: Partial<Record<string, ProviderQuotaRoutingConfig>> = {};
|