@turmind/halo 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-out/404.html +1 -1
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +1 -1
- package/bundled-docs/design/agentcore.md +1 -0
- package/bundled-docs/design/architecture.md +3 -0
- package/bundled-docs/design/goal-mode.md +2 -0
- package/bundled-docs/design/session.md +16 -2
- package/bundled-docs/design/storage.md +3 -2
- package/bundled-docs/design/wechat.md +3 -2
- package/bundled-docs/dev/deploy.md +1 -1
- package/bundled-docs/dev/env.md +2 -2
- package/bundled-docs/dev/tools.md +1 -1
- package/bundled-docs/guide/channels/wechat.md +1 -0
- package/bundled-docs/requirements/settings.md +1 -1
- package/dist/index.js +28 -5
- package/package.json +1 -1
- /package/admin-out/_next/static/{UspaVl1mRKHJayF8tgex9 → u4Su_gs1Pb6p_XeR45rCm}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{UspaVl1mRKHJayF8tgex9 → u4Su_gs1Pb6p_XeR45rCm}/_ssgManifest.js +0 -0
package/admin-out/404.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--u4Su_gs1Pb6p_XeR45rCm--><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="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-fc1574554e7b1108.js"/><script src="/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js" async=""></script><script src="/_next/static/chunks/635-d7fd95bff8674116.js" async=""></script><script src="/_next/static/chunks/main-app-483b436873410ddc.js" async=""></script><script src="/_next/static/chunks/212-70bd2971392bde84.js" async=""></script><script src="/_next/static/chunks/app/layout-c2fa8035998e48d5.js" async=""></script><meta name="robots" content="noindex"/><meta name="next-size-adjust" content=""/><title>404: This page could not be found.</title><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_f367f3 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-fc1574554e7b1108.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[59391,[\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"ThemeProvider\"]\n3:I[26967,[\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"AppI18nProvider\"]\n4:I[66048,[],\"\"]\n5:I[29926,[],\"\"]\n6:I[11789,[],\"OutletBoundary\"]\n8:I[21528,[],\"AsyncMetadataOutlet\"]\na:I[11789,[],\"ViewportBoundary\"]\nc:I[11789,[],\"MetadataBoundary\"]\nd:\"$Sreact.suspense\"\nf: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"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"u4Su_gs1Pb6p_XeR45rCm\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/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\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L6\",null,{\"children\":[\"$L7\",[\"$\",\"$L8\",null,{\"promise\":\"$@9\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$La\",null,{\"children\":\"$Lb\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lc\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$d\",null,{\"fallback\":null,\"children\":\"$Le\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$f\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n7:null\n"])</script><script>self.__next_f.push([1,"10:I[89592,[],\"IconMark\"]\n9:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Halo - Multi-Agent Workspace\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Multi-agent collaboration workspace for complex project delivery\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon.svg?3a6863548daca964\",\"type\":\"image/svg+xml\",\"sizes\":\"any\"}],[\"$\",\"$L10\",\"4\",{}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"e:\"$9:metadata\"\n"])</script></body></html>
|
package/admin-out/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--u4Su_gs1Pb6p_XeR45rCm--><html lang="en" class="dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/e4af272ccee01ff0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/0d0ca7b3c8cc5562.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/659eccb5db697b76.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-fc1574554e7b1108.js"/><script src="/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js" async=""></script><script src="/_next/static/chunks/635-d7fd95bff8674116.js" async=""></script><script src="/_next/static/chunks/main-app-483b436873410ddc.js" async=""></script><script src="/_next/static/chunks/212-70bd2971392bde84.js" async=""></script><script src="/_next/static/chunks/app/layout-c2fa8035998e48d5.js" async=""></script><script src="/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js" async=""></script><script src="/_next/static/chunks/438-866a1a20e9efa78a.js" async=""></script><script src="/_next/static/chunks/app/page-71c576815b4a30d9.js" async=""></script><meta name="next-size-adjust" content=""/><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_f367f3 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div class="flex h-screen w-screen items-center justify-center bg-[var(--background)]"><div class="text-sm text-[var(--muted-foreground)]">Loading...</div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-fc1574554e7b1108.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[59391,[\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"ThemeProvider\"]\n3:I[26967,[\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"AppI18nProvider\"]\n4:I[66048,[],\"\"]\n5:I[29926,[],\"\"]\n6:I[57917,[],\"ClientPageRoot\"]\n7:I[61089,[\"560\",\"static/chunks/1d2d5650-810a3ca1ae20cab5.js\",\"438\",\"static/chunks/438-866a1a20e9efa78a.js\",\"212\",\"static/chunks/212-70bd2971392bde84.js\",\"974\",\"static/chunks/app/page-71c576815b4a30d9.js\"],\"default\"]\na:I[11789,[],\"OutletBoundary\"]\nc:I[21528,[],\"AsyncMetadataOutlet\"]\ne:I[11789,[],\"ViewportBoundary\"]\n10:I[11789,[],\"MetadataBoundary\"]\n11:\"$Sreact.suspense\"\n13:I[79836,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/0d0ca7b3c8cc5562.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"u4Su_gs1Pb6p_XeR45rCm\",\"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>
|
package/admin-out/index.txt
CHANGED
|
@@ -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":"
|
|
17
|
+
0:{"P":null,"b":"u4Su_gs1Pb6p_XeR45rCm","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"}]]
|
|
@@ -50,6 +50,7 @@ So **1 user = 1 runtime session id = 1 workspace**: users never see each
|
|
|
50
50
|
other's history; reconnecting with the same id resumes the same conversation.
|
|
51
51
|
`HALO_WORKSPACE` points at an EFS mount (`/mnt/efs`) — microVMs are ephemeral,
|
|
52
52
|
EFS makes the workspaces survive session termination and image rollouts.
|
|
53
|
+
`~/.halo/global/runs.db` (the [run ledger](session.md#run-ledger--restart-nudge-for-interrupted-roots-halo-globalrunsdb)) lives under the container's `HALO_HOME`, which is **not** on EFS — it dies with the microVM, so the restart nudge doesn't apply in agentcore mode (the writes themselves are harmless, just moot).
|
|
53
54
|
|
|
54
55
|
## WS protocol quirks (why /init exists)
|
|
55
56
|
|
|
@@ -170,6 +170,9 @@ File: `ws/terminal-manager.ts`. Spawns a shell via node-pty. On disconnect, deta
|
|
|
170
170
|
### Self-Evolution — workspace prompt-tuning loop
|
|
171
171
|
Files: `evolution/{ticker,evo-wrapper,enqueue,spawn,archive}.ts`, `db/evo-db.ts`, `routes/evolution.ts`. Internal agents `__evo_agent__` / `__score__` / `__apply_agent__` (in `templates/agents/`) drive a 12-phase orchestration: snapshot → evo drafts → wrapper dry-runs → scorer grades → reviewer approves → apply agent merges → wrapper history-snapshots + cps to main. Per-task wrapper Node child process owns all sub-cli calls; ticker is stateless and lives in the server. State in `~/.halo/global/evo.db`. See [plans/self-evolution.md](../plans/self-evolution.md).
|
|
172
172
|
|
|
173
|
+
### Run ledger — restart nudge for interrupted roots
|
|
174
|
+
Files: `agents/run-ledger.ts` (sweep + skip rules) + `db/runs-db.ts` (the global `~/.halo/global/runs.db` table `running_sessions`, same singleton pattern as `evo.db`/`cron.db`). `runSession` inserts its session id on entry and deletes it in the finally (server-only, gated on `reconcileOrphansOnBoot`), so whatever is left at boot is exactly what the previous process was mid-run on; the sweep drains those rows and nudges each interrupted root. `index.ts` runs this eagerly: right after constructing the `SessionManagerRegistry` and before `bootChannels`, it loops `listRunningWorkspaces()` and claims+builds a `SessionManager` for each one that isn't already owned by another live server, rather than waiting for someone to open the workspace. See [design/session.md](session.md#run-ledger--restart-nudge-for-interrupted-roots-halo-globalrunsdb).
|
|
175
|
+
|
|
173
176
|
## REST routes
|
|
174
177
|
|
|
175
178
|
See [dev/api.md](../dev/api.md).
|
|
@@ -121,6 +121,8 @@ All five verbs are builtin deterministic code (`SUBCOMMAND_ROUTES`, no backing s
|
|
|
121
121
|
|
|
122
122
|
Continuation over death-handling: in-flight promises die with the process, goal state survives in the db. The SessionManager constructor (gated on `reconcileOrphansOnBoot` **and** the `.halo/runtime.lock` workspace claim — same ownership gate as the orphan reconcile) sweeps every goal at status `running` and delivers a deterministic nudge to G: *"server restarted, the in-flight round was lost; call goal_context, re-read GOAL_SPEC.md and your own transcript, re-dispatch."* Counters and caps were already in the db, so nothing is forgotten. `intake` needs no nudge (the user drives it); `paused` / `halted` / terminal goals stay put — a user-initiated pause still requires an explicit `/goal resume`.
|
|
123
123
|
|
|
124
|
+
Plain root sessions get the equivalent treatment from the run-ledger sweep (`sweepInterruptedRuns`, right after `sweepActiveGoals` in the same constructor chain — see [session.md](session.md#run-ledger--restart-nudge-for-interrupted-roots-halo-globalrunsdb)). A goal-bound W is excluded from that sweep **only** while its goal is `running` — that's the one state where this sweep above re-dispatches it, so a direct nudge would land a bogus round report on G. In `intake` or `paused`, G isn't re-dispatching anything, so W is nudged like any plain root.
|
|
125
|
+
|
|
124
126
|
## Admin surface & WS
|
|
125
127
|
|
|
126
128
|
- **`goal:changed`** is broadcast on every `writeGoalState` (and on the G-delete dissolve path) with `{goalSessionId, workerSessionId, status, round, maxRounds}`. The broadcast is **server-global with no workspace marker**; the admin re-fetches through the seed endpoint under its active project, which naturally filters cross-workspace events. See [ws.md](ws.md).
|
|
@@ -151,7 +151,21 @@ Hierarchical encoding: `root_id>child_segment>grandchild_segment`.
|
|
|
151
151
|
|
|
152
152
|
A sub-session whose process was killed mid-run never got its `stoppedAt` written, so it stays `stoppedAt IS NULL` forever — displaying as a false "running" and permanently blocking its parent's auto-report bubbling (`tryReportToParent` sees a "live" child that will never report back). When the server process first builds a workspace's SessionManager, `reconcileOrphansOnBoot` batch-stamps `stoppedAt` on every non-root, non-stopped, non-archived session. Only the long-lived server passes `reconcileOrphansOnBoot: true` through the registry — CLI/TUI/channel-subprocess/evo-wrapper share the same db while the server may be running sessions, so they never reconcile. If an orphan is later revived via `query_session`, that path clears `stoppedAt` again, so nothing is trapped permanently.
|
|
153
153
|
|
|
154
|
-
**Workspace-level gate (`.halo/runtime.lock`)**: `server.lock` ownership alone is not sufficient — two servers with different `HALO_HOME` (e.g. prod + dev) each hold their own `server.lock` yet can point at the *same* workspace directory, and one server's boot reconcile would batch-stop the other's actually-live sub-sessions (the incident this gate exists for). So the reconcile additionally requires `claimWorkspaceRuntime(workspaceRoot)` (`agents/workspace-runtime-lock.ts`) — a pid marker at `<workspace>/.halo/runtime.lock` with a liveness probe. Claim fails → skip reconcile and log a warning: **prefer missing a crash-orphan cleanup over stopping another process's live sessions**. `reconcileOrphansOnBoot: true` therefore means "reconcile if the workspace claim succeeds," not "always." Lock protocol details in [storage.md](storage.md#workspace-runtime-lock); known residual: when two servers both actively use one workspace long-term, the non-owner never reconciles — its own crash orphans stay un-cleaned until the owner restarts and takes over.
|
|
154
|
+
**Workspace-level gate (`.halo/runtime.lock`)**: `server.lock` ownership alone is not sufficient — two servers with different `HALO_HOME` (e.g. prod + dev) each hold their own `server.lock` yet can point at the *same* workspace directory, and one server's boot reconcile would batch-stop the other's actually-live sub-sessions (the incident this gate exists for). So the reconcile additionally requires `claimWorkspaceRuntime(workspaceRoot)` (`agents/workspace-runtime-lock.ts`) — a pid marker at `<workspace>/.halo/runtime.lock` with a liveness probe. Claim fails → skip reconcile and log a warning: **prefer missing a crash-orphan cleanup over stopping another process's live sessions**. `reconcileOrphansOnBoot: true` therefore means "reconcile if the workspace claim succeeds," not "always." Lock protocol details in [storage.md](storage.md#workspace-runtime-lock); known residual: when two servers both actively use one workspace long-term, the non-owner never reconciles — its own crash orphans stay un-cleaned until the owner restarts and takes over. The run-ledger eager boot loop below deals with the *un-opened* case for the same residual: a workspace whose runtime another live process owns is skipped before a SessionManager is even built, so the rows wait for a later boot instead of freezing behind a cached non-owner instance.
|
|
155
|
+
|
|
156
|
+
The constructor chain is now `reconcile orphans → sweepActiveGoals → sweepInterruptedRuns` — all three gated on the same `reconcileOrphansOnBoot` + `.halo/runtime.lock` claim, so a workspace that fails the claim runs none of them.
|
|
157
|
+
|
|
158
|
+
### Run ledger — restart nudge for interrupted roots (`~/.halo/global/runs.db`)
|
|
159
|
+
|
|
160
|
+
The orphan reconcile above fixes the *children*'s `stoppedAt`; nobody told the **root** the server restarted — asked "done yet?" it still answers "waiting on reports" forever. Goal sessions already had a restart nudge (`sweepActiveGoals`); plain roots didn't, because "which session is running right now" only ever lived in memory.
|
|
161
|
+
|
|
162
|
+
**Ledger** (`db/runs-db.ts`): a global `~/.halo/global/runs.db` (same singleton pattern as `evo.db` / `cron.db` — `setRunsDb`/`getRunsDb`, WAL), one table `running_sessions(workspace TEXT, session_id TEXT, started_at INTEGER, PK(workspace, session_id))`. `runSession` inserts the id on entry and deletes it in its `finally`, so the steady state is an **empty table** — whatever is still there at boot is exactly the set of runs the previous process died in the middle of; no pid, no liveness probe needed. Only the long-lived server writes: `SessionManager.ledgerEnabled = opts.reconcileOrphansOnBoot === true`, the same flag that gates the orphan reconcile — cli / cron / TUI / evo-wrapper runs end with their terminal and never enter the table. Writes go through a private `ledgerWrite` wrapper that try/catches and only `console.warn('[RunLedger] ledger write failed …')` on failure: a sqlite hiccup (disk full, EIO) must not drop a message or strand a session at `promise = null` without a `complete` — worst case is one stale/missing row, i.e. one spurious or missed nudge at next boot.
|
|
163
|
+
|
|
164
|
+
**Sweep** (`sweepInterruptedRuns`, `agents/run-ledger.ts`): runs from the SessionManager constructor right after `sweepActiveGoals`, under the same ownership gate. `drainRunning(workspace)` reads and deletes that workspace's rows in one transaction **before** any nudge goes out — the invariant being that a nudge's own re-dispatch inserts a fresh row, which must never be mistaken for a leftover on some future sweep. Ids are grouped by root (`id.split('>')[0]`); each root gets one append-then-send nudge (text starts `[System] The server restarted at <ISO> while you were mid-run…`, telling it sub-agents are stopped-but-revivable via `query_session`; sub-sessions are not listed — the root's own transcript / `session_list` has them). Skipped per root: row missing or `archivedAt` set; `goal` column set (a goal session — `sweepActiveGoals` owns its nudge); a goal-bound worker whose goal is `running` (G's own restart nudge re-dispatches it; a direct nudge would land a bogus round report on G) — but **only** `running`: `intake`/`paused` workers are nudged like plain roots, safe because `sendUserMessage` bypasses the routing overlay and `deliverGoalRound` returns early for a non-running goal; `cron-*` ids; `internal: true` agents (synchronous read of `agent.yaml`, since the sweep runs from a sync constructor). Principle: exactly one entry point gets woken per tree — a plain tree wakes its root, a `running` goal tree wakes G, a cron tree wakes nobody (it waits for its next fire).
|
|
165
|
+
|
|
166
|
+
**Eager boot** (`index.ts`): rather than waiting for someone to open a workspace, right after constructing `SessionManagerRegistry` (before `bootChannels`) the server calls `listRunningWorkspaces()` and for each workspace with leftover rows whose `.halo` dir still exists: `claimWorkspaceRuntime(ws)` first — if another live server owns it, `continue` (rows stay for a later boot, and no SessionManager is built, since caching a non-owner SM would freeze "not owner" for the whole process lifetime) — otherwise `registry.getOrCreate(ws)`, which fires the same constructor chain (claim → reconcile → `sweepActiveGoals` → `sweepInterruptedRuns`). An interrupted root with nobody around is exactly the one that must nudge itself without a human opening the tab first.
|
|
167
|
+
|
|
168
|
+
`runs.db`/`-wal`/`-shm` join the sandbox `hidden_files` default alongside `evo.db` / `cron.db` (see [storage.md](storage.md)). Known gap carried from the design doc: `<ISO>` in the nudge is sweep time, not the actual interruption time — for a workspace that sat locked by another server before this boot could claim it, that's a real but accepted drift.
|
|
155
169
|
|
|
156
170
|
### Message queue and drain
|
|
157
171
|
|
|
@@ -159,7 +173,7 @@ A sub-session whose process was killed mid-run never got its `stoppedAt` written
|
|
|
159
173
|
|
|
160
174
|
A `QueuedMessage` is `{ text, sourceSessionId?, images? }`: `sourceSessionId` is set for agent→agent entries (drives the `(from: session X)` prefix and the sibling-status suffix) and absent for user messages; `images` is the user multimodal payload (agent entries have none).
|
|
161
175
|
|
|
162
|
-
**runSession loop** (`runSession(sessionId, message)`, `message: string | ContentBlock[]`): runs the opening turn when there is one, then drains. An empty **string** message means "the work is already in `messageQueue`" (the `querySession` idle path) — it skips the opening turn and goes straight to drain. The per-turn reset (fresh `toolCallLog` / `warnedToolHashes`, `interruptRequested = false`) runs before the opening turn; `drainQueue` repeats the same reset before each merged batch so a stale interrupt flag never leaks across turns.
|
|
176
|
+
**runSession loop** (`runSession(sessionId, message)`, `message: string | ContentBlock[]`): runs the opening turn when there is one, then drains. An empty **string** message means "the work is already in `messageQueue`" (the `querySession` idle path) — it skips the opening turn and goes straight to drain. The per-turn reset (fresh `toolCallLog` / `warnedToolHashes`, `interruptRequested = false`) runs before the opening turn; `drainQueue` repeats the same reset before each merged batch so a stale interrupt flag never leaks across turns. Server-only: right before `session.promise = runFn()` it inserts the session id into the run ledger, and in the `finally` (right after `session.promise = null`, before emitting `complete`) it deletes it — see [Run ledger](#run-ledger--restart-nudge-for-interrupted-roots-halo-globalrunsdb) above.
|
|
163
177
|
|
|
164
178
|
**Arrival stamp.** Both paths run the turn through `runAgentTurn`, which prepends `[<ISO-8601 UTC>] ` (e.g. `[2026-09-12T15:17:44.153Z] `) to the first text block of the model-facing input — an image-only input gets a stamp-only text block unshifted. The model has no clock; without this it couldn't tell a reply that came two days later from one that came instantly, or how long a sub-agent report took to land. Stamped **once, before the retry loop**, so every attempt (and the [multimodal 4xx degrade](#multimodal-4xx-degrade) rebuild) reuses the identical input. Agent reports therefore read `[<iso>] (from: session X)\n…` — stamp outermost, no special-casing. Only `agent.messages` changes: the UI `type:'user'` event keeps the raw text (see [Turn matching](#exchange-deletion-soft-ui--hard-raw) for the strip on compare), and the self-compact instruction / `[Conversation Summary]` injection / `tool_result` messages are never stamped.
|
|
165
179
|
|
|
@@ -25,6 +25,7 @@ Defines the persisted-data format for every Halo surface. Format changes must re
|
|
|
25
25
|
│ │ └── <sessionId>.json
|
|
26
26
|
│ ├── evo.db # Cross-workspace evolution queue (evolution_runs + evolution_applies)
|
|
27
27
|
│ ├── cron.db # Cross-workspace cron jobs + run history (cron_jobs + cron_runs)
|
|
28
|
+
│ ├── runs.db # Run ledger: (workspace, session_id) rows for server-driven runs in flight; steady state empty (running_sessions)
|
|
28
29
|
│ ├── logs/ # Runtime logs
|
|
29
30
|
│ │ ├── evo/ # Per-evo-run wrapper logs
|
|
30
31
|
│ │ └── cron/ # Per-cron-run cli stdout/stderr (30-day retention)
|
|
@@ -327,7 +328,7 @@ general: # built-in declarer (the server itself
|
|
|
327
328
|
summarize_timeout_sec: 300
|
|
328
329
|
sandbox:
|
|
329
330
|
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh"
|
|
330
|
-
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc"
|
|
331
|
+
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/evo.db,~/.halo/global/evo.db-wal,~/.halo/global/evo.db-shm,~/.halo/global/cron.db,~/.halo/global/cron.db-wal,~/.halo/global/cron.db-shm,~/.halo/global/runs.db,~/.halo/global/runs.db-wal,~/.halo/global/runs.db-shm"
|
|
331
332
|
logging:
|
|
332
333
|
level: warn
|
|
333
334
|
|
|
@@ -383,7 +384,7 @@ A value of the form `<<ENV_NAME>>` is replaced at read time with `process.env.EN
|
|
|
383
384
|
| `general.limits.ws_event_buffer` | 5000 | Events buffered per detached WS session before oldest are dropped on reattach |
|
|
384
385
|
| `general.limits.terminal_scrollback_bytes` | 50000 | Off-screen scrollback bytes retained per detached persistent terminal |
|
|
385
386
|
| `general.sandbox.hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh` | bwrap tmpfs overlay (Linux only) |
|
|
386
|
-
| `general.sandbox.hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc` | bwrap /dev/null bind (Linux only) |
|
|
387
|
+
| `general.sandbox.hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/evo.db,~/.halo/global/evo.db-wal,~/.halo/global/evo.db-shm,~/.halo/global/cron.db,~/.halo/global/cron.db-wal,~/.halo/global/cron.db-shm,~/.halo/global/runs.db,~/.halo/global/runs.db-wal,~/.halo/global/runs.db-shm` | bwrap /dev/null bind (Linux only) |
|
|
387
388
|
| `general.logging.level` | warn | `debug` / `info` / `warn` / `error` |
|
|
388
389
|
|
|
389
390
|
Schema source: [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`.
|
|
@@ -46,10 +46,11 @@ Sub-sessions inherit the parent's access level — a readonly channel can't dele
|
|
|
46
46
|
|
|
47
47
|
WeChat bot accounts are stored in the unified channel DB: `~/.halo/secrets/channels/channels.db`, table `channel_accounts` with `channel_type = 'wechat'`. See [storage.md](storage.md#channel_accounts) for the full schema.
|
|
48
48
|
|
|
49
|
-
WeChat-specific config JSON fields: `botToken`, `baseUrl`, `userId`, `syncBuf`, `lastActiveChatId`.
|
|
49
|
+
WeChat-specific config JSON fields: `botToken`, `baseUrl`, `userId`, `syncBuf`, `lastActiveChatId`, `contextTokens`.
|
|
50
50
|
|
|
51
51
|
- `userId` is the QR-bind owner (the ilink_user_id of whoever scanned the QR code to register this bot). Used by the wechat cron-dispatcher as the default proactive-send target — sending here means cron output goes back to the bot's owner, which is the common-case "report to me on a schedule" intent.
|
|
52
52
|
- `lastActiveChatId` is a runtime cache of the most recent inbound `from_user_id`. Written by `rememberLastActiveChat()` in `channels/shared/accounts.ts` with a per-process hash so unchanged values never touch the db (idempotent — hot path stays in memory). Used as a fallback for cron sends in the shared-bot case where you want to reply to whoever talked last.
|
|
53
|
+
- `contextTokens` is a `Record<userId, context_token>` — the most recent inbound `context_token` per user, written by `rememberWechatContextToken()` (same in-process dedupe / write-on-change shape as `lastActiveChatId`). The ilink gateway expects every outbound `sendmessage` to echo the recipient's latest inbound token; the chat-reply path already carries it in the in-memory bridge route, but cron dispatch runs long after the inbound and needs a persisted copy.
|
|
53
54
|
|
|
54
55
|
### Proactive sending (cron)
|
|
55
56
|
|
|
@@ -61,7 +62,7 @@ The wechat cron-dispatcher (`channels/wechat/cron-dispatcher.ts`) registers itse
|
|
|
61
62
|
|
|
62
63
|
WeChat is single-recipient per dispatch (no fan-out across `allowedUsers` like telegram has). If none of the three yields an id, dispatch fails with a clear "no wechat target — bind the account first" message.
|
|
63
64
|
|
|
64
|
-
The text is chunked exactly like a chat reply — `splitText(text, WECHAT_TEXT_LIMIT)` (3500 chars, see [Event coalescing](#event-coalescing-wechatresponder)) — and the chunks are sent sequentially so a long report arrives in order. Before this, a whole report went out in one `sendmessage` call and anything over the gateway's 16 KB ceiling failed outright with `ret=-2 "prepare failed"` (the stock-report job hit it routinely). A mid-chunk failure rethrows as `chunk i/n: <error>`, so the run row's dispatch result shows how much of the report already landed (chunks before `i` were delivered).
|
|
65
|
+
The text is chunked exactly like a chat reply — `splitText(text, WECHAT_TEXT_LIMIT)` (3500 chars, see [Event coalescing](#event-coalescing-wechatresponder)) — and the chunks are sent sequentially so a long report arrives in order. Before this, a whole report went out in one `sendmessage` call and anything over the gateway's 16 KB ceiling failed outright with `ret=-2 "prepare failed"` (the stock-report job hit it routinely). A mid-chunk failure rethrows as `chunk i/n: <error>`, so the run row's dispatch result shows how much of the report already landed (chunks before `i` were delivered). Every chunk carries the recipient's persisted `context_token` (`config.contextTokens[chatId]`, see above) — the ilink gateway expects outbound to echo the latest inbound token, and sending without it was the second `ret=-2` cause: 6 failures in 10 days on payloads far under 16 KB, one of them 6 minutes after the user had just been chatting with the bot, which rules out the earlier "no recent inbound message" theory. Accounts with no inbound since 1.1.8 have no token yet and still send bare (as before) until the owner's next message. `api.ts`'s `ret=-2` hint names both causes (oversized payload / missing token).
|
|
65
66
|
|
|
66
67
|
## Modules
|
|
67
68
|
|
|
@@ -34,7 +34,7 @@ pnpm --filter @turmind/halo-admin build # next build + copy-monaco; never a ba
|
|
|
34
34
|
|
|
35
35
|
## 2. Runtime data locations
|
|
36
36
|
|
|
37
|
-
No directory needs to be created by hand. SQLite databases are created automatically on first use: per-workspace state at `<workspace>/.halo/halo.db`, plus global queues at `~/.halo/global/evo.db` and `~/.halo/global/
|
|
37
|
+
No directory needs to be created by hand. SQLite databases are created automatically on first use: per-workspace state at `<workspace>/.halo/halo.db`, plus global queues at `~/.halo/global/evo.db`, `~/.halo/global/cron.db` and `~/.halo/global/runs.db`.
|
|
38
38
|
|
|
39
39
|
## 3. Run `halo setup`
|
|
40
40
|
|
package/bundled-docs/dev/env.md
CHANGED
|
@@ -67,7 +67,7 @@ Your deployment scripts should also prepend this line.
|
|
|
67
67
|
|
|
68
68
|
## Runtime directories
|
|
69
69
|
|
|
70
|
-
- SQLite: `<workspace>/.halo/halo.db` (per-workspace, auto-created on first use) + global queues `~/.halo/global/evo.db` and `~/.halo/global/
|
|
70
|
+
- SQLite: `<workspace>/.halo/halo.db` (per-workspace, auto-created on first use) + global queues `~/.halo/global/evo.db`, `~/.halo/global/cron.db` and `~/.halo/global/runs.db`
|
|
71
71
|
- Session files: `.halo/sessions/{agentId}/{sessionId}.json`
|
|
72
72
|
- Global config: `~/.halo/global/`
|
|
73
73
|
- Per-project config: `<workspace>/.halo/`
|
|
@@ -126,7 +126,7 @@ settings.yaml only (no env override):
|
|
|
126
126
|
- `general.server.trust_proxy` (default `false`) — trust `x-forwarded-for` for client IP resolution, scope: global (enable only behind a reverse proxy you control)
|
|
127
127
|
- `general.sandbox.hidden_dirs` (default `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs`) — bwrap tmpfs overlays, scope: global
|
|
128
128
|
- `general.sandbox.writable_dirs` (default empty) — dirs bind-mounted read-write inside the bwrap sandbox, for external CLIs that keep local state (e.g. `~/.kiro`); not applied to readonly sessions, scope: global
|
|
129
|
-
- `general.sandbox.hidden_files` (default `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron}.db` + their `-wal`/`-shm` files) — bwrap /dev/null binds, scope: global
|
|
129
|
+
- `general.sandbox.hidden_files` (default `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + their `-wal`/`-shm` files) — bwrap /dev/null binds, scope: global
|
|
130
130
|
- `general.logging.level` (default `warn`) — log level: debug | info | warn | error
|
|
131
131
|
|
|
132
132
|
`<provider-id>.secrets.*` (server-only, hard-rejected by `{{}}` substitution; declared in `models/<provider-id>.yaml` `secrets:`):
|
|
@@ -138,7 +138,7 @@ Sensitive directories and files are hidden from workspace/readonly sessions via
|
|
|
138
138
|
| Setting | Default | Method |
|
|
139
139
|
|---|---|---|
|
|
140
140
|
| `hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs` | `--tmpfs` overlay (empty directory) |
|
|
141
|
-
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron}.db` + their `-wal`/`-shm` files | `--ro-bind /dev/null` (empty file) |
|
|
141
|
+
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + their `-wal`/`-shm` files | `--ro-bind /dev/null` (empty file) |
|
|
142
142
|
| `writable_dirs` | (empty) | `--bind` read-write — for external CLIs that keep local state (e.g. `~/.kiro`); not applied to readonly sessions |
|
|
143
143
|
|
|
144
144
|
Changes take effect immediately — `config.ts` reads settings.yaml via an mtime-watched lazy cache, so the next `shell_exec` reads the latest values. These keys are `globalOnly` in the schema — a workspace `settings.yaml` cannot override them, since they define the security boundary agents run inside.
|
|
@@ -97,6 +97,7 @@ Fan-out across multiple WeChat users is not supported — one bot, one owner.
|
|
|
97
97
|
| Two halo processes both poll the same bot | Each process gets a copy of every message, replied twice. Make sure only one server runs — see `~/.halo/global/server.pid` |
|
|
98
98
|
| Group chat doesn't trigger the bot | Expected — groups aren't supported in v1 |
|
|
99
99
|
| Sent voice message returns no reply | Check that the account has `workspace` or `full` access; readonly cannot save inbound media |
|
|
100
|
+
| Cron push fails with `ret=-2 prepare failed` while chat replies work | Two causes: the report exceeds 16 KB (halo chunks at 3500 chars, so this is rare now), or the account has no stored `context_token` for the recipient yet — the gateway wants outbound to echo the user's latest inbound token, which halo persists on every inbound message since 1.1.8. Send the bot any message once and the next cron push carries it |
|
|
100
101
|
|
|
101
102
|
## Multi-workspace / multi-bot
|
|
102
103
|
|
|
@@ -18,7 +18,7 @@ general: # built-in declarer (server itself)
|
|
|
18
18
|
...
|
|
19
19
|
sandbox:
|
|
20
20
|
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh"
|
|
21
|
-
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc"
|
|
21
|
+
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/runs.db"
|
|
22
22
|
writable_dirs: "" # e.g. ~/.kiro,~/.local/share/kiro-cli — rw bind-mounts in bwrap; ignored for readonly sessions
|
|
23
23
|
logging:
|
|
24
24
|
level: warn
|
package/dist/index.js
CHANGED
|
@@ -19948,6 +19948,19 @@ function rememberLastActiveChat(db, accountId, chatId) {
|
|
|
19948
19948
|
}
|
|
19949
19949
|
_lastActiveChatCache.set(accountId, chatId);
|
|
19950
19950
|
}
|
|
19951
|
+
function rememberWechatContextToken(db, accountId, userId, token) {
|
|
19952
|
+
const key = `${accountId}:${userId}`;
|
|
19953
|
+
if (_wechatContextTokenCache.get(key) === token)
|
|
19954
|
+
return;
|
|
19955
|
+
const existing = getAccount(db, accountId);
|
|
19956
|
+
if (!existing)
|
|
19957
|
+
return;
|
|
19958
|
+
const tokens = existing.config.contextTokens ?? {};
|
|
19959
|
+
if (tokens[userId] !== token) {
|
|
19960
|
+
patchConfig(db, accountId, { contextTokens: { ...tokens, [userId]: token } });
|
|
19961
|
+
}
|
|
19962
|
+
_wechatContextTokenCache.set(key, token);
|
|
19963
|
+
}
|
|
19951
19964
|
function patchConfig(db, accountId, configPatch) {
|
|
19952
19965
|
const existing = getAccount(db, accountId);
|
|
19953
19966
|
if (!existing)
|
|
@@ -19958,7 +19971,7 @@ function patchConfig(db, accountId, configPatch) {
|
|
|
19958
19971
|
function deleteAccount(db, accountId) {
|
|
19959
19972
|
db.delete(channelAccounts).where(eq12(channelAccounts.accountId, accountId)).run();
|
|
19960
19973
|
}
|
|
19961
|
-
var ACCOUNT_ACCESS_LEVELS, CHAT_ACCESS_LEVELS, _lastActiveChatCache;
|
|
19974
|
+
var ACCOUNT_ACCESS_LEVELS, CHAT_ACCESS_LEVELS, _lastActiveChatCache, _wechatContextTokenCache;
|
|
19962
19975
|
var init_accounts = __esm({
|
|
19963
19976
|
"../server/dist/channels/shared/accounts.js"() {
|
|
19964
19977
|
"use strict";
|
|
@@ -19967,6 +19980,7 @@ var init_accounts = __esm({
|
|
|
19967
19980
|
ACCOUNT_ACCESS_LEVELS = ["full", "workspace", "readonly", "observer"];
|
|
19968
19981
|
CHAT_ACCESS_LEVELS = ["full", "workspace", "readonly"];
|
|
19969
19982
|
_lastActiveChatCache = /* @__PURE__ */ new Map();
|
|
19983
|
+
_wechatContextTokenCache = /* @__PURE__ */ new Map();
|
|
19970
19984
|
}
|
|
19971
19985
|
});
|
|
19972
19986
|
|
|
@@ -24789,7 +24803,7 @@ async function sendMessage(params) {
|
|
|
24789
24803
|
try {
|
|
24790
24804
|
const parsed = JSON.parse(raw);
|
|
24791
24805
|
if (parsed.ret !== void 0 && parsed.ret !== 0 || parsed.errcode !== void 0 && parsed.errcode !== 0) {
|
|
24792
|
-
const hint = parsed.ret === -2 ? " (gateway rejected the message \u2014 payload too large (>16KB
|
|
24806
|
+
const hint = parsed.ret === -2 ? " (gateway rejected the message \u2014 payload too large (>16KB) or outbound sent without the context_token from the recipient's last inbound message)" : "";
|
|
24793
24807
|
throw new Error(`[wechat:sendmessage] gateway error ret=${parsed.ret} errcode=${parsed.errcode} ${parsed.errmsg ?? ""}${hint}`);
|
|
24794
24808
|
}
|
|
24795
24809
|
} catch (err) {
|
|
@@ -25655,6 +25669,8 @@ async function handleInbound(args) {
|
|
|
25655
25669
|
if (!fromUserId)
|
|
25656
25670
|
return;
|
|
25657
25671
|
const lang = getLang(storedAccount);
|
|
25672
|
+
if (msg.context_token)
|
|
25673
|
+
rememberWechatContextToken(db, storedAccount.accountId, fromUserId, msg.context_token);
|
|
25658
25674
|
const currentPath = resolveAccountWorkspace(storedAccount);
|
|
25659
25675
|
if (!currentPath) {
|
|
25660
25676
|
console.log(`[wechat] ${storedAccount.accountId} workspace missing (path=${storedAccount.workspacePath})`);
|
|
@@ -26002,6 +26018,12 @@ function readLastActiveChatId(accountId) {
|
|
|
26002
26018
|
const v = acct.config?.lastActiveChatId;
|
|
26003
26019
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
26004
26020
|
}
|
|
26021
|
+
function readContextToken(accountId, userId) {
|
|
26022
|
+
const acct = getAccount(getChannelDb(), accountId);
|
|
26023
|
+
const tokens = acct?.config?.contextTokens;
|
|
26024
|
+
const v = tokens?.[userId];
|
|
26025
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
26026
|
+
}
|
|
26005
26027
|
async function dispatch2(accountId, text6, explicitChatId, media) {
|
|
26006
26028
|
const acct = getAccount4(getChannelDb(), accountId);
|
|
26007
26029
|
if (!acct)
|
|
@@ -26014,10 +26036,11 @@ async function dispatch2(accountId, text6, explicitChatId, media) {
|
|
|
26014
26036
|
}
|
|
26015
26037
|
const out = [];
|
|
26016
26038
|
if (text6) {
|
|
26039
|
+
const contextToken = readContextToken(accountId, chatId);
|
|
26017
26040
|
const chunks = splitText(text6, WECHAT_TEXT_LIMIT);
|
|
26018
26041
|
for (const [i, chunk] of chunks.entries()) {
|
|
26019
26042
|
try {
|
|
26020
|
-
await sendToUser({ account: acct, toUserId: chatId, text: chunk });
|
|
26043
|
+
await sendToUser({ account: acct, toUserId: chatId, text: chunk, contextToken });
|
|
26021
26044
|
} catch (err) {
|
|
26022
26045
|
throw new Error(`chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`);
|
|
26023
26046
|
}
|
|
@@ -29291,7 +29314,7 @@ var init_dist2 = __esm({
|
|
|
29291
29314
|
init_git_ssh();
|
|
29292
29315
|
__dirname3 = path66.dirname(fileURLToPath3(import.meta.url));
|
|
29293
29316
|
PORT = config.server.port;
|
|
29294
|
-
HALO_VERSION = "1.1.
|
|
29317
|
+
HALO_VERSION = "1.1.8";
|
|
29295
29318
|
PROJECT_ROOT = findProjectRoot();
|
|
29296
29319
|
HALO_HOME4 = path66.join(homedir24(), ".halo");
|
|
29297
29320
|
GIT_SHA = readGitSha();
|
|
@@ -30205,7 +30228,7 @@ import path67 from "node:path";
|
|
|
30205
30228
|
import fs60 from "node:fs";
|
|
30206
30229
|
import { homedir as homedir25 } from "node:os";
|
|
30207
30230
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
30208
|
-
var VERSION = "1.1.
|
|
30231
|
+
var VERSION = "1.1.8";
|
|
30209
30232
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
30210
30233
|
|
|
30211
30234
|
Commands:
|
package/package.json
CHANGED
/package/admin-out/_next/static/{UspaVl1mRKHJayF8tgex9 → u4Su_gs1Pb6p_XeR45rCm}/_buildManifest.js
RENAMED
|
File without changes
|
/package/admin-out/_next/static/{UspaVl1mRKHJayF8tgex9 → u4Su_gs1Pb6p_XeR45rCm}/_ssgManifest.js
RENAMED
|
File without changes
|