agent-relay-server 0.39.0 → 0.41.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.
- package/docs/openapi.json +29 -1
- package/package.json +2 -2
- package/public/assets/{activity-b8kUF4hR.js → activity-DAz3DcDA.js} +2 -2
- package/public/assets/{activity-b8kUF4hR.js.map → activity-DAz3DcDA.js.map} +1 -1
- package/public/assets/{agents-C_ZSRXRa.js → agents-BJ7qRWxt.js} +2 -2
- package/public/assets/{agents-C_ZSRXRa.js.map → agents-BJ7qRWxt.js.map} +1 -1
- package/public/assets/{analytics-3JbZ6M41.js → analytics-MNCS2qnT.js} +2 -2
- package/public/assets/{analytics-3JbZ6M41.js.map → analytics-MNCS2qnT.js.map} +1 -1
- package/public/assets/{automation-CVu2C3sk.js → automation-COp2Kb3h.js} +2 -2
- package/public/assets/{automation-CVu2C3sk.js.map → automation-COp2Kb3h.js.map} +1 -1
- package/public/assets/chat-D_O8VqMR.js +2 -0
- package/public/assets/chat-D_O8VqMR.js.map +1 -0
- package/public/assets/display-ConJ9cJB.js.map +1 -1
- package/public/assets/{formatted-body-impl-CSAGn8zM.js → formatted-body-impl-BbMHqkCy.js} +2 -2
- package/public/assets/{formatted-body-impl-CSAGn8zM.js.map → formatted-body-impl-BbMHqkCy.js.map} +1 -1
- package/public/assets/{index-n5wxGWSq.js → index-DgJOEApM.js} +5 -5
- package/public/assets/{index-n5wxGWSq.js.map → index-DgJOEApM.js.map} +1 -1
- package/public/assets/{index-Bins8N_5.css → index-Dop-uXiy.css} +1 -1
- package/public/assets/{maintenance-CCcvsBbm.js → maintenance-BHv90kXZ.js} +2 -2
- package/public/assets/{maintenance-CCcvsBbm.js.map → maintenance-BHv90kXZ.js.map} +1 -1
- package/public/assets/{managed-agents-DHfO5gVD.js → managed-agents-BAlvkxfo.js} +2 -2
- package/public/assets/{managed-agents-DHfO5gVD.js.map → managed-agents-BAlvkxfo.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-C1jNXAEL.js → markdown-preview-impl-6k_FWjmd.js} +2 -2
- package/public/assets/{markdown-preview-impl-C1jNXAEL.js.map → markdown-preview-impl-6k_FWjmd.js.map} +1 -1
- package/public/assets/{memory-VmjJ-YZ8.js → memory-DBjqdVpg.js} +2 -2
- package/public/assets/{memory-VmjJ-YZ8.js.map → memory-DBjqdVpg.js.map} +1 -1
- package/public/assets/{messages-DXN6Rkjt.js → messages-axoaJY2N.js} +2 -2
- package/public/assets/{messages-DXN6Rkjt.js.map → messages-axoaJY2N.js.map} +1 -1
- package/public/assets/{orchestrators-CLyYvfn1.js → orchestrators-DMfHZ44H.js} +2 -2
- package/public/assets/{orchestrators-CLyYvfn1.js.map → orchestrators-DMfHZ44H.js.map} +1 -1
- package/public/assets/{overview-DZmVU2Fh.js → overview-DqxYWpUT.js} +2 -2
- package/public/assets/{overview-DZmVU2Fh.js.map → overview-DqxYWpUT.js.map} +1 -1
- package/public/assets/{pairs-BeEORfZF.js → pairs-D3wnzC1V.js} +2 -2
- package/public/assets/{pairs-BeEORfZF.js.map → pairs-D3wnzC1V.js.map} +1 -1
- package/public/assets/{security-DIe-a7rD.js → security-KWrb7PKj.js} +2 -2
- package/public/assets/{security-DIe-a7rD.js.map → security-KWrb7PKj.js.map} +1 -1
- package/public/assets/{settings-BeFPCFUZ.js → settings-PXMEzNAm.js} +2 -2
- package/public/assets/{settings-BeFPCFUZ.js.map → settings-PXMEzNAm.js.map} +1 -1
- package/public/assets/{tasks-BVXiBzUI.js → tasks-CFYShBCz.js} +2 -2
- package/public/assets/{tasks-BVXiBzUI.js.map → tasks-CFYShBCz.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-CN311GDZ.js → terminal-viewer-impl-BwPYZlWI.js} +2 -2
- package/public/assets/{terminal-viewer-impl-CN311GDZ.js.map → terminal-viewer-impl-BwPYZlWI.js.map} +1 -1
- package/public/assets/{work-queue-BnQftDAS.js → work-queue-VQ_6QDJc.js} +2 -2
- package/public/assets/{work-queue-BnQftDAS.js.map → work-queue-VQ_6QDJc.js.map} +1 -1
- package/public/index.html +2 -2
- package/runner/src/adapter.ts +12 -3
- package/src/bus.ts +2 -0
- package/src/config-store.ts +20 -8
- package/src/context-advisory.ts +110 -0
- package/src/context-threshold-config.ts +40 -0
- package/src/db/agents.ts +21 -5
- package/src/db/continuation-archives.ts +179 -0
- package/src/db/continuations.ts +151 -0
- package/src/db/index.ts +2 -0
- package/src/db/messages.ts +36 -2
- package/src/db/migrations.ts +34 -0
- package/src/db/schema.ts +24 -0
- package/src/lifecycle-manager.ts +2 -2
- package/src/maintenance.ts +11 -17
- package/src/mcp.ts +77 -0
- package/src/routes/commands.ts +24 -0
- package/src/routes/continuation-archives.ts +37 -0
- package/src/routes/index.ts +2 -0
- package/src/security.ts +2 -0
- package/src/self-resume-config.ts +44 -0
- package/src/self-resume.ts +232 -0
- package/src/services/send-message.ts +4 -0
- package/src/workspace-pr-completion.ts +121 -0
- package/public/assets/chat-BiCWbMPO.js +0 -2
- package/public/assets/chat-BiCWbMPO.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{An as e,Zt as t,at as n,cn as r,i}from"./lucide-react-DLQFnqNm.js";import{i as a,t as o}from"./store-Bo72e9My.js";import{H as s,ct as c,d as l}from"./display-ConJ9cJB.js";import{t as u}from"./badge-JVybSpzR.js";import{t as d}from"./button-BsMqBNJb.js";import{N as f,s as p,z as m}from"./index-
|
|
2
|
-
//# sourceMappingURL=work-queue-
|
|
1
|
+
import{An as e,Zt as t,at as n,cn as r,i}from"./lucide-react-DLQFnqNm.js";import{i as a,t as o}from"./store-Bo72e9My.js";import{H as s,ct as c,d as l}from"./display-ConJ9cJB.js";import{t as u}from"./badge-JVybSpzR.js";import{t as d}from"./button-BsMqBNJb.js";import{N as f,s as p,z as m}from"./index-DgJOEApM.js";import{n as h,t as g}from"./card-I8w4U656.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),r=o(e=>e.compose),i=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=m(),C=S.filter(e=>e.claimable).length;function w(e){if(!r.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)(n,{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:r.from,onChange:e=>i({compose:{...r,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)(p,{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:e,now:n,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[e.severity]||c.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)(t,{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)(r,{className:`w-3 h-3 inline mr-1`}),s(n,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)(t,{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)(i,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
|
|
2
|
+
//# sourceMappingURL=work-queue-VQ_6QDJc.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-BnQftDAS.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-VQ_6QDJc.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-DgJOEApM.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-DQVmg1Zk.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/lucide-react-DLQFnqNm.js">
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<link rel="modulepreload" crossorigin href="./assets/badge-JVybSpzR.js">
|
|
36
36
|
<link rel="modulepreload" crossorigin href="./assets/input-dPzf0luy.js">
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/themes-mW9zZP4O.js">
|
|
38
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
38
|
+
<link rel="stylesheet" crossorigin href="./assets/index-Dop-uXiy.css">
|
|
39
39
|
</head>
|
|
40
40
|
<body class="bg-background text-foreground antialiased">
|
|
41
41
|
<div id="root"></div>
|
package/runner/src/adapter.ts
CHANGED
|
@@ -36,13 +36,17 @@ export type ProviderStatusUpdate = SemanticStatus | ProviderStatusEvent;
|
|
|
36
36
|
* same lane Claude's transcript capture uses. Provider-independent boundary.
|
|
37
37
|
*/
|
|
38
38
|
export interface ProviderSessionEvent {
|
|
39
|
-
type: "prompt" | "response" | "reasoning" | "tool";
|
|
39
|
+
type: "prompt" | "response" | "narration" | "reasoning" | "tool";
|
|
40
40
|
body: string;
|
|
41
41
|
origin?: "chat" | "terminal" | "provider";
|
|
42
42
|
turnId?: string;
|
|
43
43
|
label?: string;
|
|
44
44
|
status?: "running" | "completed" | "failed";
|
|
45
45
|
streaming?: boolean;
|
|
46
|
+
/** Stable provider-side step id (Codex app-server item id). Carried into
|
|
47
|
+
* MessageSessionMeta.stepId so the server upserts the step's row in place instead of
|
|
48
|
+
* appending a duplicate (a tool's running→completed, a streamed reasoning row). */
|
|
49
|
+
stepId?: string;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export interface ProviderConfig {
|
|
@@ -132,7 +136,8 @@ export interface ProviderAdapter {
|
|
|
132
136
|
provider: string;
|
|
133
137
|
spawn(config: RunnerSpawnConfig): Promise<ManagedProcess>;
|
|
134
138
|
shutdown(process: ManagedProcess, opts: { graceful: boolean; timeoutMs: number }): Promise<void>;
|
|
135
|
-
compact?(process: ManagedProcess): Promise<Record<string, unknown> | void>;
|
|
139
|
+
compact?(process: ManagedProcess, opts?: { instructions?: string }): Promise<Record<string, unknown> | void>;
|
|
140
|
+
compactSupportsInstructions?: boolean;
|
|
136
141
|
clearContext?(process: ManagedProcess): Promise<Record<string, unknown> | void>;
|
|
137
142
|
// Normalize the session so far into the provider-agnostic SessionEvent stream the
|
|
138
143
|
// Insights context-ratio signal (#183/#184) reduces. Called by the runner's
|
|
@@ -143,6 +148,10 @@ export interface ProviderAdapter {
|
|
|
143
148
|
// ignore it and return their accumulated log. Return null when there is nothing to
|
|
144
149
|
// measure. Best-effort: may be omitted by providers without a session view yet.
|
|
145
150
|
collectSessionEvents?(process: ManagedProcess, ctx: { transcriptPath?: string }): Promise<SessionEvent[] | null>;
|
|
151
|
+
// Full searchable transcript/archive source for destructive boundaries. The runner
|
|
152
|
+
// slices the returned stream by cursor, so adapters should return the session-so-far
|
|
153
|
+
// view when they have one.
|
|
154
|
+
collectSessionArchiveSegment?(process: ManagedProcess, ctx: { transcriptPath?: string }): Promise<string | null>;
|
|
146
155
|
// Interrupt the in-flight turn without ending the session (ESC for Claude's
|
|
147
156
|
// tmux pane, turn/interrupt for the Codex app-server). Provider-independent at
|
|
148
157
|
// the runner boundary; each adapter does what its provider actually supports.
|
|
@@ -181,7 +190,7 @@ export function profileAllowsRelayFeature(config: RunnerSpawnConfig, feature: ke
|
|
|
181
190
|
return config.agentProfile?.relay?.[feature] !== false;
|
|
182
191
|
}
|
|
183
192
|
|
|
184
|
-
export const RELAY_CONTEXT = `[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id — relay fills it from your token; use relay_whoami only if you need to reason about yourself. relay_spawn_targets / relay_spawn_agent / relay_shutdown_agent only appear if your profile grants spawning (a live-children quota); when present, call relay_spawn_targets FIRST for the live host/provider/model matrix + your quota, then stand up long-living child agents and shut down your own — find them later with relay_find_agents spawnedBy:me. CLI fallback: agent-relay /reply <messageId> --stdin < response.md; if a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide`;
|
|
193
|
+
export const RELAY_CONTEXT = `[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_compact_and_resume, relay_recall, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id — relay fills it from your token; use relay_whoami only if you need to reason about yourself. relay_compact_and_resume is for clean-seam self-resume after a context advisory: pass workingState and optional ruledOut; Relay owns the objective envelope. relay_recall searches your own archived pre-compaction segments by keyword when a discarded detail is needed. relay_spawn_targets / relay_spawn_agent / relay_shutdown_agent only appear if your profile grants spawning (a live-children quota); when present, call relay_spawn_targets FIRST for the live host/provider/model matrix + your quota, then stand up long-living child agents and shut down your own — find them later with relay_find_agents spawnedBy:me. CLI fallback: agent-relay /reply <messageId> --stdin < response.md; if a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide`;
|
|
185
194
|
|
|
186
195
|
// #306 — deliver the FULL message body by default. Only a pathological body beyond this
|
|
187
196
|
// high cap truncates (with a get-message hint) so it can't nuke an agent's context; the 99%
|
package/src/bus.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { commandAuthorizationResource, dispatchCommand } from "./services/dispat
|
|
|
12
12
|
import { ServiceAuthError } from "./services/errors";
|
|
13
13
|
import { ShutdownAuthError, ShutdownTargetError, shutdownAgent, shutdownInputFromBusFrame } from "./services/shutdown-agent";
|
|
14
14
|
import { applyCommandToRecipe } from "./recipe-runner";
|
|
15
|
+
import { enqueueContinuationInjectionAfterClear } from "./self-resume";
|
|
15
16
|
import { messageMatchesAgent, targetMatchesAgent } from "./agent-ref";
|
|
16
17
|
import {
|
|
17
18
|
BusProtocolError,
|
|
@@ -219,6 +220,7 @@ function handleCommandFrame(
|
|
|
219
220
|
// Production command completions arrive here over the bus, not the HTTP
|
|
220
221
|
// PATCH route — arm the stall watch for finished compact/clear commands.
|
|
221
222
|
noteCompactionCommandCompleted(updated.type, updated.status, updated.id, updated.target);
|
|
223
|
+
enqueueContinuationInjectionAfterClear(updated);
|
|
222
224
|
}
|
|
223
225
|
sendCommandResult(ws, frameId, "succeeded", updated ? { command: updated } : undefined);
|
|
224
226
|
return;
|
package/src/config-store.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getDb, ValidationError } from "./db";
|
|
2
2
|
import { cleanEnum, cleanString, cleanStringArray } from "./validation";
|
|
3
|
+
import { DEFAULT_CONTEXT_THRESHOLD_CONFIG, validateContextThresholdConfig } from "./context-threshold-config";
|
|
4
|
+
import { DEFAULT_SELF_RESUME_CONFIG, validateSelfResumeConfig } from "./self-resume-config";
|
|
3
5
|
import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
4
6
|
import { errMessage, isRecord, SPAWN_PROVIDERS, VALID_EFFORTS, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
|
|
5
7
|
import type {
|
|
@@ -12,6 +14,7 @@ import type {
|
|
|
12
14
|
ManagedAgentState,
|
|
13
15
|
ManagedAgentStatus,
|
|
14
16
|
NotificationsConfig,
|
|
17
|
+
SelfResumeConfig,
|
|
15
18
|
SpawnApprovalMode,
|
|
16
19
|
SpawnPolicy,
|
|
17
20
|
SpawnProvider,
|
|
@@ -29,6 +32,8 @@ const INSIGHTS_NAMESPACE = "insights";
|
|
|
29
32
|
const INSIGHTS_KEY = "default";
|
|
30
33
|
const NOTIFICATIONS_NAMESPACE = "notifications";
|
|
31
34
|
const NOTIFICATIONS_KEY = "default";
|
|
35
|
+
const SELF_RESUME_NAMESPACE = "self-resume";
|
|
36
|
+
const SELF_RESUME_KEY = "default";
|
|
32
37
|
const WORKSPACE_NAMESPACE = "workspace";
|
|
33
38
|
const WORKSPACE_KEY = "default";
|
|
34
39
|
const LANDING_NAMESPACE = "landing";
|
|
@@ -475,17 +480,11 @@ function validateInsightsConfig(value: unknown): InsightsConfig {
|
|
|
475
480
|
// Relay-driven lifecycle push notifications (#239 event bus). Default-on; the
|
|
476
481
|
// operator can flip the master switch or individual events off via the generic
|
|
477
482
|
// config route. Push messages wake recipients, so they must be suppressible.
|
|
478
|
-
const NOTIFICATIONS_CONFIG_DEFAULTS: NotificationsConfig = {
|
|
479
|
-
enabled: true,
|
|
480
|
-
branchLanded: true,
|
|
481
|
-
agentReady: true,
|
|
482
|
-
agentExited: true,
|
|
483
|
-
agentSpawnFailed: true,
|
|
484
|
-
};
|
|
483
|
+
const NOTIFICATIONS_CONFIG_DEFAULTS: NotificationsConfig = { enabled: true, branchLanded: true, agentReady: true, agentExited: true, agentSpawnFailed: true, contextThreshold: DEFAULT_CONTEXT_THRESHOLD_CONFIG };
|
|
485
484
|
|
|
486
485
|
function validateNotificationsConfig(value: unknown): NotificationsConfig {
|
|
487
486
|
if (!isRecord(value)) throw new ValidationError("notifications config value must be an object");
|
|
488
|
-
const bool = (key:
|
|
487
|
+
const bool = (key: "enabled" | "branchLanded" | "agentReady" | "agentExited" | "agentSpawnFailed"): boolean =>
|
|
489
488
|
value[key] === undefined ? NOTIFICATIONS_CONFIG_DEFAULTS[key] : cleanBoolean(value[key], key);
|
|
490
489
|
return {
|
|
491
490
|
enabled: bool("enabled"),
|
|
@@ -493,6 +492,7 @@ function validateNotificationsConfig(value: unknown): NotificationsConfig {
|
|
|
493
492
|
agentReady: bool("agentReady"),
|
|
494
493
|
agentExited: bool("agentExited"),
|
|
495
494
|
agentSpawnFailed: bool("agentSpawnFailed"),
|
|
495
|
+
contextThreshold: validateContextThresholdConfig(value.contextThreshold),
|
|
496
496
|
};
|
|
497
497
|
}
|
|
498
498
|
|
|
@@ -537,6 +537,7 @@ function normalizeValue(namespace: string, key: string, value: unknown): unknown
|
|
|
537
537
|
if (namespace === STEWARD_NAMESPACE) return validateStewardConfig(value);
|
|
538
538
|
if (namespace === INSIGHTS_NAMESPACE) return validateInsightsConfig(value);
|
|
539
539
|
if (namespace === NOTIFICATIONS_NAMESPACE) return validateNotificationsConfig(value);
|
|
540
|
+
if (namespace === SELF_RESUME_NAMESPACE) return validateSelfResumeConfig(value);
|
|
540
541
|
if (namespace === WORKSPACE_NAMESPACE) return validateWorkspaceConfig(value);
|
|
541
542
|
if (namespace === LANDING_NAMESPACE) return validateLandingConfig(value);
|
|
542
543
|
if (JSON.stringify(value) === undefined) throw new ValidationError("value must be valid JSON");
|
|
@@ -682,6 +683,17 @@ export function setInsightsConfig(value: unknown, updatedBy?: string): ConfigEnt
|
|
|
682
683
|
return setConfig(INSIGHTS_NAMESPACE, INSIGHTS_KEY, value as InsightsConfig, updatedBy);
|
|
683
684
|
}
|
|
684
685
|
|
|
686
|
+
/** Relay-owned self-resume defaults (#316), merged over defaults (always usable). */
|
|
687
|
+
export function getSelfResumeConfig(): SelfResumeConfig {
|
|
688
|
+
const entry = getConfig<Partial<SelfResumeConfig>>(SELF_RESUME_NAMESPACE, SELF_RESUME_KEY);
|
|
689
|
+
if (!entry) return { ...DEFAULT_SELF_RESUME_CONFIG, budget: { ...DEFAULT_SELF_RESUME_CONFIG.budget } };
|
|
690
|
+
return validateSelfResumeConfig({
|
|
691
|
+
...DEFAULT_SELF_RESUME_CONFIG,
|
|
692
|
+
...entry.value,
|
|
693
|
+
budget: { ...DEFAULT_SELF_RESUME_CONFIG.budget, ...(entry.value.budget ?? {}) },
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
685
697
|
/** Global workspace config, merged over defaults (always returns a usable value). */
|
|
686
698
|
export function getWorkspaceConfig(): WorkspaceConfig {
|
|
687
699
|
const entry = getConfig<Partial<WorkspaceConfig>>(WORKSPACE_NAMESPACE, WORKSPACE_KEY);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getNotificationsConfig } from "./config-store";
|
|
2
|
+
import { getDb } from "./db/connection.ts";
|
|
3
|
+
import { emitRelayEvent } from "./events";
|
|
4
|
+
import { notifySystemMessage } from "./notify";
|
|
5
|
+
import type { AgentCard, Command, ContextState } from "./types";
|
|
6
|
+
|
|
7
|
+
interface ContextAdvisoryInput {
|
|
8
|
+
agentId: string;
|
|
9
|
+
tokens: number;
|
|
10
|
+
percent: number;
|
|
11
|
+
windowTokens: number;
|
|
12
|
+
band: number;
|
|
13
|
+
hardBackstopPercent: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* #315 — context pressure is a relay-native signal: durable event for observers,
|
|
18
|
+
* plus an agent-facing system push when lifecycle notifications are enabled.
|
|
19
|
+
*/
|
|
20
|
+
function notifyContextAdvisory(input: ContextAdvisoryInput): void {
|
|
21
|
+
const payload = {
|
|
22
|
+
kind: "agent.context_threshold",
|
|
23
|
+
agentId: input.agentId,
|
|
24
|
+
tokens: input.tokens,
|
|
25
|
+
percent: input.percent,
|
|
26
|
+
windowTokens: input.windowTokens,
|
|
27
|
+
band: input.band,
|
|
28
|
+
hardBackstopPercent: input.hardBackstopPercent,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
emitRelayEvent({
|
|
32
|
+
type: "agent.context_threshold",
|
|
33
|
+
source: "server",
|
|
34
|
+
subject: input.agentId,
|
|
35
|
+
data: payload,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const config = getNotificationsConfig();
|
|
39
|
+
if (!config.enabled || !config.contextThreshold.enabled) return;
|
|
40
|
+
|
|
41
|
+
notifySystemMessage(input.agentId, {
|
|
42
|
+
subject: "Context threshold reached",
|
|
43
|
+
body: `Context advisory: ${formatTokens(input.tokens)} / ${formatTokens(input.windowTokens)} tokens used (${input.percent}% of window, ${input.band}% band). Compact at a clean seam before the ${input.hardBackstopPercent}% hard backstop may request compaction.`,
|
|
44
|
+
payload,
|
|
45
|
+
replyExpected: false,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function contextAdvisoryBand(utilization: number, advisoryUtilization: number, bandSize: number): number | null {
|
|
50
|
+
if (!Number.isFinite(utilization) || utilization < advisoryUtilization) return null;
|
|
51
|
+
const percent = Math.min(100, Math.max(0, utilization * 100));
|
|
52
|
+
const step = Math.max(1, bandSize * 100);
|
|
53
|
+
return Math.min(100, Math.floor(percent / step) * step);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function handleContextPressure(input: {
|
|
57
|
+
agent: AgentCard;
|
|
58
|
+
now: () => number;
|
|
59
|
+
hasActiveCommand: (target: string, type: string) => boolean;
|
|
60
|
+
compact: (reason: string) => Command | null;
|
|
61
|
+
}): boolean {
|
|
62
|
+
const { agent, now } = input;
|
|
63
|
+
const context = agent.context;
|
|
64
|
+
if (!context || agent.status === "busy") return false;
|
|
65
|
+
if (context.lifecycleState === "compacting" || context.lifecycleState === "hibernating") return false;
|
|
66
|
+
if (typeof context.utilization !== "number" || !Number.isFinite(context.utilization)) return false;
|
|
67
|
+
const threshold = getNotificationsConfig().contextThreshold;
|
|
68
|
+
const band = contextAdvisoryBand(context.utilization, threshold.advisoryUtilization, threshold.bandSize);
|
|
69
|
+
if (band === null) {
|
|
70
|
+
if (context.lastContextAdvisoryBand !== undefined || context.lastContextAdvisoryAt !== undefined) {
|
|
71
|
+
persistContextState(agent.id, clearContextAdvisory(context));
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (context.utilization >= threshold.hardBackstopUtilization && context.lastContextAdvisoryAt !== undefined && agent.providerCapabilities?.context?.compact === true && !input.hasActiveCommand(agent.id, "agent.compact")) {
|
|
76
|
+
input.compact("context-hard-backstop");
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (context.lastContextAdvisoryBand === band) return false;
|
|
80
|
+
const tokens = finiteNumber(context.tokensUsed);
|
|
81
|
+
const windowTokens = finiteNumber(context.tokensMax);
|
|
82
|
+
if (tokens === undefined || windowTokens === undefined) return false;
|
|
83
|
+
notifyContextAdvisory({
|
|
84
|
+
agentId: agent.id,
|
|
85
|
+
tokens,
|
|
86
|
+
percent: Math.round(context.utilization * 100),
|
|
87
|
+
windowTokens,
|
|
88
|
+
band,
|
|
89
|
+
hardBackstopPercent: Math.round(threshold.hardBackstopUtilization * 100),
|
|
90
|
+
});
|
|
91
|
+
persistContextState(agent.id, { ...context, lastContextAdvisoryBand: band, lastContextAdvisoryAt: now() });
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function clearContextAdvisory(context: ContextState): ContextState {
|
|
96
|
+
const { lastContextAdvisoryBand: _band, lastContextAdvisoryAt: _at, ...rest } = context;
|
|
97
|
+
return rest;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function persistContextState(agentId: string, context: ContextState): void {
|
|
101
|
+
getDb().query("UPDATE agents SET context_state = ? WHERE id = ?").run(JSON.stringify(context), agentId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function finiteNumber(value: unknown): number | undefined {
|
|
105
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function formatTokens(value: number): string {
|
|
109
|
+
return Math.round(value).toLocaleString("en-US");
|
|
110
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isRecord } from "agent-relay-sdk";
|
|
2
|
+
import { ValidationError } from "./db/connection.ts";
|
|
3
|
+
import type { NotificationsConfig } from "./types";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CONTEXT_THRESHOLD_CONFIG: NotificationsConfig["contextThreshold"] = {
|
|
6
|
+
enabled: true,
|
|
7
|
+
advisoryUtilization: 0.7,
|
|
8
|
+
hardBackstopUtilization: 0.9,
|
|
9
|
+
bandSize: 0.1,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function validateContextThresholdConfig(value: unknown): NotificationsConfig["contextThreshold"] {
|
|
13
|
+
const input = isRecord(value) ? value : {};
|
|
14
|
+
const bandSize = cleanFraction(input.bandSize, "contextThreshold.bandSize", DEFAULT_CONTEXT_THRESHOLD_CONFIG.bandSize);
|
|
15
|
+
if (bandSize <= 0) throw new ValidationError("contextThreshold.bandSize must be greater than 0");
|
|
16
|
+
const advisoryUtilization = cleanFraction(input.advisoryUtilization, "contextThreshold.advisoryUtilization", DEFAULT_CONTEXT_THRESHOLD_CONFIG.advisoryUtilization);
|
|
17
|
+
const hardBackstopUtilization = cleanFraction(input.hardBackstopUtilization, "contextThreshold.hardBackstopUtilization", DEFAULT_CONTEXT_THRESHOLD_CONFIG.hardBackstopUtilization);
|
|
18
|
+
if (hardBackstopUtilization <= advisoryUtilization) {
|
|
19
|
+
throw new ValidationError("contextThreshold.hardBackstopUtilization must be greater than contextThreshold.advisoryUtilization");
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
enabled: input.enabled === undefined ? DEFAULT_CONTEXT_THRESHOLD_CONFIG.enabled : cleanBoolean(input.enabled, "contextThreshold.enabled"),
|
|
23
|
+
advisoryUtilization,
|
|
24
|
+
hardBackstopUtilization,
|
|
25
|
+
bandSize,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function cleanBoolean(value: unknown, field: string): boolean {
|
|
30
|
+
if (typeof value !== "boolean") throw new ValidationError(`${field} must be a boolean`);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cleanFraction(value: unknown, field: string, fallback: number): number {
|
|
35
|
+
if (value === undefined || value === null) return fallback;
|
|
36
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
37
|
+
throw new ValidationError(`${field} must be a number between 0 and 1`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
package/src/db/agents.ts
CHANGED
|
@@ -15,11 +15,13 @@ import {
|
|
|
15
15
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
16
16
|
import { matchAgents } from "../agent-ref";
|
|
17
17
|
import { evaluatePoolBindings, expectedChannelAgentId, upsertChannelForAgent } from "./channels.ts";
|
|
18
|
+
import { seedContinuationObjectiveFromSpawnPrompt } from "./continuations.ts";
|
|
18
19
|
import { ValidationError, getDb } from "./connection.ts";
|
|
19
20
|
import { rowToAgent, rowToContextSnapshot } from "./mappers.ts";
|
|
20
21
|
import { closeOpenPairsForAgent } from "./pairs.ts";
|
|
21
22
|
import { TASK_SELECT, insertTaskEvent } from "./tasks.ts";
|
|
22
23
|
import { electWorkspaceStewards, electWorkspaceStewardsForAgent } from "./workspaces.ts";
|
|
24
|
+
import { DEFAULT_SELF_RESUME_CONFIG } from "../self-resume-config";
|
|
23
25
|
import type {
|
|
24
26
|
AgentCard,
|
|
25
27
|
ActivityEvent,
|
|
@@ -153,6 +155,7 @@ export function upsertAgent(input: RegisterAgentInput): AgentCard {
|
|
|
153
155
|
const labelProvided = Object.prototype.hasOwnProperty.call(input, "label");
|
|
154
156
|
const readyProvided = Object.prototype.hasOwnProperty.call(input, "ready");
|
|
155
157
|
const instanceProvided = Boolean(input.instanceId);
|
|
158
|
+
const contextState = mergeRelayContextMarkers(input.id, input.context);
|
|
156
159
|
const stmt = getDb().query(`
|
|
157
160
|
INSERT INTO agents (id, name, kind, label, tags, machine, rig, capabilities, ready, status, instance_id, epoch, provider_capabilities, context_state, meta, spawned_by, last_seen, created_at)
|
|
158
161
|
VALUES ($id, $name, $kind, $label, $tags, $machine, $rig, $capabilities, $ready, $status, $instanceId, $initialEpoch, $providerCapabilities, $contextState, $meta, $spawnedBy, $now, $now)
|
|
@@ -195,14 +198,15 @@ export function upsertAgent(input: RegisterAgentInput): AgentCard {
|
|
|
195
198
|
$instanceProvided: instanceProvided ? 1 : 0,
|
|
196
199
|
$initialEpoch: instanceProvided ? 1 : 0,
|
|
197
200
|
$providerCapabilities: input.providerCapabilities ? JSON.stringify(input.providerCapabilities) : null,
|
|
198
|
-
$contextState:
|
|
201
|
+
$contextState: contextState ? JSON.stringify(contextState) : null,
|
|
199
202
|
$meta: JSON.stringify(input.meta ?? {}),
|
|
200
203
|
$spawnedBy: input.spawnedBy ?? null,
|
|
201
204
|
$now: now,
|
|
202
205
|
});
|
|
203
|
-
if (
|
|
206
|
+
if (contextState) recordContextSnapshot(input.id, contextState, now);
|
|
204
207
|
|
|
205
208
|
const agent = getAgent(input.id)!;
|
|
209
|
+
seedContinuationObjectiveFromSpawnPrompt(agent.id, stringValue(agent.meta?.spawnRequestId), DEFAULT_SELF_RESUME_CONFIG, now);
|
|
206
210
|
if (agent.kind === "channel") upsertChannelForAgent(agent);
|
|
207
211
|
evaluatePoolBindings();
|
|
208
212
|
// A (re)joining agent may revive a dormant repo steward — re-elect for the
|
|
@@ -335,6 +339,7 @@ export function heartbeat(
|
|
|
335
339
|
if (!validateAgentSession(id, guard).ok) return false;
|
|
336
340
|
const now = Date.now();
|
|
337
341
|
if (runtime?.providerCapabilities || runtime?.context) {
|
|
342
|
+
const contextState = mergeRelayContextMarkers(id, runtime.context);
|
|
338
343
|
const result = getDb()
|
|
339
344
|
.query(`
|
|
340
345
|
UPDATE agents SET
|
|
@@ -347,10 +352,10 @@ export function heartbeat(
|
|
|
347
352
|
.run(
|
|
348
353
|
now,
|
|
349
354
|
runtime.providerCapabilities ? JSON.stringify(runtime.providerCapabilities) : null,
|
|
350
|
-
|
|
355
|
+
contextState ? JSON.stringify(contextState) : null,
|
|
351
356
|
id,
|
|
352
357
|
);
|
|
353
|
-
if (result.changes > 0 &&
|
|
358
|
+
if (result.changes > 0 && contextState) recordContextSnapshot(id, contextState, now);
|
|
354
359
|
return result.changes > 0;
|
|
355
360
|
}
|
|
356
361
|
const result = getDb()
|
|
@@ -412,6 +417,18 @@ export function recordContextSnapshot(agentId: string, context: ContextState, no
|
|
|
412
417
|
pruneContextSnapshots(agentId, now - DAY_MS);
|
|
413
418
|
}
|
|
414
419
|
|
|
420
|
+
function mergeRelayContextMarkers(agentId: string, next: ContextState | undefined): ContextState | undefined {
|
|
421
|
+
if (!next) return undefined;
|
|
422
|
+
const row = getDb().query("SELECT context_state FROM agents WHERE id = ?").get(agentId) as { context_state?: string | null } | undefined;
|
|
423
|
+
const previous = row?.context_state ? parseJson<ContextState | undefined>(row.context_state, undefined) : undefined;
|
|
424
|
+
if (!previous) return next;
|
|
425
|
+
return {
|
|
426
|
+
...next,
|
|
427
|
+
lastContextAdvisoryBand: next.lastContextAdvisoryBand ?? previous.lastContextAdvisoryBand,
|
|
428
|
+
lastContextAdvisoryAt: next.lastContextAdvisoryAt ?? previous.lastContextAdvisoryAt,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
415
432
|
|
|
416
433
|
export function reapStaleAgents(ttlMs: number = STALE_TTL_MS): string[] {
|
|
417
434
|
const now = Date.now();
|
|
@@ -548,4 +565,3 @@ export function runtimeTokenJtisFromMeta(meta: Record<string, unknown>): string[
|
|
|
548
565
|
}
|
|
549
566
|
|
|
550
567
|
// --- Tasks ---
|
|
551
|
-
|