@turmind/halo 1.1.0 → 1.1.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><!--lOrGs0dR_jmXz9PgabRBc--><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-3dd0043ed0520b9d.js" async=""></script><script src="/_next/static/chunks/main-app-483b436873410ddc.js" async=""></script><script src="/_next/static/chunks/212-394c4fd40216e633.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/4-e7ba0b1ced30190b.js" async=""></script><script src="/_next/static/chunks/app/page-86cb00582129ae76.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-394c4fd40216e633.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"ThemeProvider\"]\n3:I[26967,[\"212\",\"static/chunks/212-394c4fd40216e633.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"AppI18nProvider\"]\n4:I[66048,[],\"\"]\n5:I[29926,[],\"\"]\n6:I[57917,[],\"ClientPageRoot\"]\n7:I[74003,[\"560\",\"static/chunks/1d2d5650-810a3ca1ae20cab5.js\",\"4\",\"static/chunks/4-e7ba0b1ced30190b.js\",\"212\",\"static/chunks/212-394c4fd40216e633.js\",\"974\",\"static/chunks/app/page-86cb00582129ae76.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\":\"lOrGs0dR-jmXz9PgabRBc\",\"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><!--Qre3_OObLUgfkv8BEU08q--><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-686cd94fdf9df63b.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-686cd94fdf9df63b.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\":\"Qre3-OObLUgfkv8BEU08q\",\"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-394c4fd40216e633.js","177","static/chunks/app/layout-c2fa8035998e48d5.js"],"ThemeProvider"]
3
- 3:I[26967,["212","static/chunks/212-394c4fd40216e633.js","177","static/chunks/app/layout-c2fa8035998e48d5.js"],"AppI18nProvider"]
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"]
4
4
  4:I[66048,[],""]
5
5
  5:I[29926,[],""]
6
6
  6:I[57917,[],"ClientPageRoot"]
7
- 7:I[74003,["560","static/chunks/1d2d5650-810a3ca1ae20cab5.js","4","static/chunks/4-e7ba0b1ced30190b.js","212","static/chunks/212-394c4fd40216e633.js","974","static/chunks/app/page-86cb00582129ae76.js"],"default"]
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-686cd94fdf9df63b.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":"lOrGs0dR-jmXz9PgabRBc","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":"Qre3-OObLUgfkv8BEU08q","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"}]]
@@ -211,6 +211,14 @@ The gate lives at the **tool callback layer** (`agents/session-tools.ts`), not i
211
211
 
212
212
  Because every event reduces into the **root's** UIState (a sub-agent's stream / tool calls land in `rootState.subSessionLogs[subId]`, keyed by the sub's full id), `getSessionView(subId)` must read from there — not from `uiStore.ensureUIState(subId)`, which is keyed by the sub's own id and would only ever see a cold disk-seeded snapshot (and, since the root is self-driven, never re-read disk → a live viewer would freeze on the first frame). So `getSessionView` special-cases a sub-session whose root holds it in `subSessionLogs` (read via `uiStore.getCachedUIState(rootId)`): it snapshots that sub-log directly (in-flight buffers included, fresher than disk). For the root-view path it calls `uiStore.prepareForView(sessionId, selfDriven)`, which evicts a stale cache when the session isn't self-driven so the snapshot reflects disk. Once the sub finishes, `agent_done` deletes the sub-log and the call falls through to the on-disk file. This is what lets the TUI `/log` viewer refresh a running sub-agent's log in real time.
213
213
 
