@turmind/halo 1.4.4 → 1.4.6

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/README.md CHANGED
@@ -142,6 +142,7 @@ Configured per-agent through one provider-agnostic runtime. AWS Bedrock Claude i
142
142
  | Qwen (Aliyun) | |
143
143
  | Hunyuan (Tencent) | |
144
144
  | Doubao (Volcengine) | |
145
+ | Zhipu AI (GLM) | GLM-5.3 family; vision on the Flash models |
145
146
 
146
147
  ![Settings — all model providers, configurable per agent](assets/models.jpg)
147
148
 
@@ -1 +1 @@
1
- <!DOCTYPE html><!--pMc5AOPA8YmYr6tFONvcu--><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\":\"pMc5AOPA8YmYr6tFONvcu\",\"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><!--qYSILjqBjfgTggO0T8E7T--><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\":\"qYSILjqBjfgTggO0T8E7T\",\"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><!--pMc5AOPA8YmYr6tFONvcu--><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\":\"pMc5AOPA8YmYr6tFONvcu\",\"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><!--qYSILjqBjfgTggO0T8E7T--><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\":\"qYSILjqBjfgTggO0T8E7T\",\"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":"pMc5AOPA8YmYr6tFONvcu","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":"qYSILjqBjfgTggO0T8E7T","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.
@@ -300,7 +301,7 @@ A `toolUseId`-based algorithm that repairs message arrays damaged by abort / int
300
301
 
301
302
  **Partial batches land, only the cut tool is synthesized** (`agent-loop.ts`, the tool loop's `try/finally`): in a parallel `tool_use` batch the loop pushes the accumulated `tool_result` user message in a `finally`, so both interrupt exits — the loop's own cancel check (soft interrupt: the consumer aborts after a `tool_result`, the next tool sees the signal) and the consumer breaking its `for await` (hard interrupt: finishes the generator at the `yield` via `.return()`) — still land the results that finished. Before this, either exit skipped the push, so every completed result was lost and repair marked the **whole** batch `[interrupted]`; the model then re-ran work that had already happened, which for side-effecting calls (commit, append, send) is a double execution — and `continue_task`'s "re-issue interrupted calls" instruction made that hazard live. The tool the abort landed on is dropped on purpose (its result is a killed shell's partial output), so repair pairs exactly that id — and any never-started ones — with the do-not-retry marker.
302
303
 
303
- **Provider-side consumers**: the OpenAI-style agents (DeepSeek / Kimi / Doubao / Hunyuan / Mantle / generic OpenAI) convert a user turn's `tool_result` blocks into tool-role messages — any non-`tool_result` content coalesced into the same turn (e.g. the synthesized result landing alongside real user text, or a stop-fold) is emitted as a following user message rather than silently dropped.
304
+ **Provider-side consumers**: the OpenAI-style agents (DeepSeek / Kimi / Doubao / Hunyuan / Zhipu / Mantle / generic OpenAI) convert a user turn's `tool_result` blocks into tool-role messages — any non-`tool_result` content coalesced into the same turn (e.g. the synthesized result landing alongside real user text, or a stop-fold) is emitted as a following user message rather than silently dropped.
304
305
 
305
306
  ### UI-side interrupt marker (`markPendingToolCallsInterrupted`)
306
307
 
@@ -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 |
@@ -381,7 +382,7 @@ The transient-5xx branch is the one that all providers share, and getting the **
381
382
  **httpStatus extraction — three-step fallback** (top of the `catch` block):
382
383
 
383
384
  1. **AWS SDK structured field** — `err.$metadata.httpStatusCode`. Present on every Bedrock error.
384
- 2. **Regex parse of the message string** — for the nine fetch-based providers (anthropic / openai / deepseek / doubao / hunyuan / kimi / minimax / qwen / mantle), which throw plain string `Error`s with the status embedded. The patterns cover `API error <NNN>`, `] <NNN>`, and `status=<NNN>`.
385
+ 2. **Regex parse of the message string** — for the ten fetch-based providers (anthropic / openai / deepseek / doubao / hunyuan / kimi / minimax / qwen / zhipu / mantle), which throw plain string `Error`s with the status embedded. The patterns cover `API error <NNN>`, `] <NNN>`, and `status=<NNN>`.
385
386
  3. **`undefined`** — neither source yielded a status; the error falls through to the keyword/name-based branches instead.
