agent-relay-server 0.106.0 → 0.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/docs/openapi.json +29 -1
  2. package/package.json +5 -4
  3. package/public/assets/{MessageBubble-CPYQQLDI.js → MessageBubble-PEdvVh_m.js} +2 -2
  4. package/public/assets/{MessageBubble-CPYQQLDI.js.map → MessageBubble-PEdvVh_m.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-B7xZt5DI.js → PlanGraphPanel-DSts7Rtq.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-B7xZt5DI.js.map → PlanGraphPanel-DSts7Rtq.js.map} +1 -1
  7. package/public/assets/{activity-UKQrQ8_0.js → activity-BcYf8YNO.js} +2 -2
  8. package/public/assets/{activity-UKQrQ8_0.js.map → activity-BcYf8YNO.js.map} +1 -1
  9. package/public/assets/{agent-profiles-Zr_5nNEJ.js → agent-profiles-CuTQtmV6.js} +2 -2
  10. package/public/assets/{agent-profiles-Zr_5nNEJ.js.map → agent-profiles-CuTQtmV6.js.map} +1 -1
  11. package/public/assets/{agents-FdrfmJsj.js → agents-CgRFJyks.js} +2 -2
  12. package/public/assets/{agents-FdrfmJsj.js.map → agents-CgRFJyks.js.map} +1 -1
  13. package/public/assets/{analytics-VximqXQ9.js → analytics-C3OTFIXo.js} +18 -18
  14. package/public/assets/{analytics-VximqXQ9.js.map → analytics-C3OTFIXo.js.map} +1 -1
  15. package/public/assets/{automation-DlEcJvgu.js → automation-CH9YGYoE.js} +2 -2
  16. package/public/assets/{automation-DlEcJvgu.js.map → automation-CH9YGYoE.js.map} +1 -1
  17. package/public/assets/{chat-D1bunuI-.js → chat-BLcm2Aht.js} +2 -2
  18. package/public/assets/{chat-D1bunuI-.js.map → chat-BLcm2Aht.js.map} +1 -1
  19. package/public/assets/{formatted-body-impl-4v9SkUD1.js → formatted-body-impl-BJTthVHg.js} +2 -2
  20. package/public/assets/{formatted-body-impl-4v9SkUD1.js.map → formatted-body-impl-BJTthVHg.js.map} +1 -1
  21. package/public/assets/{index-Vmhw442d.js → index-3ydeNO0c.js} +6 -6
  22. package/public/assets/index-3ydeNO0c.js.map +1 -0
  23. package/public/assets/{insights-CntgHcTp.js → insights-DklrIhb6.js} +2 -2
  24. package/public/assets/{insights-CntgHcTp.js.map → insights-DklrIhb6.js.map} +1 -1
  25. package/public/assets/{maintenance-Dw7frQtu.js → maintenance-FAAMGpuZ.js} +2 -2
  26. package/public/assets/{maintenance-Dw7frQtu.js.map → maintenance-FAAMGpuZ.js.map} +1 -1
  27. package/public/assets/{managed-agents-Bj--bFmZ.js → managed-agents-CVca-s3t.js} +2 -2
  28. package/public/assets/{managed-agents-Bj--bFmZ.js.map → managed-agents-CVca-s3t.js.map} +1 -1
  29. package/public/assets/{markdown-preview-impl-DGLCocU8.js → markdown-preview-impl-BPW4OQo9.js} +2 -2
  30. package/public/assets/{markdown-preview-impl-DGLCocU8.js.map → markdown-preview-impl-BPW4OQo9.js.map} +1 -1
  31. package/public/assets/{memory-yj2MIxUA.js → memory-BzuDTHC5.js} +2 -2
  32. package/public/assets/{memory-yj2MIxUA.js.map → memory-BzuDTHC5.js.map} +1 -1
  33. package/public/assets/{messages-BT5mL7iz.js → messages-_W24PNvT.js} +2 -2
  34. package/public/assets/{messages-BT5mL7iz.js.map → messages-_W24PNvT.js.map} +1 -1
  35. package/public/assets/{orchestrators-BVDAhrPk.js → orchestrators-Du6I_n8i.js} +2 -2
  36. package/public/assets/{orchestrators-BVDAhrPk.js.map → orchestrators-Du6I_n8i.js.map} +1 -1
  37. package/public/assets/{overview-DlqJIqYK.js → overview-Dyvm1qpT.js} +2 -2
  38. package/public/assets/{overview-DlqJIqYK.js.map → overview-Dyvm1qpT.js.map} +1 -1
  39. package/public/assets/{pairs-DQ4tNCo3.js → pairs-B8x1RCNd.js} +2 -2
  40. package/public/assets/{pairs-DQ4tNCo3.js.map → pairs-B8x1RCNd.js.map} +1 -1
  41. package/public/assets/{prompt-settings-DlsqOP0C.js → prompt-settings-xVXmvxB5.js} +2 -2
  42. package/public/assets/{prompt-settings-DlsqOP0C.js.map → prompt-settings-xVXmvxB5.js.map} +1 -1
  43. package/public/assets/{registry-Cw2fdWrn.js → registry-ypiICauZ.js} +2 -2
  44. package/public/assets/{registry-Cw2fdWrn.js.map → registry-ypiICauZ.js.map} +1 -1
  45. package/public/assets/{security-ucr7IT1V.js → security-BfOQ6T5r.js} +2 -2
  46. package/public/assets/{security-ucr7IT1V.js.map → security-BfOQ6T5r.js.map} +1 -1
  47. package/public/assets/{select-D8UWCtYZ.js → select-Dx8Ro-A3.js} +2 -2
  48. package/public/assets/{select-D8UWCtYZ.js.map → select-Dx8Ro-A3.js.map} +1 -1
  49. package/public/assets/settings-87clTx4N.js +6 -0
  50. package/public/assets/settings-87clTx4N.js.map +1 -0
  51. package/public/assets/{tasks-jRUvxUd5.js → tasks-1Kvp53wh.js} +2 -2
  52. package/public/assets/{tasks-jRUvxUd5.js.map → tasks-1Kvp53wh.js.map} +1 -1
  53. package/public/assets/{teams-Dn0yggD-.js → teams-DJWp4AKi.js} +2 -2
  54. package/public/assets/{teams-Dn0yggD-.js.map → teams-DJWp4AKi.js.map} +1 -1
  55. package/public/assets/{terminal-viewer-impl-fLly20IP.js → terminal-viewer-impl-DgMuRAe_.js} +2 -2
  56. package/public/assets/{terminal-viewer-impl-fLly20IP.js.map → terminal-viewer-impl-DgMuRAe_.js.map} +1 -1
  57. package/public/assets/types-DorDDvEb.js.map +1 -1
  58. package/public/assets/{user-avatar-BWLUHt2e.js → user-avatar-CfAUS3PZ.js} +2 -2
  59. package/public/assets/{user-avatar-BWLUHt2e.js.map → user-avatar-CfAUS3PZ.js.map} +1 -1
  60. package/public/assets/{work-queue-BAnNY0xE.js → work-queue-D-Otxd9u.js} +2 -2
  61. package/public/assets/{work-queue-BAnNY0xE.js.map → work-queue-D-Otxd9u.js.map} +1 -1
  62. package/public/index.html +1 -1
  63. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  64. package/src/branch-landed.ts +76 -1
  65. package/src/db/provider-quotas.ts +6 -0
  66. package/src/notification-types.ts +2 -0
  67. package/src/routes/commands.ts +17 -1
  68. package/src/routes/index.ts +2 -1
  69. package/src/routes/provider-quotas.ts +13 -1
  70. package/src/security.ts +2 -0
  71. package/src/services/transient-agent-reaper.ts +16 -1
  72. package/public/assets/index-Vmhw442d.js.map +0 -1
  73. package/public/assets/settings-Dvf_ps1B.js +0 -6
  74. package/public/assets/settings-Dvf_ps1B.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{$n as t,Yn as n,Zn as r,ur as i}from"./lucide-react-C4eXYCwx.js";import{_ as a,rt as o}from"./display-BWi8DQCp.js";import{D as s}from"./index-Vmhw442d.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,n=null;return(async()=>{try{let r=await t(d);if(e)return;n=URL.createObjectURL(r),p(n)}catch{e||p(null)}})(),()=>{e=!0,n&&URL.revokeObjectURL(n)}},[d]);let m=e?.displayName||e?.handle||`User`;if(f)return(0,l.jsx)(`img`,{src:f,alt:m,title:m,className:n(`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:n(`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-BWLUHt2e.js.map
