agent-relay-server 0.90.0 → 0.91.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +1 -1
- package/package.json +2 -2
- package/public/assets/{MessageBubble-bI9fFi55.js → MessageBubble-Cl3gC5sO.js} +2 -2
- package/public/assets/{MessageBubble-bI9fFi55.js.map → MessageBubble-Cl3gC5sO.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-a3Z57nIZ.js → PlanGraphPanel-BAu-lTm9.js} +2 -2
- package/public/assets/{PlanGraphPanel-a3Z57nIZ.js.map → PlanGraphPanel-BAu-lTm9.js.map} +1 -1
- package/public/assets/{activity-VTlBVIox.js → activity-navjipA_.js} +2 -2
- package/public/assets/{activity-VTlBVIox.js.map → activity-navjipA_.js.map} +1 -1
- package/public/assets/{agents-C2TNRCn1.js → agents-B09rJRik.js} +2 -2
- package/public/assets/{agents-C2TNRCn1.js.map → agents-B09rJRik.js.map} +1 -1
- package/public/assets/{automation-CUSyRZla.js → automation-BsXT9mKj.js} +2 -2
- package/public/assets/{automation-CUSyRZla.js.map → automation-BsXT9mKj.js.map} +1 -1
- package/public/assets/chat-Cmv-YVII.js +2 -0
- package/public/assets/chat-Cmv-YVII.js.map +1 -0
- package/public/assets/{formatted-body-impl-DBbmythT.js → formatted-body-impl-CwziXint.js} +2 -2
- package/public/assets/{formatted-body-impl-DBbmythT.js.map → formatted-body-impl-CwziXint.js.map} +1 -1
- package/public/assets/{index-CiBdEE9x.js → index-Dm1zlklN.js} +5 -5
- package/public/assets/{index-CiBdEE9x.js.map → index-Dm1zlklN.js.map} +1 -1
- package/public/assets/{insights-9P_MGbMS.js → insights-CGH0lI9i.js} +2 -2
- package/public/assets/{insights-9P_MGbMS.js.map → insights-CGH0lI9i.js.map} +1 -1
- package/public/assets/{maintenance-CA1XujU3.js → maintenance-BhpfPv6u.js} +2 -2
- package/public/assets/{maintenance-CA1XujU3.js.map → maintenance-BhpfPv6u.js.map} +1 -1
- package/public/assets/{managed-agents-DCfSlqW7.js → managed-agents-CwjcuvR6.js} +2 -2
- package/public/assets/{managed-agents-DCfSlqW7.js.map → managed-agents-CwjcuvR6.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-MgZ3Nr7K.js → markdown-preview-impl-KBRWvxHt.js} +2 -2
- package/public/assets/{markdown-preview-impl-MgZ3Nr7K.js.map → markdown-preview-impl-KBRWvxHt.js.map} +1 -1
- package/public/assets/{memory-DatJLjrc.js → memory-CgpkkEKK.js} +2 -2
- package/public/assets/{memory-DatJLjrc.js.map → memory-CgpkkEKK.js.map} +1 -1
- package/public/assets/{messages-CQsmbtzx.js → messages-BV1z5tMO.js} +2 -2
- package/public/assets/{messages-CQsmbtzx.js.map → messages-BV1z5tMO.js.map} +1 -1
- package/public/assets/{orchestrators-CAWYWXmh.js → orchestrators-Bv6T_n-l.js} +2 -2
- package/public/assets/{orchestrators-CAWYWXmh.js.map → orchestrators-Bv6T_n-l.js.map} +1 -1
- package/public/assets/{overview-EqnqXVUb.js → overview-BdErDjo5.js} +2 -2
- package/public/assets/{overview-EqnqXVUb.js.map → overview-BdErDjo5.js.map} +1 -1
- package/public/assets/{pairs-BfKenirO.js → pairs-BpzbJ5OL.js} +2 -2
- package/public/assets/{pairs-BfKenirO.js.map → pairs-BpzbJ5OL.js.map} +1 -1
- package/public/assets/{security-CJVJ9MgS.js → security-CRsIxNpi.js} +2 -2
- package/public/assets/{security-CJVJ9MgS.js.map → security-CRsIxNpi.js.map} +1 -1
- package/public/assets/{select-BcmIzk-E.js → select-DmZ1eEzM.js} +2 -2
- package/public/assets/{select-BcmIzk-E.js.map → select-DmZ1eEzM.js.map} +1 -1
- package/public/assets/{settings-BWO-UyHv.js → settings-BqmYUpOu.js} +2 -2
- package/public/assets/{settings-BWO-UyHv.js.map → settings-BqmYUpOu.js.map} +1 -1
- package/public/assets/{tasks-DTdI_MJi.js → tasks-DZ8C8K2N.js} +2 -2
- package/public/assets/{tasks-DTdI_MJi.js.map → tasks-DZ8C8K2N.js.map} +1 -1
- package/public/assets/{teams-C9C3KVaV.js → teams-VmX_6z_9.js} +2 -2
- package/public/assets/{teams-C9C3KVaV.js.map → teams-VmX_6z_9.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BDBa94-B.js → terminal-viewer-impl-BJilWDSR.js} +2 -2
- package/public/assets/{terminal-viewer-impl-BDBa94-B.js.map → terminal-viewer-impl-BJilWDSR.js.map} +1 -1
- package/public/assets/{user-avatar-BL-CJwnn.js → user-avatar-jqI4eKDf.js} +2 -2
- package/public/assets/{user-avatar-BL-CJwnn.js.map → user-avatar-jqI4eKDf.js.map} +1 -1
- package/public/assets/{work-queue-Vc_2-4DG.js → work-queue-lFDBNpds.js} +2 -2
- package/public/assets/{work-queue-Vc_2-4DG.js.map → work-queue-lFDBNpds.js.map} +1 -1
- package/public/index.html +1 -1
- package/runner/src/adapter.ts +4 -0
- package/src/config-store.ts +14 -0
- package/src/mcp/tools-spawn.ts +3 -0
- package/src/routes/agents-spawn.ts +2 -0
- package/src/self-resume.ts +21 -5
- package/src/services/register-agent.ts +4 -1
- package/src/services/spawn-agent.ts +18 -0
- package/src/shared-mcp.ts +57 -0
- package/public/assets/chat-BEssCn_H.js +0 -2
- package/public/assets/chat-BEssCn_H.js.map +0 -1
|
@@ -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-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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-avatar-
|
|
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,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-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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-Vc_2-4DG.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-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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"}
|
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-Dm1zlklN.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">
|
package/runner/src/adapter.ts
CHANGED
|
@@ -100,6 +100,10 @@ export interface RunnerSpawnConfig {
|
|
|
100
100
|
// Stage 2 (#215): the MCP endpoint the agent connects to — the runner-local proxy URL when the
|
|
101
101
|
// proxy is active. Undefined → the adapter targets the relay's MCP endpoint directly (Stage 1).
|
|
102
102
|
relayMcpEndpoint?: string;
|
|
103
|
+
// #672: the shared host-listener URL the per-agent `callmux bridge` connects to, when the
|
|
104
|
+
// profile/spawn opted into `agentProfile.sharedMcp`. Resolved runner-side (env → default);
|
|
105
|
+
// the launch assembler only emits the bridge entry when sharedMcp is on.
|
|
106
|
+
sharedMcpUrl?: string;
|
|
103
107
|
monitor?: {
|
|
104
108
|
deliver(messages: Message[]): Promise<number[]>;
|
|
105
109
|
};
|
package/src/config-store.ts
CHANGED
|
@@ -6,6 +6,7 @@ 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 { SHARED_MCP_POLICY_NAMES } from "./shared-mcp";
|
|
9
10
|
import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
10
11
|
import { errMessage, isRecord, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES } from "agent-relay-sdk"; import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
11
12
|
import type {
|
|
@@ -237,6 +238,17 @@ function cleanAgentProfileProjectMcp(value: unknown): AgentProfile["projectMcp"]
|
|
|
237
238
|
return { policy: cleanEnum(policy, "projectMcp.policy", PROJECT_MCP_POLICY_NAMES) as NonNullable<AgentProfile["projectMcp"]>["policy"] };
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
// #672 — shared host-listener MCP projection profile default. Mirrors projectMcp:
|
|
242
|
+
// boolean | { policy } → normalized { policy? } | undefined (OFF).
|
|
243
|
+
function cleanAgentProfileSharedMcp(value: unknown): AgentProfile["sharedMcp"] {
|
|
244
|
+
if (value === undefined || value === null || value === false) return undefined;
|
|
245
|
+
if (value === true) return {};
|
|
246
|
+
if (!isRecord(value)) throw new ValidationError("sharedMcp must be a boolean or { policy } object");
|
|
247
|
+
const policy = value.policy;
|
|
248
|
+
if (policy === undefined || policy === null) return {};
|
|
249
|
+
return { policy: cleanEnum(policy, "sharedMcp.policy", SHARED_MCP_POLICY_NAMES) as NonNullable<AgentProfile["sharedMcp"]>["policy"] };
|
|
250
|
+
}
|
|
251
|
+
|
|
240
252
|
function agentProfileDefaults(input: Pick<AgentProfile, "name" | "base"> & Partial<AgentProfile>): AgentProfile {
|
|
241
253
|
const isolated = input.base !== "host";
|
|
242
254
|
// "vanilla" (#552) additionally suppresses the REPO instruction cascade
|
|
@@ -258,6 +270,7 @@ function agentProfileDefaults(input: Pick<AgentProfile, "name" | "base"> & Parti
|
|
|
258
270
|
},
|
|
259
271
|
...(input.projectInstructions ? { projectInstructions: input.projectInstructions } : {}),
|
|
260
272
|
...(input.projectMcp ? { projectMcp: input.projectMcp } : {}),
|
|
273
|
+
...(input.sharedMcp ? { sharedMcp: input.sharedMcp } : {}),
|
|
261
274
|
relay: {
|
|
262
275
|
context: input.relay?.context ?? !isolated,
|
|
263
276
|
skills: input.relay?.skills ?? !isolated,
|
|
@@ -333,6 +346,7 @@ function validateAgentProfile(key: string, value: unknown): AgentProfile {
|
|
|
333
346
|
},
|
|
334
347
|
projectInstructions: cleanAgentProfileProjectInstructions(value.projectInstructions),
|
|
335
348
|
projectMcp: cleanAgentProfileProjectMcp(value.projectMcp),
|
|
349
|
+
sharedMcp: cleanAgentProfileSharedMcp(value.sharedMcp),
|
|
336
350
|
relay: {
|
|
337
351
|
context: relay.context === undefined ? defaults.relay.context : cleanBoolean(relay.context, "relay.context"),
|
|
338
352
|
skills: relay.skills === undefined ? defaults.relay.skills : cleanBoolean(relay.skills, "relay.skills"),
|
package/src/mcp/tools-spawn.ts
CHANGED
|
@@ -5,6 +5,7 @@ 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 { parseSharedMcp, SHARED_MCP_POLICY_NAMES } from "../shared-mcp";
|
|
8
9
|
import { countLiveSpawnedAgents, listAgents, listWorkspaces, ValidationError } from "../db";
|
|
9
10
|
import { listCommands } from "../commands-db";
|
|
10
11
|
import { DEFAULT_SPAWN_PROFILE_NAME } from "../config-store";
|
|
@@ -36,6 +37,7 @@ export const SPAWN_TOOLS: ToolDefinition[] = [
|
|
|
36
37
|
systemPromptAppend: { type: "string" },
|
|
37
38
|
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 },
|
|
38
39
|
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: "#672 — shared 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 },
|
|
39
41
|
profile: { type: "string", description: `Agent profile name to apply (env, instructions, permissions, MCP/skills, spawn quota). Omit to use \`${DEFAULT_SPAWN_PROFILE_NAME}\`.` },
|
|
40
42
|
tags: { type: "array", items: { type: "string" } },
|
|
41
43
|
capabilities: { type: "array", items: { type: "string" } },
|
|
@@ -111,6 +113,7 @@ export async function relaySpawnAgent(auth: McpAuthContext, args: Record<string,
|
|
|
111
113
|
systemPromptAppend: optionalString(args.systemPromptAppend, "systemPromptAppend", 64_000),
|
|
112
114
|
projectInstructions: parseProjectInstructions(args.projectInstructions, (m) => new ValidationError(m)),
|
|
113
115
|
projectMcp: parseProjectMcp(args.projectMcp, (m) => new ValidationError(m)),
|
|
116
|
+
sharedMcp: parseSharedMcp(args.sharedMcp, (m) => new ValidationError(m)),
|
|
114
117
|
tags: optionalStringArray(args.tags, "tags"),
|
|
115
118
|
capabilities: optionalStringArray(args.capabilities, "capabilities"),
|
|
116
119
|
providerArgs: optionalStringArray(args.providerArgs, "providerArgs"),
|
|
@@ -8,6 +8,7 @@ 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 { parseSharedMcp } from "../shared-mcp";
|
|
11
12
|
import { authContextFromRequest } from "../services/auth-context";
|
|
12
13
|
import { listHostDirectories } from "../agent-spawn";
|
|
13
14
|
import { rankRouteCandidates, type RouteAdvisorInput } from "../context-router";
|
|
@@ -123,6 +124,7 @@ export const postAgentSpawn: Handler = async (req) => {
|
|
|
123
124
|
systemPromptAppend: cleanString(parsed.body.systemPromptAppend, "systemPromptAppend", { max: 64_000 }),
|
|
124
125
|
projectInstructions: parseProjectInstructions(parsed.body.projectInstructions, (m) => new ValidationError(m)),
|
|
125
126
|
projectMcp: parseProjectMcp(parsed.body.projectMcp, (m) => new ValidationError(m)),
|
|
127
|
+
sharedMcp: parseSharedMcp(parsed.body.sharedMcp, (m) => new ValidationError(m)),
|
|
126
128
|
tags: cleanStringArray(parsed.body.tags, "tags", { itemMax: 80, maxItems: 50 }),
|
|
127
129
|
capabilities: cleanStringArray(parsed.body.capabilities, "capabilities", { itemMax: 80, maxItems: 50 }),
|
|
128
130
|
providerArgs: cleanStringArray(parsed.body.providerArgs, "providerArgs", { itemMax: 80, maxItems: 50 }),
|
package/src/self-resume.ts
CHANGED
|
@@ -26,9 +26,10 @@ interface CompactAndResumeInput {
|
|
|
26
26
|
interface CompactAndResumeResult {
|
|
27
27
|
ok: boolean;
|
|
28
28
|
mode: SelfResumeMode;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
generation: number;
|
|
30
|
+
commandId: string;
|
|
31
|
+
continuationChars?: number;
|
|
32
|
+
ledgerEntries: number;
|
|
32
33
|
exhausted?: boolean;
|
|
33
34
|
reason?: string;
|
|
34
35
|
}
|
|
@@ -54,7 +55,15 @@ export function compactAndResume(input: CompactAndResumeInput): CompactAndResume
|
|
|
54
55
|
if (budget.kind === "hard-stop") {
|
|
55
56
|
const command = createCleanStop(agent, budget.reason);
|
|
56
57
|
notifySelfResumeBudgetExhausted(agent, envelope, budget.reason);
|
|
57
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
mode,
|
|
61
|
+
generation: envelope.generation,
|
|
62
|
+
commandId: command.id,
|
|
63
|
+
ledgerEntries: envelope.ledger.length,
|
|
64
|
+
exhausted: true,
|
|
65
|
+
reason: budget.reason,
|
|
66
|
+
};
|
|
58
67
|
}
|
|
59
68
|
if (budget.kind === "warning") {
|
|
60
69
|
notifySelfResumeBudgetWarning(agent, envelope, budget.reason);
|
|
@@ -84,7 +93,14 @@ export function compactAndResume(input: CompactAndResumeInput): CompactAndResume
|
|
|
84
93
|
? createNativeCompact(agent.id, generation, continuation)
|
|
85
94
|
: createClearThenInject(agent.id, generation, continuation);
|
|
86
95
|
emitCommandEvent(command, "command.requested");
|
|
87
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
mode,
|
|
99
|
+
generation,
|
|
100
|
+
commandId: command.id,
|
|
101
|
+
continuationChars: continuation.length,
|
|
102
|
+
ledgerEntries: updated.ledger.length,
|
|
103
|
+
};
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
export function enqueueContinuationInjectionAfterSelfResume(command: Command): Command | null {
|
|
@@ -42,7 +42,10 @@ export function registerAgent(input: RegisterAgentInput, ctx: AuthContext): Agen
|
|
|
42
42
|
if (teamLineage) input.teamId = teamLineage;
|
|
43
43
|
|
|
44
44
|
const existing = getAgent(input.id);
|
|
45
|
-
|
|
45
|
+
// Registration labels are spawn defaults. Runtime /label changes remain authoritative.
|
|
46
|
+
const registrationInput = existing ? { ...input } : input;
|
|
47
|
+
if (existing) delete registrationInput.label;
|
|
48
|
+
const agent = upsertAgent(registrationInput);
|
|
46
49
|
|
|
47
50
|
// Managed came-up-running reconcile — no-op for non-managed agents (the guard lives
|
|
48
51
|
// in markManagedAgentRunning). Clears backoff, flushes the policy queue, transitions
|
|
@@ -34,6 +34,7 @@ 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
38
|
import { autoSeedProjectForCwd } from "./project";
|
|
38
39
|
import { hasComponentScope, isComponentAuthorizedFor } from "../security";
|
|
39
40
|
import { isPathWithinBase } from "../utils";
|
|
@@ -84,6 +85,16 @@ export interface SpawnAgentInput {
|
|
|
84
85
|
* to projectInstructions; carried to the runner on the in-flight `agentProfile.projectMcp`.
|
|
85
86
|
*/
|
|
86
87
|
projectMcp?: ProjectMcpPolicy;
|
|
88
|
+
/**
|
|
89
|
+
* #672 — shared host-listener MCP. When set, the runner emits a per-agent `callmux bridge`
|
|
90
|
+
* stdio entry so the agent's NON-IDENTITY MCP (tokenlean, github) flows through the
|
|
91
|
+
* orchestrator-owned shared listener instead of per-agent server processes — the session
|
|
92
|
+
* survives a listener/downstream restart, with `--cwd "$WORKTREE"` session-cwd isolation.
|
|
93
|
+
* Additive-from-empty + opt-in: absent (undefined) = OFF → the launch is byte-identical to
|
|
94
|
+
* today. Sibling to projectMcp; carried to the runner on the in-flight `agentProfile.sharedMcp`.
|
|
95
|
+
* The identity-bearing relay endpoint stays per-agent (#215), never on the shared listener.
|
|
96
|
+
*/
|
|
97
|
+
sharedMcp?: SharedMcpPolicy;
|
|
87
98
|
/**
|
|
88
99
|
* Legacy report-back contract flag. Spawned children now report up by lineage: their
|
|
89
100
|
* turn-final response is delivered to the spawner automatically, without a reply obligation.
|
|
@@ -188,6 +199,13 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
188
199
|
// turn a profile default OFF — opt-in is the trust gate, not opt-out.
|
|
189
200
|
if (input.projectMcp !== undefined) agentProfile.projectMcp = input.projectMcp;
|
|
190
201
|
|
|
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
|
+
|
|
191
209
|
// --- THE GATE (uniform across transports) ---------------------------------
|
|
192
210
|
// Coarse capability: spawning requires command:spawn. The MCP dispatcher already enforced
|
|
193
211
|
// this; the HTTP route did not — asserting here makes it transport-invariant.
|
|
@@ -0,0 +1,57 @@
|
|
|
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.
|
|
6
|
+
//
|
|
7
|
+
// WHY: a shared listener turns "an MCP/listener restart kills the agent" into "the agent never
|
|
8
|
+
// notices" (the bridge reconnects + retries once on the next tool call — the #659 work-loss
|
|
9
|
+
// class), and collapses N per-agent server processes into one shared set (~100MB saved per
|
|
10
|
+
// extra listener). The spike (#671) proved both: session survives a listener restart, and
|
|
11
|
+
// session-cwd routing gives each isolated worktree its own cwd from the single shared listener.
|
|
12
|
+
//
|
|
13
|
+
// INVARIANT (epic #670): callmux owns transport; relay owns authz. The shared listener carries
|
|
14
|
+
// ONLY non-identity servers — the identity-bearing relay endpoint stays per-agent on #215 and
|
|
15
|
+
// is NEVER routed through the shared listener (the spike proved a shared listener can't carry
|
|
16
|
+
// per-agent relay tokens; that's Phase 2, gated on callmux#36).
|
|
17
|
+
//
|
|
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.
|
|
22
|
+
|
|
23
|
+
export const SHARED_MCP_POLICY_NAMES = ["all"] as const;
|
|
24
|
+
export type SharedMcpPolicyName = (typeof SHARED_MCP_POLICY_NAMES)[number];
|
|
25
|
+
|
|
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). */
|
|
29
|
+
export interface SharedMcpPolicy {
|
|
30
|
+
policy?: SharedMcpPolicyName;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 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)
|
|
38
|
+
* - true → {} (ON, default policy)
|
|
39
|
+
* - { policy } → validated policy
|
|
40
|
+
* Throws `onInvalid(message)` for a malformed shape or unknown policy name.
|
|
41
|
+
*/
|
|
42
|
+
export function parseSharedMcp(
|
|
43
|
+
value: unknown,
|
|
44
|
+
onInvalid: (message: string) => Error,
|
|
45
|
+
): SharedMcpPolicy | undefined {
|
|
46
|
+
if (value === undefined || value === null || value === false) return undefined;
|
|
47
|
+
if (value === true) return {};
|
|
48
|
+
if (typeof value !== "object") {
|
|
49
|
+
throw onInvalid("sharedMcp must be a boolean or { policy } object");
|
|
50
|
+
}
|
|
51
|
+
const policy = (value as Record<string, unknown>).policy;
|
|
52
|
+
if (policy === undefined || policy === null) return {};
|
|
53
|
+
if (typeof policy !== "string" || !SHARED_MCP_POLICY_NAMES.includes(policy as SharedMcpPolicyName)) {
|
|
54
|
+
throw onInvalid(`sharedMcp.policy must be one of: ${SHARED_MCP_POLICY_NAMES.join(", ")}`);
|
|
55
|
+
}
|
|
56
|
+
return { policy: policy as SharedMcpPolicyName };
|
|
57
|
+
}
|