386
387
 
387
388
  **Retry decision** — a failure is treated as a transient server-side error (→ retry with backoff) when **either**:
@@ -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)
@@ -25,6 +25,7 @@ Total effort: typically a few hours depending on how weird the upstream API is a
25
25
  | `qwen` | `QwenAgent` | `qwen-agent.ts` | Aliyun Bailian Anthropic-compatible API, thinking enable/disable both honored, prompt caching (5m only), vision on Plus, max no vision |
26
26
  | `hunyuan` | `HunyuanAgent` | `hunyuan-agent.ts` | Tencent Hy3 preview via OpenAI-compatible (tokenhub.tencentmaas.com), reasoning_effort low/high (no_think = omit), automatic prompt caching (cached_tokens), no vision |
27
27
  | `doubao` | `DoubaoAgent` | `doubao-agent.ts` | ByteDance Doubao Seed-2.0 (pro/lite/mini/code) on Volcengine Ark, OpenAI-compatible + `thinking:{type:'enabled'/'disabled'}` (no `auto`), automatic prompt caching, no vision |
28
+ | `zhipu` | `ZhipuAgent` | `zhipu-agent.ts` | Zhipu GLM-5.x on open.bigmodel.cn, OpenAI-compatible. Output cap via `max_tokens` only (`max_completion_tokens` is silently ignored). Thinking branched on modelId: **glm-5.3\*** always thinks — always sends `thinking:{type:'enabled',clear_thinking:false}`, `reasoning_effort` only when low/high/max (`disabled` / other efforts 400); other ids (glm-5.2) honour `thinking:{type:'disabled'}`. Vision per model: `glm-5.3-flash` / `glm-5.3-flashx` see `image_url` data URLs; `glm-5.3` / `glm-5.2` are text-only and 400 on any image part (code 1210, classified `multimodal_4xx` so leftover history images degrade). Automatic caching, `prompt_tokens_details.cached_tokens` (included in `prompt_tokens`, subtracted). |
28
29
  | `openai` | `OpenAIAgent` | `openai-agent.ts` | **Generic OpenAI-compatible**. Use for native OpenAI or any third-party OpenAI gateway (incl. Ollama / llama.cpp). User fills endpoint + model id; capabilities openly editable in the form. Forgiving about cache field names (`prompt_tokens_details.cached_tokens`, `prompt_cache_hit_tokens`, `cache_read_tokens` all read) and reasoning field names (`reasoning_content` for OpenAI o-series/DeepSeek, `reasoning` for Ollama/llama.cpp). |
29
30
  | `anthropic` | `AnthropicAgent` | `anthropic-agent.ts` | **Generic Anthropic Messages**. Use for api.anthropic.com or any Anthropic-compatible gateway. User fills endpoint + model id. Standard `cache_control: ephemeral` with optional `ttl: '1h'`. |
