agent-relay-server 0.83.0 → 0.83.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 +1 -1
- package/public/assets/{MessageBubble-BmgXwQ3z.js → MessageBubble-BTBQr8uG.js} +2 -2
- package/public/assets/{MessageBubble-BmgXwQ3z.js.map → MessageBubble-BTBQr8uG.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-FfVHi1Zs.js → PlanGraphPanel-CoWJJVyD.js} +2 -2
- package/public/assets/{PlanGraphPanel-FfVHi1Zs.js.map → PlanGraphPanel-CoWJJVyD.js.map} +1 -1
- package/public/assets/{activity-CvpXBhkZ.js → activity-XqRFu_9T.js} +2 -2
- package/public/assets/{activity-CvpXBhkZ.js.map → activity-XqRFu_9T.js.map} +1 -1
- package/public/assets/{agents-D198jJhF.js → agents-BWmmWj4Q.js} +2 -2
- package/public/assets/{agents-D198jJhF.js.map → agents-BWmmWj4Q.js.map} +1 -1
- package/public/assets/{automation-BHsr7Ztm.js → automation-DRtOENs0.js} +2 -2
- package/public/assets/{automation-BHsr7Ztm.js.map → automation-DRtOENs0.js.map} +1 -1
- package/public/assets/{chat-DA5mq5zD.js → chat-gfUxbTus.js} +2 -2
- package/public/assets/{chat-DA5mq5zD.js.map → chat-gfUxbTus.js.map} +1 -1
- package/public/assets/{formatted-body-impl-CzXDNAoZ.js → formatted-body-impl-DpNHGXXQ.js} +2 -2
- package/public/assets/{formatted-body-impl-CzXDNAoZ.js.map → formatted-body-impl-DpNHGXXQ.js.map} +1 -1
- package/public/assets/{index-y_a_P2yU.js → index-D8TVUdlC.js} +11 -11
- package/public/assets/index-D8TVUdlC.js.map +1 -0
- package/public/assets/{insights-CFWUwbIj.js → insights-DPaJ5Hme.js} +2 -2
- package/public/assets/{insights-CFWUwbIj.js.map → insights-DPaJ5Hme.js.map} +1 -1
- package/public/assets/{maintenance-B37meN9r.js → maintenance-DsszLb92.js} +2 -2
- package/public/assets/{maintenance-B37meN9r.js.map → maintenance-DsszLb92.js.map} +1 -1
- package/public/assets/{managed-agents-D_uKTfD5.js → managed-agents-DqWPxFE0.js} +2 -2
- package/public/assets/{managed-agents-D_uKTfD5.js.map → managed-agents-DqWPxFE0.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DbhvZmjh.js → markdown-preview-impl-CWR4ILHK.js} +2 -2
- package/public/assets/{markdown-preview-impl-DbhvZmjh.js.map → markdown-preview-impl-CWR4ILHK.js.map} +1 -1
- package/public/assets/{memory-CLleEaHN.js → memory-C2s9bJ38.js} +2 -2
- package/public/assets/{memory-CLleEaHN.js.map → memory-C2s9bJ38.js.map} +1 -1
- package/public/assets/{messages-CHxeZT9m.js → messages-DHRJNAvy.js} +2 -2
- package/public/assets/{messages-CHxeZT9m.js.map → messages-DHRJNAvy.js.map} +1 -1
- package/public/assets/{orchestrators-DCwgTm8t.js → orchestrators-BGNNllJc.js} +2 -2
- package/public/assets/{orchestrators-DCwgTm8t.js.map → orchestrators-BGNNllJc.js.map} +1 -1
- package/public/assets/{overview-DM4t8PeA.js → overview-C1vZfyhW.js} +2 -2
- package/public/assets/{overview-DM4t8PeA.js.map → overview-C1vZfyhW.js.map} +1 -1
- package/public/assets/{pairs-DRtIJUsl.js → pairs-C1uzo8x0.js} +2 -2
- package/public/assets/{pairs-DRtIJUsl.js.map → pairs-C1uzo8x0.js.map} +1 -1
- package/public/assets/{security-D3T8nhcY.js → security-XhAAP6q3.js} +2 -2
- package/public/assets/{security-D3T8nhcY.js.map → security-XhAAP6q3.js.map} +1 -1
- package/public/assets/{select-CGh6MQBA.js → select-DEGUFEjx.js} +2 -2
- package/public/assets/{select-CGh6MQBA.js.map → select-DEGUFEjx.js.map} +1 -1
- package/public/assets/{settings-DmEscdjG.js → settings-DTbVUdlZ.js} +2 -2
- package/public/assets/{settings-DmEscdjG.js.map → settings-DTbVUdlZ.js.map} +1 -1
- package/public/assets/{tasks-L9hPxGh2.js → tasks-zwGo7hon.js} +2 -2
- package/public/assets/{tasks-L9hPxGh2.js.map → tasks-zwGo7hon.js.map} +1 -1
- package/public/assets/{teams-Bnqfnebk.js → teams-Brc5D8ox.js} +2 -2
- package/public/assets/{teams-Bnqfnebk.js.map → teams-Brc5D8ox.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DZ-cASX7.js → terminal-viewer-impl-C6yUAL8G.js} +2 -2
- package/public/assets/{terminal-viewer-impl-DZ-cASX7.js.map → terminal-viewer-impl-C6yUAL8G.js.map} +1 -1
- package/public/assets/{user-avatar-Gil1NpJ8.js → user-avatar-Bw1R011j.js} +2 -2
- package/public/assets/{user-avatar-Gil1NpJ8.js.map → user-avatar-Bw1R011j.js.map} +1 -1
- package/public/assets/{work-queue-DXfNzGs1.js → work-queue-ge4R7keo.js} +2 -2
- package/public/assets/{work-queue-DXfNzGs1.js.map → work-queue-ge4R7keo.js.map} +1 -1
- package/public/index.html +1 -1
- package/src/upgrade-install-verify.ts +160 -0
- package/src/upgrade.ts +40 -11
- package/public/assets/index-y_a_P2yU.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{C 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{C as s}from"./index-D8TVUdlC.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-Bw1R011j.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-avatar-
|
|
1
|
+
{"version":3,"file":"user-avatar-Bw1R011j.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-DYaQAXRv.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{B as f,K 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-DYaQAXRv.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{B as f,K as p,s as m}from"./index-D8TVUdlC.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-ge4R7keo.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-DXfNzGs1.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-ge4R7keo.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-D8TVUdlC.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">
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { chmodSync, existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Post-install verification + repair for a runtime-prefix upgrade (#631).
|
|
6
|
+
*
|
|
7
|
+
* A clean `npm install` exit code is NOT proof the upgrade landed: npm reports
|
|
8
|
+
* "up to date" (exit 0, nothing installed) when its hidden
|
|
9
|
+
* `node_modules/.package-lock.json` claims the target version while the extracted
|
|
10
|
+
* package dirs are still stale (lock-vs-disk divergence). That silently stranded
|
|
11
|
+
* macmini2 on 0.82.0 during the v0.83.0 deploy. This module asserts the ACTUAL
|
|
12
|
+
* on-disk versions, repairs the divergence by forcing a real re-extraction, and
|
|
13
|
+
* restores the exec bit that extraction drops from bin entries.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Metadata describing what a runtime-prefix install action must verify. */
|
|
17
|
+
export type VerifyInstall = {
|
|
18
|
+
/** Runtime prefix whose node_modules holds the installed packages. */
|
|
19
|
+
prefix: string;
|
|
20
|
+
targetVersion: string;
|
|
21
|
+
/** Package names (no version spec) to assert + repair + restore exec bits on. */
|
|
22
|
+
packages: string[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type CommandOutput = { exitCode: number; stdout: string; stderr: string };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Filesystem operations the verification needs, factored out so tests can drive
|
|
29
|
+
* the assertion/repair/exec-bit logic without a real npm install or touching
|
|
30
|
+
* disk. Defaults to {@link defaultInstallIo}.
|
|
31
|
+
*/
|
|
32
|
+
export type InstallIo = {
|
|
33
|
+
/** Actual installed version from `<prefix>/node_modules/<pkg>/package.json`. */
|
|
34
|
+
readInstalledVersion: (prefix: string, pkg: string) => string | undefined;
|
|
35
|
+
/** Delete `<prefix>/node_modules/<pkg>` so the next install must re-extract it. */
|
|
36
|
+
forceReextract: (prefix: string, pkg: string) => void;
|
|
37
|
+
/** Delete npm's hidden `<prefix>/node_modules/.package-lock.json`. */
|
|
38
|
+
removeHiddenLock: (prefix: string) => void;
|
|
39
|
+
/** chmod +x each of a package's bin entry targets; returns the files changed. */
|
|
40
|
+
restoreExecBits: (prefix: string, pkg: string) => string[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type VersionMismatch = { pkg: string; expected: string; actual?: string };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Assert the runtime packages reached the target version after npm reported
|
|
47
|
+
* success; on divergence, force a clean re-extraction and re-assert; finally
|
|
48
|
+
* restore the exec bit that npm extraction drops from bin entries. Appends a
|
|
49
|
+
* human-readable trace to `lines` and throws a loud, specific error on failure.
|
|
50
|
+
*/
|
|
51
|
+
export async function verifyAndRepairInstall(
|
|
52
|
+
action: { label: string; command: string[] },
|
|
53
|
+
verify: VerifyInstall,
|
|
54
|
+
runInstall: (command: string[]) => Promise<CommandOutput>,
|
|
55
|
+
io: InstallIo,
|
|
56
|
+
lines: string[],
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
const findMismatches = (): VersionMismatch[] =>
|
|
59
|
+
verify.packages
|
|
60
|
+
.map((pkg) => ({ pkg, expected: verify.targetVersion, actual: io.readInstalledVersion(verify.prefix, pkg) }))
|
|
61
|
+
.filter((m) => m.actual !== verify.targetVersion);
|
|
62
|
+
|
|
63
|
+
let mismatches = findMismatches();
|
|
64
|
+
if (mismatches.length > 0) {
|
|
65
|
+
// Lock-vs-disk divergence: npm's hidden node_modules/.package-lock.json
|
|
66
|
+
// claimed the target while the extracted dirs lagged, so `npm install`
|
|
67
|
+
// returned "up to date" and installed nothing. Drop the stale dirs + hidden
|
|
68
|
+
// lock so the reinstall is forced to actually re-extract the tarballs.
|
|
69
|
+
lines.push(
|
|
70
|
+
`Installed versions disagree with ${verify.targetVersion} after npm reported success ` +
|
|
71
|
+
`(lock-vs-disk divergence): ${formatMismatches(mismatches)}. Forcing a clean re-extraction.`,
|
|
72
|
+
);
|
|
73
|
+
for (const m of mismatches) io.forceReextract(verify.prefix, m.pkg);
|
|
74
|
+
io.removeHiddenLock(verify.prefix);
|
|
75
|
+
|
|
76
|
+
const result = await runInstall(action.command);
|
|
77
|
+
if (result.stdout.trim()) lines.push(result.stdout.trim());
|
|
78
|
+
if (result.stderr.trim()) lines.push(result.stderr.trim());
|
|
79
|
+
if (result.exitCode !== 0) {
|
|
80
|
+
const detail = (result.stderr.trim() || result.stdout.trim()).slice(-2000);
|
|
81
|
+
throw new Error(`${action.label} re-extraction failed with exit code ${result.exitCode}${detail ? `:\n${detail}` : ""}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
mismatches = findMismatches();
|
|
85
|
+
if (mismatches.length > 0) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`${action.label} did not install ${verify.targetVersion}: ${formatMismatches(mismatches)}. ` +
|
|
88
|
+
`npm reported success but the runtime is unchanged; re-run the upgrade or inspect ${join(verify.prefix, "node_modules")}.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
lines.push(`Re-extraction reconciled the runtime to ${verify.targetVersion}.`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// npm extraction creates bin entry files mode 0644 (no +x). The systemd units
|
|
95
|
+
// ExecStart the .bin symlink → src/index.ts directly, which needs the exec bit
|
|
96
|
+
// or it dies with status=203/EXEC. Restore +x on every upgraded package's bin
|
|
97
|
+
// targets so a re-extracted runtime still starts.
|
|
98
|
+
const restored: string[] = [];
|
|
99
|
+
for (const pkg of verify.packages) restored.push(...io.restoreExecBits(verify.prefix, pkg));
|
|
100
|
+
if (restored.length > 0) {
|
|
101
|
+
lines.push(`Restored exec bit on ${restored.length} runtime bin entr${restored.length === 1 ? "y" : "ies"}.`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatMismatches(mismatches: VersionMismatch[]): string {
|
|
106
|
+
return mismatches.map((m) => `${m.pkg} (expected ${m.expected}, got ${m.actual ?? "not installed"})`).join(", ");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Strip the version spec from a package install arg (`name@1.2.3` → `name`). */
|
|
110
|
+
export function packageName(spec: string): string {
|
|
111
|
+
const at = spec.lastIndexOf("@");
|
|
112
|
+
return at > 0 ? spec.slice(0, at) : spec;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const defaultInstallIo: InstallIo = {
|
|
116
|
+
readInstalledVersion(prefix, pkg) {
|
|
117
|
+
const manifest = join(prefix, "node_modules", pkg, "package.json");
|
|
118
|
+
if (!existsSync(manifest)) return undefined;
|
|
119
|
+
try {
|
|
120
|
+
return (JSON.parse(readFileSync(manifest, "utf8")) as { version?: string }).version;
|
|
121
|
+
} catch {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
forceReextract(prefix, pkg) {
|
|
126
|
+
rmSync(join(prefix, "node_modules", pkg), { recursive: true, force: true });
|
|
127
|
+
},
|
|
128
|
+
removeHiddenLock(prefix) {
|
|
129
|
+
rmSync(join(prefix, "node_modules", ".package-lock.json"), { force: true });
|
|
130
|
+
},
|
|
131
|
+
restoreExecBits(prefix, pkg) {
|
|
132
|
+
const pkgDir = join(prefix, "node_modules", pkg);
|
|
133
|
+
const manifest = join(pkgDir, "package.json");
|
|
134
|
+
if (!existsSync(manifest)) return [];
|
|
135
|
+
let bin: unknown;
|
|
136
|
+
try {
|
|
137
|
+
bin = (JSON.parse(readFileSync(manifest, "utf8")) as { bin?: unknown }).bin;
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
const targets = typeof bin === "string"
|
|
142
|
+
? [bin]
|
|
143
|
+
: bin && typeof bin === "object"
|
|
144
|
+
? Object.values(bin as Record<string, unknown>).filter((v): v is string => typeof v === "string")
|
|
145
|
+
: [];
|
|
146
|
+
const restored: string[] = [];
|
|
147
|
+
for (const rel of targets) {
|
|
148
|
+
const file = join(pkgDir, rel);
|
|
149
|
+
if (!existsSync(file)) continue;
|
|
150
|
+
try {
|
|
151
|
+
chmodSync(file, 0o755);
|
|
152
|
+
restored.push(file);
|
|
153
|
+
} catch {
|
|
154
|
+
// Best-effort: a chmod failure is surfaced later by the service refusing
|
|
155
|
+
// to start; don't abort the whole upgrade on a single unreadable bin.
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return restored;
|
|
159
|
+
},
|
|
160
|
+
};
|
package/src/upgrade.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { VERSION, cliRelayUrl, relayAuthHeaders } from "./config";
|
|
|
5
5
|
import type { RuntimeContracts, RuntimePackageMetadata } from "./contracts";
|
|
6
6
|
import { defaultRuntimePrefix, runtimeBinPath } from "./runtime-prefix";
|
|
7
7
|
import { resolveLocalOrchestratorId } from "./upgrade-local-id";
|
|
8
|
+
import { defaultInstallIo, packageName, verifyAndRepairInstall, type InstallIo, type VerifyInstall } from "./upgrade-install-verify";
|
|
8
9
|
import { errMessage, runInstallWithRetry } from "agent-relay-sdk";
|
|
9
10
|
import { shellEscape as shellQuote } from "agent-relay-sdk/shell-utils";
|
|
10
11
|
|
|
@@ -72,6 +73,12 @@ type UpgradeAction = {
|
|
|
72
73
|
reason: string;
|
|
73
74
|
mutates: boolean;
|
|
74
75
|
retryInstall?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Post-install verification metadata for a runtime-prefix install (#631).
|
|
78
|
+
* When set, the executor asserts each package's ACTUAL on-disk version equals
|
|
79
|
+
* the target, repairs lock-vs-disk divergence, and restores bin exec bits.
|
|
80
|
+
*/
|
|
81
|
+
verifyInstall?: VerifyInstall;
|
|
75
82
|
};
|
|
76
83
|
|
|
77
84
|
type UpgradePlan = {
|
|
@@ -183,6 +190,12 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
|
|
|
183
190
|
reason: `Update server${runnerRequested ? ", provider runner" : ""}${orchestratorRequested ? ", and orchestrator" : ""} packages to ${targetVersion}.`,
|
|
184
191
|
mutates: true,
|
|
185
192
|
retryInstall: true,
|
|
193
|
+
// Only the isolated runtime prefix has a deterministic node_modules path we
|
|
194
|
+
// can read actual versions from (and it's the systemd-managed deploy that
|
|
195
|
+
// strands hosts on a silent no-op). Global bun/npm installs are left as-is.
|
|
196
|
+
verifyInstall: snapshot.packageManager === "runtime-npm"
|
|
197
|
+
? { prefix: snapshot.runtimePrefix, targetVersion, packages: packages.map(packageName) }
|
|
198
|
+
: undefined,
|
|
186
199
|
});
|
|
187
200
|
if (snapshot.packageManager === "runtime-npm") {
|
|
188
201
|
actions.push({
|
|
@@ -268,6 +281,8 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
|
|
|
268
281
|
type ExecuteUpgradeOptions = {
|
|
269
282
|
dryRun?: boolean;
|
|
270
283
|
runner?: Runner;
|
|
284
|
+
/** Filesystem ops for post-install verification (injectable for tests). */
|
|
285
|
+
installIo?: InstallIo;
|
|
271
286
|
/** Re-register grace window for post-restart version checks (default 30s). */
|
|
272
287
|
verifyTimeoutMs?: number;
|
|
273
288
|
/** Poll interval while waiting for re-register (default 1s). */
|
|
@@ -283,6 +298,7 @@ type ExecuteUpgradeOptions = {
|
|
|
283
298
|
export async function executeUpgradePlan(plan: UpgradePlan, options: ExecuteUpgradeOptions = {}): Promise<string> {
|
|
284
299
|
if (options.dryRun) return formatUpgradePlan(plan, { dryRun: true });
|
|
285
300
|
const runner = options.runner ?? runCommand;
|
|
301
|
+
const io = options.installIo ?? defaultInstallIo;
|
|
286
302
|
const sleep = options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
|
|
287
303
|
const probeServerVersion = options.probeServerVersion ?? runningServerVersion;
|
|
288
304
|
const probeOrchestrators = options.probeOrchestrators ?? runningOrchestrators;
|
|
@@ -301,23 +317,36 @@ export async function executeUpgradePlan(plan: UpgradePlan, options: ExecuteUpgr
|
|
|
301
317
|
return value;
|
|
302
318
|
};
|
|
303
319
|
const lines = [`Upgrading Agent Relay to ${plan.targetVersion}`];
|
|
320
|
+
const runInstall = (command: string[]) => runInstallWithRetry(command, runner, {
|
|
321
|
+
sleep,
|
|
322
|
+
retries: options.installRetries,
|
|
323
|
+
baseDelayMs: options.installRetryBaseMs,
|
|
324
|
+
onRetry: ({ attempt, delayMs }) => {
|
|
325
|
+
lines.push(`npm cache race detected; retrying install attempt ${attempt} in ${delayMs}ms.`);
|
|
326
|
+
},
|
|
327
|
+
});
|
|
304
328
|
for (const action of plan.actions) {
|
|
305
329
|
lines.push(`\n${action.label}`);
|
|
306
330
|
lines.push(`$ ${action.command.map(shellQuote).join(" ")}`);
|
|
307
|
-
const result = action.retryInstall
|
|
308
|
-
? await runInstallWithRetry(action.command, runner, {
|
|
309
|
-
sleep,
|
|
310
|
-
retries: options.installRetries,
|
|
311
|
-
baseDelayMs: options.installRetryBaseMs,
|
|
312
|
-
onRetry: ({ attempt, delayMs }) => {
|
|
313
|
-
lines.push(`npm cache race detected; retrying install attempt ${attempt} in ${delayMs}ms.`);
|
|
314
|
-
},
|
|
315
|
-
})
|
|
316
|
-
: runner(action.command);
|
|
331
|
+
const result = action.retryInstall ? await runInstall(action.command) : runner(action.command);
|
|
317
332
|
if (result.stdout.trim()) lines.push(result.stdout.trim());
|
|
318
333
|
if (result.stderr.trim()) lines.push(result.stderr.trim());
|
|
319
334
|
if (result.exitCode !== 0) {
|
|
320
|
-
|
|
335
|
+
// Surface the child command's own output in the thrown error, not just the
|
|
336
|
+
// exit code (#631). The CLI reports only the error message on failure, so a
|
|
337
|
+
// bare "failed with exit code 1" hides the npm resolver failure and forces
|
|
338
|
+
// guess-and-check. The orchestrator self-upgrade path (self-upgrade.ts)
|
|
339
|
+
// already attaches the captured output — mirror it here so the local path
|
|
340
|
+
// is just as diagnosable.
|
|
341
|
+
const detail = (result.stderr.trim() || result.stdout.trim()).slice(-2000);
|
|
342
|
+
throw new Error(`${action.label} failed with exit code ${result.exitCode}${detail ? `:\n${detail}` : ""}`);
|
|
343
|
+
}
|
|
344
|
+
// A clean exit code is NOT proof the upgrade landed: npm reports "up to date"
|
|
345
|
+
// (exit 0, nothing installed) when its hidden lock claims the target while the
|
|
346
|
+
// extracted dirs are stale. Assert actual on-disk versions, repair divergence,
|
|
347
|
+
// and restore exec bits dropped by re-extraction (#631).
|
|
348
|
+
if (action.verifyInstall) {
|
|
349
|
+
await verifyAndRepairInstall(action, action.verifyInstall, runInstall, io, lines);
|
|
321
350
|
}
|
|
322
351
|
}
|
|
323
352
|
if (plan.actions.some((action) => action.command.join(" ") === "systemctl --user restart agent-relay.service")) {
|