@turmind/halo 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- <!DOCTYPE html><!--snq8MT7CRlizWQcyu_Kt4--><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/7b736202f0b1dbfd.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/e890aa0d13e94290.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-74f577c1b094f942.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-cb287787a4aca288.js" async=""></script><script src="/_next/static/chunks/app/layout-ea161287d934ba96.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_d8ebd1 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-74f577c1b094f942.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-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.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/7b736202f0b1dbfd.css\",\"style\"]\n:HL[\"/_next/static/css/e890aa0d13e94290.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"snq8MT7CRlizWQcyu-Kt4\",\"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/7b736202f0b1dbfd.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"link\",\"1\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/e890aa0d13e94290.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_d8ebd1 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><!--52ayDC9phpvb_95S7qHHq--><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/7b736202f0b1dbfd.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/e890aa0d13e94290.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-74f577c1b094f942.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-cb287787a4aca288.js" async=""></script><script src="/_next/static/chunks/app/layout-ea161287d934ba96.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_d8ebd1 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-74f577c1b094f942.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-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.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/7b736202f0b1dbfd.css\",\"style\"]\n:HL[\"/_next/static/css/e890aa0d13e94290.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"52ayDC9phpvb-95S7qHHq\",\"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/7b736202f0b1dbfd.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"link\",\"1\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/e890aa0d13e94290.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_d8ebd1 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><!--snq8MT7CRlizWQcyu_Kt4--><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/7b736202f0b1dbfd.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/e890aa0d13e94290.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-74f577c1b094f942.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-cb287787a4aca288.js" async=""></script><script src="/_next/static/chunks/app/layout-ea161287d934ba96.js" async=""></script><script src="/_next/static/chunks/1d2d5650-d8d130c85727fdbb.js" async=""></script><script src="/_next/static/chunks/87-6a2716c98a819c4e.js" async=""></script><script src="/_next/static/chunks/app/page-c9fb252d0cf34c20.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_d8ebd1 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-74f577c1b094f942.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-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[5444,[],\"ClientPageRoot\"]\n7:I[3124,[\"560\",\"static/chunks/1d2d5650-d8d130c85727fdbb.js\",\"87\",\"static/chunks/87-6a2716c98a819c4e.js\",\"989\",\"static/chunks/989-cb287787a4aca288.js\",\"974\",\"static/chunks/app/page-c9fb252d0cf34c20.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/7b736202f0b1dbfd.css\",\"style\"]\n:HL[\"/_next/static/css/e890aa0d13e94290.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"snq8MT7CRlizWQcyu-Kt4\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/7b736202f0b1dbfd.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"link\",\"1\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/e890aa0d13e94290.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_d8ebd1 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><!--52ayDC9phpvb_95S7qHHq--><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/7b736202f0b1dbfd.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/e890aa0d13e94290.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-74f577c1b094f942.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-cb287787a4aca288.js" async=""></script><script src="/_next/static/chunks/app/layout-ea161287d934ba96.js" async=""></script><script src="/_next/static/chunks/1d2d5650-d8d130c85727fdbb.js" async=""></script><script src="/_next/static/chunks/87-6a2716c98a819c4e.js" async=""></script><script src="/_next/static/chunks/app/page-c9fb252d0cf34c20.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_d8ebd1 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-74f577c1b094f942.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-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-cb287787a4aca288.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[5444,[],\"ClientPageRoot\"]\n7:I[3124,[\"560\",\"static/chunks/1d2d5650-d8d130c85727fdbb.js\",\"87\",\"static/chunks/87-6a2716c98a819c4e.js\",\"989\",\"static/chunks/989-cb287787a4aca288.js\",\"974\",\"static/chunks/app/page-c9fb252d0cf34c20.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/7b736202f0b1dbfd.css\",\"style\"]\n:HL[\"/_next/static/css/e890aa0d13e94290.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"52ayDC9phpvb-95S7qHHq\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/7b736202f0b1dbfd.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"link\",\"1\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/e890aa0d13e94290.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_d8ebd1 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>
@@ -15,7 +15,7 @@ e:I[23632,[],"ViewportBoundary"]
15
15
  :HL["/_next/static/css/7b736202f0b1dbfd.css","style"]
16
16
  :HL["/_next/static/css/e890aa0d13e94290.css","style"]
17
17
  :HL["/_next/static/css/659eccb5db697b76.css","style"]
18
- 0:{"P":null,"b":"snq8MT7CRlizWQcyu-Kt4","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7b736202f0b1dbfd.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/e890aa0d13e94290.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_d8ebd1 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
+ 0:{"P":null,"b":"52ayDC9phpvb-95S7qHHq","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7b736202f0b1dbfd.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/e890aa0d13e94290.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_d8ebd1 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}
19
19
  8:{}
20
20
  9:"$0:f:0:1:2:children:1:props:children:0:props:params"
21
21
  f:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
@@ -159,7 +159,7 @@ File: `agents/session-skill-commands.ts`. Stateless, pure reads. Resolves which
159
159
 
160
160
  ### SessionStateStore — rawMessages disk persistence
161
161
 
162
- File: `agents/session-state-store.ts`. Stateless. Saves/loads an agent's `rawMessages` (LLM-facing history) to its `.json` file via read-merge-write — the `rawMessages` half of session persistence (SessionUIStore owns the UI-log half; both write the same file). `saveAgentState` takes a narrow `SavableSession` (8 fields), not the full AgentSession. Host surface: workspaceRoot + `isSessionDeleted` (the tombstone short-circuit so a late save can't resurrect a deleted file).
162
+ File: `agents/session-state-store.ts`. Stateless (the writer's append baseline lives in `session-log.ts`). Saves/loads an agent's `rawMessages` (LLM-facing history) to its `<seg>.raw.jsonl`, one message per line — a save appends the new messages, rewrites the last line in place when only it changed, and does an atomic full rewrite for anything else (compaction, repair, deleteExchange) — plus the row's `output` / `lastActivityAt` (throttled on the per-event save points). It is the raw half of session persistence; SessionUIStore owns the UI-log half in its own `<seg>.ui.jsonl`, so neither rewrites the other. `saveAgentState` takes a narrow `SavableSession` (5 fields), not the full AgentSession. Host surface: workspaceRoot + `isSessionDeleted` (the tombstone short-circuit so a late save can't resurrect a deleted file) + `metaDb` (the db holding the session's row).
163
163
 
164
164
  ## Helper modules
165
165
 
@@ -63,7 +63,7 @@ bindOrCreateSession → createSession(...) → subscribeSession(client, sm, sid)
63
63
  Nothing is lost without a listener: `SessionUIStore.emitEvent` folds every event into the root's `UIState` (`reduceIntoUIState → applyEvent`) and persists the log it changed **before** it looks up listeners. Listeners are a pure fan-out for live UI; the persistence path is independent of them.
64
64
 
65
65
  - **Which file**: `applyEvent` returns the log it touched. A root event writes the root's session file; a sub-agent event (`taskId` set) writes only that sub-session's **own** file from `subSessionLogs[taskId]` — the root's file isn't rewritten for sub-agent traffic. The sub log is dropped from memory at its `agent_done` (its file is complete by then).
66
- - **When**: `applyEvent`'s `persist` hint — `'flush'` writes now (root `complete`, a sub's `agent_done`), `'debounce'` coalesces within 500 ms (tool calls / results, usage, …), absent = no write (the next write carries the change). Timers are keyed by the log's own id, so root and sub writes don't cancel each other.
66
+ - **When**: `applyEvent`'s `persist` hint — `'flush'` writes now (`tool_call`, `tool_result`, `usage`, root `complete`, a sub's `agent_done`, `error`, `call_discarded`), `'debounce'` coalesces within 500 ms (`user` / `system` / `context` rows), absent = no write (the next write carries the change). Timers are keyed by the log's own id, so root and sub writes don't cancel each other.
67
67
  - **Dirty tracking**: a root event marks the root `uiStateDirty` until `persistLog` lands it. `isUIStateDirty(rootId)` is what the WS saves below gate on — a state only seeded from disk (viewing a session another process, e.g. a cron `halo cli` child, is driving) is clean and is never written back, so it can't overwrite that process's newer file.
