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.
Files changed (66) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +2 -2
  3. package/public/assets/{MessageBubble-Cl3gC5sO.js → MessageBubble-BORB9AKu.js} +2 -2
  4. package/public/assets/{MessageBubble-Cl3gC5sO.js.map → MessageBubble-BORB9AKu.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-BAu-lTm9.js → PlanGraphPanel-BwEOOqdj.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-BAu-lTm9.js.map → PlanGraphPanel-BwEOOqdj.js.map} +1 -1
  7. package/public/assets/{activity-navjipA_.js → activity-DD9bl5uf.js} +2 -2
  8. package/public/assets/{activity-navjipA_.js.map → activity-DD9bl5uf.js.map} +1 -1
  9. package/public/assets/{agents-B09rJRik.js → agents-DJgqCIqi.js} +2 -2
  10. package/public/assets/{agents-B09rJRik.js.map → agents-DJgqCIqi.js.map} +1 -1
  11. package/public/assets/{automation-BsXT9mKj.js → automation-Vbei8P9W.js} +2 -2
  12. package/public/assets/{automation-BsXT9mKj.js.map → automation-Vbei8P9W.js.map} +1 -1
  13. package/public/assets/chat-Dw5xpc5W.js +2 -0
  14. package/public/assets/{chat-Cmv-YVII.js.map → chat-Dw5xpc5W.js.map} +1 -1
  15. package/public/assets/{formatted-body-impl-CwziXint.js → formatted-body-impl-BisZ0FtO.js} +2 -2
  16. package/public/assets/{formatted-body-impl-CwziXint.js.map → formatted-body-impl-BisZ0FtO.js.map} +1 -1
  17. package/public/assets/{index-Dm1zlklN.js → index-Bsi2NH0v.js} +5 -5
  18. package/public/assets/{index-Dm1zlklN.js.map → index-Bsi2NH0v.js.map} +1 -1
  19. package/public/assets/index-umexz3cs.css +2 -0
  20. package/public/assets/{insights-CGH0lI9i.js → insights-D9ytYZyE.js} +2 -2
  21. package/public/assets/{insights-CGH0lI9i.js.map → insights-D9ytYZyE.js.map} +1 -1
  22. package/public/assets/{maintenance-BhpfPv6u.js → maintenance-BA93FvLR.js} +2 -2
  23. package/public/assets/{maintenance-BhpfPv6u.js.map → maintenance-BA93FvLR.js.map} +1 -1
  24. package/public/assets/{managed-agents-CwjcuvR6.js → managed-agents-DGwdEIzA.js} +2 -2
  25. package/public/assets/{managed-agents-CwjcuvR6.js.map → managed-agents-DGwdEIzA.js.map} +1 -1
  26. package/public/assets/{markdown-preview-impl-KBRWvxHt.js → markdown-preview-impl-C7FDXM5c.js} +2 -2
  27. package/public/assets/{markdown-preview-impl-KBRWvxHt.js.map → markdown-preview-impl-C7FDXM5c.js.map} +1 -1
  28. package/public/assets/{memory-CgpkkEKK.js → memory-CHzkRJqf.js} +2 -2
  29. package/public/assets/{memory-CgpkkEKK.js.map → memory-CHzkRJqf.js.map} +1 -1
  30. package/public/assets/{messages-BV1z5tMO.js → messages-B7CJ5tgT.js} +2 -2
  31. package/public/assets/{messages-BV1z5tMO.js.map → messages-B7CJ5tgT.js.map} +1 -1
  32. package/public/assets/{orchestrators-Bv6T_n-l.js → orchestrators-DOy9xIx2.js} +2 -2
  33. package/public/assets/{orchestrators-Bv6T_n-l.js.map → orchestrators-DOy9xIx2.js.map} +1 -1
  34. package/public/assets/{overview-BdErDjo5.js → overview-DLbv1BWa.js} +2 -2
  35. package/public/assets/{overview-BdErDjo5.js.map → overview-DLbv1BWa.js.map} +1 -1
  36. package/public/assets/{pairs-BpzbJ5OL.js → pairs-BDQo-k3F.js} +2 -2
  37. package/public/assets/{pairs-BpzbJ5OL.js.map → pairs-BDQo-k3F.js.map} +1 -1
  38. package/public/assets/{security-CRsIxNpi.js → security-9WrCcrWs.js} +2 -2
  39. package/public/assets/{security-CRsIxNpi.js.map → security-9WrCcrWs.js.map} +1 -1
  40. package/public/assets/{select-DmZ1eEzM.js → select-CRjeGZ9a.js} +2 -2
  41. package/public/assets/{select-DmZ1eEzM.js.map → select-CRjeGZ9a.js.map} +1 -1
  42. package/public/assets/{settings-BqmYUpOu.js → settings-Durch6xW.js} +2 -2
  43. package/public/assets/{settings-BqmYUpOu.js.map → settings-Durch6xW.js.map} +1 -1
  44. package/public/assets/{tasks-DZ8C8K2N.js → tasks-86St2vpn.js} +2 -2
  45. package/public/assets/{tasks-DZ8C8K2N.js.map → tasks-86St2vpn.js.map} +1 -1
  46. package/public/assets/{teams-VmX_6z_9.js → teams-C9WUxzud.js} +2 -2
  47. package/public/assets/{teams-VmX_6z_9.js.map → teams-C9WUxzud.js.map} +1 -1
  48. package/public/assets/{terminal-viewer-impl-BJilWDSR.js → terminal-viewer-impl-yXWyt2fm.js} +2 -2
  49. package/public/assets/{terminal-viewer-impl-BJilWDSR.js.map → terminal-viewer-impl-yXWyt2fm.js.map} +1 -1
  50. package/public/assets/{user-avatar-jqI4eKDf.js → user-avatar-tomKla2H.js} +2 -2
  51. package/public/assets/{user-avatar-jqI4eKDf.js.map → user-avatar-tomKla2H.js.map} +1 -1
  52. package/public/assets/{work-queue-lFDBNpds.js → work-queue-BtLMzf0g.js} +2 -2
  53. package/public/assets/{work-queue-lFDBNpds.js.map → work-queue-BtLMzf0g.js.map} +1 -1
  54. package/public/index.html +2 -2
  55. package/src/automations/dispatch.ts +3 -1
  56. package/src/config-store.ts +24 -0
  57. package/src/managed-policy.ts +3 -1
  58. package/src/mcp/tools-spawn.ts +13 -1
  59. package/src/project-settings.ts +27 -0
  60. package/src/project-skills.ts +27 -0
  61. package/src/routes/agent-sessions.ts +3 -1
  62. package/src/routes/agents-spawn.ts +4 -0
  63. package/src/services/spawn-agent.ts +13 -9
  64. package/src/shared-mcp.ts +46 -17
  65. package/public/assets/chat-Cmv-YVII.js +0 -2
  66. 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-Dm1zlklN.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-jqI4eKDf.js.map
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-jqI4eKDf.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
+ {"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-Dm1zlklN.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-lFDBNpds.js.map
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":"sXAWM,EAAwC,CAC5C,KAAM,kDACN,QAAS,wDACT,QAAS,2DACT,YAAa,wDACd,CAED,SAAgB,GAAgB,CAC9B,IAAM,EAAM,GAAQ,CACd,EAAa,EAAe,GAAM,EAAE,WAAW,CAC/C,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAM,EAAe,GAAM,EAAE,IAAI,CACjC,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAiB,EAAe,GAAM,EAAE,eAAe,CACvD,EAAY,EAAe,GAAM,EAAE,UAAU,CAC7C,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAkB,EAAe,GAAM,EAAE,gBAAgB,CACzD,EAAqB,EAAe,GAAM,EAAE,mBAAmB,CAE/D,EAAgB,GAAkB,CAClC,EAAQ,GAAmB,CAE3B,EAAiB,EAAM,OAAQ,GAAM,EAAE,UAAU,CAAC,OAExD,SAAS,EAAY,EAAqB,CACxC,GAAI,CAAC,EAAQ,KAAM,CAAE,EAAU,aAAc,mCAAmC,CAAE,OAC9E,EAAK,aAAe,QAAU,EAAK,KAAM,EAAY,EAAK,KAAK,GAAG,CAC7D,EAAK,aAAe,WAAa,EAAK,SAAS,EAAQ,EAAK,QAAQ,GAAG,CAGlF,SAAS,EAAa,EAAqB,CAEzC,EAAY,cAAe,WADb,EAAK,QAAU,EAAK,aAAe,OAAS,SAAS,EAAK,KAAO,YAAY,EAAK,MACpD,+BAAkC,CACxE,EAAK,aAAe,QAAU,EAAK,KAAM,EAAmB,EAAK,KAAM,WAAW,CAC7E,EAAK,aAAe,WAAa,EAAK,SAAS,EAAgB,EAAK,QAAQ,GAAG,EACxF,CAGJ,SAAS,EAAa,EAAqB,CACrC,EAAK,MAAM,EAAe,EAAK,KAAK,CAG1C,OACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qBAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,6DAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,gCAAkC,CAAA,EACxD,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,iCAAwB,aAAe,CAAA,CACpD,EAAiB,IAChB,EAAA,EAAA,MAAC,EAAD,CAAO,UAAU,wEAAjB,CACG,EAAe,aACV,GAEN,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,QAAD,CAAO,UAAU,kDAAyC,WAAgB,CAAA,EAC1E,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,mEACV,MAAO,EAAQ,KACf,SAAW,GAAM,EAAI,CAAE,QAAS,CAAE,GAAG,EAAS,KAAM,EAAE,OAAO,MAAO,CAAE,CAAC,UAHzE,EAKE,EAAA,EAAA,KAAC,SAAD,CAAQ,MAAM,YAAG,kBAAwB,CAAA,CACxC,EAAc,IAAK,IAAM,EAAA,EAAA,KAAC,SAAD,CAAmB,MAAO,EAAE,YAAK,EAAY,EAAE,CAAU,CAA5C,EAAE,GAA0C,CAAC,CAC7E,GACL,GACF,IAEN,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,mCACpB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,CACG,EAAM,SAAW,IAChB,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,2DAAkD,sBAAyB,CAAA,CAE3F,EAAM,IAAK,IACV,EAAA,EAAA,KAAC,EAAD,CAEQ,OACD,MACL,UAAY,GAAO,EAAW,GAAM,EAAY,EAAW,GAAI,CAAG,EAAG,MAAM,IAAI,CAC/E,YAAe,EAAY,EAAK,CAChC,aAAgB,EAAa,EAAK,CAClC,SAAU,EAAK,SAAa,EAAa,EAAK,CAAG,IAAA,GACjD,CAPK,EAAK,GAOV,CACF,CACE,GACK,CAAA,CACT,GAIV,SAAS,EAAc,CACrB,OAAM,MAAK,YAAW,UAAS,WAAU,YAQxC,CACD,IAAM,EAAgB,EAAgB,EAAK,WAAa,EAAgB,KAClE,EAAc,EAAc,EAAK,SAAW,kDAElD,OACE,EAAA,EAAA,KAAC,EAAD,CAAM,UAAW,EAAK,UAAY,4BAA8B,aAC9D,EAAA,EAAA,KAAC,EAAD,CAAa,UAAU,gBACrB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kCAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kDAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,wCAAgC,EAAK,MAAa,CAAA,EAClE,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAkB,EAAK,SAAiB,CAAA,EAC1G,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAgB,EAAK,OAAe,CAAA,CACrG,EAAK,aAAe,YACnB,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAU,uEAA8D,MAAW,CAAA,CAE1G,GACL,EAAK,OACJ,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,2DAAmD,EAAK,KAAS,CAAA,EAEhF,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2EAAf,CACG,EAAK,QACJ,EAAA,EAAA,MAAC,OAAD,CAAM,MAAM,iBAAZ,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,sBAAwB,CAAA,CAAC,EAAU,EAAK,MAAM,CACjE,GAER,EAAK,SACJ,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,qBAAa,EAAK,OAAc,CAAA,EAElD,EAAA,EAAA,MAAC,OAAD,CAAM,MAAO,OAAO,EAAK,UAAU,UAAnC,EACE,EAAA,EAAA,KAAC,EAAD,CAAU,UAAU,sBAAwB,CAAA,CAAC,EAAQ,EAAK,EAAK,UAAU,CACpE,GACH,GACF,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,wCAAf,CACG,EAAK,YACJ,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,UAAU,2DAA2D,QAAS,WAAhG,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,mBAAqB,CAAA,CAAA,SACtC,GAEV,IACC,EAAA,EAAA,KAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,cAAc,QAAS,WAAU,SAEtE,CAAA,EAEX,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,oFAAoF,QAAS,WAA3I,EACE,EAAA,EAAA,KAAC,EAAD,CAAG,UAAU,mBAAqB,CAAA,CAAA,UAC3B,GACL,GACF,GACM,CAAA,CACT,CAAA"}
1
+ {"version":3,"file":"work-queue-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-Dm1zlklN.js"></script>
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-F-FSPv4X.css">
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 agentProfile = withResolvedProvisioning(policy.profile ? getAgentProfile(policy.profile)?.value : undefined, policy.provider);
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",
@@ -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"),
@@ -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 agentProfile = withResolvedProvisioning(policy.profile ? getAgentProfile(policy.profile)?.value : undefined, policy.provider);
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,
@@ -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
- sharedMcp: { type: "object", description: "#672shared host-listener MCP (default OFF). When set, 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 a listener/downstream restart and each isolated worktree keeps session-cwd isolation. Rides the controlled `--mcp-config` path (composes with `--strict-mcp-config`). The identity-bearing relay endpoint stays per-agent (#215), never on the shared listener. `policy: \"all\"` (the default) routes the listener's full non-identity set. Omit to keep current per-agent injection.", properties: { policy: { type: "string", enum: SHARED_MCP_POLICY_NAMES } }, additionalProperties: false },
42
+ projectSkills: { type: "object", description: "#668project `.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 agentProfile = withResolvedProvisioning(profileName ? getAgentProfile(profileName)?.value : undefined, provider);
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 { SharedMcpPolicy } from "../shared-mcp";
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?: SharedMcpPolicy;
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
- const agentProfile = withResolvedProvisioning(getAgentProfile(profileName)?.value, input.provider);
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
- // #672 — shared host-listener MCP: a per-spawn `sharedMcp` overrides the profile default,
203
- // same opt-in trust gate as projectMcp (an explicit `false` parses to undefined and so
204
- // cannot turn a profile default OFF). The runner emits the bridge entry from
205
- // `agentProfile.sharedMcp` (it owns the spawn cwd → `--cwd "$WORKTREE"`); thread the resolved
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
- // Shared host-listener MCP (#672, Phase 1 of epic #670) — the relay-owned, OPT-IN path that
2
- // routes a spawned worker's NON-IDENTITY MCP (tokenlean, github) through ONE orchestrator-owned
3
- // shared `callmux` listener per host, attached via a per-agent `callmux bridge` with
4
- // `--cwd "$WORKTREE"` for session-cwd isolation. Sibling seam to project-mcp (#667): same
5
- // opt-in + parse shape, default OFF.
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 OPT-IN DECISION (this module + the spawn
19
- // param/profile field). The orchestrator owns the shared listener lifecycle/config (#673),
20
- // and runner owns bridge-arg emission (launch-assembly.ts → sharedMcpBridgeServer), the
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 (a non-undefined value) means "route
27
- * non-identity MCP through the shared listener"; absence means OFF. `policy` selects the
28
- * server set (`all` = the listener's full non-identity set; the sole value today). */
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 = OFF). Shared by the MCP and HTTP spawn paths so they cannot drift. Mirrors
36
- * parseProjectMcp exactly:
37
- * - undefined / null / false undefined (OFF)
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
- ): SharedMcpPolicy | undefined {
46
- if (value === undefined || value === null || value === false) return undefined;
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");