214
+ ### Idle UIState eviction
215
+
216
+ `uiStates` used to be a monotonic cache — every root session ever opened or driven (admin view, channel message, cron run) kept its full UI messageLog in memory for the process lifetime, with eviction only on manual archive or delete. `SessionUIStore` now runs an **idle sweep**: an unref'd per-manager `setInterval` (60s period, never holds a CLI/TUI process open) drops any root's UIState once it has sat untouched — no event reduced in, no view built — past a **10min TTL**. The TTL is deliberately above `config.timeout.sessionGrace` (5min, see [WS disconnect resilience](#ws-disconnect-resilience)) so a WS detach → grace-reattach cycle lands on the still-warm state instead of paying a rehydration read.
217
+
218
+ An idle-sweep candidate is refused eviction while `hasActiveWorkInTree(rootId)` is true — any session in the root's tree (root or `root>…` descendants) with an in-flight `promise` or `isCompacting` exempts the **whole tree**, because a fire-and-forget sub-agent's live stream/tool buffers hang off the *root's* UIState even after the root itself released between turns. This was chosen over the two eviction points that looked simpler but fail on verified facts: dropping in `releaseSession` (turn end) would force an admin-open or mid-conversation channel session to re-read its full file from disk on every next message — hot-path I/O; "drop when no active subscribers" would exempt exactly the leak's dominant source, since a channel account's `InboundBridge` listener subscribes permanently.
219
+
220
+ `dropUIState` flushes a pending debounced persist **before** evicting — otherwise an immediate rehydration would read the shorter on-disk file, and that rehydrated state's next persist would overwrite the orphaned timer's later, complete write. After eviction, the next access (`ensureUIState`) rehydrates from disk exactly as it already does for a cold session — same code path, no special-casing.
221
+
214
222
  ### SQLite metadata
215
223
 
216
224
  Table `agent_sessions` holds metadata only (no runtime state):
@@ -391,6 +399,10 @@ Segments are immutable and append-only (`N` from 1, monotonically). Deletion is
391
399
 
392
400
  **The anchor moves up, never down.** `noteArchiveAnchor` no-ops for an equal or lower count (keeping per-turn snapshots — which omit the field, read as 0 — and ordinary re-subscribes idempotent), but a **higher** count re-anchors. Needed because a compact that fires while the session is open archives a new segment, and the next subscribe/reattach snapshot both carries the higher count *and* replaces the view with the shrunken active log: with a permanently pinned anchor those just-archived turns were in neither place — gone from the view and below a cursor counting down from the old anchor — showing a hole until the user reopened the session. Re-anchoring restarts the walk instead of extending it (prepends are oldest-first, so a newer segment can't splice onto an already-pulled tail); the re-pull costs no requests because `segmentCache` still holds them. Archived history renders collapsed and **read-only** — no Delete button, which matches `deleteExchange` refusing archived logs.
393
401
 
402
+ **Expand grows upward, toggle sits below.** The archived block lives above the live conversation, so opening it inserts content **above** the viewport; the toggle pins the distance-from-bottom across the click (the same anchor trick as the load-older prepend above) instead of letting the browser keep `scrollTop`, which would otherwise land the reader on the *oldest* archived message. Collapse is symmetric. The toggle bar itself renders **below** the expanded `MessageList`, not above it — since expansion grows upward, a bar above the content would end up a full scroll away once open, forcing a climb to collapse it. Its chevron points **up** when expanded (content it collapses is above it, not below). One scroll-to-top gesture (or the row's click) pulls exactly one segment; pulled segments live in `segmentCache`, a bounded FIFO capped at 20 (`SEGMENT_CACHE_LIMIT`, keyed `sessionId\0n`) — small enough to bound memory across a session of casual archive-browsing, and moot for any one segment since a committed segment is immutable and never re-fetched once cached.
403
+
404
+ **The Sessions tab's detail panel walks the same segments through a second store.** `session-chat-panel.tsx`'s viewer isn't wired to the Chat panel's singleton `archive-store` — repointing that store at an arbitrary selected session would clobber whatever the live chat is showing — so it keeps its own `session-archive-store.ts`, same one-segment-per-gesture walk, different anchor source: a non-live selection anchors from the `archiveCount` header of the sidebar's `GET /sessions/logs/:id` (the on-disk commit marker, read once per selection); selecting the live session (the one the open chat is on) instead mirrors the chat archive store's anchor, since that one already arrived off the subscribe snapshot and the sidebar skips the GET for it. Rendering differs too: pulled segments prepend **expanded** above the active log rather than behind the Chat panel's collapsed toggle, with a boundary row (`Archived · N segment(s) · M messages`) marking where the archive ends and the active log begins; archived rows render read-only, matching `deleteExchange` refusing sessions with any archived segment. No segment cache in this store — each gesture issues its own GET, acceptable since browsing the Sessions tab is comparatively rare next to the Chat panel's own scroll-up. A compact firing while the panel is open follows the same anchor-mobility rule as above for a non-live selection (the file-size `file:changed` refetch re-anchors, restarting the walk); for the live selection it waits on the next subscribe/reattach, same as the Chat panel.
405
+
394
406
  ## Model message format dependencies
395
407
 
396
408
  A sub-agent's `rawMessages` is stored directly as the runtime's `agent.messages`, whose format depends on the underlying provider. When swapping models, watch these:
@@ -4,6 +4,8 @@ All REST endpoints are served by Hono on port 9527 at `/api/`.
4
4
 
5
5
  Auth: most `/api/*` routes require a valid JWT cookie (`halo_token`). Exceptions in `PUBLIC_PATHS` (`middleware/auth.ts`) bypass the cookie: `/api/auth/login|check|logout` (but **not** `/api/auth/change-password`), the web-channel routes (`/api/web/chat|stop|history|subscribe|file`), `/api/show/state|session`, and `/api/metrics` — these are unauthenticated or use a web-channel `x-token` instead.
6
6
 
7
+ Compression: every response negotiates gzip/deflate via `hono/compress` (`threshold: 1024` — bodies under 1 KiB pass through uncompressed), mounted after CORS so it covers both `/api/*` JSON and the served admin static assets. `text/event-stream` (SSE) is excluded, so `/api/web/chat`'s streamed reply is unaffected; WS upgrades never enter the Hono fetch pipeline either.
8
+
7
9
  ## Health
8
10
 
9
11
  | Method | Path | Purpose |
@@ -24,13 +24,15 @@ Hover actions on user-role turns: the blue sticky user bubble, plus the sub-agen
24
24
  - **Delete** (confirm dialog) — removes the whole exchange (the user turn + all responses up to the next user turn) with **two-layer semantics**: the LLM context (`rawMessages`) drops the turn physically — the model never sees it again, freeing context; the UI keeps the messages, rendered greyed-out with a "deleted" tag, as an audit trail. No undo; a deleted exchange loses its Delete button. Rejected with an error toast while the agent is running or compacting. Root sessions only (sub-session logs don't offer Delete). If the turn was already compacted out of raw context, only the UI marking happens (silent degrade). Once a session has **archived history** the action is refused for the whole session with a plain explanatory notice (not an `Error:` bubble) — per-turn positions can no longer be mapped once older turns left the active file. Design details in [design/session.md](../design/session.md#exchange-deletion-soft-ui--hard-raw), protocol in [design/ws.md](../design/ws.md).
25
25
 
26
26
  ### Archived history (scroll up to load)
27
- Long sessions get their older exchanges moved out of the active log on compact, so opening a session stays fast no matter how long it has run. What the user sees:
27
+ Long sessions get their older exchanges moved out of the active log on compact, so opening a session stays fast no matter how long it has run. Loading older messages is **two-tiered** — a local, no-network tier first, then the network-backed archive:
28
28
 
29
- - A **"Load earlier messages"** row appears above the conversation only for sessions that actually have archived history; it loads one archive chunk per click, oldest-newer order preserved, and becomes **"No earlier messages"** once everything is loaded
30
- - Scrolling to the very top of the message area triggers the same load automatically (re-armed only after scrolling back down, so one gesture doesn't cascade through the whole archive), and the reading position is preserved when the older messages prepend — the view doesn't jump
31
- - Loaded history sits in a **collapsed** block ("Archived · N segment(s) · M messages") — the user scrolled up for older context, not to have hundreds of exchanges re-flow the view; expanding renders them like normal messages
29
+ - A session opens rendering only the **most recent 30 exchanges**; older ones already sit in memory (loaded with the session) but aren't mounted, so a long session doesn't cost a slow first render
30
+ - Scrolling to the very top (or clicking the **"Show earlier messages"** row) widens this local window by 30 more exchanges at a time, straight from memory — no request. This can repeat until every in-memory exchange is shown
31
+ - Only once the local window is fully expanded does the next scroll-to-top (or the "Load earlier messages" row that takes over the same slot) reach for the **network** tier: it loads one archived segment per gesture, oldest-newer order preserved, and becomes "No earlier messages" once everything is loaded. The two tiers can't be skipped or reordered — the archive row only appears once there's nothing left to expand locally
32
+ - Either tier preserves the reading position when older messages prepend — the view doesn't jump
33
+ - Loaded archive segments sit in a **collapsed** block ("Archived · N segment(s) · M messages") — the user scrolled up for older context, not to have hundreds of exchanges re-flow the view; expanding renders them like normal messages, growing **upward** so the viewport stays pinned to the newest end, with the expand/collapse toggle below the revealed content
32
34
  - Archived exchanges are **read-only**: Copy still works, Delete is absent (the server refuses it for archived sessions)
33
- - Chunks already loaded aren't re-fetched; a failed load can simply be retried with the same row
35
+ - Segments already loaded aren't re-fetched; a failed load can simply be retried with the same row
34
36
 
35
37
  ### Session sidebar (right side)
36
38
  A fixed, collapsible list of the workspace's root sessions on the right edge of the chat panel (terminal-list style, ~200px). Replaces the old popup dropdown — the History button (with count badge) left of the composer and the "N previous sessions" link in the empty state both just toggle it now. Open/collapsed state persists globally in `localStorage` (`halo_session_sidebar_open`), default open.
@@ -17,6 +17,7 @@ Session history viewer: hierarchy tree, message playback, debug mode, system pro
17
17
  - All messages rendered by role
18
18
  - Assistant messages render Markdown
19
19
  - System messages summarise tool calls
20
+ - For a session whose history was compacted, scrolling to the top shows a "load earlier messages" row; scrolling further or clicking it pulls one archived segment at a time. Pulled history renders expanded above the active log, with a divider marking where it ends, and is read-only (no delete)
20
21
 
21
22
  ### Debug mode
22
23
  Top Debug toggle (Bug icon). When on:
package/dist/index.js CHANGED
@@ -5062,7 +5062,7 @@ var init_ui_log_builder = __esm({
5062
5062
  import fsSync5 from "node:fs";
5063
5063
  import path19 from "node:path";
5064
5064
  import { eq as eq3 } from "drizzle-orm";
5065
- var SessionUIStore;
5065
+ var UI_STATE_IDLE_TTL_MS, UI_STATE_SWEEP_PERIOD_MS, SessionUIStore;
5066
5066
  var init_session_ui_store = __esm({
5067
5067
  "packages/server/dist/agents/session-ui-store.js"() {
5068
5068
  "use strict";
@@ -5071,6 +5071,8 @@ var init_session_ui_store = __esm({
5071
5071
  init_session_store();
5072
5072
  init_session_archive();
5073
5073
  init_ui_log_builder();
5074
+ UI_STATE_IDLE_TTL_MS = 10 * 6e4;
5075
+ UI_STATE_SWEEP_PERIOD_MS = 6e4;
5074
5076
  SessionUIStore = class {
5075
5077
  host;
5076
5078
  db;
@@ -5090,9 +5092,13 @@ var init_session_ui_store = __esm({
5090
5092
  uiStateProjectPaths = /* @__PURE__ */ new Map();
5091
5093
  /** Debounced persist timers — prevents flooding disk with writes during rapid tool loops */
5092
5094
  persistTimers = /* @__PURE__ */ new Map();
5095
+ /** Last activity (event reduced / view built) per uiStates key — the idle
5096
+ * sweep's eviction clock. Invariant: every uiStates key has an entry. */
5097
+ uiStateTouched = /* @__PURE__ */ new Map();
5093
5098
  constructor(host) {
5094
5099
  this.host = host;
5095
5100
  this.db = host.getDb();
5101
+ setInterval(() => this.sweepIdleUIStates(), UI_STATE_SWEEP_PERIOD_MS).unref();
5096
5102
  }
5097
5103
  // ── Event routing ──────────────────────────────────────────────────
5098
5104
  /** Store global event handler (backward compat — Phase 2 only) */
@@ -5229,6 +5235,7 @@ var init_session_ui_store = __esm({
5229
5235
  * from the on-disk messages so the view is complete.
5230
5236
  */
5231
5237
  ensureUIState(rootId) {
5238
+ this.uiStateTouched.set(rootId, Date.now());
5232
5239
  const existing = this.uiStates.get(rootId);
5233
5240
  if (existing)
5234
5241
  return existing;
@@ -5449,10 +5456,48 @@ var init_session_ui_store = __esm({
5449
5456
  /**
5450
5457
  * Drop the in-memory UIState for a session (e.g. after stopSession / clear).
5451
5458
  * The disk file stays. This is a no-op if not loaded.
5459
+ *
5460
+ * A pending debounced persist is flushed BEFORE eviction: dropping first
5461
+ * would let a later ensureUIState rehydrate from a disk file that's missing
5462
+ * the last batch, and the next persist of that shorter log would overwrite
5463
+ * the orphaned timer's eventual write — messages lost. Must not be called
5464
+ * while the session tree is mid-turn (callers gate on that): flushing then
5465
+ * would snapshot a temp in-flight assistant message that the turn's own
5466
+ * flush would later duplicate.
5452
5467
  */
5453
5468
  dropUIState(sessionId) {
5469
+ const state = this.uiStates.get(sessionId);
5470
+ if (state && this.persistTimers.has(sessionId))
5471
+ this.flushPersist(sessionId, state);
5454
5472
  this.uiStates.delete(sessionId);
5455
5473
  this.uiStateProjectPaths.delete(sessionId);
5474
+ this.uiStateTouched.delete(sessionId);
5475
+ }
5476
+ /**
5477
+ * Evict UIStates whose tree has been idle past the TTL. This is THE eviction
5478
+ * point for the common leak (channel / cron / admin-viewed sessions that just
5479
+ * stop getting traffic) — archive and delete only cover explicit user action.
5480
+ *
5481
+ * Safety gates, in order:
5482
+ * - tree activity: an in-flight turn/compact anywhere in the tree keeps the
5483
+ * root's state (it holds the live sub buffers + streaming state);
5484
+ * - pending debounced persist counts as activity-in-progress? No — drop
5485
+ * flushes it first (see dropUIState), so a just-debounced write is safe.
5486
+ *
5487
+ * Re-entry cost after eviction: one loadSessionMessages disk read on the next
5488
+ * event/view — turn-start cadence at worst (ensureSession already does a
5489
+ * full-file read there anyway), never per-event.
5490
+ */
5491
+ sweepIdleUIStates() {
5492
+ const cutoff = Date.now() - UI_STATE_IDLE_TTL_MS;
5493
+ for (const [rootId, touched] of this.uiStateTouched) {
5494
+ if (touched > cutoff)
5495
+ continue;
5496
+ if (this.host.hasActiveWorkInTree(rootId))
5497
+ continue;
5498
+ this.dropUIState(rootId);
5499
+ console.debug(`[SessionUIStore] evicted idle UIState ${rootId}`);
5500
+ }
5456
5501
  }
5457
5502
  // ── Operations the manager's session-lifecycle methods delegate here ──
5458
5503
  // These exist so the manager never reaches into the maps above directly.
@@ -5493,6 +5538,7 @@ var init_session_ui_store = __esm({
5493
5538
  purge(id) {
5494
5539
  this.uiStates.delete(id);
5495
5540
  this.uiStateProjectPaths.delete(id);
5541
+ this.uiStateTouched.delete(id);
5496
5542
  const timer = this.persistTimers.get(id);
5497
5543
  if (timer) {
5498
5544
  clearTimeout(timer);
@@ -11416,6 +11462,19 @@ ${result.summary}`, agentName: "user", report: true, taskId });
11416
11462
  }
11417
11463
  return false;
11418
11464
  }
11465
+ /** Any in-flight turn or compact anywhere in a root's session tree (root or
11466
+ * `root>…` descendants). SessionUIStoreHost member — the UI store's idle
11467
+ * sweep gates eviction on this, because a root's UIState carries its running
11468
+ * subs' live buffers even while the root itself is between turns. */
11469
+ hasActiveWorkInTree(rootId) {
11470
+ for (const [id, session] of this.sessions) {
11471
+ if (id !== rootId && !id.startsWith(`${rootId}>`))
11472
+ continue;
11473
+ if (session.promise !== null || session.isCompacting)
11474
+ return true;
11475
+ }
11476
+ return false;
11477
+ }
11419
11478
  /** Get context config for a session */
11420
11479
  async getContextConfig(sessionId) {
11421
11480
  const session = this.sessions.get(sessionId);
@@ -28648,6 +28707,7 @@ var init_auth = __esm({
28648
28707
  var dist_exports = {};
28649
28708
  import { Hono as Hono21 } from "hono";
28650
28709
  import { cors } from "hono/cors";
28710
+ import { compress } from "hono/compress";
28651
28711
  import { serve } from "@hono/node-server";
28652
28712
  import { serveStatic } from "@hono/node-server/serve-static";
28653
28713
  import { WebSocketServer } from "ws";
@@ -28809,7 +28869,7 @@ var init_dist2 = __esm({
28809
28869
  init_git_ssh();
28810
28870
  __dirname3 = path63.dirname(fileURLToPath3(import.meta.url));
28811
28871
  PORT = config.server.port;
28812
- HALO_VERSION = "1.1.0";
28872
+ HALO_VERSION = "1.1.1";
28813
28873
  PROJECT_ROOT = findProjectRoot();
28814
28874
  HALO_HOME4 = path63.join(homedir24(), ".halo");
28815
28875
  GIT_SHA = readGitSha();
@@ -28873,6 +28933,7 @@ var init_dist2 = __esm({
28873
28933
  maxAge: 86400,
28874
28934
  credentials: allowlist.length > 0
28875
28935
  }));
28936
+ app.use("/*", compress({ threshold: 1024 }));
28876
28937
  app.use("/api/*", authMiddleware());
28877
28938
  authRoutes = createAuthRoutes();
28878
28939
  app.route("/api", authRoutes);
@@ -29710,7 +29771,7 @@ import path64 from "node:path";
29710
29771
  import fs57 from "node:fs";
29711
29772
  import { homedir as homedir25 } from "node:os";
29712
29773
  import { parseArgs as parseArgs2 } from "node:util";
29713
- var VERSION = "1.1.0";
29774
+ var VERSION = "1.1.1";
29714
29775
  var HELP_TOP = `Usage: halo <command> [options]
29715
29776
 
29716
29777
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "1.1.0",
3
+ "version": "1.1.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",
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[212],{8808:(e,t,o)=>{o.d(t,{I18nProvider:()=>d,useI18n:()=>i,useT:()=>l});var n=o(69625),a=o(43853),s=o(17246);function r(e){return"zh"===e?"zh-CN":"en-US"}let c=(0,a.createContext)({lang:"en",bcp:"en-US",t:e=>e,setLang:()=>{},refreshFromServer:async()=>{}});function i(){return(0,a.useContext)(c)}function l(){return(0,a.useContext)(c).t}function d(e){let{en:t,zh:o,children:i}=e,[l,d]=(0,a.useState)("en");(0,a.useEffect)(()=>{d(function(){let e=localStorage.getItem("halo_lang");return"zh"===e||"en"===e?e:navigator.language.startsWith("zh")?"zh":"en"}())},[]);let u=(0,a.useCallback)(async()=>{try{var e,t,o;let n=(await s.F.settings.getSchema()).sections.find(e=>"general"===e.namespaceId),a=null==n?void 0:n.fields.find(e=>"language"===e.key),r=(o=null!=(t=null!=(e=null==a?void 0:a.value)?e:null==a?void 0:a.default)?t:null,"string"==typeof o&&o.toLowerCase().startsWith("zh")?"zh":"en");d(r),localStorage.setItem("halo_lang",r)}catch(e){}},[]);(0,a.useEffect)(()=>{u()},[u]);let g=(0,a.useCallback)(e=>{d(e),localStorage.setItem("halo_lang",e),s.F.settings.patch("global","general.language",r(e)).catch(()=>{})},[]),p=(0,a.useCallback)((e,n)=>{var a,s;let r=null!=(s=null!=(a=("zh"===l?o:t)[e])?a:t[e])?s:e;if(n)for(let[e,t]of Object.entries(n))r=r.replaceAll("{".concat(e,"}"),String(t));return r},[l,t,o]);return(0,n.jsx)(c.Provider,{value:{lang:l,bcp:r(l),t:p,setLang:g,refreshFromServer:u},children:i})}},17246:(e,t,o)=>{o.d(t,{F:()=>s});let n="/api";async function a(e,t){let o=await fetch("".concat(n).concat(e),{headers:{"Content-Type":"application/json",...null==t?void 0:t.headers},...t});if(!o.ok){let e=await o.text().catch(()=>"");throw Error("API error ".concat(o.status,": ").concat(e||o.statusText))}return o.json()}let s={health:()=>fetch("".concat(n,"/health")).then(e=>e.json()),fs:{home:()=>a("/fs/home"),exists:e=>a("/fs/exists?path=".concat(encodeURIComponent(e))),browse:e=>a("/fs/browse?path=".concat(encodeURIComponent(e))),resolveWorkspace:e=>a("/fs/workspace/resolve",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})})},files:{read:(e,t)=>a("/files?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t))),stat:(e,t)=>a("/files/stat?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t))),tree(e,t){let o=new URLSearchParams;return o.set("projectId",e),t&&o.set("path",t),a("/files/tree?".concat(o.toString()))},search(e,t,o,n){let s=new URLSearchParams;return s.set("projectId",e),s.set("q",t),o&&s.set("limit",String(o)),n&&s.set("dirsOnly","1"),a("/files/search?".concat(s.toString()))},upload(e,t,o,a){let s=new FormData;for(let n of(s.append("projectId",t),o&&s.append("targetDir",o),e))s.append("files",n);return new Promise((e,t)=>{let o=new XMLHttpRequest;o.open("POST","".concat(n,"/files/upload")),o.withCredentials=!0,o.upload.onprogress=e=>{a&&(e.lengthComputable?a(e.loaded,e.total):a(e.loaded,0))},o.onload=()=>{if(o.status>=200&&o.status<300)try{e(JSON.parse(o.responseText))}catch(e){t(e)}else t(Error("Upload failed: ".concat(o.status," ").concat(o.statusText)))},o.onerror=()=>t(Error("Upload failed: network error")),o.onabort=()=>t(Error("Upload aborted")),o.send(s)})},save:(e,t,o)=>a("/files",{method:"PUT",body:JSON.stringify({path:e,content:t,projectId:o})}),create:(e,t)=>a("/files/new",{method:"POST",body:JSON.stringify({path:e,projectId:t})}),mkdir:(e,t)=>a("/files/mkdir",{method:"POST",body:JSON.stringify({path:e,projectId:t})}),rename:(e,t,o)=>a("/files/rename",{method:"POST",body:JSON.stringify({oldPath:e,newPath:t,projectId:o})}),remove:(e,t)=>a("/files?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t)),{method:"DELETE"}),downloadUrl:(e,t)=>"/api/files/download?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t)),viewUrl:(e,t)=>"/api/files/download?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t),"&inline=1")},dataPreview:{sqliteTables:(e,t,o)=>a("/data-preview/sqlite/tables?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t)),{signal:o}),sqliteRows:(e,t,o,n,s,r)=>a("/data-preview/sqlite/rows?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t),"&table=").concat(encodeURIComponent(o),"&offset=").concat(n,"&limit=").concat(s),{signal:r}),parquet:(e,t,o,n,s)=>a("/data-preview/parquet?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t),"&offset=").concat(o,"&limit=").concat(n),{signal:s}),csv:(e,t,o,n,s)=>a("/data-preview/csv?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t),"&offset=").concat(o,"&limit=").concat(n),{signal:s})},git:{status:e=>a("/git/status?projectId=".concat(encodeURIComponent(e))),ignored:e=>a("/git/ignored?projectId=".concat(encodeURIComponent(e))),diff(e,t,o,n,s){let r=new URLSearchParams;return r.set("projectId",e),r.set("path",t),r.set("staged",o?"1":"0"),n&&r.set("from",n),s&&r.set("commit",s),a("/git/diff?".concat(r.toString()))},log(e,t){let o=new URLSearchParams({projectId:e});return t&&o.set("limit",String(t)),a("/git/log?".concat(o.toString()))},commitFiles(e,t){let o=new URLSearchParams({projectId:e,hash:t});return a("/git/commit-files?".concat(o.toString()))},stage:(e,t)=>a("/git/stage",{method:"POST",body:JSON.stringify({projectId:e,paths:t})}),unstage:(e,t)=>a("/git/unstage",{method:"POST",body:JSON.stringify({projectId:e,paths:t})}),commit:(e,t)=>a("/git/commit",{method:"POST",body:JSON.stringify({projectId:e,message:t})}),push:e=>a("/git/push",{method:"POST",body:JSON.stringify({projectId:e})}),pull:e=>a("/git/pull",{method:"POST",body:JSON.stringify({projectId:e})}),init:e=>a("/git/init",{method:"POST",body:JSON.stringify({projectId:e})}),remotes:e=>a("/git/remotes?projectId=".concat(encodeURIComponent(e))),addRemote:(e,t,o)=>a("/git/remote",{method:"POST",body:JSON.stringify({projectId:e,url:t,name:o})}),getCredentials:()=>a("/git/credentials"),saveCredentials:e=>a("/git/credentials",{method:"POST",body:JSON.stringify(e)}),deleteCredential:e=>a("/git/credentials/".concat(encodeURIComponent(e)),{method:"DELETE"}),sshKeys:()=>a("/git/ssh/keys"),sshAgent:()=>a("/git/ssh/agent"),unlockSshKey:e=>a("/git/ssh/unlock",{method:"POST",body:JSON.stringify(e)}),getRemoteProtocol:e=>a("/git/remote/protocol?projectId=".concat(encodeURIComponent(e))),setRemoteProtocol:(e,t)=>a("/git/remote/protocol",{method:"POST",body:JSON.stringify({projectId:e,to:t})})},agentConfigs:{list(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/agent-configs".concat(t))},create:e=>a("/agent-configs",{method:"POST",body:JSON.stringify(e)}),getYaml(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/yaml").concat(n))},saveYaml:(e,t,o)=>a("/agent-configs/".concat(e,"/yaml"),{method:"PUT",body:JSON.stringify({yaml:t,scope:null==o?void 0:o.scope,projectId:null==o?void 0:o.projectId})}),remove(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e).concat(n),{method:"DELETE"})},toggle(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/toggle").concat(n),{method:"PATCH"})},tools:()=>a("/agent-configs/tools"),models:()=>a("/agent-configs/models"),getMdFile(e,t,o){let n=new URLSearchParams;(null==o?void 0:o.scope)&&n.set("scope",o.scope),(null==o?void 0:o.projectId)&&n.set("projectId",o.projectId);let s=n.toString()?"?".concat(n):"";return a("/agent-configs/".concat(e,"/md/").concat(t).concat(s))},saveMdFile:(e,t,o,n)=>a("/agent-configs/".concat(e,"/md/").concat(t),{method:"PUT",body:JSON.stringify({content:o,scope:null==n?void 0:n.scope,projectId:null==n?void 0:n.projectId})}),getMdAll(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/md-all").concat(n))},listSessions(e,t){let o=new URLSearchParams;(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId),(null==t?void 0:t.source)&&o.set("source",t.source);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/sessions").concat(n))},getSession(e,t,o){let n=new URLSearchParams;(null==o?void 0:o.projectId)&&n.set("projectId",o.projectId),(null==o?void 0:o.source)&&n.set("source",o.source);let s=n.toString()?"?".concat(n):"";return a("/agent-configs/".concat(e,"/sessions/").concat(t).concat(s))},saveSession:(e,t)=>a("/agent-configs/".concat(e,"/sessions"),{method:"POST",body:JSON.stringify(t)}),deleteSession(e,t,o){let n=new URLSearchParams;(null==o?void 0:o.projectId)&&n.set("projectId",o.projectId),(null==o?void 0:o.source)&&n.set("source",o.source);let s=n.toString()?"?".concat(n):"";return a("/agent-configs/".concat(e,"/sessions/").concat(t).concat(s),{method:"DELETE"})},deleteAllSessions(e,t){let o=new URLSearchParams;(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId),(null==t?void 0:t.source)&&o.set("source",t.source),o.set("all","1");let n="?".concat(o);return a("/agent-configs/".concat(e,"/sessions").concat(n),{method:"DELETE"})}},sessionLogs:{list(e,t){let o=new URLSearchParams;e&&o.set("projectId",e),(null==t?void 0:t.includeArchived)&&o.set("includeArchived","1"),(null==t?void 0:t.parentId)!==void 0&&o.set("parentId",t.parentId),(null==t?void 0:t.rootOnly)&&o.set("rootOnly","1"),(null==t?void 0:t.cursor)!==void 0&&o.set("cursor",String(t.cursor)),(null==t?void 0:t.limit)!==void 0&&o.set("limit",String(t.limit));let n=o.toString()?"?".concat(o):"";return a("/sessions/logs".concat(n))},goal:e=>a("/sessions/goal?projectId=".concat(encodeURIComponent(e))),get(e,t){let o=new URLSearchParams;t&&o.set("projectId",t);let n=o.toString()?"?".concat(o):"";return a("/sessions/logs/".concat(e).concat(n))},delete(e,t){let o=new URLSearchParams;t&&o.set("projectId",t);let n=o.toString()?"?".concat(o):"";return a("/sessions/logs/".concat(e).concat(n),{method:"DELETE"})},archiveSegment:(e,t,o)=>a("/sessions/logs/".concat(e,"/archive/").concat(t,"?projectId=").concat(encodeURIComponent(o))),rename(e,t,o){let n=new URLSearchParams;o&&n.set("projectId",o);let s=n.toString()?"?".concat(n):"";return a("/sessions/logs/".concat(e).concat(s),{method:"PATCH",body:JSON.stringify({title:t})})}},commands:{list(e,t,o){let n=new URLSearchParams;e&&n.set("projectId",e),t&&n.set("sessionId",t),o&&n.set("agentId",o);let s=n.toString()?"?".concat(n.toString()):"";return a("/commands".concat(s))}},skills:{list(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/skills".concat(t))},create:e=>a("/skills",{method:"POST",body:JSON.stringify(e)}),remove(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/skills/".concat(e).concat(n),{method:"DELETE"})},toggle(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/skills/".concat(e,"/toggle").concat(n),{method:"PATCH"})}},wechat:{listAccounts:()=>a("/wechat/accounts"),startLogin:e=>a("/wechat/login/start",{method:"POST",body:JSON.stringify({sessionKey:e})}),waitLogin:e=>a("/wechat/login/wait",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/wechat/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/wechat/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"})},telegram:{listAccounts:()=>a("/telegram/accounts"),createAccount:e=>a("/telegram/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/telegram/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/telegram/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"})},web:{listAccounts:()=>a("/web/accounts"),createAccount:e=>a("/web/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/web/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/web/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"})},slack:{listAccounts:()=>a("/slack/accounts"),createAccount:e=>a("/slack/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/slack/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/slack/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"}),searchTargets:(e,t)=>a("/slack/accounts/".concat(encodeURIComponent(e),"/search?q=").concat(encodeURIComponent(t)))},feishu:{listAccounts:()=>a("/feishu/accounts"),createAccount:e=>a("/feishu/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/feishu/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/feishu/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"}),searchTargets:(e,t)=>a("/feishu/accounts/".concat(encodeURIComponent(e),"/search?q=").concat(encodeURIComponent(t)))},settings:{get(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/settings".concat(t))},getSchema(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/settings/schema".concat(t))},save:(e,t,o)=>a("/settings",{method:"PUT",body:JSON.stringify({scope:e,projectId:o,settings:t})}),patch:(e,t,o,n)=>a("/settings",{method:"PATCH",body:JSON.stringify({scope:e,projectId:n,key:t,value:o})}),remove:(e,t,o)=>a("/settings",{method:"DELETE",body:JSON.stringify({scope:e,projectId:o,key:t})})},evolution:{listRuns(e){let t=new URLSearchParams;(null==e?void 0:e.archived)&&t.set("archived","1"),(null==e?void 0:e.limit)!==void 0&&t.set("limit",String(e.limit)),(null==e?void 0:e.before)!==void 0&&t.set("before",String(e.before));let o=t.toString()?"?".concat(t):"";return a("/evolution/runs".concat(o))},getRun:e=>a("/evolution/runs/".concat(encodeURIComponent(e))),approve:(e,t)=>a("/evolution/runs/".concat(encodeURIComponent(e),"/approve"),{method:"POST",body:JSON.stringify({reviewerHint:t})}),reject:e=>a("/evolution/runs/".concat(encodeURIComponent(e),"/reject"),{method:"POST",body:"{}"}),addHint:(e,t)=>a("/evolution/runs/".concat(encodeURIComponent(e),"/hint"),{method:"POST",body:JSON.stringify({hint:t})}),retry:(e,t)=>a("/evolution/runs/".concat(encodeURIComponent(e),"/retry"),{method:"POST",body:JSON.stringify({hint:t})}),delete:e=>a("/evolution/runs/".concat(encodeURIComponent(e)),{method:"DELETE"})},cron:{listJobs(e){let t=new URLSearchParams;(null==e?void 0:e.limit)!==void 0&&t.set("limit",String(e.limit)),(null==e?void 0:e.before)!==void 0&&t.set("before",String(e.before));let o=t.toString()?"?".concat(t):"";return a("/cron/jobs".concat(o))},createJob:e=>a("/cron/jobs",{method:"POST",body:JSON.stringify(e)}),updateJob:(e,t)=>a("/cron/jobs/".concat(encodeURIComponent(e)),{method:"PUT",body:JSON.stringify(t)}),deleteJob:e=>a("/cron/jobs/".concat(encodeURIComponent(e)),{method:"DELETE"}),runNow:e=>a("/cron/jobs/".concat(encodeURIComponent(e),"/run-now"),{method:"POST",body:"{}"}),listRuns(e,t){let o=new URLSearchParams;(null==t?void 0:t.limit)&&o.set("limit",String(t.limit)),(null==t?void 0:t.before)&&o.set("before",t.before);let n=o.toString()?"?".concat(o):"";return a("/cron/jobs/".concat(encodeURIComponent(e),"/runs").concat(n))},getRunLog:e=>a("/cron/runs/".concat(encodeURIComponent(e),"/log")),listChannelTargets:()=>a("/cron/channel-targets"),meta:()=>a("/cron/meta")}}},26967:(e,t,o)=>{o.d(t,{AppI18nProvider:()=>c});var n=o(69625),a=o(8808);let s={"channels.wechat":"WeChat","tg.title":"Telegram Bot","tg.desc":"Add a bot created by BotFather. Telegram messages will go to the bound workspace.","tg.add":"Add Bot","tg.loading":"Loading...","tg.empty":"No bots yet. Create one with @BotFather on Telegram, then click Add above.","tg.saveFailed":"Save failed: {error}","tg.switchFailed":"Switch failed: {error}","tg.confirmDelete":"Delete bot @{name}?","tg.deleteFailed":"Delete failed: {error}","tg.label":"Name","tg.labelPlaceholder":"Bot name","tg.workspace":"Bind workspace (absolute path)","tg.accessLevel":"Access level","tg.readonly":"Readonly — read only (default)","tg.wsWrite":"Workspace — read-write within workspace","tg.full":"Full — full access","tg.allowedUsers":"Allowed users (comma-separated user_id or @username, empty = no limit)","tg.save":"Save","tg.cancel":"Cancel","tg.whitelist":"Whitelist","tg.pathMissing":"(path does not exist)","tg.disable":"Disable","tg.enable":"Enable","tg.edit":"Edit","tg.delete":"Delete","tg.addTitle":"Add Telegram Bot","tg.tokenLabel":"Bot Token (from @BotFather)","tg.bindWorkspace":"Bind to workspace","tg.nameOptional":"Name (optional)","tg.namePlaceholder":"e.g. SA Agent","tg.allowedUsersOptional":"Allowed users (optional, comma-separated)","tg.allowedUsersHint":"user_id or @username, empty = no limit","tg.addBtn":"Add","slack.title":"Slack Bot","slack.desc":"Add a Slack app with Socket Mode enabled. The bot listens via a long-lived ws connection — no public webhook needed.","slack.add":"Add Bot","slack.loading":"Loading...","slack.empty":"No Slack bots yet. Create an app at api.slack.com, enable Socket Mode, then click Add above.","slack.saveFailed":"Save failed: {error}","slack.switchFailed":"Switch failed: {error}","slack.confirmDelete":"Delete bot for team {name}?","slack.deleteFailed":"Delete failed: {error}","slack.label":"Name","slack.labelPlaceholder":"Bot name","slack.workspace":"Bind workspace (absolute path)","slack.accessLevel":"Access level","slack.readonly":"Readonly — read only (default)","slack.wsWrite":"Workspace — read-write within workspace","slack.full":"Full — full access","slack.save":"Save","slack.cancel":"Cancel","slack.pathMissing":"(path does not exist)","slack.disable":"Disable","slack.enable":"Enable","slack.edit":"Edit","slack.delete":"Delete","slack.addTitle":"Add Slack Bot","slack.botTokenLabel":"Bot Token (xoxb-…)","slack.botTokenHint":"OAuth & Permissions → Bot User OAuth Token","slack.appTokenLabel":"App Token (xapp-…)","slack.appTokenHint":"Basic Information → App-Level Tokens (with connections:write)","slack.bindWorkspace":"Bind to workspace","slack.nameOptional":"Name (optional)","slack.namePlaceholder":"e.g. SA Agent","slack.addBtn":"Add","feishu.title":"Feishu Bot","feishu.desc":"Add a Feishu (Lark) app with long-connect events enabled. The bot listens via a wss connection — no public webhook needed.","feishu.add":"Add Bot","feishu.loading":"Loading...","feishu.empty":"No Feishu bots yet. Create an app in the Feishu open platform, enable long-connect events, then click Add above.","feishu.saveFailed":"Save failed: {error}","feishu.switchFailed":"Switch failed: {error}","feishu.confirmDelete":"Delete bot for app {name}?","feishu.deleteFailed":"Delete failed: {error}","feishu.label":"Name","feishu.labelPlaceholder":"Bot name","feishu.workspace":"Bind workspace (absolute path)","feishu.accessLevel":"Access level","feishu.readonly":"Readonly — read only (default)","feishu.wsWrite":"Workspace — read-write within workspace","feishu.full":"Full — full access","feishu.save":"Save","feishu.cancel":"Cancel","feishu.pathMissing":"(path does not exist)","feishu.disable":"Disable","feishu.enable":"Enable","feishu.edit":"Edit","feishu.delete":"Delete","feishu.addTitle":"Add Feishu Bot","feishu.appIdLabel":"App ID","feishu.appSecretLabel":"App Secret","feishu.botOpenIdLabel":"Bot Open ID","feishu.botOpenIdHint":"The bot's own open_id, used to detect mentions in groups","feishu.verificationTokenLabel":"Verification Token (optional)","feishu.encryptKeyLabel":"Encrypt Key (optional)","feishu.bindWorkspace":"Bind to workspace","feishu.nameOptional":"Name (optional)","feishu.namePlaceholder":"e.g. SA Agent","feishu.addBtn":"Add","web.title":"Web Access","web.desc":"Create tokens for web frontend access. Each token binds to one workspace.","web.create":"Create Access","web.loading":"Loading...","web.empty":"No web access yet. Click Create above to generate a token.","web.saveFailed":"Save failed: {error}","web.switchFailed":"Switch failed: {error}","web.confirmDelete":"Delete this access? The token will be invalidated.","web.deleteFailed":"Delete failed: {error}","web.label":"Name","web.labelPlaceholder":"Access name","web.workspace":"Bind workspace (absolute path)","web.accessLevel":"Access level","web.readonly":"Readonly — read only (default)","web.wsWrite":"Workspace — read-write within workspace","web.full":"Full — full access","web.observer":"Observer — global read-only (dashboards/metrics)","web.save":"Save","web.cancel":"Cancel","web.pathMissing":"(path does not exist)","web.copied":"Copied","web.copyToken":"Copy Token","web.disable":"Disable","web.enable":"Enable","web.edit":"Edit","web.delete":"Delete","web.createSuccess":"Created successfully","web.tokenNotice":"Save this token (shown only once):","web.done":"Done","web.createTitle":"Create Web Access","web.bindWorkspace":"Bind to workspace","web.nameOptional":"Name (optional)","web.namePlaceholder":"e.g. SA Demo","web.createBtn":"Create","wx.title":"WeChat Bot","wx.desc":"After scanning, WeChat messages will go to the bound workspace session.","wx.add":"Add Bot","wx.loading":"Loading...","wx.empty":'No bots yet. Click "Add Bot" above to scan with WeChat.',"wx.saveFailed":"Save failed: {error}","wx.switchFailed":"Switch failed: {error}","wx.confirmDelete":'Delete bot "{name}"? Friends will get no response.',"wx.deleteFailed":"Delete failed: {error}","wx.label":"Name","wx.labelPlaceholder":"Bot name","wx.workspace":"Bind workspace (absolute path)","wx.accessLevel":"Access level","wx.readonly":"Readonly — read workspace files / grep / glob (default)","wx.wsWrite":"Workspace — read-write within workspace","wx.full":"Full — full access, including write and shell","wx.readonlyHint":"Readonly: WeChat users can only let agent read files/search, cannot write, run commands or access network. Sub-agents inherit the same level.","wx.save":"Save","wx.cancel":"Cancel","wx.disable":"Disable","wx.enable":"Enable","wx.rescan":"Re-scan (refresh token)","wx.edit":"Edit","wx.delete":"Delete","wx.generatingQr":"Generating QR code...","wx.pathRequired":"Workspace must be an absolute path","wx.qrFailed":"Failed to generate QR code","wx.scanPrompt":"Scan with WeChat","wx.connected":"Connected ({id})","wx.loginFailed":"Login failed","wx.rescanBtn":"Re-scan","wx.addTitle":"Add WeChat Bot","wx.bindWorkspace":"Bind to workspace","wx.bindHint":"WeChat messages will go to this workspace session, visible in web panel","wx.nameOptional":"Name (optional)","wx.namePlaceholder":"e.g. Work Assistant","wx.readonlyFull":"Full — same as web","wx.readonlyShareHint":"Readonly: WeChat users cannot write files, run shell, or access network. Sub-agents inherit. Strongly recommended for sharing with clients.","wx.nextStep":"Next: Scan","wx.scanTitle":"Scan with WeChat","wx.bindTo":"Bind to","wx.restart":"Start over","media.image":"Image","media.video":"Video","media.voice":"Voice","media.file":"File","chat.imageSent":"[Sent {n} image(s)]","chat.sendFailed":"Send failed — the server never confirmed receipt. Please resend.","chat.sendFailedBadge":"send failed","chat.interrupted":"No response received — the connection may have dropped. Resend if needed.","chat.archive.loadOlder":"Load earlier messages","chat.archive.loading":"Loading earlier messages…","chat.archive.noEarlier":"No earlier messages","chat.archive.header":"Archived \xb7 {segments} segment(s) \xb7 {messages} messages","goal.banner.intake":"\uD83C\uDFAF Goal mode \xb7 intake — define the goal contract","goal.banner.running":"\uD83C\uDFAF Goal mode \xb7 round {round}/{max}","goal.banner.paused":"⏸ Goal paused — talk to the worker directly, /goal resume to continue","goal.banner.halted":"⛔ Goal halted — view the diagnosis in the goal session","goal.banner.done":"✅ Goal done — view the final report","goal.banner.jump":"Go to the goal session","goal.banner.worker":"Worker →","goal.banner.jump_worker":"Go to the worker session","goal.banner.dismiss":"Dismiss","goal.inputLocked":"Goal mode is driving this session — talk in the goal session, or /goal pause to take over","link.fresh":"Connected","link.stale":"Connection stale — probing…","link.down":"Disconnected","agent.agents":"Agents","agent.selectToEdit":"Select an agent to edit","agent.back":"Back","agent.test":"Test","agent.edit":"Edit","agent.editHint":"Open agent.yaml + AGENT.md in a mini workspace","agent.saving":"saving…","agent.loading":"Loading...","agent.mdPreviewHint":'preview (read-only — edit via "Edit" above)',"agent.basic":"Basic","agent.name":"name","agent.description":"description","agent.priority":"priority","agent.model":"Model","agent.provider":"provider","agent.modelId":"id","agent.endpoint":"endpoint","agent.maxTokens":"maxTokens","agent.maxTokensDefault":"Default: {max} (model max)","agent.maxTokensAuto":"Leave blank for model default","agent.systemPrompt":"System Prompt","agent.systemPromptHint":"(fallback if no AGENT.md)","agent.systemPromptPlaceholder":"Instructions for this agent...","agent.context":"Context","agent.team":"Team","agent.teamHint":"(pick agents to delegate to — selecting any grants the session/delegation tools; none = no delegation)","agent.loadingTools":"Loading tools...","agent.noSkills":"No skills available. Create skills first.","skill.skills":"Skills","skill.noSkills":"No skills yet. Click + on a section to create one.","settings.general":"General","settings.params":"Params","settings.emptyParams":"No params yet. Add entries under params.x.y in YAML view — they can be referenced as {{params.x.y}} in AGENT.md / SKILL.md.","settings.emptyGeneral":"No settings in this section yet. Switch to YAML view to add manually.","settings.nav.system":"System","settings.nav.security":"Security","settings.nav.providers":"Model Providers","settings.nav.skills":"Skills","settings.nav.agents":"Agents","settings.nav.orphans":"Orphans","settings.nav.unclaimed":"Unclaimed values","settings.section.namespace":"Namespace","settings.section.declaredBy":"Declared by","settings.section.declaredBy.general":"General","settings.section.declaredBy.provider":"Provider","settings.section.declaredBy.skill":"Skill","settings.section.declaredBy.agent":"Agent","settings.section.settings":"Settings","settings.section.params":"Params","settings.section.secrets":"Secrets","settings.section.secretsHint":"Server-side only. Never injected into agent prompts or shell commands. {placeholder} placeholders are pulled from the process environment at read time.","settings.field.reset":"Reset","settings.field.resetTitleWorkspace":"Remove workspace override (inherit from global)","settings.field.resetTitleGlobal":"Remove global value (revert to default)","settings.source.workspace":"workspace","settings.source.global":"global","settings.source.inherited":"inherited from global","settings.source.unset":"unset","settings.orphans.title":"Unclaimed values","settings.orphans.intro":"These values exist in settings.yaml but no provider or skill currently declares them. They might be left over from an uninstalled package — or the declaration was removed. Safe to keep (they re-attach automatically if their namespace comes back) or remove.","settings.orphans.remove":"Remove","settings.orphans.confirm":"Remove orphan {key}? This deletes the value from settings.yaml.","settings.empty":"No section selected.","settings.loading":"Loading…","security.changePassword.title":"Change Password","security.changePassword.desc":"Change the admin login password. Existing sessions stay signed in.","security.changePassword.current":"Current password","security.changePassword.new":"New password","security.changePassword.confirm":"Confirm new password","security.changePassword.rule":"At least 8 characters, with at least one letter and one digit.","security.changePassword.tooShort":"Password must be at least 8 characters","security.changePassword.needLetter":"Password must contain at least one letter","security.changePassword.needDigit":"Password must contain at least one digit","security.changePassword.sameAsOld":"New password must differ from the current password","security.changePassword.mismatch":"Passwords don't match","security.changePassword.submit":"Change password","security.changePassword.submitting":"Changing…","security.changePassword.success":"Password changed. Use the new password next time you log in.","security.changePassword.connectionFailed":"Connection failed","security.logout":"Log out","security.logout.desc":"Sign out on this browser and return to the login page.","nav.explorer":"Explorer","nav.sourceControl":"Source Control","nav.sessions":"Sessions","nav.channels":"Channels","nav.settings":"Settings","nav.canvas":"Canvas","nav.chat":"CHAT","nav.terminal":"TERMINAL","status.busy":"Working…","status.idle":"Idle","status.notifyBody":"Agent finished responding.","sc.refresh":"Refresh","sc.staged":"Staged Changes","sc.changes":"Changes","sc.stageAll":"Stage All Changes","sc.unstageAll":"Unstage All Changes","sc.commit":"Commit","sc.commitPlaceholder":"Message (Ctrl+Enter to commit)","sc.push":"Push","sc.pull":"Pull","sc.clean":"No changes","sc.initRepo":"Initialize Repository","sc.initRepoDesc":"Start tracking this folder with git. Creates a .gitignore and an initial commit.","sc.initializing":"Initializing…","sc.addRemoteDesc":"No remote configured. Add the URL of an empty repository you created on GitHub/GitLab to push to.","sc.remotePlaceholder":"https://github.com/user/repo.git","sc.addRemote":"Add Remote","sc.adding":"Adding…","sc.authError":"Push failed: Git credentials needed. Configure them, then push again.","sc.openCreds":"Open credential settings","sc.noWorkspace":"Open a folder to use source control","sc.selectFile":"Select a file to view its diff","sc.loadingDiff":"Loading diff…","sc.diffStaged":"(staged)","sc.diffWorking":"(working tree)","sc.viewChanges":"Changes","sc.viewGraph":"Graph","sc.noCommits":"No commits yet","sc.noCommitFiles":"No files changed","sc.pushed":"Pushed to remote","sc.unpushed":"Local only — not pushed","sc.cred.title":"Git Credentials","sc.cred.tabHttps":"HTTPS","sc.cred.tabSsh":"SSH","sc.cred.httpsDesc":"Sign in with your GitHub/GitLab username and a Personal Access Token (not your account password). Stored on disk — configure once, survives restarts.","sc.cred.configuredList":"Configured credentials","sc.cred.noCredsYet":"No credentials configured yet","sc.cred.delete":"Delete","sc.cred.confirmDelete":"Confirm delete?","sc.cred.addNew":"Add credential","sc.cred.host":"Host","sc.cred.username":"Username","sc.cred.token":"Personal Access Token","sc.cred.tokenHint":"Stored locally with 0600 permissions, never displayed again.","sc.cred.tokenLink":"How to create a PAT","sc.cred.required":"Host, username and token are all required.","sc.cred.save":"Save","sc.cred.saving":"Saving…","sc.cred.cancel":"Cancel","sc.cred.close":"Close","sc.cred.sshDesc":"Use an SSH key for push/pull. halo runs one ssh-agent shared with its built-in terminal — unlock a key below and push/pull just works. The passphrase goes straight to ssh-add; halo never stores it.","sc.cred.sshAgentOn":"ssh-agent ready \xb7 {count} key(s) loaded","sc.cred.sshAgentOff":"ssh-agent not running","sc.cred.sshRefresh":"Refresh","sc.cred.sshKeys":"Keys in ~/.ssh","sc.cred.sshNoKeys":"No private keys found in ~/.ssh","sc.cred.sshLoaded":"Loaded","sc.cred.sshUnlock":"Unlock","sc.cred.sshUnlocking":"Unlocking…","sc.cred.sshPassphrase":"Key passphrase","sc.cred.sshUnlockHint":"Enter the passphrase to load this key into the agent. It goes straight to ssh-add — never stored or logged.","sc.cred.sshUnlockFailed":"Could not unlock the key","sc.cred.sshRemote":"Remote protocol (origin)","sc.cred.sshRestartNote":"Note: a loaded SSH key lives in the agent only while the server runs — after a halo restart, unlock it again (like re-unlocking SSH after a reboot). HTTPS tokens are stored on disk and survive restarts.","common.language":"Language","common.global":"Global","common.workspace":"Workspace","dataPreview.range":"{from}–{to} of {total} rows","dataPreview.prev":"Previous page","dataPreview.next":"Next page","dataPreview.empty":"No rows","dataPreview.noTables":"No tables in this database","cron.title":"Cron Jobs","cron.new":"New","cron.refresh":"Refresh","cron.empty":'No cron jobs yet. Click "New" to create one.',"cron.loading":"Loading…","cron.error":"Error: {error}","cron.confirmDelete":"Delete this cron job? Run history is also removed.","cron.err.wechat.noInbound":"This user has never messaged the bot — WeChat ilink requires an inbound from them first before any push works. Ask them to send any message to the bot, then retry.","cron.next":"next:","cron.runNow":"Run now","cron.edit":"Edit","cron.delete":"Delete","cron.disabled":"disabled","cron.field.schedule":"Schedule:","cron.field.runAt":"Run at:","cron.field.agent":"Agent:","cron.field.workspace":"Workspace:","cron.field.targets":"Targets:","cron.field.targetsLogOnly":"— (log only)","cron.field.prompt":"Prompt","cron.runHistory":"Run history","cron.noRuns":"No runs yet.","cron.dispatch":"Dispatch:","cron.loadOlder":"Load older runs","cron.log.title":"Log: {id}","cron.log.close":"Close","cron.pickJob":'Pick a job, or click "New" to create one.',"cron.form.create":"Create cron job","cron.form.edit":"Edit cron job","cron.form.cancel":"Cancel","cron.form.save":"Save","cron.form.create.btn":"Create","cron.form.saving":"Saving…","cron.form.label":"Label (optional)","cron.form.label.placeholder":"Daily report","cron.form.workspace":"Workspace path *","cron.form.workspace.placeholder":"/abs/path/to/workspace","cron.form.agent":"Agent *","cron.form.mode":"Trigger","cron.form.mode.recurring":"Recurring (cron)","cron.form.mode.oneShot":"One-time (specific instant)","cron.form.schedule":"Schedule (cron expression) *","cron.form.schedule.placeholder":"0 9 * * *","cron.form.schedule.hint":"Standard 5-field cron. Examples: 0 9 * * * = daily 09:00 \xb7 */15 * * * * = every 15 min \xb7 0 9 * * 1 = Mondays 09:00","cron.form.runAt":"Fire at *","cron.form.runAt.hint":"Job fires once at this instant (interpreted in the chosen timezone), then auto-disables.","cron.form.timezone":"Timezone","cron.form.timezone.hostDefault":"Default — host TZ ({tz})","cron.form.userPrompt":"User prompt *","cron.form.userPrompt.placeholder":"Generate today's status digest from .halo/memory/","cron.form.targets":"Channel targets (where to deliver the output)","cron.form.targets.empty":"No bound channels yet.","cron.form.targets.disabled":"(disabled)","cron.form.targets.noActiveChat":"No user has messaged this account yet — cron can't deliver until at least one inbound message","cron.form.targets.noActiveChatLabel":"(no active chat)","cron.form.targets.hint":"Leave empty to log only (no channel delivery).","cron.form.enabled":"Enabled (start scheduling immediately)","cron.form.err.workspace":"Workspace path required","cron.form.err.agent":"Agent required","cron.form.err.schedule":"Schedule required","cron.form.err.runAt":"Fire time required","cron.form.err.runAtPast":"Fire time must be in the future","cron.form.err.prompt":"Prompt required","evolution.title":"Evolution","evolution.refresh":"Refresh","evolution.filter.all":"All","evolution.filter.awaiting_review":"Awaiting review","evolution.filter.pending":"Pending","evolution.filter.running":"Running","evolution.filter.approved":"Approved","evolution.filter.applied":"Applied","evolution.filter.skipped":"Skipped","evolution.filter.rejected":"Rejected","evolution.filter.failed":"Failed","evolution.filter.timeout":"Timeout","evolution.filter.archived":"Archived","evolution.list.empty":"No runs match this filter.","evolution.list.pickRun":"Select a run to view details.","evolution.list.loading":"Loading…","evolution.list.noRun":"No run.","evolution.list.noHint":"no hint","evolution.list.attempts":"{n} attempts","evolution.list.applyFailed":"apply failed","evolution.detail.workspace":"Workspace:","evolution.detail.hint":"Hint:","evolution.detail.failure":"Failure: {reason}","evolution.detail.skipped":"Evo skipped this run on its own — no patch to apply, no need to approve / reject. See .skip.md below for the reason.","evolution.detail.applyFailed":"Approved, but the apply failed its regression check and the main workspace was left unchanged. Add a hint and retry.","evolution.action.approve":"Approve","evolution.action.reject":"Reject","evolution.action.confirmReject":"Reject this run?","evolution.action.addHint":"Add hint","evolution.action.retry":"Retry","evolution.action.retryWithHint":"Retry with hint","evolution.action.confirmApprove":"Confirm approve","evolution.action.confirmRetry":"Confirm retry","evolution.action.add":"Add","evolution.action.cancel":"Cancel","evolution.action.retryNeedsHint":"Retry needs a hint — same input + no new direction would just reproduce the same output.","evolution.action.delete":"Delete","evolution.action.confirmDelete":"Delete this run? Its patch, score, logs, and snapshot are removed for good.","evolution.retry.title":"Retry hint — reviewer's steering for the next pass. Replaces the existing user hint, evo sees this as the new ask.","evolution.retry.placeholder":"e.g. last time you proposed an architecture change — try a smaller patch focused on the user pattern","evolution.retry.tooltip":"Send the run back to evo with a new hint","evolution.approve.title":"Optional reviewer hint — passed to the apply agent. Leave empty to approve as-is.","evolution.approve.placeholder":"e.g. only apply the INSTRUCTIONS.md change, not the AGENT.md one","evolution.hint.title":"Append a memo to the run's hint. Status doesn't change; this is just a note for later.","evolution.hint.placeholder":"e.g. revisit after the upcoming Bedrock model upgrade","evolution.score.title":"Score","evolution.score.lint":"lint","evolution.score.behavior":"behavior","evolution.score.scope":"scope","evolution.score.avg":"avg","evolution.score.confidence":"Confidence:","evolution.snapshot.title":"Source session ({n} msgs)","evolution.snapshot.firstUser":"First user:","evolution.snapshot.firstAssistant":"First reply:","evolution.skip.title":".skip.md (why evo skipped this run)","evolution.patch.title":"patch.md","evolution.patch.empty":"no patch produced yet","evolution.logs.title":"Run logs","evolution.logs.wrapper":"wrapper.log (orchestration)","evolution.logs.subCli":"sub-cli.log (evo / score / dry-run stdout+stderr)","time.justNow":"just now","time.minutes":"{n}m ago","time.hours":"{n}h ago","time.days":"{n}d ago","workspace.pin":"Keep window on top","workspace.unpin":"Stop keeping on top","workspace.notifyOn":"Chime when agent finishes (banner too if unfocused) — on","workspace.notifyOff":"Chime when agent finishes (banner too if unfocused) — off","capture.button":"Share a window/screen for the AI to see","capture.bound":"Sharing: {name} (AI can request a screenshot)","capture.unbind":"Stop sharing","capture.pickTitle":"Choose a window or screen to share","capture.permissionHint":"Screen Recording permission is needed. Enable Halo under System Settings → Privacy & Security → Screen Recording, then fully quit and reopen Halo.","capture.openSettings":"Open Settings","capture.loading":"Loading sources…","capture.empty":"No windows or screens found.","capture.noPreview":"No preview (grant Screen Recording)","capture.llmPrompt":'[System: the user is sharing the "{name}" window. When you need to see its current contents to answer, output a line containing exactly <<<CAPTURE>>> and the system will screenshot it and send it back to you as the next message. Do not output the marker unless you actually need to look.]',"capture.cameraName":"Camera","capture.cameraButton":"Turn on the camera for the AI to take a photo","capture.cameraBound":"Camera on (AI can take a photo) — click to turn off","capture.cameraPick":"Choose camera","capture.cameraCurrent":"current","capture.cameraUse":"Use this camera","capture.cameraPermissionHint":"Camera permission is needed. Enable Halo under System Settings → Privacy & Security → Camera.","capture.cameraLlmPrompt":"[System: the user has turned the camera on for you. When you need to see what the camera is pointed at to answer, output a line containing exactly <<<CAPTURE>>> and the system will take a photo and send it back to you as the next message. Do not output the marker unless you actually need to look.]","capture.dismiss":"Dismiss","face.button":"Open the assistant's face"},r={"channels.wechat":"微信","tg.title":"Telegram Bot","tg.desc":"添加 BotFather 创建的 bot,用 Telegram 对话即进入绑定的 workspace","tg.add":"添加 Bot","tg.loading":"加载中...","tg.empty":"还没有 Bot。在 Telegram 找 @BotFather 创建一个,然后点右上角添加。","tg.saveFailed":"保存失败: {error}","tg.switchFailed":"切换失败: {error}","tg.confirmDelete":"确定删除 Bot @{name}?","tg.deleteFailed":"删除失败: {error}","tg.label":"名称","tg.labelPlaceholder":"Bot 名称","tg.workspace":"绑定 workspace(绝对路径)","tg.accessLevel":"权限级别","tg.readonly":"Readonly — 仅读取(默认)","tg.wsWrite":"Workspace — workspace 内读写","tg.full":"Full — 完整权限","tg.allowedUsers":"允许的用户(逗号分隔 user_id 或 @username,留空=不限制)","tg.save":"保存","tg.cancel":"取消","tg.whitelist":"白名单","tg.pathMissing":"(路径不存在)","tg.disable":"停用","tg.enable":"启用","tg.edit":"编辑","tg.delete":"删除","tg.addTitle":"添加 Telegram Bot","tg.tokenLabel":"Bot Token(从 @BotFather 获取)","tg.bindWorkspace":"绑定到 workspace","tg.nameOptional":"名称(可选)","tg.namePlaceholder":"比如:SA Agent","tg.allowedUsersOptional":"允许的用户(可选,逗号分隔)","tg.allowedUsersHint":"user_id 或 @username,留空=不限制","tg.addBtn":"添加","slack.title":"Slack Bot","slack.desc":"添加启用了 Socket Mode 的 Slack 应用。Bot 通过长连 ws 接收消息——无需公网 webhook。","slack.add":"添加 Bot","slack.loading":"加载中...","slack.empty":"还没有 Slack Bot。在 api.slack.com 创建应用、启用 Socket Mode,然后点右上角添加。","slack.saveFailed":"保存失败:{error}","slack.switchFailed":"切换失败:{error}","slack.confirmDelete":"确定删除 team {name} 的 bot?","slack.deleteFailed":"删除失败:{error}","slack.label":"名称","slack.labelPlaceholder":"Bot 名称","slack.workspace":"绑定 workspace(绝对路径)","slack.accessLevel":"权限级别","slack.readonly":"只读 — 仅查看(默认)","slack.wsWrite":"Workspace — 工作区内可读写","slack.full":"完全 — 全部权限","slack.save":"保存","slack.cancel":"取消","slack.pathMissing":"(路径不存在)","slack.disable":"禁用","slack.enable":"启用","slack.edit":"编辑","slack.delete":"删除","slack.addTitle":"添加 Slack Bot","slack.botTokenLabel":"Bot Token(xoxb-…)","slack.botTokenHint":"OAuth & Permissions → Bot User OAuth Token","slack.appTokenLabel":"App Token(xapp-…)","slack.appTokenHint":"Basic Information → App-Level Tokens(需 connections:write 权限)","slack.bindWorkspace":"绑定 workspace","slack.nameOptional":"名称(可选)","slack.namePlaceholder":"比如 SA Agent","slack.addBtn":"添加","feishu.title":"飞书 Bot","feishu.desc":"添加启用了「长连接」事件订阅模式的飞书应用。Bot 通过 wss 长连接收消息——无需公网 webhook。","feishu.add":"添加 Bot","feishu.loading":"加载中...","feishu.empty":"还没有飞书 Bot。在飞书开放平台创建应用,启用长连接事件订阅,然后点右上角添加。","feishu.saveFailed":"保存失败:{error}","feishu.switchFailed":"切换失败:{error}","feishu.confirmDelete":"确定删除 app {name} 的 bot?","feishu.deleteFailed":"删除失败:{error}","feishu.label":"名称","feishu.labelPlaceholder":"Bot 名称","feishu.workspace":"绑定 workspace(绝对路径)","feishu.accessLevel":"权限级别","feishu.readonly":"只读 — 仅查看(默认)","feishu.wsWrite":"Workspace — 工作区内可读写","feishu.full":"完全 — 全部权限","feishu.save":"保存","feishu.cancel":"取消","feishu.pathMissing":"(路径不存在)","feishu.disable":"禁用","feishu.enable":"启用","feishu.edit":"编辑","feishu.delete":"删除","feishu.addTitle":"添加飞书 Bot","feishu.appIdLabel":"App ID","feishu.appSecretLabel":"App Secret","feishu.botOpenIdLabel":"Bot Open ID","feishu.botOpenIdHint":"机器人自己的 open_id,用于在群里识别 @ 提及","feishu.verificationTokenLabel":"Verification Token(可选)","feishu.encryptKeyLabel":"Encrypt Key(可选)","feishu.bindWorkspace":"绑定 workspace","feishu.nameOptional":"名称(可选)","feishu.namePlaceholder":"比如 SA Agent","feishu.addBtn":"添加","web.title":"Web 接入","web.desc":"创建 Token 给 Web 前端接入,每个 Token 绑定一个 workspace","web.create":"创建接入","web.loading":"加载中...","web.empty":"还没有 Web 接入。点右上角创建一个 Token。","web.saveFailed":"保存失败: {error}","web.switchFailed":"切换失败: {error}","web.confirmDelete":"确定删除此接入?Token 将失效。","web.deleteFailed":"删除失败: {error}","web.label":"名称","web.labelPlaceholder":"接入名称","web.workspace":"绑定 workspace(绝对路径)","web.accessLevel":"权限级别","web.readonly":"Readonly — 仅读取(默认)","web.wsWrite":"Workspace — workspace 内读写","web.full":"Full — 完整权限","web.observer":"Observer — 全局只读(看板/监控)","web.save":"保存","web.cancel":"取消","web.pathMissing":"(路径不存在)","web.copied":"已复制","web.copyToken":"复制 Token","web.disable":"停用","web.enable":"启用","web.edit":"编辑","web.delete":"删除","web.createSuccess":"创建成功","web.tokenNotice":"请保存以下 Token(只显示一次):","web.done":"完成","web.createTitle":"创建 Web 接入","web.bindWorkspace":"绑定到 workspace","web.nameOptional":"名称(可选)","web.namePlaceholder":"比如:SA Demo","web.createBtn":"创建","wx.title":"微信机器人","wx.desc":"扫码登录后,微信消息会进入绑定的 workspace 会话","wx.add":"添加机器人","wx.loading":"加载中...","wx.empty":'还没有机器人。点右上角"添加机器人"用微信扫码接入。',"wx.saveFailed":"保存失败: {error}","wx.switchFailed":"切换失败: {error}","wx.confirmDelete":'确定删除机器人 "{name}"?微信里的好友会变成无响应状态。',"wx.deleteFailed":"删除失败: {error}","wx.label":"名称","wx.labelPlaceholder":"Bot 名称","wx.workspace":"绑定 workspace(绝对路径)","wx.accessLevel":"权限级别","wx.readonly":"Readonly — 仅读 workspace 内文件 / grep / glob(默认)","wx.wsWrite":"Workspace — workspace 内读写","wx.full":"Full — 完整权限,含写入和 shell","wx.readonlyHint":"Readonly:微信用户只能让 agent 查文件/搜索,无法写入、跑命令或联网。子 agent 自动继承同档位","wx.save":"保存","wx.cancel":"取消","wx.disable":"停用","wx.enable":"启用","wx.rescan":"重新扫码(刷新 token)","wx.edit":"编辑","wx.delete":"删除","wx.generatingQr":"正在生成二维码...","wx.pathRequired":"workspace 必须是绝对路径","wx.qrFailed":"生成二维码失败","wx.scanPrompt":"请使用微信扫描二维码","wx.connected":"连接成功 ({id})","wx.loginFailed":"登录失败","wx.rescanBtn":"重新扫码","wx.addTitle":"添加微信机器人","wx.bindWorkspace":"绑定到 workspace","wx.bindHint":"微信消息会进入该 workspace 的会话,web 端打开能看到","wx.nameOptional":"名称(可选)","wx.namePlaceholder":"比如:工作助理","wx.readonlyFull":"Full — 和 web 端等权","wx.readonlyShareHint":"Readonly:微信用户无法写入文件、跑 shell、或联网。子 agent 自动继承同档位。给客户分享时强烈建议用 Readonly","wx.nextStep":"下一步:扫码","wx.scanTitle":"微信扫码","wx.bindTo":"绑定到","wx.restart":"重新开始","media.image":"图片","media.video":"视频","media.voice":"语音","media.file":"文件","chat.imageSent":"[已发送 {n} 张图片]","chat.sendFailed":"发送失败——服务器未确认收到这条消息,请重新发送。","chat.sendFailedBadge":"发送失败","chat.interrupted":"未收到响应——连接可能已断开,如有需要请重发。","chat.archive.loadOlder":"加载更早的消息","chat.archive.loading":"正在加载更早的消息……","chat.archive.noEarlier":"没有更早的消息","chat.archive.header":"已归档 \xb7 {segments} 段 \xb7 {messages} 条消息","goal.banner.intake":"\uD83C\uDFAF 目标模式 \xb7 契约制定中——正在和你确认目标","goal.banner.running":"\uD83C\uDFAF 目标模式 \xb7 第 {round}/{max} 轮","goal.banner.paused":"⏸ 目标已暂停——可直接与工作会话对话,/goal resume 继续","goal.banner.halted":"⛔ 目标已中止——到目标会话查看诊断","goal.banner.done":"✅ 目标完成——查看最终报告","goal.banner.jump":"跳转到目标会话","goal.banner.worker":"工作会话 →","goal.banner.jump_worker":"跳转到工作会话","goal.banner.dismiss":"关闭","goal.inputLocked":"目标模式接管中——请在目标会话中对话,或 /goal pause 手动接管","link.fresh":"已连接","link.stale":"连接不稳定——探测中…","link.down":"连接已断开","agent.agents":"Agents","agent.selectToEdit":"选择一个 Agent 进行编辑","agent.back":"返回","agent.test":"测试","agent.edit":"编辑","agent.editHint":"打开 agent.yaml + AGENT.md 编辑器","agent.saving":"保存中…","agent.loading":"加载中...","agent.mdPreviewHint":'预览(只读 — 点击上方"编辑"修改)',"agent.basic":"基本信息","agent.name":"名称","agent.description":"描述","agent.priority":"优先级","agent.model":"模型","agent.provider":"提供商","agent.modelId":"模型 ID","agent.endpoint":"端点","agent.maxTokens":"最大 Token","agent.maxTokensDefault":"默认: {max} (模型上限)","agent.maxTokensAuto":"留空使用模型默认值","agent.systemPrompt":"系统提示词","agent.systemPromptHint":"(无 AGENT.md 时的 fallback)","agent.systemPromptPlaceholder":"给这个 agent 的指令...","agent.context":"上下文","agent.team":"团队","agent.teamHint":"(选择可委派的成员 — 选中任意一个即获得会话/委派工具;不选则无法委派)","agent.loadingTools":"加载中...","agent.noSkills":"暂无可用 Skills。请先创建。","skill.skills":"Skills","skill.noSkills":"暂无 Skills。点击分组旁的 + 创建。","settings.general":"常规","settings.params":"参数","settings.emptyParams":"暂无参数。在 YAML 视图中添加 params.x.y 条目,可在 AGENT.md / SKILL.md 中通过 {{params.x.y}} 引用。","settings.emptyGeneral":"该分类暂无配置项。切换到 YAML 视图手动添加。","settings.nav.system":"系统","settings.nav.security":"安全","settings.nav.providers":"模型供应商","settings.nav.skills":"技能","settings.nav.agents":"Agent","settings.nav.orphans":"孤儿项","settings.nav.unclaimed":"无主值","settings.section.namespace":"命名空间","settings.section.declaredBy":"声明方","settings.section.declaredBy.general":"系统","settings.section.declaredBy.provider":"模型供应商","settings.section.declaredBy.skill":"技能","settings.section.declaredBy.agent":"Agent","settings.section.settings":"设置","settings.section.params":"参数","settings.section.secrets":"密钥","settings.section.secretsHint":"仅服务端可见,永不注入到 Agent 提示词或 shell 命令中。{placeholder} 占位符会在读取时从进程环境变量中替换。","settings.field.reset":"重置","settings.field.resetTitleWorkspace":"移除 workspace 覆盖(回退到 global)","settings.field.resetTitleGlobal":"移除 global 值(回到默认)","settings.source.workspace":"workspace","settings.source.global":"global","settings.source.inherited":"继承自 global","settings.source.unset":"未设置","settings.orphans.title":"无主值","settings.orphans.intro":"这些值在 settings.yaml 中存在,但当前没有任何模型供应商或技能声明它们。可能是卸载了对应包,或声明被移除。保留(如果对应命名空间回归会自动接上)或删除都安全。","settings.orphans.remove":"移除","settings.orphans.confirm":"移除孤儿项 {key}?将从 settings.yaml 删除该值。","settings.empty":"未选中分类。","settings.loading":"加载中…","security.changePassword.title":"修改密码","security.changePassword.desc":"修改 admin 登录密码。已登录的会话保持有效。","security.changePassword.current":"原密码","security.changePassword.new":"新密码","security.changePassword.confirm":"确认新密码","security.changePassword.rule":"至少 8 位,且包含至少一个字母和一个数字。","security.changePassword.tooShort":"密码至少需要 8 位","security.changePassword.needLetter":"密码需包含至少一个字母","security.changePassword.needDigit":"密码需包含至少一个数字","security.changePassword.sameAsOld":"新密码不能与原密码相同","security.changePassword.mismatch":"两次输入的密码不一致","security.changePassword.submit":"修改密码","security.changePassword.submitting":"修改中…","security.changePassword.success":"密码已修改,下次登录请使用新密码。","security.changePassword.connectionFailed":"连接失败","security.logout":"退出登录","security.logout.desc":"在此浏览器上退出登录并返回登录页。","nav.explorer":"文件","nav.sourceControl":"源代码管理","nav.sessions":"会话","nav.channels":"渠道","nav.settings":"设置","nav.canvas":"画布","nav.chat":"对话","nav.terminal":"终端","status.busy":"运行中…","status.idle":"空闲","status.notifyBody":"Agent 已完成回复。","sc.refresh":"刷新","sc.staged":"已暂存的更改","sc.changes":"更改","sc.stageAll":"暂存所有更改","sc.unstageAll":"取消暂存所有更改","sc.commit":"提交","sc.commitPlaceholder":"消息(Ctrl+Enter 提交)","sc.push":"推送","sc.pull":"拉取","sc.clean":"没有更改","sc.initRepo":"初始化仓库","sc.initRepoDesc":"用 git 开始跟踪此文件夹。会创建 .gitignore 和一个初始提交。","sc.initializing":"初始化中…","sc.addRemoteDesc":"尚未配置远程仓库。填入你在 GitHub/GitLab 上新建的空仓库地址即可推送。","sc.remotePlaceholder":"https://github.com/user/repo.git","sc.addRemote":"添加远程仓库","sc.adding":"添加中…","sc.authError":"推送失败:需要先配置 Git 凭据。配置后再推送。","sc.openCreds":"打开凭据设置","sc.noWorkspace":"打开一个文件夹以使用源代码管理","sc.selectFile":"选择文件查看差异对比","sc.loadingDiff":"正在加载差异…","sc.diffStaged":"(已暂存)","sc.diffWorking":"(工作区)","sc.viewChanges":"更改","sc.viewGraph":"提交历史","sc.noCommits":"暂无提交","sc.noCommitFiles":"无文件变更","sc.pushed":"已推送到远程","sc.unpushed":"仅本地 — 未推送","sc.cred.title":"Git 凭据","sc.cred.tabHttps":"HTTPS","sc.cred.tabSsh":"SSH","sc.cred.httpsDesc":"用 GitHub/GitLab 用户名 + 个人访问令牌(PAT,不是账号密码)登录。凭据存在磁盘上——配一次,重启不丢。","sc.cred.configuredList":"已配置的凭据","sc.cred.noCredsYet":"还没有配置任何凭据","sc.cred.delete":"删除","sc.cred.confirmDelete":"确认删除?","sc.cred.addNew":"添加新凭据","sc.cred.host":"地址","sc.cred.username":"用户名","sc.cred.token":"个人访问令牌","sc.cred.tokenHint":"以 0600 权限保存在本地,不会再次显示。","sc.cred.tokenLink":"如何生成 PAT","sc.cred.required":"地址、用户名和令牌都必填。","sc.cred.save":"保存","sc.cred.saving":"保存中…","sc.cred.cancel":"取消","sc.cred.close":"关闭","sc.cred.sshDesc":"用 SSH 密钥推送/拉取。halo 自持一个与内置终端共享的 ssh-agent——在下面解锁密钥,推送/拉取即可生效。密码直接交给 ssh-add,halo 不会保存。","sc.cred.sshAgentOn":"ssh-agent 就绪 \xb7 已加载 {count} 个密钥","sc.cred.sshAgentOff":"ssh-agent 未运行","sc.cred.sshRefresh":"刷新","sc.cred.sshKeys":"~/.ssh 中的密钥","sc.cred.sshNoKeys":"~/.ssh 中未找到私钥","sc.cred.sshLoaded":"已加载","sc.cred.sshUnlock":"解锁","sc.cred.sshUnlocking":"解锁中…","sc.cred.sshPassphrase":"密钥密码","sc.cred.sshUnlockHint":"输入密码将此密钥加载进 agent。密码直接交给 ssh-add——不保存、不记录日志。","sc.cred.sshUnlockFailed":"无法解锁该密钥","sc.cred.sshRemote":"Remote 协议(origin)","sc.cred.sshRestartNote":"注意:加载的 SSH 密钥只在服务运行期间存在于 agent 中——halo 重启后需要重新解锁一次(类似电脑重启后要重新解锁 SSH)。HTTPS 令牌存在磁盘上,重启不丢。","common.language":"语言","common.global":"全局","common.workspace":"工作区","dataPreview.range":"第 {from}–{to} 行 / 共 {total} 行","dataPreview.prev":"上一页","dataPreview.next":"下一页","dataPreview.empty":"无数据行","dataPreview.noTables":"该数据库中没有表","cron.title":"定时任务","cron.new":"新建","cron.refresh":"刷新","cron.empty":"还没有定时任务。点击「新建」创建一个。","cron.loading":"加载中…","cron.error":"错误:{error}","cron.confirmDelete":"删除这个定时任务?运行历史也会一起删掉。","cron.err.wechat.noInbound":"该用户尚未给此 bot 发过消息——微信 ilink 协议要求对方先开口,bot 才能主动推送。请让 ta 给 bot 发任意一条消息后再试。","cron.next":"下一次:","cron.runNow":"立即运行","cron.edit":"编辑","cron.delete":"删除","cron.disabled":"已禁用","cron.field.schedule":"调度:","cron.field.runAt":"触发时刻:","cron.field.agent":"Agent:","cron.field.workspace":"工作区:","cron.field.targets":"通知目标:","cron.field.targetsLogOnly":"— (仅记录日志)","cron.field.prompt":"提示词","cron.runHistory":"运行历史","cron.noRuns":"还没有运行记录。","cron.dispatch":"通知:","cron.loadOlder":"加载更早的运行","cron.log.title":"日志:{id}","cron.log.close":"关闭","cron.pickJob":"挑一个任务,或点「新建」创建一个。","cron.form.create":"新建定时任务","cron.form.edit":"编辑定时任务","cron.form.cancel":"取消","cron.form.save":"保存","cron.form.create.btn":"创建","cron.form.saving":"保存中…","cron.form.label":"标签(可选)","cron.form.label.placeholder":"每日报告","cron.form.workspace":"工作区路径 *","cron.form.workspace.placeholder":"/绝对路径/到/workspace","cron.form.agent":"Agent *","cron.form.mode":"触发方式","cron.form.mode.recurring":"周期性(cron 表达式)","cron.form.mode.oneShot":"一次性(指定时刻触发)","cron.form.schedule":"调度(cron 表达式)*","cron.form.schedule.placeholder":"0 9 * * *","cron.form.schedule.hint":"标准 5 段 cron。例:0 9 * * * = 每天 09:00 \xb7 */15 * * * * = 每 15 分钟 \xb7 0 9 * * 1 = 每周一 09:00","cron.form.runAt":"触发时刻 *","cron.form.runAt.hint":"任务会在选中的时区下,按这个时刻触发一次,触发后自动禁用。","cron.form.timezone":"时区","cron.form.timezone.hostDefault":"默认 — 主机时区({tz})","cron.form.userPrompt":"用户提示词 *","cron.form.userPrompt.placeholder":"从 .halo/memory/ 整理今天的状态摘要","cron.form.targets":"通知目标(最终输出发到哪里)","cron.form.targets.empty":"还没有绑定的渠道。","cron.form.targets.disabled":"(已禁用)","cron.form.targets.noActiveChat":"还没有用户跟这个账号说过话 —— 在收到至少一条消息之前,cron 没法发送。","cron.form.targets.noActiveChatLabel":"(没有活跃会话)","cron.form.targets.hint":"留空表示仅记录日志(不通过渠道发送)。","cron.form.enabled":"启用(创建后立刻开始排期)","cron.form.err.workspace":"需要工作区路径","cron.form.err.agent":"需要选择 Agent","cron.form.err.schedule":"需要 cron 表达式","cron.form.err.runAt":"需要触发时刻","cron.form.err.runAtPast":"触发时刻必须在未来","cron.form.err.prompt":"需要填写提示词","evolution.title":"自我进化","evolution.refresh":"刷新","evolution.filter.all":"全部","evolution.filter.awaiting_review":"待审","evolution.filter.pending":"待跑","evolution.filter.running":"运行中","evolution.filter.approved":"已批准","evolution.filter.applied":"已应用","evolution.filter.skipped":"已跳过","evolution.filter.rejected":"已拒绝","evolution.filter.failed":"失败","evolution.filter.timeout":"超时","evolution.filter.archived":"已归档","evolution.list.empty":"当前筛选下没有运行记录。","evolution.list.pickRun":"选一个运行查看详情。","evolution.list.loading":"加载中…","evolution.list.noRun":"没有数据。","evolution.list.noHint":"没有提示","evolution.list.attempts":"{n} 次尝试","evolution.list.applyFailed":"应用失败","evolution.detail.workspace":"工作区:","evolution.detail.hint":"提示:","evolution.detail.failure":"失败:{reason}","evolution.detail.skipped":"Evo 主动跳过了这次 run —— 没有 patch 需要应用,无需 approve / reject。原因见下方 .skip.md。","evolution.detail.applyFailed":"已批准,但应用时未通过回归校验,主工作区未改动。补充提示后可重试。","evolution.action.approve":"批准","evolution.action.reject":"拒绝","evolution.action.confirmReject":"确定拒绝这次 run?","evolution.action.addHint":"添加备注","evolution.action.retry":"重试","evolution.action.retryWithHint":"带提示重试","evolution.action.confirmApprove":"确认批准","evolution.action.confirmRetry":"确认重试","evolution.action.add":"添加","evolution.action.cancel":"取消","evolution.action.retryNeedsHint":"重试需要带提示 —— 相同输入没有新指令,只会跑出同样的结果。","evolution.action.delete":"删除","evolution.action.confirmDelete":"删除这次 run?它的 patch、评分、日志和快照会被一并永久删除。","evolution.retry.title":"重试提示 —— 给 evo 的新指引,会替换原来的用户提示。","evolution.retry.placeholder":"例如:上次提了架构层面的改动,这次试试更小的、聚焦在用户实际模式上的 patch","evolution.retry.tooltip":"把这次 run 带新提示发回 evo","evolution.approve.title":"可选的审核提示 —— 会传给 apply agent。留空就按 patch 原样应用。","evolution.approve.placeholder":"例如:只应用 INSTRUCTIONS.md 那部分,不要碰 AGENT.md","evolution.hint.title":"给这次 run 加一条备忘。状态不变,仅作为后续查阅的笔记。","evolution.hint.placeholder":"例如:等下次 Bedrock 模型升级之后再回来看","evolution.score.title":"评分","evolution.score.lint":"lint","evolution.score.behavior":"行为","evolution.score.scope":"范围","evolution.score.avg":"平均","evolution.score.confidence":"置信度:","evolution.snapshot.title":"源会话({n} 条消息)","evolution.snapshot.firstUser":"首条用户消息:","evolution.snapshot.firstAssistant":"首条回复:","evolution.skip.title":".skip.md(evo 跳过本次 run 的理由)","evolution.patch.title":"patch.md","evolution.patch.empty":"还没有产出 patch","evolution.logs.title":"运行日志","evolution.logs.wrapper":"wrapper.log(编排层)","evolution.logs.subCli":"sub-cli.log(evo / score / dry-run 的标准输出)","time.justNow":"刚刚","time.minutes":"{n} 分钟前","time.hours":"{n} 小时前","time.days":"{n} 天前","workspace.pin":"窗口置顶","workspace.unpin":"取消置顶","workspace.notifyOn":"完成回复时响提示音(失焦时另弹通知)—— 已开","workspace.notifyOff":"完成回复时响提示音(失焦时另弹通知)—— 已关","capture.button":"共享一个窗口/屏幕给 AI 查看","capture.bound":"正在共享:{name}(AI 可主动截图查看)","capture.unbind":"停止共享","capture.pickTitle":"选择要共享的窗口或屏幕","capture.permissionHint":"需要「屏幕录制」权限。请在 系统设置 → 隐私与安全性 → 屏幕录制 里勾选 Halo,然后彻底退出并重新打开 Halo。","capture.openSettings":"打开系统设置","capture.loading":"正在加载来源…","capture.empty":"未找到窗口或屏幕。","capture.noPreview":"无预览(请授予屏幕录制权限)","capture.llmPrompt":"[系统:用户正在共享「{name}」窗口。当你需要查看它的当前画面来回答时,单独输出一行 <<<CAPTURE>>>,系统会自动截图并作为下一条消息发给你。不需要查看时不要输出该标记。]","capture.cameraName":"摄像头","capture.cameraButton":"开启摄像头,让 AI 主动拍照","capture.cameraBound":"摄像头已开启(AI 可主动拍照)— 点击关闭","capture.cameraPick":"选择摄像头","capture.cameraCurrent":"当前","capture.cameraUse":"使用此摄像头","capture.cameraPermissionHint":"需要「摄像头」权限。请在 系统设置 → 隐私与安全性 → 摄像头 里勾选 Halo。","capture.cameraLlmPrompt":"[系统:用户已为你开启摄像头。当你需要查看摄像头当前画面来回答时,单独输出一行 <<<CAPTURE>>>,系统会自动拍一张照片并作为下一条消息发给你。不需要查看时不要输出该标记。]","capture.dismiss":"关闭","face.button":"打开 AI 的面孔"};function c(e){let{children:t}=e;return(0,n.jsx)(a.I18nProvider,{en:s,zh:r,children:t})}},38045:(e,t,o)=>{o.d(t,{LangSwitcher:()=>r});var n=o(69625),a=o(8808);let s=[{value:"en",label:"EN"},{value:"zh",label:"中"}];function r(e){let{className:t}=e,{lang:o,setLang:r}=(0,a.useI18n)();return(0,n.jsx)("select",{value:o,onChange:e=>r(e.target.value),className:"px-1 py-0.5 text-xs rounded border border-[var(--border)] bg-transparent text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:border-[var(--foreground)] transition-colors cursor-pointer ".concat(null!=t?t:""),children:s.map(e=>(0,n.jsx)("option",{value:e.value,children:e.label},e.value))})}},59391:(e,t,o)=>{o.d(t,{ThemeProvider:()=>u,useTheme:()=>d});var n=o(69625),a=o(43853),s=o(17246);let r=["dark","light","midnight","warm"];function c(e){return"string"==typeof e&&r.includes(e)}function i(e){"undefined"!=typeof document&&("dark"===e?delete document.documentElement.dataset.theme:document.documentElement.dataset.theme=e)}let l=(0,a.createContext)({theme:"dark",setTheme:()=>{},refreshFromServer:async()=>{}});function d(){return(0,a.useContext)(l)}function u(e){let{children:t}=e,[o,r]=(0,a.useState)("dark");(0,a.useEffect)(()=>{let e=function(){let e=localStorage.getItem("halo_theme");return c(e)?e:"dark"}();r(e),i(e)},[]);let d=(0,a.useCallback)(async()=>{try{var e,t;let o=(await s.F.settings.getSchema()).sections.find(e=>"general"===e.namespaceId),n=null==o?void 0:o.fields.find(e=>"theme"===e.key),a=null!=(t=null!=(e=null==n?void 0:n.value)?e:null==n?void 0:n.default)?t:null,l=c(a)?a:"dark";r(l),i(l),localStorage.setItem("halo_theme",l)}catch(e){}},[]);(0,a.useEffect)(()=>{d()},[d]);let u=(0,a.useCallback)(e=>{r(e),i(e),localStorage.setItem("halo_theme",e),s.F.settings.patch("global","general.theme",e).catch(()=>{})},[]);return(0,n.jsx)(l.Provider,{value:{theme:o,setTheme:u,refreshFromServer:d},children:t})}},87826:(e,t,o)=>{o.d(t,{LanguageSelect:()=>s});var n=o(69625),a=o(8808);function s(e){let{value:t,onChange:o}=e,s=(0,a.useT)();return(0,n.jsxs)("div",{children:[(0,n.jsx)("label",{className:"text-[10px] text-[var(--muted-foreground)]",children:s("common.language")}),(0,n.jsxs)("select",{value:t,onChange:e=>o(e.target.value),className:"mt-0.5 w-full rounded border border-[var(--border)] bg-[var(--card)] px-2 py-1 text-xs",children:[(0,n.jsx)("option",{value:"en",children:"English"}),(0,n.jsx)("option",{value:"zh",children:"中文"})]})]})}}}]);