68
68
 
69
69
  ### When save fires
@@ -73,7 +73,7 @@ Background state persists in four scenarios:
73
73
  1. **SessionUIStore's own persist** — the per-event rule above; this is what keeps a background session's files current
74
74
  2. **`unsubscribeSession`** — a session leaves the connection's set (`unsubscribe`, or a workspace switch dropping every subscription); `saveSession` runs before the listener is released. It returns early unless `isUIStateDirty`
75
75
  3. **Disconnect** — WS closes (or errors): every subscription without active work in its tree is saved and released (same dirty gate); the rest detach (below)
76
- 4. **Server shutdown** — `landForShutdown()` (SessionManager; its UI half is SessionUIStore `flushAll()`) lands every loaded tree: a dirty root plus every live sub log, since pending debounce timers would never fire
76
+ 4. **Server shutdown** — `landForShutdown()` (SessionManager). Its UI half, SessionUIStore `flushAll()`, lands every loaded tree: a dirty root plus every live sub log, since pending debounce timers would never fire. Its raw log + row half covers only the sessions this process is running (`promise !== null`, subs too) — an idle in-memory copy was saved on the spot and may be stale ([session.md](session.md#session-files))
77
77
 
78
78
  Until 1.5.3-alpha, scenarios 2 and 3 went through `client.backgroundSaves`: the save closures `session:clear` registered, run on the next subscribe to that session or flushed on disconnect.
79
79
 
@@ -29,8 +29,8 @@ Schedules are **durable** — the source of truth is the `cron_jobs` table; in-m
29
29
 
30
30
  The runner arms its own `setTimeout` toward each target in hops of at most 30 s; a hop that wakes early re-arms toward the **same** target, and the next target is computed (`nextRun(now)`) only after a fire — a sleep / clock jump over several fires runs once, no catch-up. Why not croner's own timers: in croner 10.0.1 a timer waking a fraction of a ms early re-plans from a fresh clock read, and when the clock crosses the fire instant mid-re-plan, an instance that has never fired (every job right after a restart, every one-shot) silently skips that fire — ~10% of first fires after a restart in production; the upstream fix is only in 11.0.0-dev (`test/cron-fire-race.test.ts`).
31
31
 
32
- - **Recurring**: standard cron expression (`job.schedule`), optional `timezone`, optional **active window** `[activeFrom, activeUntil)` (epoch ms, `active_from` / `active_until`, NULL = unbounded; `CRON_MIGRATIONS` slot 2). The window is passed to croner as native `startAt` / `stopAt` (`activeWindowOptions` in `runner.ts`; `startAt` is `activeFrom − 1 ms` because croner's startAt is exclusive), so fires outside it simply never happen — no `cron_runs` row. "Pause until X" = push `activeFrom` to X; there is no skip-list for a gap in the middle. Past `activeUntil` the job stays `enabled` (so it can be extended later) but `scheduleJob` creates no croner and logs `past activeUntil; not scheduled` once — the id goes into `_expired` so reconcile's "fingerprint cached but not in `_active`" retry branch treats it as handled instead of re-scheduling (and re-logging) every 10s. The REST `nextRunAt` uses the same options, so the list shows the first post-pause fire; the admin badges a job "active from …" / "ended". Run-now ignores the window. Recurring only: a window together with `runAt` is rejected at every write surface (REST on the merged row, skill helper).
33
- - **One-shot**: `job.runAt` (epoch ms) — the runner's timer fires once at that instant (no croner). After it completes, `finalize` sets `enabled=0` so it never re-fires. A `runAt` already in the past at schedule time is marked `lastRunStatus='missed'` and disabled (rather than firing immediately or retrying every reconcile). The `run_at` column is `CRON_MIGRATIONS` slot 0 in `db/cron-db.ts`, run through the shared `runMigrations` / `PRAGMA user_version` mechanism (see [storage.md](storage.md#schema-change-rules)).
32
+ - **Recurring**: standard cron expression (`job.schedule`), optional `timezone`, optional **active window** `[activeFrom, activeUntil)` (epoch ms, `active_from` / `active_until`, NULL = unbounded; `CRON_MIGRATIONS` slot 2). The window is passed to croner as native `startAt` / `stopAt` (`activeWindowOptions` in `runner.ts`; `startAt` is `activeFrom − 1 ms` because croner's startAt is exclusive), so fires outside it simply never happen — no `cron_runs` row. "Pause until X" = push `activeFrom` to X; there is no skip-list for a gap in the middle. Past `activeUntil` the job stays `enabled` (so it can be extended later) but `scheduleJob` creates no croner and logs `past activeUntil; not scheduled` once — the id goes into `_expired` so reconcile's "fingerprint cached but not in `_active`" retry branch treats it as handled instead of re-scheduling (and re-logging) every 10s. A schedule or timezone croner rejects is handled the same way: one `schedule invalid` warn per fingerprint (the timezone in it), retried once the row changes. REST validates the expression but not the timezone, and croner throws on a bad timezone only at `nextRun()`, so either can reach this from REST or an out-of-band write. The REST `nextRunAt` uses the same options, so the list shows the first post-pause fire; the admin badges a job "active from …" / "ended". Run-now ignores the window. Recurring only: a window together with `runAt` is rejected at every write surface (REST on the merged row, skill helper).
33
+ - **One-shot**: `job.runAt` (epoch ms) — the runner's timer fires once at that instant (no croner). After it completes, `finalize` sets `enabled=0` so it never re-fires. A `runAt` already in the past at schedule time is marked `lastRunStatus='missed'` and disabled (rather than firing immediately or retrying every reconcile) — unless its own run is still in flight (`_inflight`, e.g. `POST /api/cron/reload` mid-run): `finalize` then records the real status. At boot, the orphan sweep already finalized a one-shot whose run it found (`enabled=0`, `failed`), so the reload never sees it as missed. The `run_at` column is `CRON_MIGRATIONS` slot 0 in `db/cron-db.ts`, run through the shared `runMigrations` / `PRAGMA user_version` mechanism (see [storage.md](storage.md#schema-change-rules)).
34
34
  - **Folder + stateless** (`CRON_MIGRATIONS` slot 4, v5): `cron_jobs.working_dir` — the folder the run focuses on, passed as `halo cli --dir` (same semantics as a sub-agent's `working_dir`: the folder's `.halo/INSTRUCTIONS.md` chain joins the prompt, plus a `Working directory:` line); stored workspace-relative, NULL = the workspace root; validated at every write surface (inside the workspace, existing — REST re-checks a stored folder when `workspacePath` moves). `cron_jobs.stateless` (0/1) — **new session every run**: each fire gets a fresh root session `cron-<id>-<base36 ms>` (`perRunSessionId`, same no-double-prefix rule as `effectiveSessionId`), so nothing accumulates across fires (each job keeps only its own newest 10 runs; per-run sessions are deleted along with their run rows, see [Retention](#retention)); mutually exclusive with `session_id` (`400` on the merged row). `cron_runs.session_id` — the session that run used, the only record of a stateless run's id (NULL on rows from before v5). `cron_runs.workspace_path` (slot 5, v6) — the workspace the run fired against (the job's at fire time), i.e. where a stateless run's session lives even after the job moves workspace or is deleted; v6 backfills it from the job row, leaving NULL on rows whose job was already gone.
35
35
 
36
36
  ### Trigger-mode exclusivity (the db contract)
@@ -93,7 +93,7 @@ The step-1 guard is per-process, so it has one blind spot: the server dies (SIGK
93
93
  1. Take the session the orphan was spawned on from the run row (`cron_runs.session_id` — the only source for a stateless job's per-run id; a row from before that column falls back to the job row's effective session, a job deleted since to the default rule on its id), then re-enter its jobId into `_inflight` and its session key into `_inflightSessions` first — the core invariant: while the orphan may be alive, no new cli for that job *or for any other job on that session* is spawned (a fire in that window is recorded as `skipped`, same as normal overlap).
94
94
  2. If the row has a pid (POSIX): **identity-check before killing** — `ps -p <pid> -o args=` must carry the session id as a whole argv token (the `-s <session>` value; whole-token so `cron-a` never matches `cron-ab`), so a recycled pid never kills an innocent process. If the session came from the job row (pre-v5 run row) and was re-pointed after the orphan was spawned, the fingerprint won't match and the orphan is left alone — the safe direction. Verified → SIGTERM (graceful path, same as timeout phase 1); still alive after a 30s async grace (`unref`ed timer, identity re-checked — pid reuse again) → SIGKILL the descendant tree.
95
95
  3. No pid / already dead / fingerprint mismatch / Windows: no kill, log only. Windows deliberately marks-only — there's no cheap command-line identity check (WMI is slow and sometimes policy-blocked), and a full-tree `taskkill /T /F` on a reused pid is irreversible while a double-written session is fixable; the orphan-survives-server-death window is also naturally smaller there.
96
- 4. Either way the row is finalized as `failed` (`failureReason` notes the orphan disposition, `completedAt` stamped) so the UI stops showing a ghost `running`, and the jobId / session key are released once the orphan is confirmed dead (or immediately when nothing was killable).
96
+ 4. Either way the row is finalized as `failed` (`failureReason` notes the orphan disposition, `completedAt` stamped) so the UI stops showing a ghost `running` — for a one-shot its job row too, as `finalize` would (`enabled=0`, `lastRunStatus='failed'`, `lastRun*` = this run, `cron:job_changed` pushed); a recurring job's row is left alone — and the jobId / session key are released once the orphan is confirmed dead (or immediately when nothing was killable).
97
97
 
98
98
  Only the kill grace is asynchronous; the boot-blocking part is one small select + row updates.
99
99
 
@@ -22,7 +22,7 @@ Their metadata row lives in a global db next to the files, `~/.halo/global/inter
22
22
 
23
23
  No file holds metadata. Everything the 1.x header carried is derived or in the session's db row: agentId = directory, leaf id = file name, archive count = segment files on disk; `title`, `exchange_count`, `context_tokens`, `total_output_tokens`, `output`, `last_activity_at` = row columns (see [SQLite metadata](#sqlite-metadata)).
24
24
 
25
- **Writes append.** `sessions/session-log.ts` keeps a process-local writer cache per file (what this process last wrote or read, stamped with the file's ino / size / mtime). A save diffs memory against it **by object identity** — a persisted row or raw message is never edited in place; every mutator replaces the object (raw: or its `content` array, or changes its length) — and appends only the new records with one `write` on an `O_APPEND` fd. A full atomic rewrite (tmp + rename) happens only for real non-append changes: compaction (raw), archive / `replaceMessageLog` / `deleteExchange`, a stamp that no longer matches (another process — a cron `halo cli` child, CLI/TUI — wrote the file: rewrite from this process's memory, the same last-writer-wins as 1.x), or the first write of a file in this process. The stamp is checked at the write itself, not by a stat before every save: an append or tail write compares it with the `fstat` of the fd it writes through and turns into a full rewrite on a mismatch; only a save that is a full rewrite anyway stats first (its exchange-count delta is relative to what the file holds); a save with nothing new touches nothing. **Another process's write** (a cron `halo cli` child, the TUI, a manual `halo cli -s`) is read, not overwritten, before this process uses its cached copies again. The root's two copies — the UIState (kept past release) and an idle AgentSession a view loaded — can part wherever one of them is read from disk while the other is still cached, so the check sits at those points, one stat per cached copy against its writer baseline: `appendUserMessage` (a turn's user row, checked before it lands — dropping the state after would discard it; this precedes the `ensureSession` check on the WS path), `ensureSession`'s restore (raw about to be read; the cached UIState is checked), `ensureUIState`'s disk seed (the UI about to be read; a cached idle root AgentSession whose raw changed reloads its history in place — `refreshStaleHistory`, in place because callers such as `querySession` hold the object and its queue), plus `deleteExchange` (reads UI before raw), manual `compactSession` (its UI seed runs after `beginCompact`, past the seed's guard) and `querySession` waking an idle root with both copies cached (no point above runs: `ensureSession` returns the cached session, the emit doesn't seed) — there only the UIState is dropped, after `ensureSession`'s await; the emit re-seeds it and `refreshStaleHistory` reloads the history in place. Never the AgentSession: a sub's auto-report calls `querySession` from its `finally`, before its release, and dropping the tree there would skip that release's flush and save. A file with no baseline — the load found a torn line or another process's open in-flight row — counts as written elsewhere unless the UIState holds unsaved rows. On a hit the tree's idle copies are dropped (`forgetExternalWrite`, which cron also calls when its child exits) and both files reload from disk; the dropped UIState's pending debounced writes are cancelled, not flushed (a flush would rewrite the other writer's file from the stale state) — a `system` / `user` row from the last ≤500ms before the other writer wrote is the cost. The view path (`prepareForView` re-seeding a root this process doesn't drive) cancels the same way when the file was written elsewhere, and flushes otherwise. Skipped while the tree has a turn or compact in flight or a queued message — live state isn't dropped, and the two writers race (the last full rewrite wins). So a normal turn after a release stats the UI log twice (`appendUserMessage`, then the restore). Known gap: another process starting a turn in the few ms between `appendUserMessage` and `ensureSession`'s restore (the `buildAgentInstance` await) makes the restore's check drop the UIState — this process's user row, already in it, is then missing from the UI log, though the turn and its raw copy run. Not when the other process is mid-turn: the seed taken over its open in-flight row has no baseline, and a state with unsaved rows and no baseline isn't dropped. Whenever a save does replace another process's write (a copy still cached with no check in between: a notification, a Stop fold), the session's other log is rewritten whole from memory in the same save (`writeUILog` → `foreign`, `writeRawLog` → true; `persistUILog` / `saveAgentState` pair them), so the two files stay a pair, never one with the other process's turns beside this process's. One exception: a sub-session's raw write after its `agent_done` rewrites raw only — its UI log left the root's UIState at `agent_done`, so there is no copy to pair it with. Never spliced into the other's file either way. Loaders that hand their result to the live session (`prime`) seed the cache, so the first save after a restore appends. Writes stay synchronous on the same call sites as 1.x, so UI and raw read→diff→write never interleave, the tombstone check and the write share one synchronous slice, and `flushAll` lands before exit. A torn last line (crash mid-write, or an EFS unmount) fails to parse: the reader drops it and the next write is a full rewrite.
25
+ **Writes append.** `sessions/session-log.ts` keeps a process-local writer cache per file (what this process last wrote or read, stamped with the file's ino / size / mtime). A save diffs memory against it **by object identity** — a persisted row or raw message is never edited in place; every mutator replaces the object (raw: or its `content` array, or changes its length) — and appends only the new records with one `write` on an `O_APPEND` fd. A full atomic rewrite (tmp + rename) happens only for real non-append changes: compaction (raw), archive / `replaceMessageLog` / `deleteExchange`, a stamp that no longer matches (another process — a cron `halo cli` child, CLI/TUI — wrote the file: rewrite from this process's memory, the same last-writer-wins as 1.x), or the first write of a file in this process. The stamp is checked at the write itself, not by a stat before every save: an append or tail write compares it with the `fstat` of the fd it writes through and turns into a full rewrite on a mismatch; only a save that is a full rewrite anyway stats first (its exchange-count delta is relative to what the file holds); a save with nothing new touches nothing. **Another process's write** (a cron `halo cli` child, the TUI, a manual `halo cli -s`) is read, not overwritten, before this process uses its cached copies again. The root's two copies — the UIState (kept past release) and an idle AgentSession a view loaded — can part wherever one of them is read from disk while the other is still cached, so the check sits at those points, one stat per cached copy against its writer baseline: `appendUserMessage` (a turn's user row, checked before it lands — dropping the state after would discard it; this precedes the `ensureSession` check on the WS path), `ensureSession`'s restore (raw about to be read; the cached UIState is checked), `ensureUIState`'s disk seed (the UI about to be read; a cached idle root AgentSession whose raw changed reloads its history in place — `refreshStaleHistory`, in place because callers such as `querySession` hold the object and its queue), plus `deleteExchange` (reads UI before raw), manual `compactSession` (its UI seed runs after `beginCompact`, past the seed's guard) and `querySession` waking an idle root with both copies cached (no point above runs: `ensureSession` returns the cached session, the emit doesn't seed) — only when the session was already in memory before the await (a cold wake's restore has just checked), and there only the UIState is dropped, after `ensureSession`'s await; the emit re-seeds it and `refreshStaleHistory` reloads the history in place. Never the AgentSession: a sub's auto-report calls `querySession` from its `finally`, before its release, and dropping the tree there would skip that release's flush and save. A file with no baseline — the load found a torn line or another process's open in-flight row — counts as written elsewhere unless the UIState holds unsaved rows. On a hit the tree's idle copies are dropped (`forgetExternalWrite`, which cron also calls when its child exits) and both files reload from disk; the dropped UIState's pending debounced writes are cancelled, not flushed, when the UI log itself was written elsewhere (a flush would rewrite the other writer's file from the stale state) — a `system` / `user` row from the last ≤500ms before the other writer wrote is the cost; after a raw-only write elsewhere the UI file is still this process's, and they flush as usual. The view path (`prepareForView` re-seeding a root this process doesn't drive) cancels the same way when the file was written elsewhere, and flushes otherwise. Skipped while the tree has a turn or compact in flight or a queued message — live state isn't dropped, and the two writers race (the last full rewrite wins). So a normal turn after a release stats the UI log twice (`appendUserMessage`, then the restore). Known gap: another process starting a turn in the few ms between `appendUserMessage` and `ensureSession`'s restore (the `buildAgentInstance` await) makes the restore's check drop the UIState — this process's user row, already in it, is then missing from the UI log, though the turn and its raw copy run. Not when the other process is mid-turn: the seed taken over its open in-flight row has no baseline, and a state with unsaved rows and no baseline isn't dropped. Whenever a save does replace another process's write (a copy still cached with no check in between: a notification, a Stop fold), the session's other log is rewritten whole from memory in the same save (`writeUILog` → `foreign`, `writeRawLog` → true; `persistUILog` / `saveAgentState` pair them), so the two files stay a pair, never one with the other process's turns beside this process's. One exception: a sub-session's raw write after its `agent_done` rewrites raw only — its UI log left the root's UIState at `agent_done`, so there is no copy to pair it with. Never spliced into the other's file either way. Loaders that hand their result to the live session (`prime`) seed the cache, so the first save after a restore appends. Writes stay synchronous on the same call sites as 1.x, so UI and raw read→diff→write never interleave, the tombstone check and the write share one synchronous slice, and `flushAll` lands before exit. A torn last line (crash mid-write, or an EFS unmount) fails to parse: the reader drops it and the next write is a full rewrite.
26
26
 
27
27
  **The in-flight assistant has a stable id.** `TurnState.inflight = { id, at }` is assigned by the first snapshot of a streaming reply and reused by `flushAssistantMessage` / `flushCompletedAssistantMessage` for the finalized row. Every persist mid-stream writes an `inflight` record carrying only the changed blocks; the final `add` with the same id retires it — exactly one row, and live views (WS / web snapshots) show the same id. An interrupt flush writes the partial reply under that id, raw gets the streamed text + `INTERRUPTED_REPLY_MARKER`, so after a reload both halves hold the partial reply once. SIGKILL loses what streamed after the last write.
28
28
 
@@ -395,14 +395,14 @@ Frontend network issues don't affect the backend:
395
395
  | 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 |
396
396
  | 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 |
397
397
  | **Transient server-side error (5xx / timeout)** | Same exponential backoff as throttling — see [Transient server-error classification](#transient-server-error-classification) below |
398
- | Transient transport error (`fetch failed`, `ECONNRESET`, headers timeout, …) | Short backoff (`1s * 2^attempt` + jitter), retry |
398
+ | Transient transport error (`fetch failed`, `ECONNRESET`, headers timeout, …; a stream that ends before its end events, `AnthropicStreamTruncated` / `OpenAIStreamTruncated`) | Short backoff (`1s * 2^attempt` + jitter), retry |
399
399
  | Corrupted messages (`tool_use ids without tool_result`) | Repair + retry |
400
400
  | **4xx multimodal rejection** (`Multimodal data is corrupted` / `Could not process image`) | Replace all image blocks in history with text placeholders, persist, retry — see [Multimodal 4xx degrade](#multimodal-4xx-degrade) below |
401
401
  | Unrecoverable error | Report to user, stop |
402
402
 
403
403
  **Refusal stop.** `stop_reason: "refusal"` (Anthropic models on Bedrock invoke + generic anthropic) is an HTTP 200 with `stop_details {category, explanation}` — not an error, so it never enters the retry matrix above. `agent-loop.ts` handles it explicitly: no assistant message is pushed, any partial `tool_use` is **not** executed, the loop ends; `session-manager.ts` emits a `system` event `⚠️ [<agent>] Model declined to respond (<category>): <explanation>` suggesting `/new`. Previously it fell through the `end_turn` path and looked like a silent stall.
404
404
 
405
- **Classification order: structured signal first, message text last.** The interrupt branch is decided by the attempt's own `AbortController` signal (every interrupt path we own goes through `abortReason()` on it), never by the words `cancelled` / `aborted` in the message — an upstream error body can legitimately read `"The operation was aborted due to …"` (Bedrock), and matching that swallowed a real failure as a user interrupt: silent `break`, no retry, no error event, an empty reply. Likewise the account-level branch trusts `httpStatus` when one was recovered: 401/402/403 is terminal, any *other* status is not, whatever the body says — an OpenAI-compatible 503 whose body reads `"authentication service temporarily unavailable"` used to be classified as a dead key and never retried. The keyword list is only consulted when no status could be extracted. Pinned by `turn-retry-idempotent.test.ts` (five classification cases).
405
+ **Classification order: structured signal first, message text last.** The interrupt branch is decided by the attempt's own `AbortController` signal (every interrupt path we own goes through `abortReason()` on it), never by the words `cancelled` / `aborted` in the message — an upstream error body can legitimately read `"The operation was aborted due to …"` (Bedrock), and matching that swallowed a real failure as a user interrupt: silent `break`, no retry, no error event, an empty reply. Likewise the account-level branch trusts `httpStatus` when one was recovered: 401/402/403 is terminal, any *other* status is not, whatever the body says — an OpenAI-compatible 503 whose body reads `"authentication service temporarily unavailable"` used to be classified as a dead key and never retried. The keyword list is only consulted when no status could be extracted. Pinned by `turn-retry-idempotent.test.ts` (seven classification cases).
406
406
 
407
407
  **Abort reasons are normalized through the `abortReason()` helper** — all three abort call sites (graceful interrupt after `tool_result`, `interruptSession`'s hard interrupt, `stopSession`'s stop) wrap the reason string in `new DOMException(reason, 'AbortError')` instead of passing it raw. Node 22 gotcha: `fetch` rejects with the abort reason **as-is**, so `controller.abort('interrupt')` surfaced the bare string `'interrupt'` (not an Error, `errName === ''`) in `runAgentTurn`'s catch — it missed the AbortError branch and logged a fake "Unrecoverable: interrupt" error. Contract pinned by a queue-semantics test.
408
408
 
@@ -433,6 +433,8 @@ Throttling is checked the same way one branch earlier — structured `Throttling
433
433
 
434
434
  Backoff is identical to throttling: `2s * 2^attempt` + up to 1s jitter, capped at 60s, for up to `config.agent.maxRetries` (5) attempts.
435
435
 
436
+ **A stream that ends early without an error is network-class.** `AnthropicStreamAccumulator.finish()` (`agents/anthropic-stream.ts` — Bedrock, generic anthropic, MiniMax, Qwen) throws `AnthropicStreamTruncated` when the stream ended cleanly but neither a `message_delta` with a `stop_reason` nor a `message_stop` arrived (seen on Bedrock: minutes of thinking, then a clean end, out=0). Both events must be missing — a compatible provider may send only one of them. `ChatCompletionAccumulator.finish()` (`agents/openai-chat-stream.ts` — openai / deepseek / kimi / zhipu / doubao / hunyuan / gemini) throws `OpenAIStreamTruncated` when no `finish_reason` arrived, or one of DeepSeek's server-side cut-offs, `insufficient_system_resource` (lack of inference resources) or `aborted` (generation interrupted — our own aborts go through the signal, never a `finish_reason`); every live-probed provider sends one on its last choices chunk, before the usage chunk and `[DONE]`. `classifyModelError` maps both to `network` (`model-error.ts` branch 3c), so the turn gets the short backoff instead of ending as an empty `end_turn` reply. An abort still surfaces as an AbortError: the callers check their signal before `finish()`.
437
+
436
438
  **Why this matters (original pain point)**: Bedrock returns a generic 500 with the message `"… is unable to process your request"` — no `Throttling`/`Overloaded`/`rate limit` keyword. The old logic, which classified retryable errors only by message-string keywords, matched nothing and let the turn die on attempt 1. Keying the retry on the **structured** `errName` / `httpStatus` instead of substrings is what makes the 5xx/timeout retry fire across every provider, Bedrock included.
437
439
 
438
440
  ## Compaction paths
@@ -60,7 +60,7 @@ Defines the persisted-data format for every Halo surface. Format changes must re
60
60
  │ └── root/ # Overrides ~/.halo/global/prompts/root/ if present
61
61
  ├── sessions/<agentId>/ # Session files: <seg>.ui.jsonl, <seg>.raw.jsonl, <seg>.ui.arch.<N>.<h>.jsonl.gz
62
62
  ├── memory/ # Project memory (dated entries)
63
- ├── canvas/self.html # Agent's visual face — platform-owned, force-copied on every open (design/express-self.md)
63
+ ├── canvas/self.html # Agent's visual face — platform-owned, force-copied whenever ensureWorkspaceHalo runs (see below; design/express-self.md)
64
64
  ├── goal/<goalId>/ # Retained legacy goal-mode artifacts (GOAL_SPEC.md + decision-<n>.md); existing goals unchanged
65
65
  ├── logs/
66
66
  ├── evo/ # Self-evolution per-workspace artifacts
@@ -80,7 +80,7 @@ Defines the persisted-data format for every Halo surface. Format changes must re
80
80
  └── docs/ # Project docs (requirements/design/dev/test/plans)
81
81
  ```
82
82
 
83
- **When `.halo/` is created.** `ensureWorkspaceHalo()` (`init.ts`) creates `sessions/ agents/ skills/ logs/ memory/ canvas/ tmp/ evo/{runs,applies,history}` and copies in `canvas/self.html`. It runs each time a directory is opened as a workspace: `POST /api/fs/workspace/resolve` (the admin folder picker), channel account binding, `/workspace switch`, and every `SessionManagerRegistry.getOrCreate()` / `getWorkspaceDb()` call. `halo.db` appears the first time the workspace db is opened. `runtime.lock` appears when the server first claims the runtime. Read-only list routes (`GET /api/sessions/logs`, `GET /api/agent-configs`) call `hasWorkspaceHalo()` first. For a directory with no `.halo/` they return an empty or global-only list, so a list request never turns a directory into a workspace. The cron form's path field used to scaffold every real directory the user typed through. Show routes use `registry.peek()` for the same reason (see [halo-city.md](halo-city.md)). Any new read-only route that takes a workspace path should do the same.
83
+ **When `.halo/` is created.** `ensureWorkspaceHalo()` (`init.ts`) creates `sessions/ agents/ skills/ logs/ memory/ canvas/ tmp/ evo/{runs,applies,history}` and copies in `canvas/self.html`. It runs each time a directory is opened as a workspace: `POST /api/fs/workspace/resolve` (the admin folder picker), channel account binding and every inbound channel message (`resolveAccountWorkspace`), `/workspace switch`, every `getWorkspaceDb()` call, and `SessionManagerRegistry.getOrCreate()` on a cache miss only (a hit returns the manager built, and `.halo/` scaffolded, earlier in this process). `halo.db` appears the first time the workspace db is opened. `runtime.lock` appears when the server first claims the runtime. Read-only list routes (`GET /api/sessions/logs`, `GET /api/agent-configs`) call `hasWorkspaceHalo()` first. For a directory with no `.halo/` they return an empty or global-only list, so a list request never turns a directory into a workspace. The cron form's path field used to scaffold every real directory the user typed through. Show routes use `registry.peek()` for the same reason (see [halo-city.md](halo-city.md)). Any new read-only route that takes a workspace path should do the same.
84
84
 
85
85
  Precedence: workspace > global, **at folder granularity**. For the same id, a workspace `agents/<id>/` or `skills/<id>/` folder entirely replaces the global one — every file in it, no per-file fallback to global (a workspace agent folder with only `AGENT.md` loses the global `agent.yaml`). Same whole-folder rule for `prompts/{bootstrap,all,root}/` (workspace scope directory replaces the global one). INSTRUCTIONS.md is the single-file exception: workspace root suppresses global.
86
86
 
@@ -149,6 +149,7 @@ export class MyProviderAgent extends AgentLoop {
149
149
  - **Tool results format.** Anthropic stores tool results as `{ role: 'user', content: [{ type: 'tool_result', ... }] }`. OpenAI-compatible APIs need separate `{ role: 'tool', tool_call_id, content }` messages, and tool-result images ride in a user message right after them. `toChatMessages()` in openai-chat-format.ts does both.
150
150
  - **Thinking/reasoning replay.** If the provider returns `reasoning_content` and requires it in follow-up messages (Kimi, DeepSeek with tool calls), it must be stored in `result.assistantBlocks` as a `{ type: 'thinking', thinking: '...' }` block and replayed as `reasoning_content` — `chatCompletionResult()` stores it and `toChatMessages()` replays it.
151
151
  - **Reasoning field name varies.** OpenAI o-series/DeepSeek emit reasoning in `message.reasoning_content`; Ollama / llama.cpp's OpenAI-compat layer uses `message.reasoning`. The shared stream accumulator (`ChatCompletionAccumulator` in `openai-chat-stream.ts`) folds both into one reasoning stream (`reasoning_content ?? reasoning`), so every `fetchChatCompletionStream` provider gets it. A vendor-specific class should read whichever its API uses.
152
+ - **The stream must carry a `finish_reason`.** `ChatCompletionAccumulator.finish()` treats a stream that ends without one as cut off: it throws `OpenAIStreamTruncated`, which the turn retries with the network backoff (a usage chunk or `[DONE]` alone doesn't count as the end). Every built-in OpenAI-family provider sends it on the last choices chunk (live-probed 2026-10-17); probe a new endpoint for it before onboarding — one that never sends it would retry every turn and then fail.
152
153
  - **Malformed tool arguments.** Parse tool-call arguments with `parseToolInput(json, toolName)` (`tool-input.ts`) — every built-in path does (Anthropic stream accumulator, `chatCompletionResult`, Mantle). It returns `{ input, error? }` and never throws: empty / missing arguments are `{}` with no error; arguments that aren't valid JSON or aren't a plain JSON object (`null`, an array, a scalar) become `{}` plus an `error` and a `[ToolInput]` warn (tool name and the first 200 chars). Pass the error through as `inputError` on the `toolCalls[]` entry (keep `input: {}` in `assistantBlocks`) — the loop then skips that call and returns an `is_error` result asking the model to resend complete JSON, instead of running the tool with `{}`. A `max_tokens` stop never runs any of the reply's tool calls; the loop answers each with an `is_error` result itself.
153
154
  - **Cancellation.** The caller passes `signal`; pass it to `fetch()` directly.
154
155
  - **Max tokens.** Use `config.maxTokens ?? resolveMaxOutputTokens(config.modelId)` for consistency.
@@ -210,7 +210,7 @@ No `/search` endpoint: the cron form takes the WeCom `chatId` verbatim (a `useri
210
210
 
211
211
  ## Cron
212
212
 
213
- File: `packages/server/src/routes/cron.ts`. Admin cookie auth. REST CRUD over `cron_jobs` + read-only `cron_runs`. Mutations call `reloadAll()` / `scheduleJob()` / `unscheduleJob()` so in-memory croner stays in sync with the db. Each mutation also broadcasts `cron:job_changed`.
213
+ File: `packages/server/src/routes/cron.ts`. Admin cookie auth. REST CRUD over `cron_jobs` + read-only `cron_runs`. Mutations call `scheduleJob()` / `unscheduleJob()` (`POST /cron/reload` calls `reloadAll()`) so the runner's in-memory schedules — its own fire timers (`armTimer` / `onTimer`), with croner only computing `nextRun` from a paused instance — stay in sync with the db. Each mutation also broadcasts `cron:job_changed`.
214
214
 
215
215
  ### Jobs
216
216
 
@@ -304,6 +304,8 @@ This installs the `halo` binary on `$PATH`. Subcommands available:
304
304
  1. `halo upgrade` — bumps the on-disk npm package
305
305
  2. `halo server restart` (under a user or system unit alike; `sudo systemctl restart halo` also works for a system unit) — server's startup check sees `~/.halo/global/.template-version` is behind the new bundled `TEMPLATE_VERSION`, runs `ensureHaloHome` automatically, then starts (`halo cli` / `halo tui` run the same check on start, so a CLI-only install refreshes without a server). Refreshes `docs/`, built-in agents, built-in skills, system prompts, and the model registry. User-owned files (USER.md, custom agents/skills, INSTRUCTIONS.md overrides) are left alone. See `init.ts` for the per-category overwrite policy.
306
306
 
307
+ **1.x → 2.0** (new session store): stop the server → `halo sessions migrate v1 v2 -w <ws>` once per workspace → start it — see [guide/cli.md → Session store migration](../guide/cli.md#session-store-migration-halo-sessions-migrate-v1-v2). On prod, 30 workspaces / 1171 sessions migrated in ~93 s, ~1.6 min of downtime in total. First run `find <ws>/.halo -not -user <service user>`: a root-owned file there fails the migration with `EACCES` (agentcore-workspace did in the 2.0 prod upgrade).
308
+
307
309
  ### Public release command reference
308
310
 
309
311
  Use only for a new public release; the retained details here are not extra preparation for installing an existing version.
package/dist/index.js CHANGED
@@ -2959,7 +2959,7 @@ function classifyKind(msg, errName, httpStatus) {
2959
2959
  if (errName === "InternalServerException" || errName === "ModelTimeoutException" || errName === "ModelStreamErrorException" || errName === "ServiceUnavailableException" || httpStatus === 500 || httpStatus === 502 || httpStatus === 503 || httpStatus === 504 || httpStatus === 529 || httpStatus === 408 || streamCode === "server_error" || streamCode === "internal_server_error" || streamCode === "service_unavailable") {
2960
2960
  return "server_error";
2961
2961
  }
2962
- if (msg === "fetch failed" || msg === "Model request timed out" || msg.includes("http2 request did not get a response") || msg.includes("AnthropicStreamTruncated") || msg.includes("UND_ERR_HEADERS_TIMEOUT") || msg.includes("HeadersTimeoutError") || msg.includes("socket hang up") || msg.includes("ECONNRESET") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("EAI_AGAIN")) {
2962
+ if (msg === "fetch failed" || msg === "Model request timed out" || msg.includes("http2 request did not get a response") || msg.includes("AnthropicStreamTruncated") || msg.includes("OpenAIStreamTruncated") || msg.includes("UND_ERR_HEADERS_TIMEOUT") || msg.includes("HeadersTimeoutError") || msg.includes("socket hang up") || msg.includes("ECONNRESET") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("EAI_AGAIN")) {
2963
2963
  return "network";
2964
2964
  }
2965
2965
  if (msg.includes("MantleEmptyResponse")) {
@@ -11447,6 +11447,12 @@ var init_session_ui_store = __esm({
11447
11447
  return false;
11448
11448
  return logWrittenElsewhere(this.uiLogFile(rootId, rootId)) ?? !this.uiStateDirty.has(rootId);
11449
11449
  }
11450
+ /** Whether the root's UI log is still the file this process last wrote /
11451
+ * read (one stat): a flush of its pending writes appends to its own file.
11452
+ * False when another process wrote it or there is no baseline to tell. */
11453
+ uiLogIsOwn(rootId) {
11454
+ return logWrittenElsewhere(this.uiLogFile(rootId, rootId)) === false;
11455
+ }
11450
11456
  /** UI log file of `id` (the root or one of its subs) under the root state's
11451
11457
  * project path. */
11452
11458
  uiLogFile(rootId, id) {
@@ -12160,12 +12166,13 @@ async function fetchChatCompletionStream(opts) {
12160
12166
  throw new DOMException("Model call aborted", "AbortError");
12161
12167
  return acc.finish();
12162
12168
  }
12163
- var ChatCompletionAccumulator;
12169
+ var OPENAI_STREAM_TRUNCATED, ChatCompletionAccumulator;
12164
12170
  var init_openai_chat_stream = __esm({
12165
12171
  "../server/dist/agents/openai-chat-stream.js"() {
12166
12172
  "use strict";
12167
12173
  init_agent_loop();
12168
12174
  init_sse();
12175
+ OPENAI_STREAM_TRUNCATED = "OpenAIStreamTruncated: stream ended without a finish_reason";
12169
12176
  ChatCompletionAccumulator = class {
12170
12177
  startTime;
12171
12178
  onDelta;
@@ -12214,8 +12221,14 @@ var init_openai_chat_stream = __esm({
12214
12221
  call.function.arguments += tc.function?.arguments ?? "";
12215
12222
  }
12216
12223
  }
12217
- /** Build the agent-facing result. Call once, after the stream has ended. */
12224
+ /** Build the agent-facing result. Call once, after the stream has ended
12225
+ * (the caller checks its abort signal first, so an abort stays AbortError). */
12218
12226
  finish() {
12227
+ if (this.finishReason === void 0)
12228
+ throw new Error(OPENAI_STREAM_TRUNCATED);
12229
+ if (this.finishReason === "insufficient_system_resource" || this.finishReason === "aborted") {
12230
+ throw new Error(`OpenAIStreamTruncated: finish_reason ${this.finishReason}`);
12231
+ }
12219
12232
  return {
12220
12233
  message: {
12221
12234
  ...this.content ? { content: this.content } : {},
@@ -15442,7 +15455,7 @@ async function initObservability(haloProcess = "server") {
15442
15455
  return true;
15443
15456
  }
15444
15457
  const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
15445
- providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "2.0.0", haloProcess);
15458
+ providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "2.0.1", haloProcess);
15446
15459
  console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, halo.process=${haloProcess}, capture_content=${config.observability.captureContent})`);
15447
15460
  return true;
15448
15461
  }
@@ -16640,12 +16653,14 @@ var init_session_manager = __esm({
16640
16653
  * (canDropTree): live state can't be discarded, and the two writers race —
16641
16654
  * the accepted cost of sharing a session.
16642
16655
  *
16643
- * The UIState's pending debounced writes are cancelled, not flushed: a
16644
- * flush would rewrite the other writer's file whole from the stale state
16645
- * just before the re-read. What they carried is made again after it (a
16656
+ * The UIState's pending debounced writes are cancelled, not flushed, when
16657
+ * the UI log itself is no longer this process's (uiLogIsOwn): a flush
16658
+ * would rewrite the other writer's file whole from the stale state just
16659
+ * before the re-read. What they carried is made again after it (a
16646
16660
  * restore's `context` row) or not yet in memory (the user row at turn
16647
16661
  * start); a notice from the last ≤500ms is lost — the other writer wrote
16648
- * in that window. */
16662
+ * in that window. Another process's raw-only write leaves the UI file
16663
+ * ours: the writes land as usual. */
16649
16664
  forgetExternalWrite(rootId) {
16650
16665
  if (!this.canDropTree(rootId))
16651
16666
  return false;
@@ -16655,9 +16670,17 @@ var init_session_manager = __esm({
16655
16670
  this.stateStore.forgetRowWrites(id);
16656
16671
  }
16657
16672
  }
16658
- this.uiStore.dropUIState(rootId, { discard: true });
16673
+ this.dropStaleUIState(rootId);
16659
16674
  return true;
16660
16675
  }
16676
+ /** Drop the root's cached UIState for a re-read: its pending writes are
16677
+ * flushed when the UI log is still this process's file, else cancelled
16678
+ * (see forgetExternalWrite). */
16679
+ dropStaleUIState(rootId) {
16680
+ if (!this.uiStore.getCachedUIState(rootId))
16681
+ return;
16682
+ this.uiStore.dropUIState(rootId, { discard: !this.uiStore.uiLogIsOwn(rootId) });
16683
+ }
16661
16684
  /** The tree's in-memory copies may be dropped: no turn / compact in flight
16662
16685
  * and no queued message — one enqueued as a turn ended waits on the idle
16663
16686
  * session for the next turn, and a drop would lose it. */
@@ -17474,14 +17497,15 @@ ${q.text}` : q.text}`
17474
17497
  async querySession(targetSessionId, sourceSessionId, message, interrupt = false) {
17475
17498
  console.debug(`[SessionManager] querySession: ${sourceSessionId} \u2192 ${targetSessionId} (interrupt=${interrupt}) \u2014 message: ${message.slice(0, 150)}`);
17476
17499
  let target;
17500
+ const cached2 = this.sessions.get(targetSessionId);
17477
17501
  try {
17478
17502
  target = await this.ensureSession(targetSessionId);
17479
17503
  } catch {
17480
17504
  return JSON.stringify({ code: 1, error: `session ${targetSessionId} not found` });
17481
17505
  }
17482
- if (target.parentId === null && this.canDropTree(targetSessionId) && this.cachedCopiesWrittenElsewhere(targetSessionId)) {
17506
+ if (target === cached2 && target.parentId === null && this.canDropTree(targetSessionId) && this.cachedCopiesWrittenElsewhere(targetSessionId)) {
17483
17507
  console.debug(`[SessionManager] querySession: ${targetSessionId} written by another process since cached \u2014 re-reading from disk`);
17484
- this.uiStore.dropUIState(targetSessionId, { discard: true });
17508
+ this.dropStaleUIState(targetSessionId);
17485
17509
  }
17486
17510
  const agentQueued = target.messageQueue.filter((q) => q.sourceSessionId !== void 0).length;
17487
17511
  if (!interrupt && target.promise !== null && agentQueued >= config.session.maxQueueSize) {
@@ -30540,7 +30564,7 @@ function buildCard(workspace, interfaceUrl, caps) {
30540
30564
  return {
30541
30565
  name: raw.name,
30542
30566
  description: raw.description,
30543
- version: typeof raw.version === "string" ? raw.version : "2.0.0",
30567
+ version: typeof raw.version === "string" ? raw.version : "2.0.1",
30544
30568
  supportedInterfaces: [{ url: interfaceUrl, protocolBinding: "JSONRPC", protocolVersion: A2A_VERSION }],
30545
30569
  capabilities: { streaming: caps.streaming, pushNotifications: true, extendedAgentCard: false },
30546
30570
  ...caps.tokenAuth === false ? {} : {
@@ -31515,6 +31539,10 @@ function sweepOrphanRuns(opts) {
31515
31539
  }).where(eq16(cronRuns.id, runId)).run();
31516
31540
  broadcast({ type: "cron:run_changed", jobId, runId, status: "failed" });
31517
31541
  console.log(`[Cron] orphan sweep: run ${runId} (job ${jobId}) \u2014 ${disposition}`);
31542
+ if (job?.runAt) {
31543
+ db3.update(cronJobs).set({ enabled: 0, lastRunStatus: "failed", lastRunAt: now, lastRunId: runId, updatedAt: now }).where(eq16(cronJobs.id, jobId)).run();
31544
+ broadcast({ type: "cron:job_changed", jobId, lastRunStatus: "failed", lastRunAt: now, ...jobChangedFields(job) });
31545
+ }
31518
31546
  if (!needsKill || pid == null) {
31519
31547
  release();
31520
31548
  continue;
@@ -31623,6 +31651,8 @@ function scheduleJob(jobId) {
31623
31651
  try {
31624
31652
  if (job.runAt) {
31625
31653
  if (job.runAt <= Date.now()) {
31654
+ if (_inflight.has(jobId))
31655
+ return;
31626
31656
  const now = Date.now();
31627
31657
  console.warn(`[Cron] ${jobId} runAt was ${new Date(job.runAt).toISOString()} (past); marking missed`);
31628
31658
  db3.update(cronJobs).set({ enabled: 0, lastRunStatus: "missed", lastRunAt: now, updatedAt: now }).where(eq16(cronJobs.id, jobId)).run();
@@ -31641,7 +31671,9 @@ function scheduleJob(jobId) {
31641
31671
  target = cron.nextRun();
31642
31672
  }
31643
31673
  } catch (err) {
31644
- console.warn(`[Cron] ${jobId} schedule invalid (${job.schedule}): ${err instanceof Error ? err.message : String(err)}`);
31674
+ _expired.add(jobId);
31675
+ _fingerprint.set(jobId, jobFingerprint(job));
31676
+ console.warn(`[Cron] ${jobId} schedule invalid (${job.schedule}${job.timezone ? `, tz ${job.timezone}` : ""}): ${err instanceof Error ? err.message : String(err)}`);
31645
31677
  return;
31646
31678
  }
31647
31679
  const entry = { jobId, cron, timer: null };
@@ -38843,7 +38875,7 @@ var init_dist2 = __esm({
38843
38875
  init_git_ssh();
38844
38876
  __dirname3 = path88.dirname(fileURLToPath3(import.meta.url));
38845
38877
  PORT = config.server.port;
38846
- HALO_VERSION = "2.0.0";
38878
+ HALO_VERSION = "2.0.1";
38847
38879
  PROJECT_ROOT = findProjectRoot();
38848
38880
  HALO_HOME4 = path88.join(homedir25(), ".halo");
38849
38881
  GIT_SHA = readGitSha();
@@ -40453,7 +40485,7 @@ function installUserUnit(opts, run = runCommand) {
40453
40485
  }
40454
40486
 
40455
40487
  // src/index.ts
40456
- var VERSION = "2.0.0";
40488
+ var VERSION = "2.0.1";
40457
40489
  var HELP_TOP = `Usage: halo <command> [options]
40458
40490
 
40459
40491
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",