@turmind/halo 1.1.2 → 1.1.3

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><!--bZcoAqHBQyOvzwS5c84EJ--><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/0d0ca7b3c8cc5562.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-fc1574554e7b1108.js"/><script src="/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js" async=""></script><script src="/_next/static/chunks/635-d7fd95bff8674116.js" async=""></script><script src="/_next/static/chunks/main-app-483b436873410ddc.js" async=""></script><script src="/_next/static/chunks/212-8ac9dfe9c24e512c.js" async=""></script><script src="/_next/static/chunks/app/layout-c2fa8035998e48d5.js" async=""></script><script src="/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js" async=""></script><script src="/_next/static/chunks/438-866a1a20e9efa78a.js" async=""></script><script src="/_next/static/chunks/app/page-2076ec04359bb395.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-fc1574554e7b1108.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[59391,[\"212\",\"static/chunks/212-8ac9dfe9c24e512c.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"ThemeProvider\"]\n3:I[26967,[\"212\",\"static/chunks/212-8ac9dfe9c24e512c.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"AppI18nProvider\"]\n4:I[66048,[],\"\"]\n5:I[29926,[],\"\"]\n6:I[57917,[],\"ClientPageRoot\"]\n7:I[61089,[\"560\",\"static/chunks/1d2d5650-810a3ca1ae20cab5.js\",\"438\",\"static/chunks/438-866a1a20e9efa78a.js\",\"212\",\"static/chunks/212-8ac9dfe9c24e512c.js\",\"974\",\"static/chunks/app/page-2076ec04359bb395.js\"],\"default\"]\na:I[11789,[],\"OutletBoundary\"]\nc:I[21528,[],\"AsyncMetadataOutlet\"]\ne:I[11789,[],\"ViewportBoundary\"]\n10:I[11789,[],\"MetadataBoundary\"]\n11:\"$Sreact.suspense\"\n13:I[79836,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/0d0ca7b3c8cc5562.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"bZcoAqHBQyOvzwS5c84EJ\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/0d0ca7b3c8cc5562.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[89592,[],\"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><!--oDvBPFdNAW2vHdHcxQ0lw--><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/0d0ca7b3c8cc5562.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-fc1574554e7b1108.js"/><script src="/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js" async=""></script><script src="/_next/static/chunks/635-d7fd95bff8674116.js" async=""></script><script src="/_next/static/chunks/main-app-483b436873410ddc.js" async=""></script><script src="/_next/static/chunks/212-70bd2971392bde84.js" async=""></script><script src="/_next/static/chunks/app/layout-c2fa8035998e48d5.js" async=""></script><script src="/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js" async=""></script><script src="/_next/static/chunks/438-866a1a20e9efa78a.js" async=""></script><script src="/_next/static/chunks/app/page-71c576815b4a30d9.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-fc1574554e7b1108.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[59391,[\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"ThemeProvider\"]\n3:I[26967,[\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"AppI18nProvider\"]\n4:I[66048,[],\"\"]\n5:I[29926,[],\"\"]\n6:I[57917,[],\"ClientPageRoot\"]\n7:I[61089,[\"560\",\"static/chunks/1d2d5650-810a3ca1ae20cab5.js\",\"438\",\"static/chunks/438-866a1a20e9efa78a.js\",\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"974\",\"static/chunks/app/page-71c576815b4a30d9.js\"],\"default\"]\na:I[11789,[],\"OutletBoundary\"]\nc:I[21528,[],\"AsyncMetadataOutlet\"]\ne:I[11789,[],\"ViewportBoundary\"]\n10:I[11789,[],\"MetadataBoundary\"]\n11:\"$Sreact.suspense\"\n13:I[79836,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/0d0ca7b3c8cc5562.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"oDvBPFdNAW2vHdHcxQ0lw\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/0d0ca7b3c8cc5562.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[89592,[],\"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,10 +1,10 @@
1
1
  1:"$Sreact.fragment"
2
- 2:I[59391,["212","static/chunks/212-8ac9dfe9c24e512c.js","177","static/chunks/app/layout-c2fa8035998e48d5.js"],"ThemeProvider"]
3
- 3:I[26967,["212","static/chunks/212-8ac9dfe9c24e512c.js","177","static/chunks/app/layout-c2fa8035998e48d5.js"],"AppI18nProvider"]
2
+ 2:I[59391,["212","static/chunks/212-70bd2971392bde84.js","177","static/chunks/app/layout-c2fa8035998e48d5.js"],"ThemeProvider"]
3
+ 3:I[26967,["212","static/chunks/212-70bd2971392bde84.js","177","static/chunks/app/layout-c2fa8035998e48d5.js"],"AppI18nProvider"]
4
4
  4:I[66048,[],""]
5
5
  5:I[29926,[],""]
6
6
  6:I[57917,[],"ClientPageRoot"]
7
- 7:I[61089,["560","static/chunks/1d2d5650-810a3ca1ae20cab5.js","438","static/chunks/438-866a1a20e9efa78a.js","212","static/chunks/212-8ac9dfe9c24e512c.js","974","static/chunks/app/page-2076ec04359bb395.js"],"default"]
7
+ 7:I[61089,["560","static/chunks/1d2d5650-810a3ca1ae20cab5.js","438","static/chunks/438-866a1a20e9efa78a.js","212","static/chunks/212-70bd2971392bde84.js","974","static/chunks/app/page-71c576815b4a30d9.js"],"default"]
8
8
  a:I[11789,[],"OutletBoundary"]
9
9
  c:I[21528,[],"AsyncMetadataOutlet"]
10
10
  e:I[11789,[],"ViewportBoundary"]
@@ -14,7 +14,7 @@ e:I[11789,[],"ViewportBoundary"]
14
14
  :HL["/_next/static/media/e4af272ccee01ff0-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
15
15
  :HL["/_next/static/css/0d0ca7b3c8cc5562.css","style"]
16
16
  :HL["/_next/static/css/659eccb5db697b76.css","style"]
17
- 0:{"P":null,"b":"bZcoAqHBQyOvzwS5c84EJ","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/0d0ca7b3c8cc5562.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":"oDvBPFdNAW2vHdHcxQ0lw","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/0d0ca7b3c8cc5562.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"}]]
@@ -76,6 +76,41 @@ whole lifetime (audit B-L3). Every command is `accessLevel: 'full'` by design:
76
76
  auth terminated upstream at AgentCore, so whoever reached this socket already
77
77
  owns the isolated per-user workspace behind it.
78
78
 
79
+ ## WS image upload protocol (chunking)
80
+
81
+ The AgentCore WS proxy hard-caps a single frame at **64KB** — send more and
82
+ the connection is cut with close code 1009 ("Policy violated: message size
83
+ limit of 64 KB for a message frame is exceeded", verified against the Tokyo
84
+ prod runtime). An inline base64 image blows past that instantly, so images
85
+ ride as chunked `image_chunk` frames, assembled server-side, then referenced
86
+ by id from the actual message frame:
87
+
88
+ ```jsonc
89
+ // 1..N chunk frames per image (inputText empty string is required — the
90
+ // proxy only forwards client frames that carry an inputText key at all)
91
+ {"inputText":"", "type":"image_chunk", "uploadId":"u1", "seq":0, "total":8,
92
+ "mimeType":"image/jpeg", "data":"<base64 slice, ≤48KB>"}
93
+
94
+ // message frame referencing the finished upload(s)
95
+ {"inputText":"describe this", "imageRefs":["u1"]}
96
+ ```
97
+
98
+ Limits (all enforced in `acceptChunk` / `claimImageRefs`): ≤4 images per
99
+ message, ≤8MB of base64 per image, ≤4 concurrent pending uploads per
100
+ connection, ≤256 chunks per upload. Assembly state (`Map<uploadId,
101
+ PendingUpload>`) is **per-connection, in-memory only** — a dropped socket
102
+ discards any partial uploads and the client just re-sends; there is no
103
+ cross-reconnect persistence by design. The server doesn't ack individual
104
+ chunks (that'd add N round-trips for no benefit since the proxy forwards
105
+ mid-stream frames fine) — it only replies with an `error` frame when a chunk
106
+ or `imageRefs` fails validation.
107
+
108
+ On a completed reference, the image is decoded and persisted via
109
+ `saveInboundMedia` into the user's workspace — the same storage path
110
+ telegram uses — and the message text gets a `[图片已保存: <path>]` marker
111
+ appended so history replay can show the image was there without re-sending
112
+ the bytes.
113
+
79
114
  ## Session lifecycle (the part everyone gets wrong)
80
115
 
81
116
  - Idle timeout (`idleRuntimeSessionTimeout`) terminates a session whose
@@ -54,7 +54,7 @@ Each fire:
54
54
  3. Check `job.workspacePath` exists — fail loudly if the workspace was moved/deleted.
55
55
  4. Spawn `halo cli -a <agent> -s cron-<jobId> -w <workspace>`, prompt on **stdin** (avoids Windows argv length limits). The stable `cron-<jobId>` session id means each fire *resumes the same session*, so the conversation accumulates and is reviewable in the admin Sessions tab. The child's pid is written back onto the `running` row (`cron_runs.pid`, nullable) — this is what makes the orphan sweep possible after a server crash.
56
56
  5. Tee stdout + stderr live to `~/.halo/global/logs/cron/<runId>.log`; capture stdout to memory for dispatch.
57
- 6. Enforce a **3600s timeout** via a Node timer (cross-platform, not the Linux-only `timeout(1)`), with a **two-phase kill**: SIGTERM the direct child first — the cli has a SIGTERM handler that stops the harness, repairs + flushes the session, kills its own shell_exec process groups, and exits 143 — then, if it's still alive after a 30s grace, SIGKILL the entire descendant tree (POSIX ppid-walk over one `ps` snapshot; `taskkill /T /F` on Windows) so no grandchild survives to double-write the workspace on the next fire. A ppid walk, not a process-group kill: the cli sandbox spawns shell_exec workers `detached` into their own groups, so `kill(-pid)` would miss them. Timed-out runs still report exit code 124. Generous because overlap-of-the-same-job is already blocked up-front in step 1; this is just the long-stop reaper for a truly stuck child.
57
+ 6. Enforce a **per-job timeout** (`job.timeoutSec ?? 3600`, the nullable `cron_jobs.timeout_sec` validated 60–21600 at every write surface; a change takes effect on the job's next fire, no croner re-instantiation needed) via a Node timer (cross-platform, not the Linux-only `timeout(1)`), with a **two-phase kill**: SIGTERM the direct child first — the cli has a SIGTERM handler that stops the harness, repairs + flushes the session, kills its own shell_exec process groups, and exits 143 — then, if it's still alive after a 30s grace, SIGKILL the entire descendant tree (POSIX ppid-walk over one `ps` snapshot; `taskkill /T /F` on Windows) so no grandchild survives to double-write the workspace on the next fire. A ppid walk, not a process-group kill: the cli sandbox spawns shell_exec workers `detached` into their own groups, so `kill(-pid)` would miss them. Timed-out runs still report exit code 124. Generous because overlap-of-the-same-job is already blocked up-front in step 1; this is just the long-stop reaper for a truly stuck child.
58
58
  7. Classify: exit 124 → `timeout`; non-zero → `failed`; zero but empty stdout → `failed` ("nothing to dispatch"); else `succeeded`.
59
59
  8. On success only, `dispatchToTargets(stdout, targets, job.workspacePath)` — the raw stdout goes down as-is; `MEDIA:` extraction happens per-target inside the dispatcher (see Dispatch model). If every target fails, downgrade to `failed`.
60
60
  9. `finalize`: write the terminal `cron_runs` row, update `cron_jobs.lastRun*`, disable one-shot jobs, broadcast. The `_inflight` entry is released in `finally`, regardless of outcome.
@@ -111,6 +111,6 @@ interface CronChannelDispatcher {
111
111
 
112
112
  ## Scope
113
113
 
114
- Supported: recurring (cron expr) + one-shot (`runAt`) schedules; per-timezone; multi-target fan-out; stable accumulating session per job; run-now (manual trigger); admin UI history with per-run logs + dispatch results.
114
+ Supported: recurring (cron expr) + one-shot (`runAt`) schedules; per-timezone; per-job configurable timeout (`timeoutSec`, 60–21600s, default 3600); multi-target fan-out; stable accumulating session per job; run-now (manual trigger); admin UI history with per-run logs + dispatch results.
115
115
 
116
116
  Not supported: sub-minute schedules below croner's resolution; in-process job execution (always a fresh cli child); conversation context carried *between different jobs* (each job has its own `cron-<jobId>` session).
@@ -170,8 +170,8 @@ File: `packages/server/src/routes/cron.ts`. Admin cookie auth. REST CRUD over `c
170
170
  | Method | Path | Purpose |
171
171
  |---|---|---|
172
172
  | GET | `/api/cron/jobs?limit=&before=` | Cursor-paginated newest-first by `createdAt`. Default `limit` 20, max 100; `before` is a `createdAt` cursor. Returns `{jobs, hasMore, nextCursor}`. Each job has `targets` decoded and a live `nextRunAt` (computed from schedule/runAt) |
173
- | POST | `/api/cron/jobs` | Create a job. Body: `{label?, workspacePath, agentId, userPrompt, schedule, runAt?, timezone?, targets?, enabled?}`. Exactly one of `schedule` (5-field cron) or `runAt` (epoch ms, future) is required; mutually exclusive. Returns `{ok, id}` |
174
- | PUT | `/api/cron/jobs/:id` | Partial update over the same fields. Re-validates `schedule` / `runAt` and enforces trigger-mode exclusivity on the **merged** row: both in one body → 400 `schedule and runAt are mutually exclusive`; supplying one side is a mode switch that implicitly clears the other; a body that clears the active side without supplying a replacement → 400 `schedule or runAt required`. `runAt` must be a finite future epoch-ms number or `null`. Flips schedule/unschedule on `enabled`. Returns `{ok}` |
173
+ | POST | `/api/cron/jobs` | Create a job. Body: `{label?, workspacePath, agentId, userPrompt, schedule, runAt?, timezone?, timeoutSec?, targets?, enabled?}`. Exactly one of `schedule` (5-field cron) or `runAt` (epoch ms, future) is required; mutually exclusive. `timeoutSec` must be an integer 60–21600 (else 400); unset = runner default 3600. Returns `{ok, id}` |
174
+ | PUT | `/api/cron/jobs/:id` | Partial update over the same fields. Re-validates `schedule` / `runAt` and enforces trigger-mode exclusivity on the **merged** row: both in one body → 400 `schedule and runAt are mutually exclusive`; supplying one side is a mode switch that implicitly clears the other; a body that clears the active side without supplying a replacement → 400 `schedule or runAt required`. `runAt` must be a finite future epoch-ms number or `null`. `timeoutSec` follows the same partial-body contract: omitted = unchanged, `null` = clear back to the default, a number is range-validated (60–21600) same as create. Flips schedule/unschedule on `enabled`. Returns `{ok}` |
175
175
  | DELETE | `/api/cron/jobs/:id` | Unschedule, delete every `cron_runs` row for the job, then delete the job. Returns `{ok}` |
176
176
  | POST | `/api/cron/jobs/:id/run-now` | Fire the job immediately (fire-and-forget). Returns `{ok}` |
177
177
  | POST | `/api/cron/reload` | Re-read every job from db and rebuild schedules. Returns `{ok}` |
package/dist/index.js CHANGED
@@ -2400,7 +2400,7 @@ var init_init = __esm({
2400
2400
  path8.resolve(__dirname, "..", "bundled-docs"),
2401
2401
  path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
2402
2402
  ];
2403
- TEMPLATE_VERSION = 49;
2403
+ TEMPLATE_VERSION = 50;
2404
2404
  VERSION_FILE = ".template-version";
2405
2405
  SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
2406
2406
  BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
@@ -20492,14 +20492,75 @@ var init_commands3 = __esm({
20492
20492
  import { Hono as Hono13 } from "hono";
20493
20493
  import path52 from "node:path";
20494
20494
  import fs44 from "node:fs";
20495
+ function sanitizeRuntimeId(runtimeSessionId) {
20496
+ return runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80);
20497
+ }
20495
20498
  function haloSessionId(runtimeSessionId) {
20496
- return `agentcore_${runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80)}`;
20499
+ return `agentcore_${sanitizeRuntimeId(runtimeSessionId)}`;
20497
20500
  }
20498
20501
  function userWorkspace(base, runtimeSessionId) {
20499
- const dir = path52.join(base, "users", runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80));
20502
+ const dir = path52.join(base, "users", sanitizeRuntimeId(runtimeSessionId));
20500
20503
  fs44.mkdirSync(dir, { recursive: true });
20501
20504
  return dir;
20502
20505
  }
20506
+ function acceptChunk(uploads, msg) {
20507
+ const uploadId = msg.uploadId;
20508
+ const seq = msg.seq;
20509
+ const total = msg.total;
20510
+ const mimeType = msg.mimeType;
20511
+ const data = msg.data;
20512
+ if (typeof uploadId !== "string" || !uploadId || uploadId.length > 64)
20513
+ return "image_chunk: uploadId must be a non-empty string (\u226464 chars)";
20514
+ if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 0)
20515
+ return "image_chunk: seq must be a non-negative integer";
20516
+ if (typeof total !== "number" || !Number.isInteger(total) || total < 1 || total > MAX_CHUNKS_PER_UPLOAD)
20517
+ return `image_chunk: total must be 1..${MAX_CHUNKS_PER_UPLOAD}`;
20518
+ if (typeof mimeType !== "string" || typeof data !== "string" || !data)
20519
+ return "image_chunk: string mimeType and non-empty string data required";
20520
+ if (seq >= total)
20521
+ return "image_chunk: seq out of range";
20522
+ let up = uploads.get(uploadId);
20523
+ if (!up) {
20524
+ if (uploads.size >= MAX_PENDING_UPLOADS)
20525
+ return `too many pending uploads (max ${MAX_PENDING_UPLOADS} per connection)`;
20526
+ up = { chunks: new Array(total), received: 0, mimeType, size: 0 };
20527
+ uploads.set(uploadId, up);
20528
+ }
20529
+ if (up.chunks.length !== total)
20530
+ return "image_chunk: total mismatch across chunks of one uploadId";
20531
+ if (up.chunks[seq] !== void 0)
20532
+ return null;
20533
+ if (up.size + data.length > MAX_IMAGE_BASE64_LENGTH) {
20534
+ uploads.delete(uploadId);
20535
+ return "image too large (max 8MB base64 per image)";
20536
+ }
20537
+ up.chunks[seq] = data;
20538
+ up.received++;
20539
+ up.size += data.length;
20540
+ return null;
20541
+ }
20542
+ function claimImageRefs(uploads, raw) {
20543
+ if (raw === void 0)
20544
+ return [];
20545
+ if (!Array.isArray(raw))
20546
+ return { error: "imageRefs must be an array" };
20547
+ if (raw.length > MAX_IMAGES_PER_MESSAGE)
20548
+ return { error: `too many images (max ${MAX_IMAGES_PER_MESSAGE} per message)` };
20549
+ const images = [];
20550
+ for (const ref of raw) {
20551
+ if (typeof ref !== "string")
20552
+ return { error: "imageRefs entries must be strings" };
20553
+ const up = uploads.get(ref);
20554
+ if (!up)
20555
+ return { error: `unknown uploadId: ${ref}` };
20556
+ if (up.received !== up.chunks.length)
20557
+ return { error: `upload ${ref} incomplete (${up.received}/${up.chunks.length} chunks)` };
20558
+ images.push({ data: up.chunks.join(""), mimeType: up.mimeType });
20559
+ }
20560
+ for (const ref of raw)
20561
+ uploads.delete(ref);
20562
+ return images;
20563
+ }
20503
20564
  async function ensureSession(sm, workspace, sessionId) {
20504
20565
  if (sm.getSessionById(sessionId))
20505
20566
  return;
@@ -20638,6 +20699,7 @@ function setupAgentCoreWebSocket(deps) {
20638
20699
  ready ??= ensureSession(sm, userWs, sessionId).then(() => attachListener());
20639
20700
  return ready;
20640
20701
  };
20702
+ const uploads = /* @__PURE__ */ new Map();
20641
20703
  ws.on("message", (raw) => {
20642
20704
  let msg;
20643
20705
  try {
@@ -20663,8 +20725,20 @@ function setupAgentCoreWebSocket(deps) {
20663
20725
  });
20664
20726
  return;
20665
20727
  }
20728
+ if (msg.type === "image_chunk") {
20729
+ const err = acceptChunk(uploads, msg);
20730
+ if (err)
20731
+ send({ type: "error", error: err });
20732
+ return;
20733
+ }
20666
20734
  const text5 = typeof msg.inputText === "string" ? msg.inputText.trim() : "";
20667
- if (!text5) {
20735
+ const claimed = claimImageRefs(uploads, msg.imageRefs);
20736
+ if (!Array.isArray(claimed)) {
20737
+ send({ type: "error", error: claimed.error });
20738
+ return;
20739
+ }
20740
+ const images = claimed;
20741
+ if (!text5 && images.length === 0) {
20668
20742
  return;
20669
20743
  }
20670
20744
  if (text5.startsWith("/")) {
@@ -20722,10 +20796,29 @@ function setupAgentCoreWebSocket(deps) {
20722
20796
  void (async () => {
20723
20797
  try {
20724
20798
  await ensureOnce();
20725
- sm.appendUserMessage(sessionId, text5);
20799
+ const notes = [];
20800
+ for (const img of images) {
20801
+ try {
20802
+ const savedPath = await saveInboundMedia({
20803
+ workspacePath: userWs,
20804
+ accountId: sanitizeRuntimeId(runtimeSessionId),
20805
+ channel: "agentcore",
20806
+ buffer: Buffer.from(img.data, "base64"),
20807
+ kind: "image",
20808
+ mimeType: img.mimeType
20809
+ });
20810
+ notes.push(`[\u56FE\u7247\u5DF2\u4FDD\u5B58: ${savedPath}]`);
20811
+ } catch (err) {
20812
+ console.log(`[AgentCore] image save failed: ${err instanceof Error ? err.message : String(err)}`);
20813
+ notes.push("[\u56FE\u7247]");
20814
+ }
20815
+ }
20816
+ const fullText = notes.length > 0 ? text5 ? `${text5}
20817
+ ${notes.join("\n")}` : notes.join("\n") : text5;
20818
+ sm.appendUserMessage(sessionId, fullText);
20726
20819
  const result = await sm.sendUserMessage(sessionId, `[channel: agentcore]
20727
20820
 
20728
- ${text5}`);
20821
+ ${fullText}`, images.length > 0 ? images : void 0);
20729
20822
  if (result === "queued")
20730
20823
  send({ type: "queued" });
20731
20824
  } catch (err) {
@@ -20736,19 +20829,25 @@ ${text5}`);
20736
20829
  ws.on("close", () => {
20737
20830
  unsubscribe?.();
20738
20831
  unsubscribe = null;
20832
+ uploads.clear();
20739
20833
  activeOverrides.delete(runtimeSessionId);
20740
20834
  });
20741
20835
  console.log(`[AgentCore] ws connected \u2014 session ${sessionId}`);
20742
20836
  });
20743
20837
  }
20744
- var SESSION_HEADER, activeOverrides;
20838
+ var SESSION_HEADER, MAX_IMAGES_PER_MESSAGE, MAX_IMAGE_BASE64_LENGTH, MAX_PENDING_UPLOADS, MAX_CHUNKS_PER_UPLOAD, activeOverrides;
20745
20839
  var init_agentcore = __esm({
20746
20840
  "packages/server/dist/routes/agentcore.js"() {
20747
20841
  "use strict";
20748
20842
  init_commands2();
20749
20843
  init_ui_log_builder();
20750
20844
  init_session_types();
20845
+ init_media_store();
20751
20846
  SESSION_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id";
20847
+ MAX_IMAGES_PER_MESSAGE = 4;
20848
+ MAX_IMAGE_BASE64_LENGTH = 8 * 1024 * 1024;
20849
+ MAX_PENDING_UPLOADS = 4;
20850
+ MAX_CHUNKS_PER_UPLOAD = 256;
20752
20851
  activeOverrides = /* @__PURE__ */ new Map();
20753
20852
  }
20754
20853
  });
@@ -22065,6 +22164,9 @@ function createCronDb(globalDir2) {
22065
22164
  if (!cols.some((c) => c.name === "run_at")) {
22066
22165
  sqlite.exec(`ALTER TABLE cron_jobs ADD COLUMN run_at INTEGER`);
22067
22166
  }
22167
+ if (!cols.some((c) => c.name === "timeout_sec")) {
22168
+ sqlite.exec(`ALTER TABLE cron_jobs ADD COLUMN timeout_sec INTEGER`);
22169
+ }
22068
22170
  const runCols = sqlite.prepare(`PRAGMA table_info(cron_runs)`).all();
22069
22171
  if (!runCols.some((c) => c.name === "pid")) {
22070
22172
  sqlite.exec(`ALTER TABLE cron_runs ADD COLUMN pid INTEGER`);
@@ -22105,6 +22207,10 @@ var init_cron_db = __esm({
22105
22207
  runAt: integer4("run_at"),
22106
22208
  /** IANA timezone name; null = host timezone. */
22107
22209
  timezone: text4("timezone"),
22210
+ /** Max seconds a single cron-fired cli may run before the runner kills
22211
+ * it. NULL = default 3600 (runner.ts CLI_TIMEOUT_SEC). Range 60–21600,
22212
+ * enforced at the write points (REST routes / skill helper). */
22213
+ timeoutSec: integer4("timeout_sec"),
22108
22214
  /** JSON array of `{channelType, accountId}` records. Cron output (final
22109
22215
  * assistant text) is dispatched to each one. Empty array = log only. */
22110
22216
  targets: text4("targets").notNull().default("[]"),
@@ -22154,6 +22260,7 @@ CREATE TABLE IF NOT EXISTS cron_jobs (
22154
22260
  schedule TEXT NOT NULL,
22155
22261
  run_at INTEGER,
22156
22262
  timezone TEXT,
22263
+ timeout_sec INTEGER,
22157
22264
  targets TEXT NOT NULL DEFAULT '[]',
22158
22265
  enabled INTEGER NOT NULL DEFAULT 1,
22159
22266
  last_run_status TEXT,
@@ -22587,6 +22694,7 @@ async function runJob(jobId, triggerKind) {
22587
22694
  const sessionId = `cron-${job.id}`;
22588
22695
  const args = ["cli", "-a", job.agentId, "-s", sessionId, "-w", job.workspacePath];
22589
22696
  const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
22697
+ const timeoutSec = job.timeoutSec ?? CLI_TIMEOUT_SEC;
22590
22698
  const teeStream = fs48.createWriteStream(logPath, { flags: "a" });
22591
22699
  teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
22592
22700
  `);
@@ -22617,7 +22725,7 @@ async function runJob(jobId, triggerKind) {
22617
22725
  const killTimer = setTimeout(() => {
22618
22726
  timedOut = true;
22619
22727
  teeStream.write(`
22620
- [runner] timeout ${CLI_TIMEOUT_SEC}s \u2014 SIGTERM, grace ${KILL_GRACE_SEC}s
22728
+ [runner] timeout ${timeoutSec}s \u2014 SIGTERM, grace ${KILL_GRACE_SEC}s
22621
22729
  `);
22622
22730
  try {
22623
22731
  child.kill("SIGTERM");
@@ -22629,7 +22737,7 @@ async function runJob(jobId, triggerKind) {
22629
22737
  if (child.pid)
22630
22738
  killTreeHard(child.pid);
22631
22739
  }, KILL_GRACE_SEC * 1e3);
22632
- }, CLI_TIMEOUT_SEC * 1e3);
22740
+ }, timeoutSec * 1e3);
22633
22741
  child.stdout?.on("data", (chunk) => {
22634
22742
  stdout += chunk.toString("utf-8");
22635
22743
  teeStream.write(chunk);
@@ -22665,7 +22773,7 @@ async function runJob(jobId, triggerKind) {
22665
22773
  let failureReason = null;
22666
22774
  if (result.exitCode === 124) {
22667
22775
  status = "timeout";
22668
- failureReason = `cli exceeded ${CLI_TIMEOUT_SEC}s timeout`;
22776
+ failureReason = `cli exceeded ${timeoutSec}s timeout`;
22669
22777
  } else if (result.exitCode !== 0) {
22670
22778
  status = "failed";
22671
22779
  failureReason = stderr.slice(-500).trim() || `cli exited ${result.exitCode}`;
@@ -22813,6 +22921,15 @@ function validateSchedule(expr) {
22813
22921
  return err instanceof Error ? err.message : String(err);
22814
22922
  }
22815
22923
  }
22924
+ function validateTimeoutSec(v) {
22925
+ if (typeof v !== "number" || !Number.isInteger(v)) {
22926
+ return "timeoutSec must be an integer (seconds)";
22927
+ }
22928
+ if (v < TIMEOUT_SEC_MIN || v > TIMEOUT_SEC_MAX) {
22929
+ return `timeoutSec must be between ${TIMEOUT_SEC_MIN} and ${TIMEOUT_SEC_MAX}`;
22930
+ }
22931
+ return null;
22932
+ }
22816
22933
  function createCronRoutes() {
22817
22934
  const router = new Hono14();
22818
22935
  router.get("/cron/jobs", async (c) => {
@@ -22856,6 +22973,11 @@ function createCronRoutes() {
22856
22973
  if (hasRunAt && body.runAt <= Date.now()) {
22857
22974
  return c.json({ error: "runAt must be in the future" }, 400);
22858
22975
  }
22976
+ if (body.timeoutSec !== void 0 && body.timeoutSec !== null) {
22977
+ const err = validateTimeoutSec(body.timeoutSec);
22978
+ if (err)
22979
+ return c.json({ error: err }, 400);
22980
+ }
22859
22981
  const id = newJobId();
22860
22982
  const now = Date.now();
22861
22983
  getCronDb().insert(cronJobs).values({
@@ -22867,6 +22989,7 @@ function createCronRoutes() {
22867
22989
  schedule: hasSchedule ? body.schedule : "",
22868
22990
  runAt: hasRunAt ? body.runAt : null,
22869
22991
  timezone: body.timezone ?? null,
22992
+ timeoutSec: body.timeoutSec ?? null,
22870
22993
  targets: JSON.stringify(body.targets ?? []),
22871
22994
  enabled: body.enabled === false ? 0 : 1,
22872
22995
  lastRunStatus: null,
@@ -22903,6 +23026,11 @@ function createCronRoutes() {
22903
23026
  if (body.runAt <= Date.now())
22904
23027
  return c.json({ error: "runAt must be in the future" }, 400);
22905
23028
  }
23029
+ if (body.timeoutSec !== void 0 && body.timeoutSec !== null) {
23030
+ const err = validateTimeoutSec(body.timeoutSec);
23031
+ if (err)
23032
+ return c.json({ error: err }, 400);
23033
+ }
22906
23034
  const hasSchedule = typeof body.schedule === "string" && body.schedule.trim().length > 0;
22907
23035
  const hasRunAt = typeof body.runAt === "number";
22908
23036
  if (hasSchedule && hasRunAt) {
@@ -22930,6 +23058,8 @@ function createCronRoutes() {
22930
23058
  patch.runAt = nextRunAt;
22931
23059
  if (body.timezone !== void 0)
22932
23060
  patch.timezone = body.timezone || null;
23061
+ if (body.timeoutSec !== void 0)
23062
+ patch.timeoutSec = body.timeoutSec;
22933
23063
  if (body.targets !== void 0)
22934
23064
  patch.targets = JSON.stringify(body.targets);
22935
23065
  if (body.enabled !== void 0)
@@ -23045,6 +23175,7 @@ function computeNextRun(j) {
23045
23175
  return null;
23046
23176
  }
23047
23177
  }
23178
+ var TIMEOUT_SEC_MIN, TIMEOUT_SEC_MAX;
23048
23179
  var init_cron = __esm({
23049
23180
  "packages/server/dist/routes/cron.js"() {
23050
23181
  "use strict";
@@ -23052,6 +23183,8 @@ var init_cron = __esm({
23052
23183
  init_runner();
23053
23184
  init_dispatcher();
23054
23185
  init_broadcast();
23186
+ TIMEOUT_SEC_MIN = 60;
23187
+ TIMEOUT_SEC_MAX = 21600;
23055
23188
  }
23056
23189
  });
23057
23190
 
@@ -28954,7 +29087,7 @@ var init_dist2 = __esm({
28954
29087
  init_git_ssh();
28955
29088
  __dirname3 = path64.dirname(fileURLToPath3(import.meta.url));
28956
29089
  PORT = config.server.port;
28957
- HALO_VERSION = "1.1.2";
29090
+ HALO_VERSION = "1.1.3";
28958
29091
  PROJECT_ROOT = findProjectRoot();
28959
29092
  HALO_HOME4 = path64.join(homedir24(), ".halo");
28960
29093
  GIT_SHA = readGitSha();
@@ -29856,7 +29989,7 @@ import path65 from "node:path";
29856
29989
  import fs58 from "node:fs";
29857
29990
  import { homedir as homedir25 } from "node:os";
29858
29991
  import { parseArgs as parseArgs2 } from "node:util";
29859
- var VERSION = "1.1.2";
29992
+ var VERSION = "1.1.3";
29860
29993
  var HELP_TOP = `Usage: halo <command> [options]
29861
29994
 
29862
29995
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -53,6 +53,7 @@ Sensible defaults if user didn't say:
53
53
  | timezone | leave unset (host time) |
54
54
  | targets | none (log only — the run shows in admin Cron tab) |
55
55
  | label | summarize from prompt + schedule |
56
+ | timeout | leave unset (3600s) — see `--timeout-sec` below |
56
57
 
57
58
  ### 2. Translate the schedule
58
59
 
@@ -78,6 +79,13 @@ today" or "send the report next Monday morning".
78
79
 
79
80
  Ambiguous time ("morning")? Ask once for the exact hour.
80
81
 
82
+ **Max run time (`--timeout-sec`, optional)** — each fire runs as a cli child
83
+ that the server kills after `timeout_sec` seconds (integer, 60–21600). Unset
84
+ = default 3600. Only set it when the user says so: a quick push job that
85
+ should fail fast ("kill it after 5 minutes" → `--timeout-sec 300`), or a
86
+ heavy analysis run that needs more than an hour (`--timeout-sec 7200`). On
87
+ `update`, pass `--timeout-sec ""` to clear back to the default.
88
+
81
89
  ### 3. Channels (only when delivery is wanted)
82
90
 
83
91
  Run `channels` first to see what's available:
@@ -106,6 +106,20 @@ def parse_targets(raw: str) -> list[dict]:
106
106
  return out
107
107
 
108
108
 
109
+ def parse_timeout_sec(raw: str | None) -> int | None:
110
+ """Validate --timeout-sec: integer seconds, 60–21600 (same bounds as the
111
+ admin REST routes). None = leave unset (runner default 3600)."""
112
+ if raw is None or not raw.strip():
113
+ return None
114
+ try:
115
+ v = int(raw.strip())
116
+ except ValueError:
117
+ die(f'invalid --timeout-sec "{raw}": expected an integer (seconds)')
118
+ if v < 60 or v > 21600:
119
+ die('--timeout-sec must be between 60 and 21600')
120
+ return v
121
+
122
+
109
123
  def parse_run_at(raw: str | None) -> int | None:
110
124
  """Accepts ISO-8601 (`2026-05-24T09:00:00`) or a unix-ms integer; returns
111
125
  epoch ms or None. Local time without offset is parsed as host TZ —
@@ -190,6 +204,7 @@ def cmd_create(args):
190
204
  die('--schedule and --run-at are mutually exclusive')
191
205
  if run_at_ms is not None and run_at_ms <= now_ms():
192
206
  die('--run-at must be in the future')
207
+ timeout_sec = parse_timeout_sec(args.timeout_sec)
193
208
 
194
209
  job_id = args.id or gen_job_id()
195
210
  now = now_ms()
@@ -202,8 +217,8 @@ def cmd_create(args):
202
217
 
203
218
  conn.execute(
204
219
  'INSERT INTO cron_jobs(id, label, workspace_path, agent_id, user_prompt, schedule, '
205
- 'run_at, timezone, targets, enabled, created_at, updated_at) '
206
- 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
220
+ 'run_at, timezone, timeout_sec, targets, enabled, created_at, updated_at) '
221
+ 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
207
222
  (
208
223
  job_id,
209
224
  args.label,
@@ -213,6 +228,7 @@ def cmd_create(args):
213
228
  args.schedule or '',
214
229
  run_at_ms,
215
230
  args.timezone,
231
+ timeout_sec,
216
232
  json.dumps(targets, ensure_ascii=False),
217
233
  0 if args.disabled else 1,
218
234
  now, now,
@@ -240,10 +256,14 @@ def cmd_update(args):
240
256
  die('--run-at must be in the future')
241
257
  sets.append(('run_at', run_at_ms))
242
258
  if args.timezone is not None: sets.append(('timezone', args.timezone))
259
+ if args.timeout_sec is not None:
260
+ # `--timeout-sec ""` clears back to the runner default (NULL),
261
+ # same convention as `--run-at ""`.
262
+ sets.append(('timeout_sec', parse_timeout_sec(args.timeout_sec)))
243
263
  if args.targets is not None:
244
264
  sets.append(('targets', json.dumps(parse_targets(args.targets), ensure_ascii=False)))
245
265
  if not sets:
246
- die('nothing to update — pass at least one --label/--workspace/--agent/--prompt/--schedule/--run-at/--timezone/--targets')
266
+ die('nothing to update — pass at least one --label/--workspace/--agent/--prompt/--schedule/--run-at/--timezone/--timeout-sec/--targets')
247
267
  sets.append(('updated_at', now_ms()))
248
268
 
249
269
  placeholders = ', '.join(f'{k} = ?' for k, _ in sets)
@@ -388,6 +408,8 @@ def main():
388
408
  c.add_argument('--run-at', dest='run_at',
389
409
  help='one-shot fire time, ISO-8601 (e.g. 2026-05-24T09:00) or unix ms — auto-disables after fire')
390
410
  c.add_argument('--timezone', help='IANA tz, e.g. Asia/Shanghai')
411
+ c.add_argument('--timeout-sec', dest='timeout_sec',
412
+ help='max run time in seconds, 60–21600 (default: unset = 3600)')
391
413
  c.add_argument('--targets',
392
414
  help='comma-separated channelType:accountId[:chatId] list, or JSON array. '
393
415
  'chatId pins delivery to a specific chat (e.g. when scheduling from inside a chat).')
@@ -403,6 +425,8 @@ def main():
403
425
  u.add_argument('--schedule')
404
426
  u.add_argument('--run-at', dest='run_at')
405
427
  u.add_argument('--timezone')
428
+ u.add_argument('--timeout-sec', dest='timeout_sec',
429
+ help='max run time in seconds, 60–21600; pass "" to clear back to default 3600')
406
430
  u.add_argument('--targets')
407
431
  u.set_defaults(func=cmd_update)
408
432