agent-relay-server 0.91.3 → 0.91.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +1 -1
- package/package.json +2 -2
- package/public/assets/{MessageBubble-Cl3gC5sO.js → MessageBubble-BORB9AKu.js} +2 -2
- package/public/assets/{MessageBubble-Cl3gC5sO.js.map → MessageBubble-BORB9AKu.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BAu-lTm9.js → PlanGraphPanel-BwEOOqdj.js} +2 -2
- package/public/assets/{PlanGraphPanel-BAu-lTm9.js.map → PlanGraphPanel-BwEOOqdj.js.map} +1 -1
- package/public/assets/{activity-navjipA_.js → activity-DD9bl5uf.js} +2 -2
- package/public/assets/{activity-navjipA_.js.map → activity-DD9bl5uf.js.map} +1 -1
- package/public/assets/{agents-B09rJRik.js → agents-DJgqCIqi.js} +2 -2
- package/public/assets/{agents-B09rJRik.js.map → agents-DJgqCIqi.js.map} +1 -1
- package/public/assets/{automation-BsXT9mKj.js → automation-Vbei8P9W.js} +2 -2
- package/public/assets/{automation-BsXT9mKj.js.map → automation-Vbei8P9W.js.map} +1 -1
- package/public/assets/chat-Dw5xpc5W.js +2 -0
- package/public/assets/{chat-Cmv-YVII.js.map → chat-Dw5xpc5W.js.map} +1 -1
- package/public/assets/{formatted-body-impl-CwziXint.js → formatted-body-impl-BisZ0FtO.js} +2 -2
- package/public/assets/{formatted-body-impl-CwziXint.js.map → formatted-body-impl-BisZ0FtO.js.map} +1 -1
- package/public/assets/{index-Dm1zlklN.js → index-Bsi2NH0v.js} +5 -5
- package/public/assets/{index-Dm1zlklN.js.map → index-Bsi2NH0v.js.map} +1 -1
- package/public/assets/index-umexz3cs.css +2 -0
- package/public/assets/{insights-CGH0lI9i.js → insights-D9ytYZyE.js} +2 -2
- package/public/assets/{insights-CGH0lI9i.js.map → insights-D9ytYZyE.js.map} +1 -1
- package/public/assets/{maintenance-BhpfPv6u.js → maintenance-BA93FvLR.js} +2 -2
- package/public/assets/{maintenance-BhpfPv6u.js.map → maintenance-BA93FvLR.js.map} +1 -1
- package/public/assets/{managed-agents-CwjcuvR6.js → managed-agents-DGwdEIzA.js} +2 -2
- package/public/assets/{managed-agents-CwjcuvR6.js.map → managed-agents-DGwdEIzA.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-KBRWvxHt.js → markdown-preview-impl-C7FDXM5c.js} +2 -2
- package/public/assets/{markdown-preview-impl-KBRWvxHt.js.map → markdown-preview-impl-C7FDXM5c.js.map} +1 -1
- package/public/assets/{memory-CgpkkEKK.js → memory-CHzkRJqf.js} +2 -2
- package/public/assets/{memory-CgpkkEKK.js.map → memory-CHzkRJqf.js.map} +1 -1
- package/public/assets/{messages-BV1z5tMO.js → messages-B7CJ5tgT.js} +2 -2
- package/public/assets/{messages-BV1z5tMO.js.map → messages-B7CJ5tgT.js.map} +1 -1
- package/public/assets/{orchestrators-Bv6T_n-l.js → orchestrators-DOy9xIx2.js} +2 -2
- package/public/assets/{orchestrators-Bv6T_n-l.js.map → orchestrators-DOy9xIx2.js.map} +1 -1
- package/public/assets/{overview-BdErDjo5.js → overview-DLbv1BWa.js} +2 -2
- package/public/assets/{overview-BdErDjo5.js.map → overview-DLbv1BWa.js.map} +1 -1
- package/public/assets/{pairs-BpzbJ5OL.js → pairs-BDQo-k3F.js} +2 -2
- package/public/assets/{pairs-BpzbJ5OL.js.map → pairs-BDQo-k3F.js.map} +1 -1
- package/public/assets/{security-CRsIxNpi.js → security-9WrCcrWs.js} +2 -2
- package/public/assets/{security-CRsIxNpi.js.map → security-9WrCcrWs.js.map} +1 -1
- package/public/assets/{select-DmZ1eEzM.js → select-CRjeGZ9a.js} +2 -2
- package/public/assets/{select-DmZ1eEzM.js.map → select-CRjeGZ9a.js.map} +1 -1
- package/public/assets/{settings-BqmYUpOu.js → settings-Durch6xW.js} +2 -2
- package/public/assets/{settings-BqmYUpOu.js.map → settings-Durch6xW.js.map} +1 -1
- package/public/assets/{tasks-DZ8C8K2N.js → tasks-86St2vpn.js} +2 -2
- package/public/assets/{tasks-DZ8C8K2N.js.map → tasks-86St2vpn.js.map} +1 -1
- package/public/assets/{teams-VmX_6z_9.js → teams-C9WUxzud.js} +2 -2
- package/public/assets/{teams-VmX_6z_9.js.map → teams-C9WUxzud.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BJilWDSR.js → terminal-viewer-impl-yXWyt2fm.js} +2 -2
- package/public/assets/{terminal-viewer-impl-BJilWDSR.js.map → terminal-viewer-impl-yXWyt2fm.js.map} +1 -1
- package/public/assets/{user-avatar-jqI4eKDf.js → user-avatar-tomKla2H.js} +2 -2
- package/public/assets/{user-avatar-jqI4eKDf.js.map → user-avatar-tomKla2H.js.map} +1 -1
- package/public/assets/{work-queue-lFDBNpds.js → work-queue-BtLMzf0g.js} +2 -2
- package/public/assets/{work-queue-lFDBNpds.js.map → work-queue-BtLMzf0g.js.map} +1 -1
- package/public/index.html +2 -2
- package/src/automations/dispatch.ts +3 -1
- package/src/config-store.ts +24 -0
- package/src/managed-policy.ts +3 -1
- package/src/mcp/tools-spawn.ts +13 -1
- package/src/project-settings.ts +27 -0
- package/src/project-skills.ts +27 -0
- package/src/routes/agent-sessions.ts +3 -1
- package/src/routes/agents-spawn.ts +4 -0
- package/src/services/spawn-agent.ts +13 -9
- package/src/shared-mcp.ts +46 -17
- package/public/assets/chat-Cmv-YVII.js +0 -2
- package/public/assets/index-F-FSPv4X.css +0 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{Hn as t,Kn as n,Wn as r,rr as i}from"./lucide-react-CsPXvXMC.js";import{_ as a,lt as o}from"./display-CWMHll1J.js";import{w as s}from"./index-
|
|
2
|
-
//# sourceMappingURL=user-avatar-
|
|
1
|
+
import{r as e}from"./chunk-CilyBKbf.js";import{Hn as t,Kn as n,Wn as r,rr as i}from"./lucide-react-CsPXvXMC.js";import{_ as a,lt as o}from"./display-CWMHll1J.js";import{w as s}from"./index-Bsi2NH0v.js";var c=e(i(),1),l=r(),u={id:o};function d({user:e,address:r,distinct:i=!1,className:o}){let d=e?.avatarUrl??(e?.avatarArtifactId?`/artifacts/${encodeURIComponent(e.avatarArtifactId)}/content`:null),[f,p]=(0,c.useState)(null);(0,c.useEffect)(()=>{if(!d){p(null);return}let e=!1,t=null;return(async()=>{try{let r=await n(d);if(e)return;t=URL.createObjectURL(r),p(t)}catch{e||p(null)}})(),()=>{e=!0,t&&URL.revokeObjectURL(t)}},[d]);let m=e?.displayName||e?.handle||`User`;if(f)return(0,l.jsx)(`img`,{src:f,alt:m,title:m,className:t(`w-7 h-7 rounded-md object-cover`,o)});if(i){let e=(m.trim()[0]||`U`).toUpperCase();return(0,l.jsx)(`span`,{title:m,className:t(`inline-flex w-7 h-7 items-center justify-center rounded-md bg-muted text-xs font-semibold ring-1 ring-foreground/10`,a(r),o),children:e})}return(0,l.jsx)(s,{agent:u,className:o})}export{d as t};
|
|
2
|
+
//# sourceMappingURL=user-avatar-tomKla2H.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-avatar-
|
|
1
|
+
{"version":3,"file":"user-avatar-tomKla2H.js","names":[],"sources":["../../dashboard/src/components/shared/user-avatar.tsx"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport { AgentTypeIcon } from '@/components/shared/agent-type-icon'\nimport { apiBlob } from '@/lib/api'\nimport { humanAccentClass } from '@/lib/display'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { cn } from '@/lib/utils'\nimport type { Agent, User } from '@/types'\n\n// Minimal stub so AgentTypeIcon resolves the generic 'user' face when no avatar exists.\nconst HUMAN_AGENT_STUB = { id: HUMAN_AGENT_ID } as unknown as Agent\n\n// A human's face (#390). Renders, in order of preference: the user's content-addressed avatar\n// image (the FK lands fully in #630 — until then `avatarArtifactId` is null and this path is\n// dormant), then a colored initial when we need to distinguish multiple operators, then the\n// generic operator icon. Degrades gracefully at every step so a single seeded user looks\n// exactly as before.\nexport function UserAvatar({\n user,\n address,\n distinct = false,\n className,\n}: {\n user: User | undefined\n /** The human address (`user` / `user:<id>`) — drives the deterministic accent color. */\n address?: string\n /** When true (multiple humans in view), fall back to a colored initial instead of the icon. */\n distinct?: boolean\n className?: string\n}) {\n const avatarUrl = user?.avatarUrl ?? (user?.avatarArtifactId ? `/artifacts/${encodeURIComponent(user.avatarArtifactId)}/content` : null)\n const [objectUrl, setObjectUrl] = useState<string | null>(null)\n\n useEffect(() => {\n if (!avatarUrl) { setObjectUrl(null); return }\n let cancelled = false\n let url: string | null = null\n void (async () => {\n try {\n const blob = await apiBlob(avatarUrl)\n if (cancelled) return\n url = URL.createObjectURL(blob)\n setObjectUrl(url)\n } catch {\n if (!cancelled) setObjectUrl(null)\n }\n })()\n return () => { cancelled = true; if (url) URL.revokeObjectURL(url) }\n }, [avatarUrl])\n\n const title = user?.displayName || user?.handle || 'User'\n\n if (objectUrl) {\n return <img src={objectUrl} alt={title} title={title} className={cn('w-7 h-7 rounded-md object-cover', className)} />\n }\n if (distinct) {\n const initial = (title.trim()[0] || 'U').toUpperCase()\n return (\n <span\n title={title}\n className={cn('inline-flex w-7 h-7 items-center justify-center rounded-md bg-muted text-xs font-semibold ring-1 ring-foreground/10', humanAccentClass(address), className)}\n >\n {initial}\n </span>\n )\n }\n return <AgentTypeIcon agent={HUMAN_AGENT_STUB} className={className} />\n}\n"],"mappings":"+NASM,EAAmB,CAAE,GAAI,EAAgB,CAO/C,SAAgB,EAAW,CACzB,OACA,UACA,WAAW,GACX,aAQC,CACD,IAAM,EAAY,GAAM,YAAc,GAAM,iBAAmB,cAAc,mBAAmB,EAAK,iBAAiB,CAAC,UAAY,MAC7H,CAAC,EAAW,IAAA,EAAA,EAAA,UAAwC,KAAK,EAE/D,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAW,CAAE,EAAa,KAAK,CAAE,OACtC,IAAI,EAAY,GACZ,EAAqB,KAWzB,OAVM,SAAY,CAChB,GAAI,CACF,IAAM,EAAO,MAAM,EAAQ,EAAU,CACrC,GAAI,EAAW,OACf,EAAM,IAAI,gBAAgB,EAAK,CAC/B,EAAa,EAAI,MACX,CACD,GAAW,EAAa,KAAK,KAElC,KACS,CAAE,EAAY,GAAU,GAAK,IAAI,gBAAgB,EAAI,GACjE,CAAC,EAAU,CAAC,CAEf,IAAM,EAAQ,GAAM,aAAe,GAAM,QAAU,OAEnD,GAAI,EACF,OAAO,EAAA,EAAA,KAAC,MAAD,CAAK,IAAK,EAAW,IAAK,EAAc,QAAO,UAAW,EAAG,kCAAmC,EAAU,CAAI,CAAA,CAEvH,GAAI,EAAU,CACZ,IAAM,GAAW,EAAM,MAAM,CAAC,IAAM,KAAK,aAAa,CACtD,OACE,EAAA,EAAA,KAAC,OAAD,CACS,QACP,UAAW,EAAG,sHAAuH,EAAiB,EAAQ,CAAE,EAAU,UAEzK,EACI,CAAA,CAGX,OAAO,EAAA,EAAA,KAAC,EAAD,CAAe,MAAO,EAA6B,YAAa,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Wn as e,bn as t,cn as n,i as r,ut as i}from"./lucide-react-CsPXvXMC.js";import{l as a,t as o}from"./store-DsqUEZOf.js";import{K as s,d as c,mt as l}from"./display-CWMHll1J.js";import{t as u}from"./badge-CK6ejiOh.js";import{t as d}from"./button-BAwKuRIq.js";import{V as f,q as p,s as m}from"./index-
|
|
2
|
-
//# sourceMappingURL=work-queue-
|
|
1
|
+
import{Wn as e,bn as t,cn as n,i as r,ut as i}from"./lucide-react-CsPXvXMC.js";import{l as a,t as o}from"./store-DsqUEZOf.js";import{K as s,d as c,mt as l}from"./display-CWMHll1J.js";import{t as u}from"./badge-CK6ejiOh.js";import{t as d}from"./button-BAwKuRIq.js";import{V as f,q as p,s as m}from"./index-Bsi2NH0v.js";import{r as h,t as g}from"./card-DDxxo-Fo.js";var _=e(),v={open:`bg-blue-500/10 text-blue-400 border-blue-500/20`,blocked:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,claimed:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,in_progress:`bg-purple-500/10 text-purple-400 border-purple-500/20`};function y(){let e=a(),t=o(e=>e.agentsById),n=o(e=>e.compose),r=o(e=>e.set),s=o(e=>e.doClaimTask),l=o(e=>e.doClaim),d=o(e=>e.openTaskEvents),h=o(e=>e.showError),g=o(e=>e.openConfirm),v=o(e=>e.doDeleteMessage),y=o(e=>e.doUpdateTaskStatus),x=f(),S=p(),C=S.filter(e=>e.claimable).length;function w(e){if(!n.from){h(`Validation`,`Select a "Claim as" agent first.`);return}e.sourceType===`task`&&e.task?s(e.task.id):e.sourceType===`message`&&e.message&&l(e.message.id)}function T(e){g(`Cancel Item`,`Cancel "${e.title||(e.sourceType===`task`?`Task #${e.id}`:`Message #${e.id}`)}"? This cannot be undone.`,()=>{e.sourceType===`task`&&e.task?y(e.task,`canceled`):e.sourceType===`message`&&e.message&&v(e.message.id)})}function E(e){e.task&&d(e.task)}return(0,_.jsxs)(`div`,{className:`space-y-4`,children:[(0,_.jsxs)(`div`,{className:`flex items-center justify-between flex-wrap gap-2`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(i,{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:n.from,onChange:e=>r({compose:{...n,from:e.target.value}}),children:[(0,_.jsx)(`option`,{value:``,children:`Select agent...`}),x.map(e=>(0,_.jsx)(`option`,{value:e.id,children:c(e)},e.id))]})]})]}),(0,_.jsx)(m,{className:`h-[calc(100dvh-11rem)]`,children:(0,_.jsxs)(`div`,{className:`space-y-2 pr-2`,children:[S.length===0&&(0,_.jsx)(`div`,{className:`text-center text-muted-foreground py-16 text-sm`,children:`Work queue is empty`}),S.map(n=>(0,_.jsx)(b,{item:n,now:e,agentName:e=>t[e]?c(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:e,now:i,agentName:a,onClaim:o,onCancel:c,onEvents:f}){let p=l[e.severity]||l.info,m=v[e.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:e.claimable?`ring-1 ring-orange-500/30`:``,children:(0,_.jsx)(h,{className:`p-3`,children:(0,_.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,_.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap mb-1`,children:[(0,_.jsx)(`span`,{className:`font-medium text-sm truncate`,children:e.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:e.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:e.status}),e.sourceType===`message`&&(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400`,children:`msg`})]}),e.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:e.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[e.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(n,{className:`w-3 h-3 inline mr-1`}),a(e.owner)]}),e.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:e.source}),(0,_.jsxs)(`span`,{title:String(e.updatedAt),children:[(0,_.jsx)(t,{className:`w-3 h-3 inline mr-1`}),s(i,e.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[e.claimable&&(0,_.jsxs)(d,{size:`sm`,className:`h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white`,onClick:o,children:[(0,_.jsx)(n,{className:`w-3.5 h-3.5 mr-1`}),` Claim`]}),f&&(0,_.jsx)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs`,onClick:f,children:`Events`}),(0,_.jsxs)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20`,onClick:c,children:[(0,_.jsx)(r,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
|
|
2
|
+
//# sourceMappingURL=work-queue-BtLMzf0g.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-lFDBNpds.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `Task #${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"work-queue-BtLMzf0g.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `Task #${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"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"}
|
package/public/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%2309090b'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='%2358a6ff'/%3E%3Ccircle cx='6' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='6' cy='24' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='24' r='2.5' fill='%233fb950'/%3E%3Cline x1='8' y1='9.5' x2='13' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='9.5' x2='19' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='8' y1='22.5' x2='13' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='22.5' x2='19' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3C/svg%3E">
|
|
13
13
|
<link rel="manifest" href="manifest.webmanifest">
|
|
14
14
|
<link rel="apple-touch-icon" href="icons/agent-relay-192.png">
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-Bsi2NH0v.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-CP_edrrL.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/lucide-react-CsPXvXMC.js">
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/badge-CK6ejiOh.js">
|
|
38
38
|
<link rel="modulepreload" crossorigin href="./assets/input-CLW2gDUM.js">
|
|
39
39
|
<link rel="modulepreload" crossorigin href="./assets/themes-mW9zZP4O.js">
|
|
40
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
40
|
+
<link rel="stylesheet" crossorigin href="./assets/index-umexz3cs.css">
|
|
41
41
|
</head>
|
|
42
42
|
<body class="bg-background text-foreground antialiased">
|
|
43
43
|
<div id="root"></div>
|
|
@@ -3,6 +3,7 @@ import { createCommand } from "../commands-db";
|
|
|
3
3
|
import { getAgentProfile, getSpawnPolicy } from "../config-store";
|
|
4
4
|
import { getOrchestrator, ingestIntegrationEvent, listAgents, ValidationError, withResolvedProvisioning } from "../db";
|
|
5
5
|
import { runnerRuntimeTokenEnv } from "../runtime-tokens";
|
|
6
|
+
import { applyDefaultSharedMcp } from "../shared-mcp";
|
|
6
7
|
import { buildSpawnCommand, resolveSpawnModelParams } from "../spawn-command";
|
|
7
8
|
import { nextScheduledAt, formatRunDate } from "./cron";
|
|
8
9
|
import {
|
|
@@ -89,7 +90,8 @@ function dispatchOnDemandAutomation(
|
|
|
89
90
|
now: number,
|
|
90
91
|
): AutomationDispatchResult {
|
|
91
92
|
if (!orchestrator.providers.includes(policy.provider)) throw new ValidationError(`orchestrator ${orchestrator.id} does not have provider available: ${policy.provider}`);
|
|
92
|
-
const
|
|
93
|
+
const resolvedProfile = withResolvedProvisioning(policy.profile ? getAgentProfile(policy.profile)?.value : undefined, policy.provider);
|
|
94
|
+
const agentProfile = resolvedProfile ? applyDefaultSharedMcp(resolvedProfile) : undefined;
|
|
93
95
|
const label = automationRunLabel(automation.id, run.id);
|
|
94
96
|
const command = createCommand({
|
|
95
97
|
type: "agent.spawn",
|
package/src/config-store.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { DEFAULT_SELF_RESUME_CONFIG, validateSelfResumeConfig } from "./self-res
|
|
|
6
6
|
import { TEAM_TEMPLATE_NAMESPACE, validateTeamTemplate } from "./team-template-config";
|
|
7
7
|
import { PROJECT_INSTRUCTION_POLICY_NAMES } from "./project-instructions";
|
|
8
8
|
import { PROJECT_MCP_POLICY_NAMES } from "./project-mcp";
|
|
9
|
+
import { PROJECT_SETTINGS_POLICY_NAMES } from "./project-settings";
|
|
10
|
+
import { PROJECT_SKILLS_POLICY_NAMES } from "./project-skills";
|
|
9
11
|
import { SHARED_MCP_POLICY_NAMES } from "./shared-mcp";
|
|
10
12
|
import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
11
13
|
import { errMessage, isRecord, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES } from "agent-relay-sdk"; import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
@@ -238,6 +240,24 @@ function cleanAgentProfileProjectMcp(value: unknown): AgentProfile["projectMcp"]
|
|
|
238
240
|
return { policy: cleanEnum(policy, "projectMcp.policy", PROJECT_MCP_POLICY_NAMES) as NonNullable<AgentProfile["projectMcp"]>["policy"] };
|
|
239
241
|
}
|
|
240
242
|
|
|
243
|
+
function cleanAgentProfileProjectSkills(value: unknown): AgentProfile["projectSkills"] {
|
|
244
|
+
if (value === undefined || value === null || value === false) return undefined;
|
|
245
|
+
if (value === true) return {};
|
|
246
|
+
if (!isRecord(value)) throw new ValidationError("projectSkills must be a boolean or { policy } object");
|
|
247
|
+
const policy = value.policy;
|
|
248
|
+
if (policy === undefined || policy === null) return {};
|
|
249
|
+
return { policy: cleanEnum(policy, "projectSkills.policy", PROJECT_SKILLS_POLICY_NAMES) as NonNullable<AgentProfile["projectSkills"]>["policy"] };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function cleanAgentProfileProjectSettings(value: unknown): AgentProfile["projectSettings"] {
|
|
253
|
+
if (value === undefined || value === null || value === false) return undefined;
|
|
254
|
+
if (value === true) return {};
|
|
255
|
+
if (!isRecord(value)) throw new ValidationError("projectSettings must be a boolean or { policy } object");
|
|
256
|
+
const policy = value.policy;
|
|
257
|
+
if (policy === undefined || policy === null) return {};
|
|
258
|
+
return { policy: cleanEnum(policy, "projectSettings.policy", PROJECT_SETTINGS_POLICY_NAMES) as NonNullable<AgentProfile["projectSettings"]>["policy"] };
|
|
259
|
+
}
|
|
260
|
+
|
|
241
261
|
// #672 — shared host-listener MCP projection profile default. Mirrors projectMcp:
|
|
242
262
|
// boolean | { policy } → normalized { policy? } | undefined (OFF).
|
|
243
263
|
function cleanAgentProfileSharedMcp(value: unknown): AgentProfile["sharedMcp"] {
|
|
@@ -270,6 +290,8 @@ function agentProfileDefaults(input: Pick<AgentProfile, "name" | "base"> & Parti
|
|
|
270
290
|
},
|
|
271
291
|
...(input.projectInstructions ? { projectInstructions: input.projectInstructions } : {}),
|
|
272
292
|
...(input.projectMcp ? { projectMcp: input.projectMcp } : {}),
|
|
293
|
+
...(input.projectSkills ? { projectSkills: input.projectSkills } : {}),
|
|
294
|
+
...(input.projectSettings ? { projectSettings: input.projectSettings } : {}),
|
|
273
295
|
...(input.sharedMcp ? { sharedMcp: input.sharedMcp } : {}),
|
|
274
296
|
relay: {
|
|
275
297
|
context: input.relay?.context ?? !isolated,
|
|
@@ -346,6 +368,8 @@ function validateAgentProfile(key: string, value: unknown): AgentProfile {
|
|
|
346
368
|
},
|
|
347
369
|
projectInstructions: cleanAgentProfileProjectInstructions(value.projectInstructions),
|
|
348
370
|
projectMcp: cleanAgentProfileProjectMcp(value.projectMcp),
|
|
371
|
+
projectSkills: cleanAgentProfileProjectSkills(value.projectSkills),
|
|
372
|
+
projectSettings: cleanAgentProfileProjectSettings(value.projectSettings),
|
|
349
373
|
sharedMcp: cleanAgentProfileSharedMcp(value.sharedMcp),
|
|
350
374
|
relay: {
|
|
351
375
|
context: relay.context === undefined ? defaults.relay.context : cleanBoolean(relay.context, "relay.context"),
|
package/src/managed-policy.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { getAgentProfile } from "./config-store";
|
|
|
2
2
|
import { withResolvedProvisioning } from "./db";
|
|
3
3
|
import { runnerRuntimeTokenEnv } from "./runtime-tokens";
|
|
4
4
|
import { buildSpawnCommand, resolveSpawnModelParams } from "./spawn-command";
|
|
5
|
+
import { applyDefaultSharedMcp } from "./shared-mcp";
|
|
5
6
|
import type { SpawnPolicy, WorkspaceMode } from "./types";
|
|
6
7
|
|
|
7
8
|
export function managedPolicyProviderArgs(policy: SpawnPolicy): string[] {
|
|
@@ -29,7 +30,8 @@ interface ManagedSpawnContext {
|
|
|
29
30
|
export function buildManagedSpawnParams(policy: SpawnPolicy, requestId: string, ctx: ManagedSpawnContext): Record<string, unknown> {
|
|
30
31
|
const providerArgs = managedPolicyProviderArgs(policy);
|
|
31
32
|
const prompt = managedPolicyLaunchPrompt(policy);
|
|
32
|
-
const
|
|
33
|
+
const resolvedProfile = withResolvedProvisioning(policy.profile ? getAgentProfile(policy.profile)?.value : undefined, policy.provider);
|
|
34
|
+
const agentProfile = resolvedProfile ? applyDefaultSharedMcp(resolvedProfile) : undefined;
|
|
33
35
|
return buildSpawnCommand({
|
|
34
36
|
provider: policy.provider,
|
|
35
37
|
orchestratorId: policy.orchestratorId,
|
package/src/mcp/tools-spawn.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { buildSpawnTargets } from "../spawn-targets";
|
|
|
5
5
|
import { McpAuthError, McpNotFoundError } from "../mcp-errors";
|
|
6
6
|
import { parseProjectInstructions, PROJECT_INSTRUCTION_POLICY_NAMES } from "../project-instructions";
|
|
7
7
|
import { parseProjectMcp, PROJECT_MCP_POLICY_NAMES } from "../project-mcp";
|
|
8
|
+
import { parseProjectSettings, PROJECT_SETTINGS_POLICY_NAMES } from "../project-settings";
|
|
9
|
+
import { parseProjectSkills, PROJECT_SKILLS_POLICY_NAMES } from "../project-skills";
|
|
8
10
|
import { parseSharedMcp, SHARED_MCP_POLICY_NAMES } from "../shared-mcp";
|
|
9
11
|
import { countLiveSpawnedAgents, listAgents, listWorkspaces, ValidationError } from "../db";
|
|
10
12
|
import { listCommands } from "../commands-db";
|
|
@@ -37,7 +39,15 @@ export const SPAWN_TOOLS: ToolDefinition[] = [
|
|
|
37
39
|
systemPromptAppend: { type: "string" },
|
|
38
40
|
projectInstructions: { type: "object", description: "#409 — project instruction projection (default OFF). When set, relay composes a knowledge preamble from the spawn cwd's resolved project (own + inherited entries) and prepends it to systemPromptAppend, instead of relying on host CLAUDE.md. `policy: \"all\"` includes ingested host content; `\"vanilla\"` excludes it (manual+promoted only). Omit to keep current behavior.", properties: { policy: { type: "string", enum: PROJECT_INSTRUCTION_POLICY_NAMES } }, additionalProperties: false },
|
|
39
41
|
projectMcp: { type: "object", description: "#667 — project `.mcp.json` MCP projection (default OFF). When set, the worker gets the MCP servers committed in the spawn cwd's `.mcp.json` (e.g. an app's playtest/manuscript servers), injected by relay through the controlled `--mcp-config` path that survives `--strict-mcp-config` — sourced from the project, not ambient host MCP, and without triggering Claude's project-trust modal. `policy: \"all\"` (the default) injects every declared server. Omit to keep current host-only behavior.", properties: { policy: { type: "string", enum: PROJECT_MCP_POLICY_NAMES } }, additionalProperties: false },
|
|
40
|
-
|
|
42
|
+
projectSkills: { type: "object", description: "#668 — project `.claude/skills` projection for Claude workers (default OFF). When set, the worker gets skill dirs committed under the spawn cwd's `.claude/skills`, materialized into Relay's managed Claude config home. Host/global skills stay excluded. `policy: \"all\"` (the default) includes every valid skill dir. Omit to keep current behavior.", properties: { policy: { type: "string", enum: PROJECT_SKILLS_POLICY_NAMES } }, additionalProperties: false },
|
|
43
|
+
projectSettings: { type: "object", description: "#669 — project `.claude/settings.json` projection for Claude workers (default OFF). When set, Relay reads the spawn cwd's committed settings, strips permission/security keys that could widen the worker, and injects the safe remainder through `--settings`. Relay approvalMode/permission gates stay authoritative. Host/global settings stay excluded. `policy: \"all\"` (the default) applies the settings file. Omit to keep current behavior.", properties: { policy: { type: "string", enum: PROJECT_SETTINGS_POLICY_NAMES } }, additionalProperties: false },
|
|
44
|
+
sharedMcp: {
|
|
45
|
+
anyOf: [
|
|
46
|
+
{ type: "boolean" },
|
|
47
|
+
{ type: "object", properties: { policy: { type: "string", enum: SHARED_MCP_POLICY_NAMES } }, additionalProperties: false },
|
|
48
|
+
],
|
|
49
|
+
description: "#689 — shared host-listener MCP. Host-MCP profiles default ON: the worker's NON-IDENTITY MCP (tokenlean, github) is routed through one orchestrator-owned shared `callmux` listener via a per-agent `callmux bridge --cwd \"$WORKTREE\"`, so the MCP session survives listener/downstream restart and keeps isolated worktree cwd routing. Profiles that suppress host MCP stay OFF unless explicitly opted in. Pass `false` as a per-spawn kill-switch to force direct/per-agent MCP injection. The identity-bearing relay endpoint stays per-agent (#215), never on the shared listener.",
|
|
50
|
+
},
|
|
41
51
|
profile: { type: "string", description: `Agent profile name to apply (env, instructions, permissions, MCP/skills, spawn quota). Omit to use \`${DEFAULT_SPAWN_PROFILE_NAME}\`.` },
|
|
42
52
|
tags: { type: "array", items: { type: "string" } },
|
|
43
53
|
capabilities: { type: "array", items: { type: "string" } },
|
|
@@ -113,6 +123,8 @@ export async function relaySpawnAgent(auth: McpAuthContext, args: Record<string,
|
|
|
113
123
|
systemPromptAppend: optionalString(args.systemPromptAppend, "systemPromptAppend", 64_000),
|
|
114
124
|
projectInstructions: parseProjectInstructions(args.projectInstructions, (m) => new ValidationError(m)),
|
|
115
125
|
projectMcp: parseProjectMcp(args.projectMcp, (m) => new ValidationError(m)),
|
|
126
|
+
projectSkills: parseProjectSkills(args.projectSkills, (m) => new ValidationError(m)),
|
|
127
|
+
projectSettings: parseProjectSettings(args.projectSettings, (m) => new ValidationError(m)),
|
|
116
128
|
sharedMcp: parseSharedMcp(args.sharedMcp, (m) => new ValidationError(m)),
|
|
117
129
|
tags: optionalStringArray(args.tags, "tags"),
|
|
118
130
|
capabilities: optionalStringArray(args.capabilities, "capabilities"),
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Project `.claude/settings.json` projection (#669) — sibling to projectMcp (#667).
|
|
2
|
+
// Relay carries only the opt-in decision; the runner reads/sanitizes the project settings
|
|
3
|
+
// and injects them through the controlled Claude `--settings` path.
|
|
4
|
+
|
|
5
|
+
export const PROJECT_SETTINGS_POLICY_NAMES = ["all"] as const;
|
|
6
|
+
export type ProjectSettingsPolicyName = (typeof PROJECT_SETTINGS_POLICY_NAMES)[number];
|
|
7
|
+
|
|
8
|
+
export interface ProjectSettingsPolicy {
|
|
9
|
+
policy?: ProjectSettingsPolicyName;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseProjectSettings(
|
|
13
|
+
value: unknown,
|
|
14
|
+
onInvalid: (message: string) => Error,
|
|
15
|
+
): ProjectSettingsPolicy | undefined {
|
|
16
|
+
if (value === undefined || value === null || value === false) return undefined;
|
|
17
|
+
if (value === true) return {};
|
|
18
|
+
if (typeof value !== "object") {
|
|
19
|
+
throw onInvalid("projectSettings must be a boolean or { policy } object");
|
|
20
|
+
}
|
|
21
|
+
const policy = (value as Record<string, unknown>).policy;
|
|
22
|
+
if (policy === undefined || policy === null) return {};
|
|
23
|
+
if (typeof policy !== "string" || !PROJECT_SETTINGS_POLICY_NAMES.includes(policy as ProjectSettingsPolicyName)) {
|
|
24
|
+
throw onInvalid(`projectSettings.policy must be one of: ${PROJECT_SETTINGS_POLICY_NAMES.join(", ")}`);
|
|
25
|
+
}
|
|
26
|
+
return { policy: policy as ProjectSettingsPolicyName };
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Project `.claude/skills` projection (#668) — sibling to projectMcp (#667).
|
|
2
|
+
// Relay carries only the opt-in decision; the runner reads the spawn cwd and materializes
|
|
3
|
+
// project-committed skills into the managed Claude config home.
|
|
4
|
+
|
|
5
|
+
export const PROJECT_SKILLS_POLICY_NAMES = ["all"] as const;
|
|
6
|
+
export type ProjectSkillsPolicyName = (typeof PROJECT_SKILLS_POLICY_NAMES)[number];
|
|
7
|
+
|
|
8
|
+
export interface ProjectSkillsPolicy {
|
|
9
|
+
policy?: ProjectSkillsPolicyName;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseProjectSkills(
|
|
13
|
+
value: unknown,
|
|
14
|
+
onInvalid: (message: string) => Error,
|
|
15
|
+
): ProjectSkillsPolicy | undefined {
|
|
16
|
+
if (value === undefined || value === null || value === false) return undefined;
|
|
17
|
+
if (value === true) return {};
|
|
18
|
+
if (typeof value !== "object") {
|
|
19
|
+
throw onInvalid("projectSkills must be a boolean or { policy } object");
|
|
20
|
+
}
|
|
21
|
+
const policy = (value as Record<string, unknown>).policy;
|
|
22
|
+
if (policy === undefined || policy === null) return {};
|
|
23
|
+
if (typeof policy !== "string" || !PROJECT_SKILLS_POLICY_NAMES.includes(policy as ProjectSkillsPolicyName)) {
|
|
24
|
+
throw onInvalid(`projectSkills.policy must be one of: ${PROJECT_SKILLS_POLICY_NAMES.join(", ")}`);
|
|
25
|
+
}
|
|
26
|
+
return { policy: policy as ProjectSkillsPolicyName };
|
|
27
|
+
}
|
|
@@ -6,6 +6,7 @@ import { ValidationError, getAgent, getAgentTimeline, getOrchestrator, markReady
|
|
|
6
6
|
import { announcePresence, viewersForAgent } from "../dashboard-presence";
|
|
7
7
|
import { buildManagedSpawnParams } from "../managed-policy";
|
|
8
8
|
import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams } from "../spawn-command";
|
|
9
|
+
import { applyDefaultSharedMcp } from "../shared-mcp";
|
|
9
10
|
import { cleanString, optionalEnum } from "../validation";
|
|
10
11
|
import { createCommand } from "../commands-db";
|
|
11
12
|
import { emitAgentStatus, emitDashboardPresence, emitNewMessage } from "../sse";
|
|
@@ -119,7 +120,8 @@ function restartSpawnParamsForAgent(
|
|
|
119
120
|
const effort = metaString(agent.meta, "effort") as ProviderEffort | undefined;
|
|
120
121
|
const providerArgs = metaStringArray(agent.meta, "providerArgs");
|
|
121
122
|
const profileName = metaString(agent.meta, "profile");
|
|
122
|
-
const
|
|
123
|
+
const resolvedProfile = withResolvedProvisioning(profileName ? getAgentProfile(profileName)?.value : undefined, provider);
|
|
124
|
+
const agentProfile = resolvedProfile ? applyDefaultSharedMcp(resolvedProfile) : undefined;
|
|
123
125
|
const workspaceMode = metaString(agent.meta, "workspaceMode") ?? "inherit";
|
|
124
126
|
const resolvedModel = resolveSpawnModelParams(provider, model, effort, { onError: "passthrough", skipDefaultWhenEmpty: true });
|
|
125
127
|
const params = buildSpawnCommand({
|
|
@@ -8,6 +8,8 @@ import { registerAgent } from "../services/register-agent";
|
|
|
8
8
|
import { spawnAgent, type SpawnAgentInput } from "../services/spawn-agent";
|
|
9
9
|
import { parseProjectInstructions } from "../project-instructions";
|
|
10
10
|
import { parseProjectMcp } from "../project-mcp";
|
|
11
|
+
import { parseProjectSettings } from "../project-settings";
|
|
12
|
+
import { parseProjectSkills } from "../project-skills";
|
|
11
13
|
import { parseSharedMcp } from "../shared-mcp";
|
|
12
14
|
import { authContextFromRequest } from "../services/auth-context";
|
|
13
15
|
import { listHostDirectories } from "../agent-spawn";
|
|
@@ -124,6 +126,8 @@ export const postAgentSpawn: Handler = async (req) => {
|
|
|
124
126
|
systemPromptAppend: cleanString(parsed.body.systemPromptAppend, "systemPromptAppend", { max: 64_000 }),
|
|
125
127
|
projectInstructions: parseProjectInstructions(parsed.body.projectInstructions, (m) => new ValidationError(m)),
|
|
126
128
|
projectMcp: parseProjectMcp(parsed.body.projectMcp, (m) => new ValidationError(m)),
|
|
129
|
+
projectSkills: parseProjectSkills(parsed.body.projectSkills, (m) => new ValidationError(m)),
|
|
130
|
+
projectSettings: parseProjectSettings(parsed.body.projectSettings, (m) => new ValidationError(m)),
|
|
127
131
|
sharedMcp: parseSharedMcp(parsed.body.sharedMcp, (m) => new ValidationError(m)),
|
|
128
132
|
tags: cleanStringArray(parsed.body.tags, "tags", { itemMax: 80, maxItems: 50 }),
|
|
129
133
|
capabilities: cleanStringArray(parsed.body.capabilities, "capabilities", { itemMax: 80, maxItems: 50 }),
|
|
@@ -34,7 +34,9 @@ import { selectSpawnOrchestrator } from "../spawn-targets";
|
|
|
34
34
|
import { DEFAULT_SPAWN_PROFILE_NAME, getAgentProfile } from "../config-store";
|
|
35
35
|
import { composeSpawnInstructionsWithEvents, type ProjectInstructionPolicy } from "../project-instructions";
|
|
36
36
|
import type { ProjectMcpPolicy } from "../project-mcp";
|
|
37
|
-
import type {
|
|
37
|
+
import type { ProjectSettingsPolicy } from "../project-settings";
|
|
38
|
+
import type { ProjectSkillsPolicy } from "../project-skills";
|
|
39
|
+
import { applyDefaultSharedMcp, type SharedMcpSpawnOverride } from "../shared-mcp";
|
|
38
40
|
import { autoSeedProjectForCwd } from "./project";
|
|
39
41
|
import { hasComponentScope, isComponentAuthorizedFor } from "../security";
|
|
40
42
|
import { isPathWithinBase } from "../utils";
|
|
@@ -85,6 +87,8 @@ export interface SpawnAgentInput {
|
|
|
85
87
|
* to projectInstructions; carried to the runner on the in-flight `agentProfile.projectMcp`.
|
|
86
88
|
*/
|
|
87
89
|
projectMcp?: ProjectMcpPolicy;
|
|
90
|
+
projectSkills?: ProjectSkillsPolicy;
|
|
91
|
+
projectSettings?: ProjectSettingsPolicy;
|
|
88
92
|
/**
|
|
89
93
|
* #672 — shared host-listener MCP. When set, the runner emits a per-agent `callmux bridge`
|
|
90
94
|
* stdio entry so the agent's NON-IDENTITY MCP (tokenlean, github) flows through the
|
|
@@ -94,7 +98,7 @@ export interface SpawnAgentInput {
|
|
|
94
98
|
* today. Sibling to projectMcp; carried to the runner on the in-flight `agentProfile.sharedMcp`.
|
|
95
99
|
* The identity-bearing relay endpoint stays per-agent (#215), never on the shared listener.
|
|
96
100
|
*/
|
|
97
|
-
sharedMcp?:
|
|
101
|
+
sharedMcp?: SharedMcpSpawnOverride;
|
|
98
102
|
/**
|
|
99
103
|
* Legacy report-back contract flag. Spawned children now report up by lineage: their
|
|
100
104
|
* turn-final response is delivered to the spawner automatically, without a reply obligation.
|
|
@@ -188,7 +192,7 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
188
192
|
// the name through unvalidated). Omitted profile uses Relay's spawn default so coordinator
|
|
189
193
|
// spawns still get the bundled Relay context/affordances. Explicit profile always wins.
|
|
190
194
|
const profileName = input.profile ?? DEFAULT_SPAWN_PROFILE_NAME;
|
|
191
|
-
|
|
195
|
+
let agentProfile = withResolvedProvisioning(getAgentProfile(profileName)?.value, input.provider);
|
|
192
196
|
if (!agentProfile) throw new McpNotFoundError(input.profile ? "agent profile not found" : `default agent profile ${DEFAULT_SPAWN_PROFILE_NAME} not found`);
|
|
193
197
|
|
|
194
198
|
// #667 — project `.mcp.json` projection: a per-spawn `projectMcp` overrides the profile
|
|
@@ -198,13 +202,13 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
198
202
|
// config). Like projectInstructions, an explicit `false` parses to undefined and so cannot
|
|
199
203
|
// turn a profile default OFF — opt-in is the trust gate, not opt-out.
|
|
200
204
|
if (input.projectMcp !== undefined) agentProfile.projectMcp = input.projectMcp;
|
|
205
|
+
if (input.projectSkills !== undefined) agentProfile.projectSkills = input.projectSkills;
|
|
206
|
+
if (input.projectSettings !== undefined) agentProfile.projectSettings = input.projectSettings;
|
|
201
207
|
|
|
202
|
-
// #
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
// decision onto the in-flight profile copy (a fresh object, never the cached config).
|
|
207
|
-
if (input.sharedMcp !== undefined) agentProfile.sharedMcp = input.sharedMcp;
|
|
208
|
+
// #689 — shared host-listener MCP defaults on only for profiles that would otherwise receive
|
|
209
|
+
// host non-identity MCP. Per-spawn false and the global env kill-switch force direct MCP.
|
|
210
|
+
// Explicit opt-in still works for zero-host profiles.
|
|
211
|
+
agentProfile = applyDefaultSharedMcp(agentProfile, input.sharedMcp);
|
|
208
212
|
|
|
209
213
|
// --- THE GATE (uniform across transports) ---------------------------------
|
|
210
214
|
// Coarse capability: spawning requires command:spawn. The MCP dispatcher already enforced
|
package/src/shared-mcp.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
import type { AgentProfile } from "agent-relay-sdk";
|
|
2
|
+
|
|
3
|
+
// Shared host-listener MCP (#672/#689, Phase A of epic #670) — the relay-owned path that
|
|
4
|
+
// routes a host-MCP spawned worker's NON-IDENTITY MCP (tokenlean, github) through ONE
|
|
5
|
+
// orchestrator-owned shared `callmux` listener per host, attached via a per-agent `callmux
|
|
6
|
+
// bridge` with `--cwd "$WORKTREE"` for session-cwd isolation. Host-MCP profiles default ON;
|
|
7
|
+
// zero-host profiles stay OFF unless explicitly opted in.
|
|
6
8
|
//
|
|
7
9
|
// WHY: a shared listener turns "an MCP/listener restart kills the agent" into "the agent never
|
|
8
10
|
// notices" (the bridge reconnects + retries once on the next tool call — the #659 work-loss
|
|
@@ -15,26 +17,52 @@
|
|
|
15
17
|
// is NEVER routed through the shared listener (the spike proved a shared listener can't carry
|
|
16
18
|
// per-agent relay tokens; that's Phase 2, gated on callmux#36).
|
|
17
19
|
//
|
|
18
|
-
// DIVISION OF LABOR: relay carries the
|
|
19
|
-
// param/profile field). The orchestrator owns the shared listener lifecycle/config (#673),
|
|
20
|
-
//
|
|
21
|
-
// filesystem/launch authority.
|
|
20
|
+
// DIVISION OF LABOR: relay carries the default/override decision (this module + the spawn
|
|
21
|
+
// param/profile field). The orchestrator owns the shared listener lifecycle/config (#673), and
|
|
22
|
+
// runner owns bridge-arg emission (launch-assembly.ts → sharedMcpBridgeServer).
|
|
22
23
|
|
|
23
24
|
export const SHARED_MCP_POLICY_NAMES = ["all"] as const;
|
|
24
25
|
export type SharedMcpPolicyName = (typeof SHARED_MCP_POLICY_NAMES)[number];
|
|
26
|
+
export const SHARED_MCP_GLOBAL_ENABLE_ENV = "AGENT_RELAY_SHARED_CALLMUX_ENABLE";
|
|
25
27
|
|
|
26
|
-
/** Per-spawn shared-listener request. Presence
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
/** Per-spawn shared-listener request. Presence means "override the profile/default";
|
|
29
|
+
* false means "force OFF". `policy` selects the server set (`all` = the listener's full
|
|
30
|
+
* non-identity set; the sole value today). */
|
|
29
31
|
export interface SharedMcpPolicy {
|
|
30
32
|
policy?: SharedMcpPolicyName;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
export type SharedMcpSpawnOverride = SharedMcpPolicy | false;
|
|
36
|
+
|
|
37
|
+
export function sharedMcpGloballyEnabled(env: Record<string, string | undefined> = process.env): boolean {
|
|
38
|
+
const raw = env[SHARED_MCP_GLOBAL_ENABLE_ENV];
|
|
39
|
+
if (raw === undefined || raw === null || raw === "") return true;
|
|
40
|
+
return !["0", "false", "off", "no"].includes(raw.trim().toLowerCase());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function profileReceivesHostMcp(profile: Pick<AgentProfile, "mcp" | "relay">): boolean {
|
|
44
|
+
return profile.mcp.mode === "host" && profile.relay.mcp !== false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function applyDefaultSharedMcp(
|
|
48
|
+
profile: AgentProfile,
|
|
49
|
+
override?: SharedMcpSpawnOverride,
|
|
50
|
+
env: Record<string, string | undefined> = process.env,
|
|
51
|
+
): AgentProfile {
|
|
52
|
+
if (!sharedMcpGloballyEnabled(env) || override === false) {
|
|
53
|
+
const { sharedMcp: _sharedMcp, ...rest } = profile;
|
|
54
|
+
return rest as AgentProfile;
|
|
55
|
+
}
|
|
56
|
+
if (override !== undefined) return { ...profile, sharedMcp: override };
|
|
57
|
+
if (profile.sharedMcp || !profileReceivesHostMcp(profile)) return profile;
|
|
58
|
+
return { ...profile, sharedMcp: {} };
|
|
59
|
+
}
|
|
60
|
+
|
|
33
61
|
/**
|
|
34
62
|
* Parse a spawn-transport's `sharedMcp` wire value into the normalized policy (or
|
|
35
|
-
* undefined =
|
|
36
|
-
*
|
|
37
|
-
* -
|
|
63
|
+
* undefined = no override). Shared by the MCP and HTTP spawn paths so they cannot drift.
|
|
64
|
+
* - undefined / null → undefined (no override)
|
|
65
|
+
* - false → false (explicit OFF kill-switch)
|
|
38
66
|
* - true → {} (ON, default policy)
|
|
39
67
|
* - { policy } → validated policy
|
|
40
68
|
* Throws `onInvalid(message)` for a malformed shape or unknown policy name.
|
|
@@ -42,8 +70,9 @@ export interface SharedMcpPolicy {
|
|
|
42
70
|
export function parseSharedMcp(
|
|
43
71
|
value: unknown,
|
|
44
72
|
onInvalid: (message: string) => Error,
|
|
45
|
-
):
|
|
46
|
-
if (value === undefined || value === null
|
|
73
|
+
): SharedMcpSpawnOverride | undefined {
|
|
74
|
+
if (value === undefined || value === null) return undefined;
|
|
75
|
+
if (value === false) return false;
|
|
47
76
|
if (value === true) return {};
|
|
48
77
|
if (typeof value !== "object") {
|
|
49
78
|
throw onInvalid("sharedMcp must be a boolean or { policy } object");
|