30
31
  | `mimo-token-plan-china` | `AnthropicAgent` | (reuses `anthropic-agent.ts`) | Xiaomi MiMo (UI name "Xiaomi MiMo"; the id keeps its historical name so existing configs don't break) — Anthropic-compatible gateway; no dedicated class, just a default endpoint in `model-runtime.ts` (`token-plan-cn.xiaomimimo.com/anthropic`). The key type picks the endpoint: Token Plan `tp-`/`ttp-` keys only work on `token-plan-{cn,sgp,ams}.xiaomimimo.com/anthropic`, pay-as-you-go `sk-` keys only on `api.xiaomimimo.com/anthropic` — all four ship as `endpointPresets` (pay-as-you-go live-probed 2026-09-24; sgp / ams from the docs only). V2.6 (`mimo-v2.6-pro` / `-flash`, both vision, 1M / 131072) is current; `mimo-v2.5(-pro)` kept in the template until the official 2026-10-21 deprecation, then drop |
@@ -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
@@ -1414,7 +1414,7 @@ function classifyKind(msg, errName, httpStatus) {
1414
1414
  if (msg.includes("reading 'role'") || msg.includes("reading 'content'") || msg.includes("failed to add message") || msg.includes("tool_use ids were found without tool_result") || msg.includes("unexpected `tool_use_id` found in `tool_result`")) {
1415
1415
  return "corrupted";
1416
1416
  }
1417
- if (httpStatus !== void 0 && httpStatus >= 400 && httpStatus < 500 && (msg.includes("Multimodal data is corrupted") || msg.includes("Could not process image"))) {
1417
+ if (httpStatus !== void 0 && httpStatus >= 400 && httpStatus < 500 && (msg.includes("Multimodal data is corrupted") || msg.includes("Could not process image") || msg.includes("\u56FE\u7247\u8F93\u5165\u683C\u5F0F/\u89E3\u6790\u9519\u8BEF") || msg.includes("\u53D6\u503C\u8303\u56F4 ['text']"))) {
1418
1418
  return "multimodal_4xx";
1419
1419
  }
1420
1420
  return "fatal";
@@ -2573,7 +2573,7 @@ var init_init = __esm({
2573
2573
  path8.resolve(__dirname, "..", "bundled-docs"),
2574
2574
  path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
2575
2575
  ];
2576
- TEMPLATE_VERSION = 66;
2576
+ TEMPLATE_VERSION = 67;
2577
2577
  VERSION_FILE = ".template-version";
2578
2578
  SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
2579
2579
  BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
@@ -5587,6 +5587,30 @@ ${body}`;
5587
5587
  await caller.sendUserMessage(to.sessionId, text6);
5588
5588
  console.debug(`[Relay] ${host.workspaceRoot}/${session.id} \u2192 ${to.workspace}/${to.sessionId}: ${body.slice(0, 120)}`);
5589
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
+ }
5590
5614
  function jsonErr2(error) {
5591
5615
  return JSON.stringify({ code: 1, error });
5592
5616
  }
@@ -5604,7 +5628,7 @@ function resolveTarget(registry2, workspace) {
5604
5628
  function buildRelayTools(host, callerSessionId) {
5605
5629
  const relaySend = {
5606
5630
  name: "relay_send",
5607
- 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.",
5608
5632
  inputSchema: {
5609
5633
  type: "object",
5610
5634
  properties: {
@@ -5658,7 +5682,7 @@ function buildRelayTools(host, callerSessionId) {
5658
5682
  await target.createSession(agentId, null, `Relay: ${params.message.slice(0, 60)}`, void 0, params.session_id);
5659
5683
  }
5660
5684
  writeReplyTo(target.getDb(), params.session_id, { workspace: host.workspaceRoot, sessionId: callerSessionId });
5661
- const prefixed = `[channel: relay | from: ${host.workspaceRoot}]
5685
+ const prefixed = `${RELAY_CHANNEL_PREFIX}${host.workspaceRoot}]
5662
5686
 
5663
5687
  ${params.message}`;
5664
5688
  target.appendUserMessage(params.session_id, params.message);
@@ -5755,13 +5779,14 @@ ${params.message}`;
5755
5779
  };
5756
5780
  return [relaySend, relayInterrupt, relayStop, relayRead, relayList];
5757
5781
  }
5758
- var _registry, WORKSPACE_SESSION_PROPS;
5782
+ var RELAY_CHANNEL_PREFIX, _registry, WORKSPACE_SESSION_PROPS;
5759
5783
  var init_relay = __esm({
5760
5784
  "../server/dist/agents/relay.js"() {
5761
5785
  "use strict";
5762
5786
  init_schema();
5763
5787
  init_config();
5764
5788
  init_commands2();
5789
+ RELAY_CHANNEL_PREFIX = "[channel: relay | from: ";
5765
5790
  _registry = null;
5766
5791
  WORKSPACE_SESSION_PROPS = {
5767
5792
  workspace: { type: "string", description: "Absolute path of the target workspace on this server." },
@@ -8850,7 +8875,7 @@ var init_doubao_agent = __esm({
8850
8875
  }
8851
8876
  });
8852
8877
 
8853
- // ../server/dist/agents/openai-agent.js
8878
+ // ../server/dist/agents/zhipu-agent.js
8854
8879
  function safeParse5(json) {
8855
8880
  try {
8856
8881
  return JSON.parse(json || "{}");
@@ -8858,6 +8883,217 @@ function safeParse5(json) {
8858
8883
  return {};
8859
8884
  }
8860
8885
  }
8886
+ var EFFORTS, ZhipuAgent;
8887
+ var init_zhipu_agent = __esm({
8888
+ "../server/dist/agents/zhipu-agent.js"() {
8889
+ "use strict";
8890
+ init_config();
8891
+ init_agent_loop();
8892
+ EFFORTS = /* @__PURE__ */ new Set(["low", "high", "max"]);
8893
+ ZhipuAgent = class extends AgentLoop {
8894
+ config;
8895
+ constructor(config2) {
8896
+ super(config2.tools);
8897
+ this.config = config2;
8898
+ }
8899
+ async callModel(signal) {
8900
+ const url = this.config.endpoint.replace(/\/+$/, "") + "/chat/completions";
8901
+ const startTime = Date.now();
8902
+ const messages2 = this.buildMessages();
8903
+ const tools = this.buildTools();
8904
+ const body = {
8905
+ model: this.config.modelId,
8906
+ messages: messages2,
8907
+ stream: false,
8908
+ max_tokens: this.config.maxTokens ?? resolveMaxOutputTokens(this.config.modelId),
8909
+ ...tools.length > 0 ? { tools } : {}
8910
+ };
8911
+ const effort = this.config.thinking?.effort;
8912
+ if (this.config.modelId.startsWith("glm-5.3")) {
8913
+ body.thinking = { type: "enabled", clear_thinking: false };
8914
+ if (effort && EFFORTS.has(effort))
8915
+ body.reasoning_effort = effort;
8916
+ } else if (this.config.thinking?.enabled && effort !== "disabled") {
8917
+ body.thinking = { type: "enabled", clear_thinking: false };
8918
+ if (effort && EFFORTS.has(effort))
8919
+ body.reasoning_effort = effort;
8920
+ } else {
8921
+ body.thinking = { type: "disabled" };
8922
+ }
8923
+ const response = await fetch(url, {
8924
+ method: "POST",
8925
+ headers: {
8926
+ "Content-Type": "application/json",
8927
+ "Authorization": `Bearer ${this.config.apiKey}`
8928
+ },
8929
+ body: JSON.stringify(body),
8930
+ signal
8931
+ });
8932
+ if (!response.ok) {
8933
+ const errText2 = await response.text().catch(() => "");
8934
+ throw new Error(`[ZhipuAgent] API error ${response.status}: ${errText2}`);
8935
+ }
8936
+ const data = await response.json();
8937
+ const choices = data.choices;
8938
+ const choice = choices?.[0];
8939
+ const msg = choice?.message;
8940
+ const finishReason = choice?.finish_reason;
8941
+ let text6 = "";
8942
+ let thinking = "";
8943
+ const toolCalls = [];
8944
+ const assistantBlocks = [];
8945
+ if (msg) {
8946
+ if (msg.reasoning_content && typeof msg.reasoning_content === "string") {
8947
+ thinking = msg.reasoning_content;
8948
+ }
8949
+ if (msg.content && typeof msg.content === "string") {
8950
+ text6 = msg.content;
8951
+ }
8952
+ const rawToolCalls = msg.tool_calls;
8953
+ if (rawToolCalls) {
8954
+ for (const tc of rawToolCalls) {
8955
+ const fn = tc.function;
8956
+ const id = tc.id;
8957
+ const name = fn?.name ?? "";
8958
+ const args = fn?.arguments ?? "{}";
8959
+ const input = safeParse5(args);
8960
+ toolCalls.push({ id, name, input });
8961
+ assistantBlocks.push({ type: "tool_use", id, name, input });
8962
+ }
8963
+ }
8964
+ }
8965
+ if (thinking) {
8966
+ assistantBlocks.unshift({ type: "thinking", thinking });
8967
+ }
8968
+ if (text6) {
8969
+ assistantBlocks.push({ type: "text", text: text6 });
8970
+ }
8971
+ const stopReason = finishReason === "tool_calls" ? "tool_use" : finishReason === "length" ? "max_tokens" : "end_turn";
8972
+ const usage = data.usage;
8973
+ const promptTokens = usage?.prompt_tokens ?? 0;
8974
+ const outputTokens = usage?.completion_tokens ?? 0;
8975
+ const details = usage?.prompt_tokens_details;
8976
+ const cachedTokens = details?.cached_tokens ?? 0;
8977
+ const inputTokens = promptTokens - cachedTokens;
8978
+ return {
8979
+ assistantBlocks,
8980
+ stopReason,
8981
+ text: text6,
8982
+ thinking,
8983
+ toolCalls,
8984
+ usage: {
8985
+ inputTokens,
8986
+ outputTokens,
8987
+ totalTokens: inputTokens + outputTokens,
8988
+ ...cachedTokens ? { cacheReadInputTokens: cachedTokens } : {}
8989
+ },
8990
+ durationMs: Date.now() - startTime
8991
+ };
8992
+ }
8993
+ buildMessages() {
8994
+ const msgs = [];
8995
+ msgs.push({ role: "system", content: this.config.systemPrompt });
8996
+ for (const msg of this.messages) {
8997
+ if (msg.role === "user") {
8998
+ if (typeof msg.content !== "string" && msg.content.some((b) => b.type === "tool_result")) {
8999
+ msgs.push(...this.convertToolResults(msg.content));
9000
+ const rest = [...toolResultImages(msg.content), ...msg.content.filter((b) => b.type !== "tool_result")];
9001
+ if (rest.length > 0) {
9002
+ msgs.push({ role: "user", content: this.convertUserContent(rest) });
9003
+ }
9004
+ } else {
9005
+ msgs.push({ role: "user", content: this.convertUserContent(msg.content) });
9006
+ }
9007
+ } else {
9008
+ msgs.push(...this.convertAssistantMessage(msg));
9009
+ }
9010
+ }
9011
+ return msgs;
9012
+ }
9013
+ convertToolResults(content) {
9014
+ const results = [];
9015
+ for (const block of content) {
9016
+ if (block.type === "tool_result") {
9017
+ const text6 = typeof block.content === "string" ? block.content : block.content.map((b) => b.type === "text" ? b.text : "[image: in the next user message]").join("\n");
9018
+ results.push({
9019
+ role: "tool",
9020
+ tool_call_id: block.tool_use_id,
9021
+ content: text6
9022
+ });
9023
+ }
9024
+ }
9025
+ return results;
9026
+ }
9027
+ convertUserContent(content) {
9028
+ if (typeof content === "string")
9029
+ return content;
9030
+ const parts = [];
9031
+ for (const block of content) {
9032
+ if (block.type === "text") {
9033
+ parts.push({ type: "text", text: block.text });
9034
+ } else if (block.type === "image") {
9035
+ parts.push({
9036
+ type: "image_url",
9037
+ image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
9038
+ });
9039
+ }
9040
+ }
9041
+ return parts;
9042
+ }
9043
+ convertAssistantMessage(msg) {
9044
+ const results = [];
9045
+ if (typeof msg.content === "string") {
9046
+ results.push({ role: "assistant", content: msg.content });
9047
+ return results;
9048
+ }
9049
+ const textParts = [];
9050
+ const toolCalls = [];
9051
+ let reasoningContent = "";
9052
+ for (const block of msg.content) {
9053
+ if (block.type === "text") {
9054
+ textParts.push(block.text);
9055
+ } else if (block.type === "thinking") {
9056
+ reasoningContent = block.thinking ?? "";
9057
+ } else if (block.type === "tool_use") {
9058
+ toolCalls.push({
9059
+ id: block.id,
9060
+ type: "function",
9061
+ function: { name: block.name, arguments: JSON.stringify(block.input) }
9062
+ });
9063
+ }
9064
+ }
9065
+ if (textParts.length > 0 || toolCalls.length > 0 || reasoningContent) {
9066
+ const assistantMsg = { role: "assistant", content: textParts.join("") || null };
9067
+ if (reasoningContent)
9068
+ assistantMsg.reasoning_content = reasoningContent;
9069
+ if (toolCalls.length > 0)
9070
+ assistantMsg.tool_calls = toolCalls;
9071
+ results.push(assistantMsg);
9072
+ }
9073
+ return results;
9074
+ }
9075
+ buildTools() {
9076
+ return this.config.tools.map((t2) => ({
9077
+ type: "function",
9078
+ function: {
9079
+ name: t2.name,
9080
+ description: t2.description,
9081
+ parameters: t2.inputSchema
9082
+ }
9083
+ }));
9084
+ }
9085
+ };
9086
+ }
9087
+ });
9088
+
9089
+ // ../server/dist/agents/openai-agent.js
9090
+ function safeParse6(json) {
9091
+ try {
9092
+ return JSON.parse(json || "{}");
9093
+ } catch {
9094
+ return {};
9095
+ }
9096
+ }
8861
9097
  var OpenAIAgent;
8862
9098
  var init_openai_agent = __esm({
8863
9099
  "../server/dist/agents/openai-agent.js"() {
@@ -8922,7 +9158,7 @@ var init_openai_agent = __esm({
8922
9158
  const id = tc.id;
8923
9159
  const name = fn?.name ?? "";
8924
9160
  const args = fn?.arguments ?? "{}";
8925
- const input = safeParse5(args);
9161
+ const input = safeParse6(args);
8926
9162
  toolCalls.push({ id, name, input });
8927
9163
  assistantBlocks.push({ type: "tool_use", id, name, input });
8928
9164
  }
@@ -9050,7 +9286,7 @@ function extractRegionFromHost(hostname) {
9050
9286
  const m = hostname.match(/^bedrock-(?:mantle|runtime)\.([a-z0-9-]+)\./);
9051
9287
  return m?.[1] ?? "us-east-1";
9052
9288
  }
9053
- function safeParse6(json) {
9289
+ function safeParse7(json) {
9054
9290
  try {
9055
9291
  return JSON.parse(json || "{}");
9056
9292
  } catch {
@@ -9123,7 +9359,7 @@ var init_mantle_agent = __esm({
9123
9359
  const callId = item.call_id ?? item.id ?? "";
9124
9360
  const name = item.name ?? "";
9125
9361
  const args = item.arguments ?? "{}";
9126
- const input2 = safeParse6(args);
9362
+ const input2 = safeParse7(args);
9127
9363
  toolCalls.push({ id: callId, name, input: input2 });
9128
9364
  assistantBlocks.push({ type: "tool_use", id: callId, name, input: input2 });
9129
9365
  }
@@ -9489,6 +9725,16 @@ function createModelRuntime(providerId, cfg) {
9489
9725
  maxTokens: cfg.maxTokens,
9490
9726
  thinking: cfg.thinking
9491
9727
  });
9728
+ case "zhipu":
9729
+ return new ZhipuAgent({
9730
+ modelId: cfg.modelId,
9731
+ endpoint: cfg.endpoint ?? "https://open.bigmodel.cn/api/paas/v4",
9732
+ apiKey: cfg.apiKey ?? "",
9733
+ systemPrompt: cfg.systemPrompt,
9734
+ tools: cfg.tools,
9735
+ maxTokens: cfg.maxTokens,
9736
+ thinking: cfg.thinking
9737
+ });
9492
9738
  case "openai":
9493
9739
  return new OpenAIAgent({
9494
9740
  modelId: cfg.modelId,
@@ -9556,6 +9802,7 @@ var init_model_runtime = __esm({
9556
9802
  init_qwen_agent();
9557
9803
  init_hunyuan_agent();
9558
9804
  init_doubao_agent();
9805
+ init_zhipu_agent();
9559
9806
  init_openai_agent();
9560
9807
  init_mantle_agent();
9561
9808
  init_anthropic_agent();
@@ -11894,7 +12141,7 @@ async function initObservability() {
11894
12141
  return true;
11895
12142
  }
11896
12143
  const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
11897
- providers = registerSdk2(process.env.OTEL_SERVICE_NAME, "1.4.4");
12144
+ providers = registerSdk2(process.env.OTEL_SERVICE_NAME, "1.4.6");
11898
12145
  console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, capture_content=${config.observability.captureContent})`);
11899
12146
  return true;
11900
12147
  }
@@ -13329,6 +13576,7 @@ ${q.text}` : q.text).join("\n\n");
13329
13576
  }
13330
13577
  if (session.messageQueue.length === 0) {
13331
13578
  if (session.selfKick && !session.interruptRequested) {
13579
+ this.sendInterimReport(session, batch);
13332
13580
  session.messageQueue.push({ text: CONTINUE_TASK_KICK });
13333
13581
  const taskId = session.parentId !== null ? session.id : void 0;
13334
13582
  this.emitEvent(session.id, { type: "user", text: CONTINUE_TASK_KICK, agentName: "user", report: true, taskId });
@@ -13341,6 +13589,41 @@ ${q.text}` : q.text).join("\n\n");
13341
13589
  }