1
+ import{r as e}from"./chunk-CilyBKbf.js";import{$n as t,Yn as n,Zn as r,ur as i}from"./lucide-react-C4eXYCwx.js";import{_ as a,rt as o}from"./display-BWi8DQCp.js";import{D as s}from"./index-3ydeNO0c.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,n=null;return(async()=>{try{let r=await t(d);if(e)return;n=URL.createObjectURL(r),p(n)}catch{e||p(null)}})(),()=>{e=!0,n&&URL.revokeObjectURL(n)}},[d]);let m=e?.displayName||e?.handle||`User`;if(f)return(0,l.jsx)(`img`,{src:f,alt:m,title:m,className:n(`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:n(`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-CfAUS3PZ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-avatar-BWLUHt2e.js","names":[],"sources":["../../dashboard/src/components/shared/user-avatar.tsx"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport { AgentTypeIcon } from '@/components/shared/agent-type-icon'\nimport { apiBlob } from '@/lib/api'\nimport { humanAccentClass } from '@/lib/display'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { cn } from '@/lib/utils'\nimport type { Agent, User } from '@/types'\n\n// Minimal stub so AgentTypeIcon resolves the generic 'user' face when no avatar exists.\nconst HUMAN_AGENT_STUB = { id: HUMAN_AGENT_ID } as unknown as Agent\n\n// A human's face (#390). Renders, in order of preference: the user's content-addressed avatar\n// image (the FK lands fully in #630 — until then `avatarArtifactId` is null and this path is\n// dormant), then a colored initial when we need to distinguish multiple operators, then the\n// generic operator icon. Degrades gracefully at every step so a single seeded user looks\n// exactly as before.\nexport function UserAvatar({\n user,\n address,\n distinct = false,\n className,\n}: {\n user: User | undefined\n /** The human address (`user` / `user:<id>`) — drives the deterministic accent color. */\n address?: string\n /** When true (multiple humans in view), fall back to a colored initial instead of the icon. */\n distinct?: boolean\n className?: string\n}) {\n const avatarUrl = user?.avatarUrl ?? (user?.avatarArtifactId ? `/artifacts/${encodeURIComponent(user.avatarArtifactId)}/content` : null)\n const [objectUrl, setObjectUrl] = useState<string | null>(null)\n\n useEffect(() => {\n if (!avatarUrl) { setObjectUrl(null); return }\n let cancelled = false\n let url: string | null = null\n void (async () => {\n try {\n const blob = await apiBlob(avatarUrl)\n if (cancelled) return\n url = URL.createObjectURL(blob)\n setObjectUrl(url)\n } catch {\n if (!cancelled) setObjectUrl(null)\n }\n })()\n return () => { cancelled = true; if (url) URL.revokeObjectURL(url) }\n }, [avatarUrl])\n\n const title = user?.displayName || user?.handle || 'User'\n\n if (objectUrl) {\n return <img src={objectUrl} alt={title} title={title} className={cn('w-7 h-7 rounded-md object-cover', className)} />\n }\n if (distinct) {\n const initial = (title.trim()[0] || 'U').toUpperCase()\n return (\n <span\n title={title}\n className={cn('inline-flex w-7 h-7 items-center justify-center rounded-md bg-muted text-xs font-semibold ring-1 ring-foreground/10', humanAccentClass(address), className)}\n >\n {initial}\n </span>\n )\n }\n return <AgentTypeIcon agent={HUMAN_AGENT_STUB} className={className} />\n}\n"],"mappings":"+NASM,EAAmB,CAAE,GAAI,EAAgB,CAO/C,SAAgB,EAAW,CACzB,OACA,UACA,WAAW,GACX,aAQC,CACD,IAAM,EAAY,GAAM,YAAc,GAAM,iBAAmB,cAAc,mBAAmB,EAAK,iBAAiB,CAAC,UAAY,MAC7H,CAAC,EAAW,IAAA,EAAA,EAAA,UAAwC,KAAK,EAE/D,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAW,CAAE,EAAa,KAAK,CAAE,OACtC,IAAI,EAAY,GACZ,EAAqB,KAWzB,OAVM,SAAY,CAChB,GAAI,CACF,IAAM,EAAO,MAAM,EAAQ,EAAU,CACrC,GAAI,EAAW,OACf,EAAM,IAAI,gBAAgB,EAAK,CAC/B,EAAa,EAAI,MACX,CACD,GAAW,EAAa,KAAK,KAElC,KACS,CAAE,EAAY,GAAU,GAAK,IAAI,gBAAgB,EAAI,GACjE,CAAC,EAAU,CAAC,CAEf,IAAM,EAAQ,GAAM,aAAe,GAAM,QAAU,OAEnD,GAAI,EACF,OAAO,EAAA,EAAA,KAAC,MAAD,CAAK,IAAK,EAAW,IAAK,EAAc,QAAO,UAAW,EAAG,kCAAmC,EAAU,CAAI,CAAA,CAEvH,GAAI,EAAU,CACZ,IAAM,GAAW,EAAM,MAAM,CAAC,IAAM,KAAK,aAAa,CACtD,OACE,EAAA,EAAA,KAAC,OAAD,CACS,QACP,UAAW,EAAG,sHAAuH,EAAiB,EAAQ,CAAE,EAAU,UAEzK,EACI,CAAA,CAGX,OAAO,EAAA,EAAA,KAAC,EAAD,CAAe,MAAO,EAA6B,YAAa,CAAA"}
1
+ {"version":3,"file":"user-avatar-CfAUS3PZ.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{Dn as e,Zn as t,hn as n,i as r,pt as i}from"./lucide-react-C4eXYCwx.js";import{f as a,t as o}from"./store-DUfrGvOE.js";import{K as s,ct as c,d as l}from"./display-BWi8DQCp.js";import{t as u}from"./badge-D8fABzMs.js";import{t as d}from"./button-KASZcscA.js";import{W as f,X as p,u as m}from"./index-Vmhw442d.js";import{r as h,t as g}from"./card-Cds0MWME.js";var _=t(),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),c=o(e=>e.doClaim),d=o(e=>e.openTaskEvents),h=o(e=>e.showError),g=o(e=>e.openConfirm),v=o(e=>e.doDeleteMessage),y=o(e=>e.doUpdateTaskStatus),x=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&&c(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:l(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]?l(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:t,now:i,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[t.severity]||c.info,m=v[t.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:t.claimable?`ring-1 ring-orange-500/30`:``,children:(0,_.jsx)(h,{className:`p-3`,children:(0,_.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,_.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap mb-1`,children:[(0,_.jsx)(`span`,{className:`font-medium text-sm truncate`,children:t.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:t.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:t.status}),t.sourceType===`message`&&(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400`,children:`msg`})]}),t.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:t.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[t.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(n,{className:`w-3 h-3 inline mr-1`}),a(t.owner)]}),t.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:t.source}),(0,_.jsxs)(`span`,{title:String(t.updatedAt),children:[(0,_.jsx)(e,{className:`w-3 h-3 inline mr-1`}),s(i,t.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[t.claimable&&(0,_.jsxs)(d,{size:`sm`,className:`h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white`,onClick:o,children:[(0,_.jsx)(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:l,children:[(0,_.jsx)(r,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
2
- //# sourceMappingURL=work-queue-BAnNY0xE.js.map
1
+ import{Dn as e,Zn as t,hn as n,i as r,pt as i}from"./lucide-react-C4eXYCwx.js";import{f as a,t as o}from"./store-DUfrGvOE.js";import{K as s,ct as c,d as l}from"./display-BWi8DQCp.js";import{t as u}from"./badge-D8fABzMs.js";import{t as d}from"./button-KASZcscA.js";import{W as f,X as p,u as m}from"./index-3ydeNO0c.js";import{r as h,t as g}from"./card-Cds0MWME.js";var _=t(),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),c=o(e=>e.doClaim),d=o(e=>e.openTaskEvents),h=o(e=>e.showError),g=o(e=>e.openConfirm),v=o(e=>e.doDeleteMessage),y=o(e=>e.doUpdateTaskStatus),x=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&&c(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:l(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]?l(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:t,now:i,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[t.severity]||c.info,m=v[t.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:t.claimable?`ring-1 ring-orange-500/30`:``,children:(0,_.jsx)(h,{className:`p-3`,children:(0,_.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,_.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap mb-1`,children:[(0,_.jsx)(`span`,{className:`font-medium text-sm truncate`,children:t.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:t.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:t.status}),t.sourceType===`message`&&(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400`,children:`msg`})]}),t.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:t.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[t.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(n,{className:`w-3 h-3 inline mr-1`}),a(t.owner)]}),t.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:t.source}),(0,_.jsxs)(`span`,{title:String(t.updatedAt),children:[(0,_.jsx)(e,{className:`w-3 h-3 inline mr-1`}),s(i,t.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[t.claimable&&(0,_.jsxs)(d,{size:`sm`,className:`h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white`,onClick:o,children:[(0,_.jsx)(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:l,children:[(0,_.jsx)(r,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
2
+ //# sourceMappingURL=work-queue-D-Otxd9u.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"work-queue-BAnNY0xE.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-D-Otxd9u.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-Vmhw442d.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-3ydeNO0c.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-C_JMBXql.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/lucide-react-C4eXYCwx.js">
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
3
  "description": "Thin Agent Relay runner bridge for Claude Code",
4
- "version": "0.106.0",
4
+ "version": "0.107.0",
5
5
  "agentRelayContracts": {
6
6
  "providerPluginProtocol": 1
7
7
  }
@@ -1,9 +1,11 @@
1
- import { errMessage } from "agent-relay-sdk";
1
+ import { errMessage, type BaseWorktreeSyncResult } from "agent-relay-sdk";
2
2
  import { emitRelayEvent } from "./events";
3
3
  import { routeNotification, INFORMATIONAL_TTL_MS } from "./notification-router";
4
4
  import { registerDefaultAudienceRules, audiencePredicates } from "./notification-rules";
5
5
  import { createActivityEvent, getAgent, updateWorkspaceStatus } from "./db";
6
6
  import { renderTemplate } from "./prompt-resolver";
7
+ import { resolveCoordinatorAgentId } from "./steward-identity";
8
+ import { stewardFallbackTarget } from "./config";
7
9
  import { handleLandedIssueLifecycle } from "./services/issue-lifecycle";
8
10
  import type { WorkspaceMergePreview, WorkspaceRecord } from "./types";
9
11
 
@@ -207,6 +209,79 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
207
209
  });
208
210
  }
209
211
 
212
+ /**
213
+ * #824 — a dirty base checkout left in a MIXED state after a land (HEAD advanced via ref
214
+ * plumbing, but the shared/primary working files did NOT catch up for some landed paths).
215
+ * The land itself SUCCEEDED — this is not a land failure — but the shared checkout now serves
216
+ * STALE content for those paths, which previously shipped a reverted file into a bootstrap
217
+ * publish (#823). The old behavior was a swallowed host-side `console.error`; this raises a
218
+ * LOUD, operator-visible signal (activity alert + a durable push to the owner and its
219
+ * coordinator) so the mixed state is reconciled before anything reads/builds/publishes from
220
+ * the checkout. Human WIP is never auto-clobbered; `preservedWipPaths` flags paths to merge by
221
+ * hand rather than blind-restore.
222
+ */
223
+ export function notifyBaseWorktreeStrand(input: {
224
+ workspace: Pick<WorkspaceRecord, "id" | "repoRoot" | "branch" | "baseRef" | "ownerAgentId">;
225
+ sync: BaseWorktreeSyncResult;
226
+ }): void {
227
+ const { workspace, sync } = input;
228
+ if (sync.reconciled) return;
229
+ const base = workspace.baseRef ?? "base";
230
+ const owner = workspace.ownerAgentId;
231
+ const coordinator = resolveCoordinatorAgentId(owner ?? undefined);
232
+ const fallback = stewardFallbackTarget();
233
+ const unsynced = sync.unsyncedPaths ?? [];
234
+ const preservedWip = sync.preservedWipPaths ?? [];
235
+ const pathLabel = unsynced.length ? unsynced.map((p) => `\`${p}\``).join(", ") : "(unknown paths)";
236
+ const wipLabel = preservedWip.length
237
+ ? ` Some carry uncommitted human WIP and were preserved (merge by hand, do NOT blind-restore): ${preservedWip.map((p) => `\`${p}\``).join(", ")}.`
238
+ : "";
239
+ const body = `⚠️ The shared \`${base}\` checkout under \`${workspace.repoRoot}\` is in a MIXED state after a land: HEAD advanced but these working files did NOT catch up — ${pathLabel}. Reads/builds/publishes from this checkout will see STALE content until reconciled (\`git -C ${workspace.repoRoot} restore --staged --worktree -- <path>\` for non-WIP paths).${wipLabel}`;
240
+
241
+ const payload = {
242
+ kind: "workspace.base-checkout-strand",
243
+ workspaceId: workspace.id,
244
+ repoRoot: workspace.repoRoot,
245
+ branch: workspace.branch,
246
+ base,
247
+ author: owner,
248
+ unsyncedPaths: unsynced,
249
+ preservedWipPaths: preservedWip,
250
+ reconciled: false,
251
+ };
252
+
253
+ emitRelayEvent({ type: "workspace.base_checkout_strand", source: "server", subject: workspace.id, data: payload });
254
+
255
+ createActivityEvent({
256
+ clientId: `workspace-base-strand-${workspace.id}-${Date.now()}`,
257
+ kind: "state",
258
+ title: "Base checkout stranded after land (mixed state)",
259
+ body: `${base} (${workspace.repoRoot}) — ${unsynced.length} landed path(s) out of sync with HEAD${preservedWip.length ? `, ${preservedWip.length} with human WIP` : ""}: ${unsynced.join(", ")}`,
260
+ meta: workspace.branch ?? workspace.id,
261
+ icon: "ti-alert-octagon",
262
+ view: "orchestrators",
263
+ metadata: { source: "server", maintenanceJobId: "workspace-base-strand", workspaceId: workspace.id, repoRoot: workspace.repoRoot, unsyncedPaths: unsynced, preservedWipPaths: preservedWip },
264
+ });
265
+
266
+ // Durable (ttl null) push to the owner and its coordinator — whoever works in the shared
267
+ // checkout must learn it's stale before acting on it. Fallback steward covers a teamless land.
268
+ const recipients = [...new Set([owner, coordinator, fallback].filter((r): r is string => Boolean(r)))];
269
+ if (recipients.length) {
270
+ routeNotification({
271
+ type: "workspace.base_checkout_strand",
272
+ scope: { author: owner, parent: coordinator ?? undefined, repoRoot: workspace.repoRoot, workspaceId: workspace.id },
273
+ recipients,
274
+ ttlMs: null,
275
+ message: {
276
+ subject: "Base checkout left stale after land — reconcile before publishing",
277
+ body,
278
+ payload: { ...payload, notification: "base-checkout-strand" },
279
+ replyExpected: false,
280
+ },
281
+ });
282
+ }
283
+ }
284
+
210
285
  /**
211
286
  * Fatal land failure wake-up for the branch owner (#538). This is intentionally separate from
212
287
  * steward-task/conflict pings: it tells the worker that its post-ready idle wait ended without
@@ -237,6 +237,12 @@ function quotaFingerprint(quota: QuotaState): string {
237
237
  name: window.name,
238
238
  unit: window.unit,
239
239
  utilization: window.utilization,
240
+ // Include resetsAt (coarsened to 5-minute buckets) so an idle provider crossing a
241
+ // window reset records a new snapshot even when utilization stays 0. Without this,
242
+ // Codex idle across a 5h boundary had no snapshot in the new window → burn
243
+ // computation found 0 same-boundary samples → low confidence → "building burn history."
244
+ // 5-min buckets absorb inter-agent resetsAt drift while distinguishing actual resets.
245
+ resetBucket: typeof window.resetsAt === "number" && Number.isFinite(window.resetsAt) ? Math.floor(window.resetsAt / 300_000) : undefined,
240
246
  })).sort((a, b) => a.name.localeCompare(b.name) || a.unit.localeCompare(b.unit)));
241
247
  }
242
248
 
@@ -23,6 +23,7 @@ export type NotificationType =
23
23
  | "workspace.conflict"
24
24
  | "workspace.steward_reassigned"
25
25
  | "workspace.stranded_escalation"
26
+ | "workspace.base_checkout_strand"
26
27
  | "workspace.auto_abandoned"
27
28
  | "workspace.review_request"
28
29
  | "workspace.review_changes_requested"
@@ -93,6 +94,7 @@ export const DEFAULT_TTL_MS: Record<NotificationType, number | null> = {
93
94
  "workspace.conflict": null,
94
95
  "workspace.steward_reassigned": null,
95
96
  "workspace.stranded_escalation": null,
97
+ "workspace.base_checkout_strand": null,
96
98
  "workspace.auto_abandoned": null,
97
99
  "workspace.review_request": null,
98
100
  "workspace.review_changes_requested": null,
@@ -12,7 +12,7 @@ import { isRecord } from "agent-relay-sdk";
12
12
  import { authContextFromRequest } from "../services/auth-context";
13
13
  import { commandAuthorizationResource as dispatchCommandAuthorizationResource, dispatchCommand } from "../services/dispatch-command";
14
14
  import { ServiceAuthError } from "../services/errors";
15
- import { notifyBranchLandFailed, notifyBranchLanded } from "../branch-landed";
15
+ import { notifyBaseWorktreeStrand, notifyBranchLandFailed, notifyBranchLanded } from "../branch-landed";
16
16
  import { applyIdleRefreshResult } from "../maintenance/workspaces";
17
17
  import { seedProviderConfigs } from "../provider-config-store";
18
18
  import { notifyAgentSpawnFailed } from "../agent-lifecycle-events";
@@ -253,6 +253,22 @@ export const patchCommand: Handler = async (req, params) => {
253
253
  // on the remote: the relay carries that ground truth in `params.prLanded`, so the
254
254
  // notice fires from it with the recycled `newBranch` — full rebase-ff parity.
255
255
  const prLanded = isRecord(command.params.prLanded) ? command.params.prLanded : undefined;
256
+ // #824 — the land advanced a DIRTY base by ref plumbing and the shared checkout was
257
+ // left in a MIXED state (HEAD moved, some landed working files didn't). The land
258
+ // succeeded, but surface the strand LOUDLY so it's reconciled before anything reads/
259
+ // builds/publishes from the stale checkout — never silently as before.
260
+ const baseWorktreeSync = isRecord(command.result.baseWorktreeSync) ? command.result.baseWorktreeSync : undefined;
261
+ if (baseWorktreeSync && baseWorktreeSync.reconciled === false && landedWorkspace) {
262
+ notifyBaseWorktreeStrand({
263
+ workspace: landedWorkspace,
264
+ sync: {
265
+ reconciled: false,
266
+ unsyncedPaths: Array.isArray(baseWorktreeSync.unsyncedPaths) ? baseWorktreeSync.unsyncedPaths.filter((p): p is string => typeof p === "string") : undefined,
267
+ preservedWipPaths: Array.isArray(baseWorktreeSync.preservedWipPaths) ? baseWorktreeSync.preservedWipPaths.filter((p): p is string => typeof p === "string") : undefined,
268
+ message: typeof baseWorktreeSync.message === "string" ? baseWorktreeSync.message : undefined,
269
+ },
270
+ });
271
+ }
256
272
  if ((command.result.merged === true || prLanded) && landedWorkspace) {
257
273
  notifyBranchLanded({
258
274
  workspace: landedWorkspace,
@@ -6,7 +6,7 @@ import { deleteMyAvatar, deleteUserAvatarById, getMe, getUserAvatar, getUsers, h
6
6
  import { deleteArtifactById, getArtifactById, getArtifactContent, getArtifacts, headArtifactContent, postArtifact } from "./artifacts";
7
7
  import { deleteAutomationById, getAutomationById, getAutomationRuns, getAutomations, patchAutomation, postAutomation, postAutomationRun } from "./automations";
8
8
  import { deleteCommandById, deleteProviderModelRoute, getProviderConfigRoute, getProviderConfigsRoute, getProvidersRoute, patchProviderModelRoute, postProviderConfigTestRoute, putProviderConfigRoute, putProviderModelRoute } from "./provider-config";
9
- import { getProviderQuotaAdvisoryRoute, getProviderQuotasRoute, postProviderQuotaLeaseAcquireRoute, postProviderQuotaLeaseReleaseRoute, postProviderQuotaPublisherLeaseAcquireRoute, postProviderQuotaPublisherLeaseReleaseRoute, postProviderQuotaRoute } from "./provider-quotas";
9
+ import { getProviderQuotaAdvisoryRoute, getProviderQuotaHistoryRoute, getProviderQuotasRoute, postProviderQuotaLeaseAcquireRoute, postProviderQuotaLeaseReleaseRoute, postProviderQuotaPublisherLeaseAcquireRoute, postProviderQuotaPublisherLeaseReleaseRoute, postProviderQuotaRoute } from "./provider-quotas";
10
10
  import { getProviderQuotaConfigRoute, getProviderQuotaConfigsRoute, putProviderQuotaConfigRoute } from "./provider-quota-config";
11
11
  import { getRoutingPolicyRoute, putRoutingPolicyRoute } from "./routing-policy";
12
12
  import { deleteProvisioningCapabilityRoute, getProvisioningCapabilitiesRoute, getProvisioningCapabilityRoute, patchProvisioningCapabilityRoute, patchProvisioningVariantRoute, postProvisioningCapabilityRoute, postProvisioningVariantImportRoute, postProvisioningVariantMcpRoute, postProvisioningVariantUpdateRoute, postProvisioningVariantValidateRoute } from "./provisioning-registry";
@@ -115,6 +115,7 @@ const routes: Route[] = [
115
115
  route("GET", "/api/agents/find", findAgents),
116
116
  route("POST", "/api/route", postRouteAdvice),
117
117
  route("GET", "/api/provider-quotas/advisory", getProviderQuotaAdvisoryRoute),
118
+ route("GET", "/api/provider-quotas/history", getProviderQuotaHistoryRoute),
118
119
  route("GET", "/api/provider-quotas", getProviderQuotasRoute),
119
120
  route("POST", "/api/provider-quotas", postProviderQuotaRoute),
120
121
  route("POST", "/api/provider-quota-leases/acquire", postProviderQuotaPublisherLeaseAcquireRoute),
@@ -1,5 +1,5 @@
1
1
  import { isRecord } from "agent-relay-sdk";
2
- import { acquireProviderQuotaLease, getOrchestrator, getProviderQuotaLease, listProviderQuotas, releaseProviderQuotaLease, upsertProviderQuota } from "../db";
2
+ import { acquireProviderQuotaLease, getOrchestrator, getProviderQuotaLease, getProviderQuotaHistory, listProviderQuotas, releaseProviderQuotaLease, upsertProviderQuota } from "../db";
3
3
  import { emitRelayEvent } from "../events";
4
4
  import { quotaStateFromUnknown, providerQuotaErrorFromUnknown } from "../quota";
5
5
  import { authContextFromRequest } from "../services/auth-context";
@@ -79,6 +79,18 @@ export const getProviderQuotaAdvisoryRoute: Handler = () => {
79
79
  return json({ advisories });
80
80
  };
81
81
 
82
+ // #609 — per-provider 30-day quota history (consumed by #607 analytics chart + #608 sparkline).
83
+ export const getProviderQuotaHistoryRoute: Handler = (req) => {
84
+ const url = new URL(req.url);
85
+ const provider = url.searchParams.get("provider");
86
+ if (!provider) return error("provider query parameter required");
87
+ const sinceMs = url.searchParams.get("sinceMs") ? Number(url.searchParams.get("sinceMs")) : undefined;
88
+ if (sinceMs !== undefined && (!Number.isFinite(sinceMs) || sinceMs < 0)) return error("sinceMs must be a positive timestamp");
89
+ const accountKey = url.searchParams.get("accountKey") ?? undefined;
90
+ const limit = parseQueryInt(url.searchParams.get("limit"), { min: 1, max: 10_000, clamp: true });
91
+ return json(getProviderQuotaHistory(provider, { sinceMs, accountKey, limit: limit ?? undefined }));
92
+ };
93
+
82
94
  export const postProviderQuotaRoute: Handler = async (req) => {
83
95
  const parsed = await parseBody<unknown>(req);
84
96
  if (!parsed.ok) return error(parsed.error, parsed.status);
package/src/security.ts CHANGED
@@ -199,6 +199,7 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
199
199
  if (pathname.startsWith("/api/pairs")) return method === "GET" ? "pairs:read" : "pairs:write";
200
200
  if (pathname === "/api/provider-quota-leases/acquire" || pathname === "/api/provider-quota-leases/release") return "quota:write";
201
201
  if (pathname === "/api/provider-quotas/advisory") return "agent:read";
202
+ if (pathname === "/api/provider-quotas/history") return "agent:read";
202
203
  if (pathname === "/api/provider-quotas") return method === "GET" ? "agent:read" : "quota:write";
203
204
  if (pathname === "/api/routing-policy") return method === "GET" ? "agent:read" : "settings:write:routing";
204
205
  if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
@@ -286,6 +287,7 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
286
287
  if (pathname.startsWith("/api/agents")) return method === "GET" ? "agent:read" : "agent:write";
287
288
  if (pathname === "/api/provider-quota-leases/acquire" || pathname === "/api/provider-quota-leases/release") return "quota:write";
288
289
  if (pathname === "/api/provider-quotas/advisory") return "agent:read";
290
+ if (pathname === "/api/provider-quotas/history") return "agent:read";
289
291
  if (pathname === "/api/provider-quotas") return method === "GET" ? "agent:read" : "quota:write";
290
292
  // Per-provider quota config (#605): reads are agent:read (the orchestrator
291
293
  // collector reads its config each tick); writes are settings:write:provider.
@@ -37,6 +37,19 @@ function defaultCommitsAhead(ws: WorkspaceRecord): number | null {
37
37
  }
38
38
  }
39
39
 
40
+ // #800 — A transient agent is protected from reaping ONLY by one of three evidence-based states:
41
+ // it is not idle (still working), it is not ready (mid-startup/teardown), or it carries a REAL
42
+ // provider block. `isProviderBlocked` reads the agent's own `meta.providerState`, which is set
43
+ // exclusively from hard evidence — an HTTP 429 / StopFailure hook, the scraped CLI usage-limit
44
+ // modal, an approval gate, or a model-unavailable hold (see runner rate-limit.ts / control-server).
45
+ // It is NEVER derived from a quota-utilization number. The reaper deliberately does not read quota
46
+ // utilization at all, so a provider merely sitting at high 7d/5h utilization can neither cause nor
47
+ // prevent a reap. This keeps the false "high-util ⇒ blocked ⇒ reaped" chain (#800) impossible by
48
+ // construction: utilization is not an input to this decision.
49
+ function reapProtected(agent: ReturnType<typeof listAgents>[number]): boolean {
50
+ return agent.status !== "idle" || !agent.ready || isProviderBlocked(agent);
51
+ }
52
+
40
53
  let _commitsAheadFn: (ws: WorkspaceRecord) => number | null = defaultCommitsAhead;
41
54
 
42
55
  export function overrideCommitsAheadForTests(fn: (ws: WorkspaceRecord) => number | null): void {
@@ -109,7 +122,9 @@ export function reapTransientAgents(): Record<string, unknown> {
109
122
  // A held agent (rate-limit hold #286, or any provider-blocked state) reads as
110
123
  // idle+ready but is NOT done — reaping it would kill work that's just waiting
111
124
  // for the limit to reset. Treat it as not-idle; the clock restarts on resume.
112
- if (agent.status !== "idle" || !agent.ready || isProviderBlocked(agent)) { transientTracker.delete(agent.id); continue; }
125
+ // #800 `reapProtected` is the single home for that decision: it honors only
126
+ // evidence-based block states, never a high quota-utilization number.
127
+ if (reapProtected(agent)) { transientTracker.delete(agent.id); continue; }
113
128
  const entry = transientTracker.get(agent.id) ?? { firstIdleAt: now };
114
129
  transientTracker.set(agent.id, entry);
115
130
  const idleForMs = now - entry.firstIdleAt;