@turmind/halo 1.4.3 → 1.4.5

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.
@@ -1 +1 @@
1
- <!DOCTYPE html><!--IHBLjOk92WBjj7mPz8oZK--><html lang="en" class="dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/e4af272ccee01ff0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/c0ea31cd787f7aa7.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-ca926228f8cba1ff.js"/><script src="/_next/static/chunks/1b86ba38-c29611e086b2ce25.js" async=""></script><script src="/_next/static/chunks/698-19872069fed27164.js" async=""></script><script src="/_next/static/chunks/main-app-340a5b58ed229c4d.js" async=""></script><script src="/_next/static/chunks/989-6a0ec35b8ae2cb39.js" async=""></script><script src="/_next/static/chunks/app/layout-5ec6c5f713a8562b.js" async=""></script><meta name="robots" content="noindex"/><meta name="next-size-adjust" content=""/><title>404: This page could not be found.</title><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_f367f3 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-ca926228f8cba1ff.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[58321,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[23632,[],\"OutletBoundary\"]\n8:I[86889,[],\"AsyncMetadataOutlet\"]\na:I[23632,[],\"ViewportBoundary\"]\nc:I[23632,[],\"MetadataBoundary\"]\nd:\"$Sreact.suspense\"\nf:I[1935,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/c0ea31cd787f7aa7.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"IHBLjOk92WBjj7mPz8oZK\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/c0ea31cd787f7aa7.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark\",\"suppressHydrationWarning\":true,\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}\"}}]}],[\"$\",\"body\",null,{\"className\":\"__variable_f367f3 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L6\",null,{\"children\":[\"$L7\",[\"$\",\"$L8\",null,{\"promise\":\"$@9\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$La\",null,{\"children\":\"$Lb\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lc\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$d\",null,{\"fallback\":null,\"children\":\"$Le\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$f\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n7:null\n"])</script><script>self.__next_f.push([1,"10:I[88809,[],\"IconMark\"]\n9:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Halo - Multi-Agent Workspace\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Multi-agent collaboration workspace for complex project delivery\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon.svg?3a6863548daca964\",\"type\":\"image/svg+xml\",\"sizes\":\"any\"}],[\"$\",\"$L10\",\"4\",{}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"e:\"$9:metadata\"\n"])</script></body></html>
1
+ <!DOCTYPE html><!--jXC1swwQxpx7Zpri_5CwJ--><html lang="en" class="dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/e4af272ccee01ff0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/c0ea31cd787f7aa7.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-ca926228f8cba1ff.js"/><script src="/_next/static/chunks/1b86ba38-c29611e086b2ce25.js" async=""></script><script src="/_next/static/chunks/698-19872069fed27164.js" async=""></script><script src="/_next/static/chunks/main-app-340a5b58ed229c4d.js" async=""></script><script src="/_next/static/chunks/989-6a0ec35b8ae2cb39.js" async=""></script><script src="/_next/static/chunks/app/layout-5ec6c5f713a8562b.js" async=""></script><meta name="robots" content="noindex"/><meta name="next-size-adjust" content=""/><title>404: This page could not be found.</title><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_f367f3 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-ca926228f8cba1ff.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[58321,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[23632,[],\"OutletBoundary\"]\n8:I[86889,[],\"AsyncMetadataOutlet\"]\na:I[23632,[],\"ViewportBoundary\"]\nc:I[23632,[],\"MetadataBoundary\"]\nd:\"$Sreact.suspense\"\nf:I[1935,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/c0ea31cd787f7aa7.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"jXC1swwQxpx7Zpri-5CwJ\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/c0ea31cd787f7aa7.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark\",\"suppressHydrationWarning\":true,\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}\"}}]}],[\"$\",\"body\",null,{\"className\":\"__variable_f367f3 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L6\",null,{\"children\":[\"$L7\",[\"$\",\"$L8\",null,{\"promise\":\"$@9\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$La\",null,{\"children\":\"$Lb\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lc\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$d\",null,{\"fallback\":null,\"children\":\"$Le\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$f\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n7:null\n"])</script><script>self.__next_f.push([1,"10:I[88809,[],\"IconMark\"]\n9:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Halo - Multi-Agent Workspace\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Multi-agent collaboration workspace for complex project delivery\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon.svg?3a6863548daca964\",\"type\":\"image/svg+xml\",\"sizes\":\"any\"}],[\"$\",\"$L10\",\"4\",{}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"e:\"$9:metadata\"\n"])</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><!--IHBLjOk92WBjj7mPz8oZK--><html lang="en" class="dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/e4af272ccee01ff0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/c0ea31cd787f7aa7.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/659eccb5db697b76.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-ca926228f8cba1ff.js"/><script src="/_next/static/chunks/1b86ba38-c29611e086b2ce25.js" async=""></script><script src="/_next/static/chunks/698-19872069fed27164.js" async=""></script><script src="/_next/static/chunks/main-app-340a5b58ed229c4d.js" async=""></script><script src="/_next/static/chunks/989-6a0ec35b8ae2cb39.js" async=""></script><script src="/_next/static/chunks/app/layout-5ec6c5f713a8562b.js" async=""></script><script src="/_next/static/chunks/1d2d5650-d8d130c85727fdbb.js" async=""></script><script src="/_next/static/chunks/23-092ac3901411582e.js" async=""></script><script src="/_next/static/chunks/app/page-bf9c0b21ae431c92.js" async=""></script><meta name="next-size-adjust" content=""/><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_f367f3 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div class="flex h-screen w-screen items-center justify-center bg-[var(--background)]"><div class="text-sm text-[var(--muted-foreground)]">Loading...</div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-ca926228f8cba1ff.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[58321,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[5444,[],\"ClientPageRoot\"]\n7:I[18100,[\"560\",\"static/chunks/1d2d5650-d8d130c85727fdbb.js\",\"23\",\"static/chunks/23-092ac3901411582e.js\",\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"974\",\"static/chunks/app/page-bf9c0b21ae431c92.js\"],\"default\"]\na:I[23632,[],\"OutletBoundary\"]\nc:I[86889,[],\"AsyncMetadataOutlet\"]\ne:I[23632,[],\"ViewportBoundary\"]\n10:I[23632,[],\"MetadataBoundary\"]\n11:\"$Sreact.suspense\"\n13:I[1935,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/c0ea31cd787f7aa7.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"IHBLjOk92WBjj7mPz8oZK\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/c0ea31cd787f7aa7.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark\",\"suppressHydrationWarning\":true,\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}\"}}]}],[\"$\",\"body\",null,{\"className\":\"__variable_f367f3 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L6\",null,{\"Component\":\"$7\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@8\",\"$@9\"]}],[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/659eccb5db697b76.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"$La\",null,{\"children\":[\"$Lb\",[\"$\",\"$Lc\",null,{\"promise\":\"$@d\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Le\",null,{\"children\":\"$Lf\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$L10\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$11\",null,{\"fallback\":null,\"children\":\"$L12\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$13\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"8:{}\n9:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"f:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\nb:null\n"])</script><script>self.__next_f.push([1,"14:I[88809,[],\"IconMark\"]\nd:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Halo - Multi-Agent Workspace\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Multi-agent collaboration workspace for complex project delivery\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon.svg?3a6863548daca964\",\"type\":\"image/svg+xml\",\"sizes\":\"any\"}],[\"$\",\"$L14\",\"4\",{}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"12:\"$d:metadata\"\n"])</script></body></html>
1
+ <!DOCTYPE html><!--jXC1swwQxpx7Zpri_5CwJ--><html lang="en" class="dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/e4af272ccee01ff0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/c0ea31cd787f7aa7.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/659eccb5db697b76.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-ca926228f8cba1ff.js"/><script src="/_next/static/chunks/1b86ba38-c29611e086b2ce25.js" async=""></script><script src="/_next/static/chunks/698-19872069fed27164.js" async=""></script><script src="/_next/static/chunks/main-app-340a5b58ed229c4d.js" async=""></script><script src="/_next/static/chunks/989-6a0ec35b8ae2cb39.js" async=""></script><script src="/_next/static/chunks/app/layout-5ec6c5f713a8562b.js" async=""></script><script src="/_next/static/chunks/1d2d5650-d8d130c85727fdbb.js" async=""></script><script src="/_next/static/chunks/23-092ac3901411582e.js" async=""></script><script src="/_next/static/chunks/app/page-bf9c0b21ae431c92.js" async=""></script><meta name="next-size-adjust" content=""/><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_f367f3 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div class="flex h-screen w-screen items-center justify-center bg-[var(--background)]"><div class="text-sm text-[var(--muted-foreground)]">Loading...</div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-ca926228f8cba1ff.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[58321,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"177\",\"static/chunks/app/layout-5ec6c5f713a8562b.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[5444,[],\"ClientPageRoot\"]\n7:I[18100,[\"560\",\"static/chunks/1d2d5650-d8d130c85727fdbb.js\",\"23\",\"static/chunks/23-092ac3901411582e.js\",\"989\",\"static/chunks/989-6a0ec35b8ae2cb39.js\",\"974\",\"static/chunks/app/page-bf9c0b21ae431c92.js\"],\"default\"]\na:I[23632,[],\"OutletBoundary\"]\nc:I[86889,[],\"AsyncMetadataOutlet\"]\ne:I[23632,[],\"ViewportBoundary\"]\n10:I[23632,[],\"MetadataBoundary\"]\n11:\"$Sreact.suspense\"\n13:I[1935,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/c0ea31cd787f7aa7.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"jXC1swwQxpx7Zpri-5CwJ\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/c0ea31cd787f7aa7.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark\",\"suppressHydrationWarning\":true,\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}\"}}]}],[\"$\",\"body\",null,{\"className\":\"__variable_f367f3 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L6\",null,{\"Component\":\"$7\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@8\",\"$@9\"]}],[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/659eccb5db697b76.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"$La\",null,{\"children\":[\"$Lb\",[\"$\",\"$Lc\",null,{\"promise\":\"$@d\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Le\",null,{\"children\":\"$Lf\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$L10\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$11\",null,{\"fallback\":null,\"children\":\"$L12\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$13\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"8:{}\n9:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"f:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\nb:null\n"])</script><script>self.__next_f.push([1,"14:I[88809,[],\"IconMark\"]\nd:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Halo - Multi-Agent Workspace\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Multi-agent collaboration workspace for complex project delivery\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon.svg?3a6863548daca964\",\"type\":\"image/svg+xml\",\"sizes\":\"any\"}],[\"$\",\"$L14\",\"4\",{}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"12:\"$d:metadata\"\n"])</script></body></html>
@@ -14,7 +14,7 @@ e:I[23632,[],"ViewportBoundary"]
14
14
  :HL["/_next/static/media/e4af272ccee01ff0-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
15
15
  :HL["/_next/static/css/c0ea31cd787f7aa7.css","style"]
16
16
  :HL["/_next/static/css/659eccb5db697b76.css","style"]
17
- 0:{"P":null,"b":"IHBLjOk92WBjj7mPz8oZK","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/c0ea31cd787f7aa7.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"dark","suppressHydrationWarning":true,"children":[["$","head",null,{"children":["$","script",null,{"dangerouslySetInnerHTML":{"__html":"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}"}}]}],["$","body",null,{"className":"__variable_f367f3 font-sans antialiased","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L6",null,{"Component":"$7","searchParams":{},"params":{},"promises":["$@8","$@9"]}],[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/659eccb5db697b76.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","$La",null,{"children":["$Lb",["$","$Lc",null,{"promise":"$@d"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Le",null,{"children":"$Lf"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L10",null,{"children":["$","div",null,{"hidden":true,"children":["$","$11",null,{"fallback":null,"children":"$L12"}]}]}]]}],false]],"m":"$undefined","G":["$13",[]],"s":false,"S":true}
17
+ 0:{"P":null,"b":"jXC1swwQxpx7Zpri-5CwJ","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/c0ea31cd787f7aa7.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"dark","suppressHydrationWarning":true,"children":[["$","head",null,{"children":["$","script",null,{"dangerouslySetInnerHTML":{"__html":"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}"}}]}],["$","body",null,{"className":"__variable_f367f3 font-sans antialiased","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L6",null,{"Component":"$7","searchParams":{},"params":{},"promises":["$@8","$@9"]}],[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/659eccb5db697b76.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","$La",null,{"children":["$Lb",["$","$Lc",null,{"promise":"$@d"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Le",null,{"children":"$Lf"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L10",null,{"children":["$","div",null,{"hidden":true,"children":["$","$11",null,{"fallback":null,"children":"$L12"}]}]}]]}],false]],"m":"$undefined","G":["$13",[]],"s":false,"S":true}
18
18
  8:{}