13342
13590
  }
13343
13591
  }
13592
+ /** continue_task interim report. The answer turn that precedes a kick is
13593
+ * otherwise lost to whoever asked: the end-of-run reports (tryReportToParent /
13594
+ * deliverRelayReport) read only the LAST turn's output, and the kick turn
13595
+ * resets it. So when the batch just answered came from the party this session
13596
+ * owes a report — its parent (sub-agent) or a relay caller (root, relay-
13597
+ * prefixed message) — forward the answer now. Neither path touches the final
13598
+ * report's state: no stoppedAt / agent_done (parent's sibling-status still sees
13599
+ * this child running), reply_to kept. Goal workers are deliberately excluded
13600
+ * (G's messages carry neither mark): an interim would wake G mid-round, and a
13601
+ * fresh query_session from G would interrupt W again. */
13602
+ sendInterimReport(session, batch) {
13603
+ const fromParent = session.parentId !== null && batch.some((q) => q.sourceSessionId === session.parentId);
13604
+ const fromRelay = session.parentId === null && batch.some((q) => q.sourceSessionId === void 0 && q.text.includes(RELAY_CHANNEL_PREFIX));
13605
+ if (!fromParent && !fromRelay || session.turnError)
13606
+ return;
13607
+ const answer = session.finalOutput || session.output;
13608
+ if (!answer)
13609
+ return;
13610
+ const cap2 = config.limits.autoReportMax;
13611
+ const body = answer.length > cap2 ? answer.slice(0, cap2) + `
13612
+
13613
+ [Interim report truncated: ${answer.length} chars total.]` : answer;
13614
+ if (fromParent) {
13615
+ 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.]
13616
+
13617
+ ${body}`;
13618
+ this.querySession(session.parentId, session.id, text6).catch((err) => {
13619
+ console.error(`[SessionManager] Interim report failed: ${session.id} \u2192 ${session.parentId}: ${err instanceof Error ? err.message : String(err)}`);
13620
+ });
13621
+ } else {
13622
+ deliverRelayInterim(this, session.id, body).catch((err) => {
13623
+ console.error(`[Relay] deliverRelayInterim failed for ${session.id}: ${err instanceof Error ? err.message : String(err)}`);
13624
+ });
13625
+ }
13626
+ }
13344
13627
  // ── Session operations ─────────────────────────────────────────────
13345
13628
  /**
13346
13629
  * UI-only repair for an aborted turn: emit a synthetic `tool_result` for
@@ -31755,7 +32038,7 @@ var init_dist2 = __esm({
31755
32038
  init_git_ssh();
31756
32039
  __dirname3 = path68.dirname(fileURLToPath3(import.meta.url));
31757
32040
  PORT = config.server.port;
31758
- HALO_VERSION = "1.4.4";
32041
+ HALO_VERSION = "1.4.6";
31759
32042
  PROJECT_ROOT = findProjectRoot();
31760
32043
  HALO_HOME4 = path68.join(homedir24(), ".halo");
31761
32044
  GIT_SHA = readGitSha();
@@ -32694,7 +32977,7 @@ import path69 from "node:path";
32694
32977
  import fs63 from "node:fs";
32695
32978
  import { homedir as homedir25 } from "node:os";
32696
32979
  import { parseArgs as parseArgs2 } from "node:util";
32697
- var VERSION = "1.4.4";
32980
+ var VERSION = "1.4.6";
32698
32981
  var HELP_TOP = `Usage: halo <command> [options]
32699
32982
 
32700
32983
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,115 @@
1
+ id: zhipu
2
+ displayName: Zhipu AI (GLM)
3
+ displayName_zh: 智谱 AI(GLM)
4
+ description: Zhipu GLM-5.x family on open.bigmodel.cn, OpenAI-compatible chat completions
5
+ description_zh: 智谱 GLM-5.x 系列(open.bigmodel.cn),OpenAI 兼容协议
6
+ defaultEndpoint: https://open.bigmodel.cn/api/paas/v4
7
+ defaultModelId: glm-5.3
8
+
9
+ secrets:
10
+ - key: api_key
11
+ description: Zhipu AI API Key. Get from https://bigmodel.cn/usercenter/proj-mgmt/apikeys
12
+ description_zh: 智谱 API Key。从 https://bigmodel.cn/usercenter/proj-mgmt/apikeys 获取
13
+ default: <<ZHIPU_API_KEY>>
14
+ secret: true
15
+
16
+ # Capabilities verified via live probes (2026-09-28):
17
+ # - Output cap: only `max_tokens` is honoured — `max_completion_tokens` is
18
+ # silently ignored (asked for 60, got 553). zhipu-agent sends max_tokens.
19
+ # - Vision is per model: glm-5.3-flash / glm-5.3-flashx read a text-bearing
20
+ # PNG / JPEG / GIF / WEBP correctly, both as image_url in a user message
21
+ # (the path tool-result images take) and inside a tool message.
22
+ # glm-5.3 / glm-5.2 are text-only and 400 on any image_url part
23
+ # (code 1210 "messages.content.type 参数非法,取值范围 ['text']").
24
+ # - Thinking: glm-5.3* always thinks — `thinking:{type:'disabled'}` and
25
+ # reasoning_effort outside { low, high, max } 400 ("该模型始终思考").
26
+ # glm-5.2 honours `disabled`. reasoning_content comes back on the
27
+ # message and is replayed verbatim with `clear_thinking:false`
28
+ # (preserved thinking, the doc's recommendation for agent loops).
29
+ # - Caching is automatic; hits show up in prompt_tokens_details.cached_tokens
30
+ # (already included in prompt_tokens) from turn 2 on the 5.3 family;
31
+ # glm-5.2 warms up a turn later.
32
+ # - Over-long input on older ids is truncated server-side instead of
33
+ # erroring — keep contextWindow accurate so compaction fires in time.
34
+ models:
35
+ - id: glm-5.3
36
+ displayName: GLM-5.3
37
+ maxOutputTokens: 131072
38
+ contextWindow: 1000000
39
+ capabilities:
40
+ image: false
41
+ video: false
42
+ audio: false
43
+ promptCaching:
44
+ default: 5m
45
+ ttlPresets:
46
+ - { value: 5m, label: 5min }
47
+ thinking:
48
+ # Always on server-side — no off switch; effort enum is low/high/max.
49
+ defaultEnabled: true
50
+ default: high
51
+ effortPresets:
52
+ - { value: low, label: Low }
53
+ - { value: high, label: High }
54
+ - { value: max, label: Max }
55
+
56
+ - id: glm-5.3-flash
57
+ displayName: GLM-5.3 Flash
58
+ maxOutputTokens: 131072
59
+ contextWindow: 1000000
60
+ capabilities:
61
+ image: true
62
+ video: false
63
+ audio: false
64
+ promptCaching:
65
+ default: 5m
66
+ ttlPresets:
67
+ - { value: 5m, label: 5min }
68
+ thinking:
69
+ defaultEnabled: true
70
+ default: high
71
+ effortPresets:
72
+ - { value: low, label: Low }
73
+ - { value: high, label: High }
74
+ - { value: max, label: Max }
75
+
76
+ - id: glm-5.3-flashx
77
+ displayName: GLM-5.3 FlashX
78
+ maxOutputTokens: 131072
79
+ contextWindow: 1000000
80
+ capabilities:
81
+ image: true
82
+ video: false
83
+ audio: false
84
+ promptCaching:
85
+ default: 5m
86
+ ttlPresets:
87
+ - { value: 5m, label: 5min }
88
+ thinking:
89
+ defaultEnabled: true
90
+ default: high
91
+ effortPresets:
92
+ - { value: low, label: Low }
93
+ - { value: high, label: High }
94
+ - { value: max, label: Max }
95
+
96
+ - id: glm-5.2
97
+ displayName: GLM-5.2
98
+ maxOutputTokens: 131072
99
+ contextWindow: 1000000
100
+ capabilities:
101
+ image: false
102
+ video: false
103
+ audio: false
104
+ promptCaching:
105
+ default: 5m
106
+ ttlPresets:
107
+ - { value: 5m, label: 5min }
108
+ thinking:
109
+ # Can be switched off (thinking:{type:'disabled'}).
110
+ defaultEnabled: true
111
+ default: high
112
+ effortPresets:
113
+ - { value: low, label: Low }
114
+ - { value: high, label: High }
115
+ - { value: max, label: Max }