19
19
  9:"$0:f:0:1:2:children:1:props:children:0:props:params"
20
20
  f:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
@@ -1,6 +1,6 @@
1
1
  # Relay — cross-workspace dispatch with auto-report
2
2
 
3
- > Tool reference: [dev/tools.md → Relay tools](../dev/tools.md#relay-tools). Code: `packages/server/src/agents/relay.ts`, wired in `session-manager.ts` (`createRelayTools`, the `deliverRelayReport` finally hook), `session-agent-builder.ts` (opt-in gate), `index.ts` (`setRelayRegistry`).
3
+ > Tool reference: [dev/tools.md → Relay tools](../dev/tools.md#relay-tools). Code: `packages/server/src/agents/relay.ts`, wired in `session-manager.ts` (`createRelayTools`, the `deliverRelayReport` finally hook, the `sendInterimReport` kick-point hook), `session-agent-builder.ts` (opt-in gate), `index.ts` (`setRelayRegistry`).
4
4
 
5
5
  ## Problem
6
6
 
@@ -26,7 +26,7 @@ Relay mirrors goal mode's shape exactly, because goal mode had already solved "d
26
26
 
27
27
  ## Invariants and why
28
28
 
29
- - **One dispatch, one report.** `reply_to` is cleared *before* the send. A delivery failure (caller workspace unreachable) can't re-fire on the next turn end, and — more importantly — a user who later chats directly in the department workspace never pings the secretary. The secretary can always re-`relay_send` if a report went missing.
29
+ - **One dispatch, one *final* report.** `reply_to` is cleared *before* the send. A delivery failure (caller workspace unreachable) can't re-fire on the next turn end, and — more importantly — a user who later chats directly in the department workspace never pings the secretary. The secretary can always re-`relay_send` if a report went missing. The one extra message is the **interim report** (below) — it never clears `reply_to`, so it doesn't consume the final one.
30
30
  - **Nested trees report once.** The quiet gate is the same one `tryReportToParent` uses, so a target that fans out three sub-agents delivers exactly one relay report, after the last child bubbles up — not one per child turn end.
31
31
  - **Full access only.** `session-agent-builder` injects the set only when `nameSet.has('relay_send') && accessLevel === null`. A sandboxed (`workspace` / `readonly`) session can't be given a tool that opens other workspaces' sqlite and session trees; listing the name in such an agent's yaml is silently ignored.
32
32
  - **Name-gated bundle.** Only `relay_send` is recognised in `tools:`; the other four (`relay_interrupt` / `relay_stop` / `relay_read` / `relay_list`) ride along. Prevents a half-configured agent that can send but never read back / stop. The admin picker mirrors this with one chip (`GET /agent-configs/tools` builds it from `buildRelayTools` against a dummy target and rewrites the description to name the full set).
@@ -35,6 +35,20 @@ Relay mirrors goal mode's shape exactly, because goal mode had already solved "d
35
35
  - **Same workspace is allowed.** `workspace` is only checked for existence + `.halo/`; passing the caller's own path makes `getOrCreate` return the caller's own `SessionManager`, and everything else is unchanged. In effect relay is a `query_session` without the own-tree scoping — it reaches any **root** session in any workspace, including new ones. Not a hole: the tool is full-access only, and a full-access agent could already `shell_exec` its way into any session file.
36
36
  - **Reports come back from roots only.** `deliverRelayReport` fires for `parentId === null`; a sub-session's turn end goes to `tryReportToParent` instead. Dispatching to a `parent>child` id delivers the message but never a report — target roots when you want to hear back. Dispatching to yourself works and terminates (one self-report, `reply_to` cleared) but is pointless.
37
37
 
38
+ ## Interim report (continue_task)
39
+
40
+ A busy target that gets a follow-up `relay_send` answers it in a drained turn and, when its original task isn't done, calls `continue_task` — `drainQueue` then runs a resume turn in the **same** `runSession`. Every turn resets `output` / `finalOutput` (`runAgentTurn`'s per-turn reset — `get_session_output`, the session file, goal no-progress hashing and the report cap all depend on it), so the final report only carries the resume turn's wrap-up and the answer to the follow-up was lost.
41
+
42
+ Fix, at the kick point (not in `runSession`'s finally): right before `CONTINUE_TASK_KICK` is queued, `SessionManager.sendInterimReport` snapshots the answer turn's `finalOutput || output` and — **only if the batch just answered contains a relay message** (`text` contains `RELAY_CHANNEL_PREFIX`, no `sourceSessionId`) — calls `deliverRelayInterim(host, sessionId, body)`: read `reply_to` (no-op without one) → append + send into the caller
43
+
44
+ ```
45
+ [Relay interim report · workspace <ws> · session <id>] The session answered your latest message (below) and is now resuming the task that message interrupted — its final [Relay report] follows when that is done.
46
+
47
+ <answer, capped at limits.autoReportMax>
48
+ ```
49
+
50
+ No quiet gate (the session is by definition not done) and **`reply_to` is kept**, so `deliverRelayReport` still fires exactly once when the resumed task ends. Skipped when: no kick (plain answer → the final report already carries it), the interrupting message was local chat (typed into the department directly — nothing owed to the secretary), the answer turn errored, or the answer is empty. The same hook covers sub-agents (interim to the parent via `querySession`, see [session.md → continue_task](session.md#message-queue-and-drain)); goal workers are excluded.
51
+
38
52
  ## Soft vs hard interrupt
39
53
 
40
54
  `relay_send` to a busy target = queued + soft interrupt (the current tool call finishes, then the message drains as part of one merged turn). That covers follow-ups and corrections — the secretary's default. `relay_interrupt` is for "stop what you're doing *now*": it aborts the in-flight turn (propagates to `shell_exec`, SIGTERMs the process group), then the queued message runs. Idle target → `relay_interrupt` degrades to a plain send (`interrupted: false`). The distinction is the same as `query_session` vs `interrupt_session` inside one workspace; relay just carries it across.
@@ -49,4 +63,4 @@ Relay mirrors goal mode's shape exactly, because goal mode had already solved "d
49
63
 
50
64
  ## Test
51
65
 
52
- `packages/server/test/relay.test.ts` — stub `RelayTarget` + registry, 11 cases: `deliverRelayReport` no-op without `reply_to`, waits while a child is active, delivers exactly one append + one send with the header then clears, `ABORTED` prefix on `turnError`, ignores sub-sessions; `relay_send` creates + stamps `reply_to` + channel-prefixes the model text, rejects a nonexistent workspace; `relay_interrupt` busy → enqueue then abort + re-stamp, idle → no abort and never creates; `relay_list` roots only with title fallback, and defaults to the caller's own workspace.
66
+ `packages/server/test/relay.test.ts` — stub `RelayTarget` + registry, 13 cases: `deliverRelayReport` no-op without `reply_to`, waits while a child is active, delivers exactly one append + one send with the header then clears, `ABORTED` prefix on `turnError`, ignores sub-sessions; `relay_send` creates + stamps `reply_to` + channel-prefixes the model text, rejects a nonexistent workspace; `relay_interrupt` busy → enqueue then abort + re-stamp, idle → no abort and never creates; `relay_list` roots only with title fallback, and defaults to the caller's own workspace. `deliverRelayInterim`: no-op without `reply_to`; delivers the interim header and keeps `reply_to`, the later final report still fires once. End-to-end through `drainQueue` (relay interim → final, no kick → no interim, local message → no interim) lives in `test/continue-task.test.ts`.
@@ -207,6 +207,7 @@ A `QueuedMessage` is `{ text, sourceSessionId?, images? }`: `sourceSessionId` is
207
207
  - **`resumedAfterInterrupt`** is snapshotted from `interruptRequested` at the top of every drained batch (before the per-batch reset) and set `false` for the opening turn. `requestSelfKick` — the tool backend — returns `not_interrupted` and sets nothing unless it is `true`, so a normal turn can't arm the flag: **kicks ≤ interrupts**, no perpetual motion.
208
208
  - **`selfKick`** lives exactly one turn: reset at the top of every drained batch. If that reset drops a `true` (the model armed it, then got interrupted *again* before the kick fired), a one-line `[System] … flag … was reset by this interrupt … call continue_task again` note is appended to that batch's merged input — the model can't see the flag, so it's told.
209
209
  - **Kick position**: inside `drainQueue`'s while-loop, after `runAgentTurn` and **before** the `batchBoundary` check. When `selfKick` is set, the queue is empty **and `interruptRequested` is false**, it pushes a synthetic `[System] You called continue_task: … resume it now …` user turn (`CONTINUE_TASK_KICK`) and traces it as a `type:'user'` event with `report: true` (not a `system` event — the kick is a raw user turn, so it needs a UI user row or `deleteExchange`'s UI span and `deleteRawTurn`'s raw span diverge; traced at enqueue like every queued message, routed into a sub-agent's log via `taskId`); the existing boundary `complete` then fires naturally so block channels flush the answer as its own message and the kick turn gets its own bubble. The next iteration sees `interruptRequested === false` → `resumedAfterInterrupt = false`, so the kick turn cannot re-arm. If something else landed mid-turn the kick is skipped — the next iteration's reset + note handles it (the newest message might be "stop"; the model decides with that in view, not the loop). The `!interruptRequested` gate covers the other abort path: **esc / `/interrupt` never kicks** — `interruptSession` sets the flag and aborts *without* enqueuing, so the queue check alone would have let an esc'd turn resume itself; esc means "stop what you're doing". Whenever the loop is about to exit on an empty queue the flag is cleared so it never dangles. It is deliberately **not** in `runSession`'s `finally`: `tryReportToParent` / `deliverGoalRound` / `deliverRelayReport` fire there and must see the *finished* work, so the kick turn has to complete first.
210
+ - **Interim report before the kick** (`sendInterimReport`, called right before `CONTINUE_TASK_KICK` is queued): the kick turn resets `output` / `finalOutput`, and every end-of-run report (`tryReportToParent` / `deliverRelayReport`) reads only the last turn — so the answer to the interrupting message would never reach whoever asked. The per-turn reset stays (get_session_output, the session file, goal no-progress hashing and the report cap depend on it); instead the answer is snapshotted here and forwarded **only when the batch came from the party this session owes a report**: a sub-agent whose batch has a message with `sourceSessionId === parentId` → `querySession(parent, self, "[Interim report: … Not the final result.]\n\n<answer>")`, **without** stamping `stoppedAt` or emitting `agent_done` (the parent's sibling-status still lists the child as running → "Do NOT wrap up yet"; the real auto-report follows at the end); a root whose batch has a relay-prefixed message → `deliverRelayInterim` (keeps `reply_to`, see [relay.md → Interim report](relay.md#interim-report-continue_task)). Nothing for local user chat (the channel already streamed the answer and a `batchBoundary` complete flushes it), a sibling/other-session message, an errored answer turn, or a goal worker (G's steering carries neither mark; an interim would wake G mid-round and its next `query_session` would interrupt W again — the round report is unchanged and counts once). Capped at `limits.autoReportMax`.
210
211
  - **Stop / delete / archive never resurrect**: `stopSession` and `stopUserSession` clear `selfKick` next to `interruptRequested`; `deleteSession` (root + children) and `archiveSession` clear it right after nulling `abortController` and *before* awaiting `session.promise` (the aborted turn's drain still runs inside that promise and would otherwise fire a real kick turn); and `requestSelfKick` returns `no_turn` when `session.abortController === null` — Stop nulls it *before* the abort lands, so a tool callback that completes after a Stop can't re-arm the flag. `enqueueUserMessage` (admin WS) mirrors `sendUserMessage` and does **not** set `interruptRequested` while compacting — there is no live turn, and `endCompact`'s drain would otherwise snapshot it into `resumedAfterInterrupt` for a turn that interrupted nothing.
211
212
 
212
213
  **Stop preserves, archive discards.** Both `stopSession` and `stopUserSession` (the user Stop button) fold the **whole** `messageQueue` into `agent.messages` as a user turn (user entries raw, agent entries with their `(from: session X)` prefix) **before** aborting, then `repairConversationMessages` — a stop **parks** the work, it never drops a queued message (mirrors interrupt, which also never loses one). `archiveSession`, by contrast, clears the queue without folding — archiving a subtree is a deliberate discard.
@@ -351,7 +352,7 @@ Frontend network issues don't affect the backend:
351
352
  | Error | Recovery |
352
353
  |---|---|
353
354
  | User abort / graceful interrupt — **our own `signal.aborted`** or `err.name === 'AbortError'` | Repair, clean exit |
354
- | Context overflow (`too many input tokens`) | **Local** (non-LLM) compact + retry — the model already refused this payload, so calling an LLM risks a second stall |
355
+ | Context overflow (`too many input tokens` / `prompt_too_long` / `ContextWindowOverflow` / `Input is too long`) | **Local** (non-LLM) compact + retry — the model already refused this payload, so calling an LLM risks a second stall. Bedrock's bare `Input is too long.` also covers a request **body** over its ~32 MB cap, whatever the token count — see [History image budget](#history-image-budget) |
355
356
  | Account-level error — **`httpStatus` 401 / 402 / 403** when a status is available; keyword match (insufficient balance / suspended / invalid key / unauthorized / authentication) only when none is | Unrecoverable — report to user, **no** retry |
356
357
  | Rate limiting / throttling — `err.name === 'ThrottlingException'`, `httpStatus 429`, or keyword (`throttl` / `rate limit` / `ServiceUnavailableException`) | Exponential backoff (`2s * 2^attempt` + jitter, capped at 60s: 2s/4s/8s/16s…), retry |
357
358
  | **Transient server-side error (5xx / timeout)** | Same exponential backoff as throttling — see [Transient server-error classification](#transient-server-error-classification) below |
@@ -402,7 +403,7 @@ Three entry points with different quality / safety trade-offs:
402
403
  | Trigger | Path | Compaction used | Rationale |
403
404
  |---|---|---|---|
404
405
  | 80% soft threshold (mid-turn auto) | `maybeAutoCompact()` via agent-loop's `beforeCallModel` hook — runs before each model call within a turn | **Self-compact** (`selfCompactSession`) — the agent summarizes its own context, then a tail micro-compact pass clears bulk tool output | The agent already has full context cached (prompt cache hit). No extra model call, no input duplication, no risk of losing tool_result semantics. Firing mid-turn (not just at turn end) stops a single long turn that accumulates many large tool results from blowing the window. |
405
- | Overflow mid-loop (`too many input tokens`) | `runAgentTurn` retry catch → `localCompactMessages` → retry | **Local** — `[role]: <first N chars>` concat, no network call | The model just refused this payload; an LLM round-trip now could stall the recovery path. Local is deterministic and instant; the next end-of-turn can re-summarize via self-compact. |
406
+ | Overflow mid-loop (`too many input tokens`, `Input is too long`, …) | `runAgentTurn` retry catch → `localCompactMessages` → retry | **Local** — `[role]: <first N chars>` concat, no network call | The model just refused this payload; an LLM round-trip now could stall the recovery path. Local is deterministic and instant; the next end-of-turn can re-summarize via self-compact. |
406
407
  | User `/session compact` (web, WeChat) | `commands/compact.ts` / `SessionManager.compactSession` | **Self-compact**, with **local fallback** on timeout/error | User explicitly requested it; self-compact reuses the cached context so it's fast. Falls back to local if anything goes wrong. |
407
408
 
408
409
  Self-compact **deep-snapshots the keep-region before running the summarize turn** (`messages.slice(cut).map(structuredClone)`), then injects a summarization instruction into the agent's own stream, captures the response, and rebuilds messages as `[summary + snapshot]`. This reuses the provider's prompt cache (no separate model needed) and preserves full semantic context including tool results.
@@ -423,6 +424,20 @@ Config (see `config.compact`): `keepMessages` / `maxSummaryInput` / `maxMessageS
423
424
 
424
425
  **Self-compact also archives the UI log.** `selfCompactSession` calls `uiStore.archiveOldMessages(session.id)` for **root sessions only** (`!session.parentId`), right before the compaction notice is emitted so the notice lands in the kept exchange rather than inside the archived segment. Compaction is the one path that already means "history shrinks here", which is why archiving hangs off it rather than off a timer or its own sweep — the size threshold below decides whether the call does anything. See [UI-log archiving](#ui-log-archiving).
425
426
 
427
+ ### History image budget
428
+
429
+ Every threshold above counts **tokens**, and an image is almost free in tokens (the flat 1500 estimate; the real cost is similar) while its base64 can exceed 1 MB. History is replayed with every request, so image bytes accumulate while the token count stays low. Bedrock rejects a request body over roughly **32 MB** with `Input is too long.` — the same text as a token overflow. Production case (2026-09-27, fixed in 1.4.4): a Blender Studio session had `view_image`d 31 PNG renders at 896×896, ~1.1 MB of base64 each, so every request was 34 MB at 113K tokens. Auto-compact never fired, and the unrecognised message ended the turn on the first attempt. Probes: 26 images (28 MB) pass, 31 (34 MB) fail, 7 noise images totalling 33.6 MB fail as well — the cap is on bytes, not on image count.
430
+
431
+ `trimHistoryImages` (`agents/history-images.ts`) is the byte-side gate. It runs **first** in the `beforeCallModel` hook (`SessionManager.trimImages`, before `maybeAutoCompact`), so it applies to every runtime and to both user uploads and images nested in `tool_result` (`view_image`):
432
+
433
+ - Trigger: history images total more than **20 MB of base64** (leaves room under the ~32 MB cap for text and tool output), or more than **100 images** (Anthropic's per-request image limit).
434
+ - Action: starting from the oldest, replace images with the text placeholder `[image removed: older image dropped to keep the request under the size limit — view_image it again if still needed]` until both totals are under **half** their limit. Cutting to half means a trim happens about once per 10 MB of new images, not on every call — each trim rewrites early history, so it costs one prompt-cache miss.
435
+ - The placeholder is a text block in the same place, so `tool_use` / `tool_result` pairing is unchanged. Persisted by the hook's own `saveAgentState`. Logged at warn, and a `system` event `Removed N older image(s) from context to keep the request under the size limit` is shown in the session (sub-agents: their own log via `taskId`).
436
+
437
+ This is the pre-call gate. As a fallback, `Input is too long` is now classified as `context_overflow`, so a payload that still gets rejected goes through local compact + retry instead of failing. `replaceImageBlocks` (the [multimodal 4xx degrade](#multimodal-4xx-degrade), which removes *every* image) lives in the same module and uses the same block walk.
438
+
439
+ The per-image side is at the entry points: the admin upload path re-encodes every attachment to JPEG with the long edge ≤1568 px, and `view_image` sends opaque PNGs over 256 KB as JPEG (see [dev/tools.md → view_image](../dev/tools.md#view_image)). IM channel inbound images (WeChat / Feishu / Telegram / Slack / WeCom / web) are forwarded as received and are only bounded by this budget. Pinned by `history-image-budget.test.ts`.
440
+
426
441
  ## UI-log archiving
427
442
 
428
443
  Files: `packages/server/src/sessions/session-archive.ts` (segment IO) · `agents/session-ui-store.ts` `archiveOldMessages` (the write) · `routes/session-archive.ts` (the read)
@@ -41,13 +41,22 @@ Replace a string in a file (exact match).
41
41
 
42
42
  ### view_image
43
43
 
44
- Read an image file and return it as a vision content block. Supports png/jpg/jpeg/gif/webp, max 5 MB.
44
+ Read an image file and return it as a vision content block. Supports png/jpg/jpeg/gif/webp.
45
45
 
46
46
  | Arg | Type | Required | Description |
47
47
  |---|---|---|---|
48
48
  | path | string | yes | Image file path |
49
49
 
50
- Returns: image content block (base64-encoded) for multimodal processing.
50
+ Returns: a text line (`Image loaded: <path> (<media type>, <KB>, md5: …)`) plus an image content block (base64-encoded) for multimodal processing.
51
+
52
+ Processing before the bytes go out — only the payload changes, the file on disk is never touched:
53
+
54
+ - **Decode check**: png/jpeg/gif are decoded with jimp first. A corrupt or truncated file returns an error and is not sent, because a bad image block in history makes every later request fail. webp has no jimp codec and skips this check.
55
+ - **Media type from bytes**: `media_type` is taken from the file's magic bytes, not its extension, so a JPEG saved as `.png` is labeled correctly.
56
+ - **Downscale**: when the base64 is over 5 MB (Anthropic's per-image limit) or the long edge is over 1568 px, the long edge is scaled to 1568 and the image is re-encoded as JPEG, starting at q82 and stepping down until it fits.
57
+ - **Large PNG → JPEG** (1.4.4): an opaque PNG over 256 KB is re-encoded as JPEG q82 and sent that way if it comes out smaller. This matches the admin upload path, which sends every attachment as JPEG. The image stays in history and is re-sent with every later request, and PNG renders are often 5–15× their JPEG size. Example: 31 Blender renders at 896×896 went from 24.3 MB to 1.4 MB; the largest was 86 KB. Transparent PNGs (JPEG would lose the alpha channel), PNGs of 256 KB or less, and flat-color PNGs that don't shrink are sent as-is. The result line then reads `— sent as jpeg q82, <KB> KB, to keep the conversation payload small`.
58
+
59
+ How many images history can hold in total is capped separately at the session level — see [design/session.md → History image budget](../design/session.md#history-image-budget).
51
60
 
52
61
  **Vision gating**: this tool is only injected into the agent's tool list when the underlying model declares `capabilities.image: true` in its provider manifest. For text-only models (DeepSeek and others), `view_image` is silently dropped at `createWorkspaceTools()` time so the model never sees it — calling it would otherwise produce a 400 from the provider.
53
62
 
@@ -333,7 +342,7 @@ Returns: full SKILL.md content (body + resource files list). For progressive dis
333
342
 
334
343
  **Built-in for every agent — the one truly unconditional tool** (`activate_skill` is gated on `skills`, session tools on `team`; not declared in `agent.yaml tools`). Wired in `session-agent-builder` (`buildContinueTaskTool`, next to the session bundle). No parameters.
335
344
 
336
- Call it when the current turn was started by an interruption (a user / parent message landed while the agent was working) and the interrupted task is **not** finished: after the current reply ends, `drainQueue` pushes a synthetic `[System] You called continue_task: … resume it now` user turn (traced as a `user` row, `report: true`) and runs one more turn. Only effective in a turn that followed an interrupt — in a normal turn it returns `not_interrupted` and sets nothing (kicks ≤ interrupts). The flag lasts one turn: a second interrupt before the kick resets it and the model is told to call again if still needed. An esc / `/interrupt` (abort without a new message) never kicks; Stop / delete / archive clear it; a callback landing after Stop gets `code: 1` (`no_turn`).
345
+ Call it when the current turn was started by an interruption (a user / parent message landed while the agent was working) and the interrupted task is **not** finished: after the current reply ends, `drainQueue` pushes a synthetic `[System] You called continue_task: … resume it now` user turn (traced as a `user` row, `report: true`) and runs one more turn. Only effective in a turn that followed an interrupt — in a normal turn it returns `not_interrupted` and sets nothing (kicks ≤ interrupts). The flag lasts one turn: a second interrupt before the kick resets it and the model is told to call again if still needed. An esc / `/interrupt` (abort without a new message) never kicks; Stop / delete / archive clear it; a callback landing after Stop gets `code: 1` (`no_turn`). Before the kick, the answer is forwarded as an **interim report** when the interrupting message came from the parent (sub-agent → `[Interim report: …]` via `query_session`, no `stoppedAt`) or from a relay caller (root → `[Relay interim report · …]`) — the final auto-report / relay report still follows once.
337
346
 
338
347
  Returns: `{ code: 0, message }` on `set` / `not_interrupted`, `{ code: 1, error }` on `no_turn`. Design in [design/session.md → continue_task](../design/session.md#message-queue-and-drain).
339
348
 
@@ -427,6 +436,8 @@ Returns `{ "code": 0, "workspace": "<realpath>", "session_id": "…", "state": "
427
436
 
428
437
  Body capped at `limits.autoReportMax` (head-kept) with a `[Report truncated: N chars total. Use relay_read("<ws>", "<id>") for the full text.]` marker. A turn killed by an unrecoverable error is prefixed `[RELAY TARGET ABORTED: … Error: <text> … Re-send with relay_send to let it resume.]` **before** the cap so it can't be sliced off. `reply_to` is cleared **before** sending: one dispatch → one report, a failed delivery can't double-fire on the next turn end, and a user chatting directly in the department workspace afterwards never pings the secretary.
429
438
 
439
+ **Interim report**: if a busy target answers a follow-up `relay_send` and then calls `continue_task` to resume what it was doing, the answer is delivered right away as `[Relay interim report · workspace <ws> · session <id>] … its final [Relay report] follows …` (capped at `limits.autoReportMax`), **without** clearing `reply_to` — so the final report still arrives once. Only relay-prefixed messages trigger it; a user chatting directly in the department workspace never does. Details in [design/relay.md → Interim report](../design/relay.md#interim-report-continue_task).
440
+
430
441
  ### relay_interrupt
431
442
 
432
443
  `relay_send` **plus a hard abort** of the target's in-flight turn — `interrupt_session` semantics across workspaces: the message is enqueued first, and if the target was busy (`state === 'queued'`) its turn is aborted immediately (propagates to `shell_exec`, SIGTERMs the process group) so the drain picks the message up now instead of after the current step. Enqueue-then-abort order matters: the finally never sees an empty queue and so never fires a spurious relay report. Idle target → behaves exactly like `relay_send` (`interrupted: false`). Unlike `relay_send` it does **not** create a missing session (`{"code": 1, "error": "session not found"}`) — there is nothing to interrupt. Use it when the target is heading the wrong way and waiting for its current step is not acceptable; for ordinary follow-ups prefer `relay_send`.
package/dist/index.js CHANGED
@@ -1392,7 +1392,7 @@ function classifyModelError(err) {
1392
1392
  return { kind: classifyKind(msg, errName, httpStatus), msg, errName, httpStatus };
1393
1393
  }
1394
1394
  function classifyKind(msg, errName, httpStatus) {
1395
- if (msg.includes("too many input tokens") || msg.includes("prompt_too_long") || msg.includes("ContextWindowOverflow")) {
1395
+ if (msg.includes("too many input tokens") || msg.includes("prompt_too_long") || msg.includes("ContextWindowOverflow") || msg.includes("Input is too long")) {
1396
1396
  return "context_overflow";
1397
1397
  }
1398
1398
  const isAccountError = httpStatus !== void 0 ? httpStatus === 401 || httpStatus === 402 || httpStatus === 403 : msg.includes("insufficient balance") || msg.includes("suspended") || msg.includes("invalid api key") || msg.includes("Invalid API Key") || msg.includes("Unauthorized") || msg.includes("authentication");
@@ -1543,6 +1543,67 @@ var init_micro_compact = __esm({
1543
1543
  }
1544
1544
  });
1545
1545
 
1546
+ // ../server/dist/agents/history-images.js
1547
+ function mapImageBlocks(messages2, fn) {
1548
+ let index = 0;
1549
+ let replaced = 0;
1550
+ const visit = (b) => {
1551
+ if (b.type !== "image")
1552
+ return b;
1553
+ const text6 = fn(index++);
1554
+ if (text6 === null)
1555
+ return b;
1556
+ replaced++;
1557
+ return { type: "text", text: text6 };
1558
+ };
1559
+ for (const m of messages2) {
1560
+ if (!Array.isArray(m.content))
1561
+ continue;
1562
+ m.content = m.content.map((b) => b.type === "tool_result" && Array.isArray(b.content) ? { ...b, content: b.content.map(visit) } : visit(b));
1563
+ }
1564
+ return replaced;
1565
+ }
1566
+ function imageSizes(messages2) {
1567
+ const sizes = [];
1568
+ for (const m of messages2) {
1569
+ if (!Array.isArray(m.content))
1570
+ continue;
1571
+ for (const b of m.content) {
1572
+ if (b.type === "image")
1573
+ sizes.push(b.source.data.length);
1574
+ else if (b.type === "tool_result" && Array.isArray(b.content)) {
1575
+ for (const ib of b.content)
1576
+ if (ib.type === "image")
1577
+ sizes.push(ib.source.data.length);
1578
+ }
1579
+ }
1580
+ }
1581
+ return sizes;
1582
+ }
1583
+ function replaceImageBlocks(messages2, reason) {
1584
+ return mapImageBlocks(messages2, () => `[image removed: ${reason}]`);
1585
+ }
1586
+ function trimHistoryImages(messages2) {
1587
+ const sizes = imageSizes(messages2);
1588
+ let total = sizes.reduce((a, b) => a + b, 0);
1589
+ if (total <= MAX_IMAGE_B64 && sizes.length <= MAX_IMAGES)
1590
+ return 0;
1591
+ let drop = 0;
1592
+ while (drop < sizes.length && (total > MAX_IMAGE_B64 / 2 || sizes.length - drop > MAX_IMAGES / 2)) {
1593
+ total -= sizes[drop++];
1594
+ }
1595
+ return mapImageBlocks(messages2, (i) => i < drop ? TRIM_PLACEHOLDER : null);
1596
+ }
1597
+ var MAX_IMAGE_B64, MAX_IMAGES, TRIM_PLACEHOLDER;
1598
+ var init_history_images = __esm({
1599
+ "../server/dist/agents/history-images.js"() {
1600
+ "use strict";
1601
+ MAX_IMAGE_B64 = 20 * 1024 * 1024;
1602
+ MAX_IMAGES = 100;
1603
+ TRIM_PLACEHOLDER = "[image removed: older image dropped to keep the request under the size limit \u2014 view_image it again if still needed]";
1604
+ }
1605
+ });
1606
+
1546
1607
  // ../server/dist/agents/agent-loop.js
1547
1608
  function toolResultImages(content) {
1548
1609
  const images = [];
@@ -5526,6 +5587,30 @@ ${body}`;
5526
5587
  await caller.sendUserMessage(to.sessionId, text6);
5527
5588
  console.debug(`[Relay] ${host.workspaceRoot}/${session.id} \u2192 ${to.workspace}/${to.sessionId}: ${body.slice(0, 120)}`);
5528
5589
  }
5590
+ async function deliverRelayInterim(host, sessionId, body) {
5591
+ const to = readReplyTo(host.getDb(), sessionId);
5592
+ if (!to)
5593
+ return;
5594
+ const registry2 = getRelayRegistry();
5595
+ if (!registry2) {
5596
+ console.warn(`[Relay] no registry \u2014 cannot deliver interim report for ${sessionId} to ${to.workspace}`);
5597
+ return;
5598
+ }
5599
+ let caller;
5600
+ try {
5601
+ caller = registry2.getOrCreate(to.workspace);
5602
+ } catch (err) {
5603
+ console.error(`[Relay] caller workspace ${to.workspace} unreachable: ${err instanceof Error ? err.message : String(err)}`);
5604
+ return;
5605
+ }
5606
+ const header = `[Relay interim report \xB7 workspace ${host.workspaceRoot} \xB7 session ${sessionId}] The session answered your latest message (below) and is now resuming the task that message interrupted \u2014 its final [Relay report] follows when that is done.`;
5607
+ const text6 = `${header}
5608
+
5609
+ ${body}`;
5610
+ caller.appendUserMessage(to.sessionId, text6);
5611
+ await caller.sendUserMessage(to.sessionId, text6);
5612
+ console.debug(`[Relay] interim ${host.workspaceRoot}/${sessionId} \u2192 ${to.workspace}/${to.sessionId}: ${body.slice(0, 120)}`);
5613
+ }
5529
5614
  function jsonErr2(error) {
5530
5615
  return JSON.stringify({ code: 1, error });
5531
5616
  }
@@ -5543,7 +5628,7 @@ function resolveTarget(registry2, workspace) {
5543
5628
  function buildRelayTools(host, callerSessionId) {
5544
5629
  const relaySend = {
5545
5630
  name: "relay_send",
5546
- description: "Dispatch a message to a session in ANOTHER workspace on this server. Creates the session if `session_id` does not exist there (with `agent_id`, or the workspace's default agent). If the session is busy the message is queued and the current step is softly interrupted (finishes its current tool, then reads your message) \u2014 use this for follow-ups and corrections too. Returns immediately; when the target's whole subtree finishes, its wrap-up is delivered to you as a `[Relay report \xB7 \u2026]` message. Do not poll \u2014 the report arrives on its own. Returns JSON with code 0 on success.",
5631
+ description: "Dispatch a message to a session in ANOTHER workspace on this server. Creates the session if `session_id` does not exist there (with `agent_id`, or the workspace's default agent). If the session is busy the message is queued and the current step is softly interrupted (finishes its current tool, then reads your message) \u2014 use this for follow-ups and corrections too. Returns immediately; when the target's whole subtree finishes, its wrap-up is delivered to you as a `[Relay report \xB7 \u2026]` message. If a busy target answers your message and then resumes the task it interrupted, that answer arrives first as a `[Relay interim report \xB7 \u2026]` \u2014 the final report still follows. Do not poll \u2014 the report arrives on its own. Returns JSON with code 0 on success.",
5547
5632
  inputSchema: {
5548
5633
  type: "object",
5549
5634
  properties: {
@@ -5597,7 +5682,7 @@ function buildRelayTools(host, callerSessionId) {
5597
5682
  await target.createSession(agentId, null, `Relay: ${params.message.slice(0, 60)}`, void 0, params.session_id);
5598
5683
  }
5599
5684
  writeReplyTo(target.getDb(), params.session_id, { workspace: host.workspaceRoot, sessionId: callerSessionId });
5600
- const prefixed = `[channel: relay | from: ${host.workspaceRoot}]
5685
+ const prefixed = `${RELAY_CHANNEL_PREFIX}${host.workspaceRoot}]
5601
5686
 
5602
5687
  ${params.message}`;
5603
5688
  target.appendUserMessage(params.session_id, params.message);
@@ -5694,13 +5779,14 @@ ${params.message}`;
5694
5779
  };
5695
5780
  return [relaySend, relayInterrupt, relayStop, relayRead, relayList];
5696
5781
  }
5697
- var _registry, WORKSPACE_SESSION_PROPS;
5782
+ var RELAY_CHANNEL_PREFIX, _registry, WORKSPACE_SESSION_PROPS;
5698
5783
  var init_relay = __esm({
5699
5784
  "../server/dist/agents/relay.js"() {
5700
5785
  "use strict";
5701
5786
  init_schema();
5702
5787
  init_config();
5703
5788
  init_commands2();
5789
+ RELAY_CHANNEL_PREFIX = "[channel: relay | from: ";
5704
5790
  _registry = null;
5705
5791
  WORKSPACE_SESSION_PROPS = {
5706
5792
  workspace: { type: "string", description: "Absolute path of the target workspace on this server." },
@@ -10636,10 +10722,12 @@ Error: "${input.path}" does not decode as a valid image (${err instanceof Error
10636
10722
  const dims = imageDimensions(buf, mediaType);
10637
10723
  const tooManyBytes = b64Len(buf.length) > B64_LIMIT;
10638
10724
  const tooBig = dims != null && Math.max(dims.w, dims.h) > MAX_EDGE;
10725
+ const PNG_TO_JPEG_MIN = 256 * 1024;
10726
+ const heavyPng = mediaType === "image/png" && buf.length > PNG_TO_JPEG_MIN && img != null && !img.hasAlpha();
10639
10727
  let outMediaType = mediaType;
10640
10728
  let outBuf = buf;
10641
10729
  let note = "";
10642
- if (tooManyBytes || tooBig) {
10730
+ if (tooManyBytes || tooBig || heavyPng) {
10643
10731
  try {
10644
10732
  img ??= await Jimp.read(buf);
10645
10733
  const longEdge = Math.max(img.bitmap.width, img.bitmap.height);
@@ -10651,9 +10739,11 @@ Error: "${input.path}" does not decode as a valid image (${err instanceof Error
10651
10739
  q -= 15;
10652
10740
  jpeg = await img.getBuffer("image/jpeg", { quality: q });
10653
10741
  }
10654
- outBuf = jpeg;
10655
- outMediaType = "image/jpeg";
10656
- note = ` \u2014 resized to ${img.bitmap.width}\xD7${img.bitmap.height}, ${(jpeg.length / 1024).toFixed(0)} KB (jpeg q${q}) to fit the model's image limits`;
10742
+ if (tooManyBytes || tooBig || jpeg.length < buf.length) {
10743
+ outBuf = jpeg;
10744
+ outMediaType = "image/jpeg";
10745
+ note = tooManyBytes || tooBig ? ` \u2014 resized to ${img.bitmap.width}\xD7${img.bitmap.height}, ${(jpeg.length / 1024).toFixed(0)} KB (jpeg q${q}) to fit the model's image limits` : ` \u2014 sent as jpeg q${q}, ${(jpeg.length / 1024).toFixed(0)} KB, to keep the conversation payload small`;
10746
+ }
10657
10747
  } catch (err) {
10658
10748
  return `${TOOL_WARN_MARKER}
10659
10749
  Error: image is too large for the model and automatic compression failed (${err instanceof Error ? err.message : String(err)}). Shrink it (lower resolution / crop the relevant region) and retry.`;
@@ -11829,7 +11919,7 @@ async function initObservability() {
11829
11919
  return true;
11830
11920
  }
11831
11921
  const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
11832
- providers = registerSdk2(process.env.OTEL_SERVICE_NAME, "1.4.3");
11922
+ providers = registerSdk2(process.env.OTEL_SERVICE_NAME, "1.4.5");
11833
11923
  console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, capture_content=${config.observability.captureContent})`);
11834
11924
  return true;
11835
11925
  }
@@ -12170,34 +12260,6 @@ function isRawTurnStart(msg) {
12170
12260
  function stripImageMarkers(s) {
12171
12261
  return s.replace(/\[图片已保存: [^\]]*\]\n?/g, "").trim();
12172
12262
  }
12173
- function replaceImageBlocks(messages2, reason) {
12174
- let replaced = 0;
12175
- const placeholder = () => ({ type: "text", text: `[image removed: ${reason}]` });
12176
- for (const m of messages2) {
12177
- if (!Array.isArray(m.content))
12178
- continue;
12179
- m.content = m.content.map((b) => {
12180
- if (b.type === "image") {
12181
- replaced++;
12182
- return placeholder();
12183
- }
12184
- if (b.type === "tool_result" && Array.isArray(b.content)) {
12185
- return {
12186
- ...b,
12187
- content: b.content.map((ib) => {
12188
- if (ib.type === "image") {
12189
- replaced++;
12190
- return { type: "text", text: `[image removed: ${reason}]` };
12191
- }
12192
- return ib;
12193
- })
12194
- };
12195
- }
12196
- return b;
12197
- });
12198
- }
12199
- return replaced;
12200
- }
12201
12263
  function generateSessionId() {
12202
12264
  const ts = Date.now().toString(36);
12203
12265
  const rand = Math.random().toString(36).slice(2, 8);
@@ -12213,6 +12275,7 @@ var init_session_manager = __esm({
12213
12275
  init_model_error();
12214
12276
  init_compact();
12215
12277
  init_micro_compact();
12278
+ init_history_images();
12216
12279
  init_agent_loader();
12217
12280
  init_broadcast();
12218
12281
  init_db();
@@ -12968,6 +13031,7 @@ var init_session_manager = __esm({
12968
13031
  const iter = session.agent.run(input, {
12969
13032
  cancelSignal: signal,
12970
13033
  beforeCallModel: async () => {
13034
+ this.trimImages(session);
12971
13035
  await this.maybeAutoCompact(session);
12972
13036
  this.saveAgentState(session);
12973
13037
  }
@@ -13290,6 +13354,7 @@ ${q.text}` : q.text).join("\n\n");
13290
13354
  }
13291
13355
  if (session.messageQueue.length === 0) {
13292
13356
  if (session.selfKick && !session.interruptRequested) {
13357
+ this.sendInterimReport(session, batch);
13293
13358
  session.messageQueue.push({ text: CONTINUE_TASK_KICK });
13294
13359
  const taskId = session.parentId !== null ? session.id : void 0;
13295
13360
  this.emitEvent(session.id, { type: "user", text: CONTINUE_TASK_KICK, agentName: "user", report: true, taskId });
@@ -13302,6 +13367,41 @@ ${q.text}` : q.text).join("\n\n");
13302
13367
  }
13303
13368
  }
13304
13369
  }
13370
+ /** continue_task interim report. The answer turn that precedes a kick is
13371
+ * otherwise lost to whoever asked: the end-of-run reports (tryReportToParent /
13372
+ * deliverRelayReport) read only the LAST turn's output, and the kick turn
13373
+ * resets it. So when the batch just answered came from the party this session
13374
+ * owes a report — its parent (sub-agent) or a relay caller (root, relay-
13375
+ * prefixed message) — forward the answer now. Neither path touches the final
13376
+ * report's state: no stoppedAt / agent_done (parent's sibling-status still sees
13377
+ * this child running), reply_to kept. Goal workers are deliberately excluded
13378
+ * (G's messages carry neither mark): an interim would wake G mid-round, and a
13379
+ * fresh query_session from G would interrupt W again. */
13380
+ sendInterimReport(session, batch) {
13381
+ const fromParent = session.parentId !== null && batch.some((q) => q.sourceSessionId === session.parentId);
13382
+ const fromRelay = session.parentId === null && batch.some((q) => q.sourceSessionId === void 0 && q.text.includes(RELAY_CHANNEL_PREFIX));
13383
+ if (!fromParent && !fromRelay || session.turnError)
13384
+ return;
13385
+ const answer = session.finalOutput || session.output;
13386
+ if (!answer)
13387
+ return;
13388
+ const cap2 = config.limits.autoReportMax;
13389
+ const body = answer.length > cap2 ? answer.slice(0, cap2) + `
13390
+
13391
+ [Interim report truncated: ${answer.length} chars total.]` : answer;
13392
+ if (fromParent) {
13393
+ const text6 = `[Interim report: answered your message (below) and is now resuming its interrupted task \u2014 the final report follows when that is done. Not the final result.]
13394
+
13395
+ ${body}`;
13396
+ this.querySession(session.parentId, session.id, text6).catch((err) => {
13397
+ console.error(`[SessionManager] Interim report failed: ${session.id} \u2192 ${session.parentId}: ${err instanceof Error ? err.message : String(err)}`);
13398
+ });
13399
+ } else {
13400
+ deliverRelayInterim(this, session.id, body).catch((err) => {
13401
+ console.error(`[Relay] deliverRelayInterim failed for ${session.id}: ${err instanceof Error ? err.message : String(err)}`);
13402
+ });
13403
+ }
13404
+ }
13305
13405
  // ── Session operations ─────────────────────────────────────────────
13306
13406
  /**
13307
13407
  * UI-only repair for an aborted turn: emit a synthetic `tool_result` for
@@ -13645,6 +13745,17 @@ ${cleaned}` : cleaned;
13645
13745
  console.warn(`[Evo] pre-compact enqueue threw for ${session.id}: ${err instanceof Error ? err.message : String(err)}`);
13646
13746
  }
13647
13747
  }
13748
+ /** Byte-budget counterpart of maybeAutoCompact (which only sees tokens):
13749
+ * drop the oldest history images once their base64 outgrows the request
13750
+ * body limit. Same beforeCallModel hook, so the trimmed state is what the
13751
+ * hook's saveAgentState persists. */
13752
+ trimImages(session) {
13753
+ const replaced = trimHistoryImages(session.agent.messages);
13754
+ if (replaced === 0)
13755
+ return;
13756
+ console.warn(`[SessionManager] Session ${session.id} history images over budget \u2014 replaced ${replaced} oldest with placeholders`);
13757
+ this.emitEvent(session.id, { type: "system", text: `Removed ${replaced} older image(s) from context to keep the request under the size limit`, taskId: session.parentId ? session.id : void 0 });
13758
+ }
13648
13759
  /** Mid-turn auto-compact: invoked by agent-loop's beforeCallModel hook.
13649
13760
  * Runs once per loop iteration — without this, a single long turn that
13650
13761
  * accumulates many large tool results (file_read, grep) blows the window
@@ -31705,7 +31816,7 @@ var init_dist2 = __esm({
31705
31816
  init_git_ssh();
31706
31817
  __dirname3 = path68.dirname(fileURLToPath3(import.meta.url));
31707
31818
  PORT = config.server.port;
31708
- HALO_VERSION = "1.4.3";
31819
+ HALO_VERSION = "1.4.5";
31709
31820
  PROJECT_ROOT = findProjectRoot();
31710
31821
  HALO_HOME4 = path68.join(homedir24(), ".halo");
31711
31822
  GIT_SHA = readGitSha();
@@ -32644,7 +32755,7 @@ import path69 from "node:path";
32644
32755
  import fs63 from "node:fs";
32645
32756
  import { homedir as homedir25 } from "node:os";
32646
32757
  import { parseArgs as parseArgs2 } from "node:util";
32647
- var VERSION = "1.4.3";
32758
+ var VERSION = "1.4.5";
32648
32759
  var HELP_TOP = `Usage: halo <command> [options]
32649
32760
 
32650
32761
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",