@turmind/halo 1.1.3 → 1.1.4
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/cron.md +1 -1
- package/bundled-docs/design/goal-mode.md +1 -0
- package/bundled-docs/design/session.md +3 -0
- package/dist/index.js +65 -8
- package/package.json +1 -1
- package/templates/agents/default/agent.yaml +1 -1
- package/templates/models/aws-bedrock-claude-invoke.yaml +28 -0
- package/templates/skills/skill/SKILL.md +1 -1
- package/templates/skills/web-search/SKILL.md +44 -0
- package/templates/skills/web-search/search.py +137 -0
- package/templates/skills/workspace/share.md +1 -1
- package/templates/skills/workspace/stage.py +1 -1
- package/templates/skills/nova-web-search/SKILL.md +0 -65
- /package/admin-out/_next/static/{oDvBPFdNAW2vHdHcxQ0lw → PuVQROsIREQUt1b6EFWJR}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{oDvBPFdNAW2vHdHcxQ0lw → PuVQROsIREQUt1b6EFWJR}/_ssgManifest.js +0 -0
package/admin-out/404.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--PuVQROsIREQUt1b6EFWJR--><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\":\"PuVQROsIREQUt1b6EFWJR\",\"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><!--PuVQROsIREQUt1b6EFWJR--><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\":\"PuVQROsIREQUt1b6EFWJR\",\"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":"PuVQROsIREQUt1b6EFWJR","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"}]]
|
|
@@ -52,7 +52,7 @@ Each fire:
|
|
|
52
52
|
1. **Concurrency guard**: if the same `jobId` is already running in this process (in-memory `_inflight` set), insert a `cron_runs` row with `status='skipped'`, `failureReason='previous run still in progress'`, broadcast, and bail without spawning. Applies to both scheduled fires and manual run-now clicks. Two overlapping cli children would double-write the same `cron-<jobId>` on-disk session state; SessionManager's per-session lock is in-process and can't see across cli children, so this set is the cheapest place to enforce serialization. The set itself is in-memory, but its restart blind spot is covered: the boot-time orphan sweep (below) re-enters stale jobIds before croner re-arms.
|
|
53
53
|
2. Insert a `cron_runs` row with `status='running'` up front (UI sees it mid-flight); broadcast `cron:run_changed`.
|
|
54
54
|
3. Check `job.workspacePath` exists — fail loudly if the workspace was moved/deleted.
|
|
55
|
-
4. Spawn `halo cli -a <agent> -s cron-<jobId> -w <workspace>`, prompt on **stdin** (avoids Windows argv length limits). The stable `cron-<jobId>` session id means each fire *resumes the same session*, so the conversation accumulates and is reviewable in the admin Sessions tab. The child's pid is written back onto the `running` row (`cron_runs.pid`, nullable) — this is what makes the orphan sweep possible after a server crash.
|
|
55
|
+
4. Spawn `halo cli -a <agent> -s cron-<jobId> -w <workspace>`, prompt on **stdin** (avoids Windows argv length limits). The stable `cron-<jobId>` session id means each fire *resumes the same session*, so the conversation accumulates and is reviewable in the admin Sessions tab. The child's pid is written back onto the `running` row (`cron_runs.pid`, nullable) — this is what makes the orphan sweep possible after a server crash. Note the **dual-writer layout** this creates: the cli child and the server share the workspace's session files. The server side is view-only for these sessions — admin subscribes seed a UIState from disk, and the WS detach-save skips it via the dirty gate (see [session.md](session.md#ws-disconnect-resilience)); without that gate a frozen server snapshot would overwrite the cli child's newer messages (the cron-session UI-log truncation incident). Live streaming of a cli-driven session into the admin is out of scope — events don't cross processes; the Sessions tab re-reads the file on open.
|
|
56
56
|
5. Tee stdout + stderr live to `~/.halo/global/logs/cron/<runId>.log`; capture stdout to memory for dispatch.
|
|
57
57
|
6. Enforce a **per-job timeout** (`job.timeoutSec ?? 3600`, the nullable `cron_jobs.timeout_sec` validated 60–21600 at every write surface; a change takes effect on the job's next fire, no croner re-instantiation needed) via a Node timer (cross-platform, not the Linux-only `timeout(1)`), with a **two-phase kill**: SIGTERM the direct child first — the cli has a SIGTERM handler that stops the harness, repairs + flushes the session, kills its own shell_exec process groups, and exits 143 — then, if it's still alive after a 30s grace, SIGKILL the entire descendant tree (POSIX ppid-walk over one `ps` snapshot; `taskkill /T /F` on Windows) so no grandchild survives to double-write the workspace on the next fire. A ppid walk, not a process-group kill: the cli sandbox spawns shell_exec workers `detached` into their own groups, so `kill(-pid)` would miss them. Timed-out runs still report exit code 124. Generous because overlap-of-the-same-job is already blocked up-front in step 1; this is just the long-stop reaper for a truly stuck child.
|
|
58
58
|
7. Classify: exit 124 → `timeout`; non-zero → `failed`; zero but empty stdout → `failed` ("nothing to dispatch"); else `succeeded`.
|
|
@@ -69,6 +69,7 @@ Gates, in order:
|
|
|
69
69
|
|
|
70
70
|
1. Root only (`parentId === null`), row carries `goalSessionId`, goal status is `running`.
|
|
71
71
|
2. **Subtree-quiet gate** (inherited from `tryReportToParent`): no active children in the db **and** an empty message queue. A W that dispatched executors and idled while they run does NOT end the round — their reports wake W first.
|
|
72
|
+
- **Aborted-round marker**: the report body is `finalOutput || output || '(no output)'`; when W's turn was killed by an unrecoverable error (`turnError` set — retry budget exhausted / account error), it is prefixed with `[WORKER ABORTED: … Error: <text> … judge this as an interrupted round, do not score it as a completed result …]`. Without it G scored mid-turn fragments (or a literal "(no output)") as real rounds — same defect as the sub-agent auto-report, same prescription (see [session.md](session.md#session-lifecycle)): prefix, don't suppress (skipping delivery would stall the loop), prepended *before* the truncation cap so it always survives. The marker rides every downstream delivery path (question-stop / halt headers describe the stop, the marker describes the round's shape — both true), and an aborted round still counts toward round accounting and the no-progress hash: three identical aborts trip the breaker, which is the right read for a worker dying the same way thrice.
|
|
72
73
|
3. **Spec tamper gate**: sha256 of `GOAL_SPEC.md` must equal the `specHash` stamped at attach. Changed or missing → halt (`spec-tampered`), back-pointer cleared, G instructed to write a halt diagnosis. Never silently restore.
|
|
73
74
|
4. **Question-stop**: a report containing `<NEED_INPUT>` is delivered with a question-stop header and consumes **neither** the round counter nor the no-progress budget — the worker is waiting, not failing. G triages: answerable from spec + scene → `goal_decide` + relay; genuine user-sovereignty fork → park to the user.
|
|
74
75
|
5. **Round accounting**: `round++`; `noProgress` increments when the report hash is unchanged from last round, else resets (a deterministic proxy for hard-stuck — semantic no-progress is G's judging duty).
|
|
@@ -139,6 +139,8 @@ Hierarchical encoding: `root_id>child_segment>grandchild_segment`.
|
|
|
139
139
|
|
|
140
140
|
Both true → sets `stoppedAt` + emits `agent_done` + `querySession`s the result back to the parent. The parent's `querySession` clears its own `stoppedAt`, handles the report, and may trigger its own `tryReportToParent` — bubbling up.
|
|
141
141
|
|
|
142
|
+
**Abnormal-termination marker**: when the reported turn was killed by an unrecoverable error (retry budget exhausted / account-level failure), the session's `turnError` field holds the error text and the auto-report is prefixed with an explicit `[SUB-AGENT ABORTED: … Error: <text> … Re-dispatch with query_session(…) to resume.]` block. Without it the parent LLM consumed mid-turn fragments (or a literal "(no output)") as completed reports — the cron-era "sub-agent reported without finishing" incident, root-caused to a Bedrock h2 hang (`TimeoutError: http2 request did not get a response`, now also in the transient-transport retry list). Prefix, don't suppress: skipping the report would leave the parent waiting forever, the partial trace has diagnostic value, and `stoppedAt` is stamped as usual so the child never shows as falsely running. The marker is prepended *before* the truncation cap so it can never be sliced off. (`deliverGoalRound` has the same `finalOutput || output` shape without an error dimension — known gap, unfixed.)
|
|
143
|
+
|
|
142
144
|
The full finally-chain order is `tryReportToParent → deliverGoalRound → releaseSession`: after the parent-report check, goal-bound **root** sessions get their round report delivered to the goal session (fire-and-forget; a no-op for everyone else — see [goal-mode.md](goal-mode.md#the-delivery-point-delivergoalround)), then the session is released.
|
|
143
145
|
|
|
144
146
|
- **Sibling-status injection for root** (`siblingStatusSuffix`): `tryReportToParent` early-returns for root (`parentId === null`) since there's no parent to bubble up to — so root never learns whether its *other* children are still running, and the root LLM could wrap up early after consuming just one child's report. When root consumes a child report (`querySession` idle branch + `drainQueue`), a sibling-status line is appended to the message fed to the LLM. "All sub-agents done" requires **both** no sibling running in the DB (`parentId = root AND stoppedAt IS NULL`) **and** an empty in-memory `messageQueue` — a child can be stopped while its report is still queued, so the DB check alone would falsely declare completion. The reporting child needs no identity exclusion: it stamped `stoppedAt` before the report was delivered, so `stoppedAt IS NULL` already excludes it (unless re-dispatched a new task, which clears `stoppedAt` — then it correctly counts as running). The line carries per-child `created` + `last active` timestamps so a capable model can tell a freshly dispatched sibling from an original-batch leftover. Mid-tier parents are excluded by design — their `tryReportToParent` bubble-up already gates them on a fully-drained subtree.
|
|
@@ -303,6 +305,7 @@ Frontend network issues don't affect the backend:
|
|
|
303
305
|
2. **Grace period**: fixed `config.timeout.sessionGrace` (5 min default) — a single `setTimeout`, no auto-extension
|
|
304
306
|
3. **Event buffering**: the detached handler uses `bufferDetachedNotification` to buffer structural events in `pendingEvents[]`. All state (messageLog / tokens / etc.) lives in SessionManager's UIState, not duplicated in the handler.
|
|
305
307
|
4. **Reconnect**: a `subscribe` with the same sessionId loads UIState from SessionManager, replays `pendingEvents`, and — if still running — resumes live streaming
|
|
308
|
+
5. **Detach-save dirty gate**: every WS write-back path (disconnect cleanup, grace expiry, session switch/clear, backgroundSaves) funnels through `saveSession()`, which skips any UIState that isn't **dirty** — i.e. this process never reduced an event / appended a notification / replaced the log since the last successful persist (`SessionUIStore.isUIStateDirty`). A clean state is either a pure disk seed (built by `prepareForView` to *view* a session another process is driving — e.g. a cron `halo cli` child appending to the same file) or already flushed; writing it back would clobber the fresher file with a frozen snapshot. This was the cron-session UI-log truncation incident: admin viewed a cron session, closed the tab, and the grace-expiry save erased the cli child's final minutes of messages (`rawMessages`/`output` survived only via `saveSessionToFile`'s read-merge). The flag is cleared on successful `persistUIState` (kept on failure so a later save retries) and on `dropUIState`/purge/`prepareForView` re-seed so a stale flag never leaks onto a fresh disk copy.
|
|
306
309
|
|
|
307
310
|
## Resilient execution loop
|
|
308
311
|
|
package/dist/index.js
CHANGED
|
@@ -2339,6 +2339,13 @@ function ensureHaloHome(haloHome2) {
|
|
|
2339
2339
|
fs7.rmSync(staleWsSkill, { recursive: true, force: true });
|
|
2340
2340
|
console.log("[Init] Removed stale `ws` skill (renamed to `workspace`)");
|
|
2341
2341
|
}
|
|
2342
|
+
for (const staleId of ["nova-web-search", "openai-web-search"]) {
|
|
2343
|
+
const staleDir = path8.join(globalDir2, "skills", staleId);
|
|
2344
|
+
if (fs7.existsSync(staleDir)) {
|
|
2345
|
+
fs7.rmSync(staleDir, { recursive: true, force: true });
|
|
2346
|
+
console.log(`[Init] Removed stale \`${staleId}\` skill (merged into \`web-search\`)`);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2342
2349
|
const docsSrc = resolveDocsSource();
|
|
2343
2350
|
if (!docsSrc) {
|
|
2344
2351
|
console.log(`[Init] No docs source found \u2014 skipping platform docs seed. Searched: ${DOCS_SRC_CANDIDATES.join(", ")}`);
|
|
@@ -2400,7 +2407,7 @@ var init_init = __esm({
|
|
|
2400
2407
|
path8.resolve(__dirname, "..", "bundled-docs"),
|
|
2401
2408
|
path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
2402
2409
|
];
|
|
2403
|
-
TEMPLATE_VERSION =
|
|
2410
|
+
TEMPLATE_VERSION = 52;
|
|
2404
2411
|
VERSION_FILE = ".template-version";
|
|
2405
2412
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
2406
2413
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -2434,7 +2441,7 @@ var init_init = __esm({
|
|
|
2434
2441
|
// Capability skills, model-invoked only (user-invocable: false — no slash
|
|
2435
2442
|
// command). Preinstalled because they're broadly useful out of the box.
|
|
2436
2443
|
"aws-knowledge",
|
|
2437
|
-
"
|
|
2444
|
+
"web-search",
|
|
2438
2445
|
// The agent's visual "face": teaches it that `.halo/canvas/self.html`
|
|
2439
2446
|
// (seeded per-workspace, see ensureWorkspaceHalo) is a live self-portrait
|
|
2440
2447
|
// it can drive in real time by emitting `<<<SHOW: …js… >>>`, which the admin
|
|
@@ -3324,7 +3331,12 @@ async function deliverGoalRound(host, worker) {
|
|
|
3324
3331
|
const activeChildren = db.select({ id: agentSessions.id }).from(agentSessions).where(and2(eq2(agentSessions.parentId, worker.id), isNull(agentSessions.stoppedAt), isNull(agentSessions.archivedAt))).all();
|
|
3325
3332
|
if (activeChildren.length > 0 || worker.messageQueue.length > 0)
|
|
3326
3333
|
return;
|
|
3327
|
-
|
|
3334
|
+
let report = worker.finalOutput || worker.output || "(no output)";
|
|
3335
|
+
if (worker.turnError) {
|
|
3336
|
+
report = `[WORKER ABORTED: this round was terminated by an unrecoverable error, NOT a normal wrap-up. Error: ${worker.turnError}. The text below is a partial trace of the aborted turn \u2014 judge this as an interrupted round, do not score it as a completed result. Re-dispatch with query_session to let the worker resume.]
|
|
3337
|
+
|
|
3338
|
+
${report}`;
|
|
3339
|
+
}
|
|
3328
3340
|
const caps = state.caps;
|
|
3329
3341
|
const currentSpecHash = hashFileOrNull(goalSpecPath(host.workspaceRoot, state.goalId));
|
|
3330
3342
|
if (!currentSpecHash || currentSpecHash !== state.specHash) {
|
|
@@ -5127,6 +5139,17 @@ var init_session_ui_store = __esm({
|
|
|
5127
5139
|
/** Last activity (event reduced / view built) per uiStates key — the idle
|
|
5128
5140
|
* sweep's eviction clock. Invariant: every uiStates key has an entry. */
|
|
5129
5141
|
uiStateTouched = /* @__PURE__ */ new Map();
|
|
5142
|
+
/** Roots whose in-memory UIState holds local mutations (event reduced in,
|
|
5143
|
+
* notification appended, log replaced) **not yet persisted** — marked on
|
|
5144
|
+
* every mutation, cleared once persistUIState lands the snapshot. A state
|
|
5145
|
+
* not in this set adds nothing to what's on disk: either it's a pure disk
|
|
5146
|
+
* seed (built to *view* a session another process may be driving — a cron
|
|
5147
|
+
* `halo cli` child shares the workspace's session files and keeps appending
|
|
5148
|
+
* after the seed), or its mutations were already flushed. The WS
|
|
5149
|
+
* detach/switch saves gate on `isUIStateDirty`: writing a clean state back
|
|
5150
|
+
* would overwrite the other process's newer messages with this process's
|
|
5151
|
+
* frozen snapshot (the cron-session UI-log truncation incident). */
|
|
5152
|
+
uiStateDirty = /* @__PURE__ */ new Set();
|
|
5130
5153
|
constructor(host) {
|
|
5131
5154
|
this.host = host;
|
|
5132
5155
|
this.db = host.getDb();
|
|
@@ -5223,6 +5246,7 @@ var init_session_ui_store = __esm({
|
|
|
5223
5246
|
reduceIntoUIState(rootId, event) {
|
|
5224
5247
|
try {
|
|
5225
5248
|
const state = this.ensureUIState(rootId);
|
|
5249
|
+
this.uiStateDirty.add(rootId);
|
|
5226
5250
|
const result = applyEvent(state, event);
|
|
5227
5251
|
if (result.subSessionSave) {
|
|
5228
5252
|
this.persistSubSession(state, rootId, result.subSessionSave);
|
|
@@ -5319,6 +5343,7 @@ var init_session_ui_store = __esm({
|
|
|
5319
5343
|
archiveCount: archive?.count,
|
|
5320
5344
|
archivedUserDelta: archive?.userDelta
|
|
5321
5345
|
});
|
|
5346
|
+
this.uiStateDirty.delete(rootId);
|
|
5322
5347
|
} catch (err) {
|
|
5323
5348
|
console.error(`[SessionUIStore] persistUIState failed for ${rootId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5324
5349
|
}
|
|
@@ -5438,6 +5463,14 @@ var init_session_ui_store = __esm({
|
|
|
5438
5463
|
getCachedUIState(rootSessionId) {
|
|
5439
5464
|
return this.uiStates.get(rootSessionId) ?? null;
|
|
5440
5465
|
}
|
|
5466
|
+
/** Whether the cached UIState holds local mutations not yet persisted.
|
|
5467
|
+
* False for a state that was only seeded from disk (view of a session
|
|
5468
|
+
* another process drives) or whose mutations already flushed — writing
|
|
5469
|
+
* such a state back is at best a no-op and at worst overwrites a fresher
|
|
5470
|
+
* file (see uiStateDirty). */
|
|
5471
|
+
isUIStateDirty(rootSessionId) {
|
|
5472
|
+
return this.uiStateDirty.has(rootSessionId);
|
|
5473
|
+
}
|
|
5441
5474
|
/**
|
|
5442
5475
|
* Get the UIState for a session, restoring it from disk if it isn't
|
|
5443
5476
|
* in memory yet. Returns null only when the session id doesn't exist
|
|
@@ -5470,6 +5503,7 @@ var init_session_ui_store = __esm({
|
|
|
5470
5503
|
timestamp: Date.now(),
|
|
5471
5504
|
agentName
|
|
5472
5505
|
});
|
|
5506
|
+
this.uiStateDirty.add(rootId);
|
|
5473
5507
|
this.persistUIState(rootId, state);
|
|
5474
5508
|
}
|
|
5475
5509
|
/**
|
|
@@ -5483,6 +5517,7 @@ var init_session_ui_store = __esm({
|
|
|
5483
5517
|
state.streamBuffer = "";
|
|
5484
5518
|
state.turnToolCalls = [];
|
|
5485
5519
|
state.turnContentBlocks = [];
|
|
5520
|
+
this.uiStateDirty.add(rootId);
|
|
5486
5521
|
this.persistUIState(rootId, state);
|
|
5487
5522
|
}
|
|
5488
5523
|
/**
|
|
@@ -5504,6 +5539,7 @@ var init_session_ui_store = __esm({
|
|
|
5504
5539
|
this.uiStates.delete(sessionId);
|
|
5505
5540
|
this.uiStateProjectPaths.delete(sessionId);
|
|
5506
5541
|
this.uiStateTouched.delete(sessionId);
|
|
5542
|
+
this.uiStateDirty.delete(sessionId);
|
|
5507
5543
|
}
|
|
5508
5544
|
/**
|
|
5509
5545
|
* Evict UIStates whose tree has been idle past the TTL. This is THE eviction
|
|
@@ -5560,8 +5596,10 @@ var init_session_ui_store = __esm({
|
|
|
5560
5596
|
* session evolving elsewhere would freeze on the first snapshot.
|
|
5561
5597
|
*/
|
|
5562
5598
|
prepareForView(sessionId, selfDriven) {
|
|
5563
|
-
if (!selfDriven)
|
|
5599
|
+
if (!selfDriven) {
|
|
5564
5600
|
this.uiStates.delete(sessionId);
|
|
5601
|
+
this.uiStateDirty.delete(sessionId);
|
|
5602
|
+
}
|
|
5565
5603
|
this.uiStateProjectPaths.set(sessionId, this.host.workspaceRoot);
|
|
5566
5604
|
return this.ensureUIState(sessionId);
|
|
5567
5605
|
}
|
|
@@ -5571,6 +5609,7 @@ var init_session_ui_store = __esm({
|
|
|
5571
5609
|
this.uiStates.delete(id);
|
|
5572
5610
|
this.uiStateProjectPaths.delete(id);
|
|
5573
5611
|
this.uiStateTouched.delete(id);
|
|
5612
|
+
this.uiStateDirty.delete(id);
|
|
5574
5613
|
const timer = this.persistTimers.get(id);
|
|
5575
5614
|
if (timer) {
|
|
5576
5615
|
clearTimeout(timer);
|
|
@@ -10392,6 +10431,7 @@ var init_session_manager = __esm({
|
|
|
10392
10431
|
draftReset,
|
|
10393
10432
|
output: "",
|
|
10394
10433
|
finalOutput: "",
|
|
10434
|
+
turnError: null,
|
|
10395
10435
|
promise: null,
|
|
10396
10436
|
abortController: null,
|
|
10397
10437
|
messageQueue: [],
|
|
@@ -10695,6 +10735,13 @@ var init_session_manager = __esm({
|
|
|
10695
10735
|
getCachedUIState(rootSessionId) {
|
|
10696
10736
|
return this.uiStore.getCachedUIState(rootSessionId);
|
|
10697
10737
|
}
|
|
10738
|
+
/** Whether a root's cached UIState holds local mutations not yet persisted.
|
|
10739
|
+
* The WS save paths gate on this — a clean state (pure disk seed, or
|
|
10740
|
+
* already flushed) must not be written back over a file that another
|
|
10741
|
+
* process (cron `halo cli` child) may have advanced since. */
|
|
10742
|
+
isUIStateDirty(rootSessionId) {
|
|
10743
|
+
return this.uiStore.isUIStateDirty(rootSessionId);
|
|
10744
|
+
}
|
|
10698
10745
|
getUIState(rootSessionId) {
|
|
10699
10746
|
return this.uiStore.getUIState(rootSessionId);
|
|
10700
10747
|
}
|
|
@@ -10802,6 +10849,7 @@ var init_session_manager = __esm({
|
|
|
10802
10849
|
const maxRetries = config.agent.maxRetries;
|
|
10803
10850
|
session.output = "";
|
|
10804
10851
|
session.finalOutput = "";
|
|
10852
|
+
session.turnError = null;
|
|
10805
10853
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
10806
10854
|
session.abortController = new AbortController();
|
|
10807
10855
|
const signal = session.abortController.signal;
|
|
@@ -10858,6 +10906,7 @@ var init_session_manager = __esm({
|
|
|
10858
10906
|
if (msg.includes("insufficient balance") || msg.includes("suspended") || msg.includes("invalid api key") || msg.includes("Invalid API Key") || msg.includes("Unauthorized") || msg.includes("authentication")) {
|
|
10859
10907
|
console.error(`[SessionManager] Session ${session.id} account error: ${msg}`);
|
|
10860
10908
|
this.emitEvent(session.id, { type: "error", error: msg, agentName: session.agentName, taskId: session.parentId ? session.id : void 0 });
|
|
10909
|
+
session.turnError = msg;
|
|
10861
10910
|
resultText = `Error: ${msg}`;
|
|
10862
10911
|
break;
|
|
10863
10912
|
}
|
|
@@ -10883,7 +10932,7 @@ var init_session_manager = __esm({
|
|
|
10883
10932
|
continue;
|
|
10884
10933
|
}
|
|
10885
10934
|
}
|
|
10886
|
-
if (msg === "fetch failed" || msg === "Model request timed out" || msg.includes("UND_ERR_HEADERS_TIMEOUT") || msg.includes("HeadersTimeoutError") || msg.includes("socket hang up") || msg.includes("ECONNRESET") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("EAI_AGAIN")) {
|
|
10935
|
+
if (msg === "fetch failed" || msg === "Model request timed out" || msg.includes("http2 request did not get a response") || msg.includes("UND_ERR_HEADERS_TIMEOUT") || msg.includes("HeadersTimeoutError") || msg.includes("socket hang up") || msg.includes("ECONNRESET") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("EAI_AGAIN")) {
|
|
10887
10936
|
if (attempt + 1 < maxRetries) {
|
|
10888
10937
|
const delay = 1e3 * Math.pow(2, attempt) + Math.random() * 500;
|
|
10889
10938
|
console.debug(`[SessionManager] Session ${session.id} transient network error, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries}): ${msg}`);
|
|
@@ -10926,6 +10975,7 @@ var init_session_manager = __esm({
|
|
|
10926
10975
|
const errDetail = [errName, httpStatus ? `HTTP ${httpStatus}` : ""].filter(Boolean).join(" ");
|
|
10927
10976
|
console.error(`[SessionManager] Session ${session.id} error (attempt ${attempt + 1}/${maxRetries})${errDetail ? ` [${errDetail}]` : ""}: ${msg}`);
|
|
10928
10977
|
this.emitEvent(session.id, { type: "error", error: msg, agentName: session.agentName, taskId: session.parentId ? session.id : void 0 });
|
|
10978
|
+
session.turnError = errName ? `${errName}: ${msg}` : msg;
|
|
10929
10979
|
resultText = `Error: ${errName ? `${errName}: ` : ""}${msg}`;
|
|
10930
10980
|
break;
|
|
10931
10981
|
}
|
|
@@ -10991,7 +11041,12 @@ var init_session_manager = __esm({
|
|
|
10991
11041
|
return;
|
|
10992
11042
|
}
|
|
10993
11043
|
this.db.update(agentSessions).set({ stoppedAt: Date.now() }).where(eq6(agentSessions.id, session.id)).run();
|
|
10994
|
-
|
|
11044
|
+
let result = session.finalOutput || session.output || "(no output)";
|
|
11045
|
+
if (session.turnError) {
|
|
11046
|
+
result = `[SUB-AGENT ABORTED: the last turn was terminated by an unrecoverable error, NOT completed. Error: ${session.turnError}. The text below is a partial trace of the aborted turn \u2014 do not treat it as a finished result. Re-dispatch with query_session("${session.id}", ...) to let it resume.]
|
|
11047
|
+
|
|
11048
|
+
${result}`;
|
|
11049
|
+
}
|
|
10995
11050
|
console.debug(`[SessionManager] Auto-report: ${session.id} \u2192 parent ${session.parentId} \u2014 result: ${result.slice(0, 150)}`);
|
|
10996
11051
|
const reportCap = config.limits.autoReportMax;
|
|
10997
11052
|
const truncatedReport = result.length > reportCap ? result.slice(0, reportCap) + `
|
|
@@ -21552,6 +21607,8 @@ function setupWebSocketHandler(deps) {
|
|
|
21552
21607
|
const state = getState(client);
|
|
21553
21608
|
if (!state)
|
|
21554
21609
|
return;
|
|
21610
|
+
if (!client.sessionManager.isUIStateDirty(client.sessionId))
|
|
21611
|
+
return;
|
|
21555
21612
|
const projectPath = resolveProjectPath2(client.projectId);
|
|
21556
21613
|
const snapshot = createSaveSnapshot(state);
|
|
21557
21614
|
if (snapshot.length === 0)
|
|
@@ -29087,7 +29144,7 @@ var init_dist2 = __esm({
|
|
|
29087
29144
|
init_git_ssh();
|
|
29088
29145
|
__dirname3 = path64.dirname(fileURLToPath3(import.meta.url));
|
|
29089
29146
|
PORT = config.server.port;
|
|
29090
|
-
HALO_VERSION = "1.1.
|
|
29147
|
+
HALO_VERSION = "1.1.4";
|
|
29091
29148
|
PROJECT_ROOT = findProjectRoot();
|
|
29092
29149
|
HALO_HOME4 = path64.join(homedir24(), ".halo");
|
|
29093
29150
|
GIT_SHA = readGitSha();
|
|
@@ -29989,7 +30046,7 @@ import path65 from "node:path";
|
|
|
29989
30046
|
import fs58 from "node:fs";
|
|
29990
30047
|
import { homedir as homedir25 } from "node:os";
|
|
29991
30048
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
29992
|
-
var VERSION = "1.1.
|
|
30049
|
+
var VERSION = "1.1.4";
|
|
29993
30050
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
29994
30051
|
|
|
29995
30052
|
Commands:
|
package/package.json
CHANGED
|
@@ -35,6 +35,34 @@ secrets:
|
|
|
35
35
|
# thinking.default → effort selected when Thinking is on (adaptive)
|
|
36
36
|
# thinking.defaultBudgetTokens → budget selected when Thinking is on (manual)
|
|
37
37
|
models:
|
|
38
|
+
- id: global.anthropic.claude-fable-5-1
|
|
39
|
+
displayName: Claude Fable 5.1
|
|
40
|
+
maxOutputTokens: 128000
|
|
41
|
+
contextWindow: 1000000
|
|
42
|
+
capabilities:
|
|
43
|
+
image: true
|
|
44
|
+
video: false
|
|
45
|
+
audio: false
|
|
46
|
+
promptCaching:
|
|
47
|
+
default: 1h
|
|
48
|
+
ttlPresets:
|
|
49
|
+
- { value: 5m, label: 5min }
|
|
50
|
+
- { value: 1h, label: 1hour }
|
|
51
|
+
thinking:
|
|
52
|
+
# Fable 5.1 — Mythos-class flagship refresh. Adaptive effort set
|
|
53
|
+
# confirmed by an invalid-value probe against Bedrock (2026-09-02):
|
|
54
|
+
# low / medium / high / xhigh / max, same as Fable 5. 1M context,
|
|
55
|
+
# 128K output. Smoke-tested on invoke (xhigh).
|
|
56
|
+
mode: adaptive
|
|
57
|
+
defaultEnabled: true
|
|
58
|
+
default: high # flagship-of-flagships — high by default; xhigh/max on demand
|
|
59
|
+
effortPresets:
|
|
60
|
+
- { value: low, label: Low }
|
|
61
|
+
- { value: medium, label: Medium }
|
|
62
|
+
- { value: high, label: High }
|
|
63
|
+
- { value: xhigh, label: XHigh }
|
|
64
|
+
- { value: max, label: Max }
|
|
65
|
+
|
|
38
66
|
- id: global.anthropic.claude-fable-5
|
|
39
67
|
displayName: Claude Fable 5
|
|
40
68
|
maxOutputTokens: 128000
|
|
@@ -46,7 +46,7 @@ simply absent. Consequences you must apply:
|
|
|
46
46
|
global skill for one workspace, copy the WHOLE folder to workspace scope
|
|
47
47
|
(including resource files — remember: no per-file fallback), then edit.
|
|
48
48
|
- **Built-in skills** (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`,
|
|
49
|
-
`self`, `aws-knowledge`, `
|
|
49
|
+
`self`, `aws-knowledge`, `web-search`): their global copies are force re-seeded on every server
|
|
50
50
|
start — direct edits to those global files are lost. Customize at workspace
|
|
51
51
|
scope instead.
|
|
52
52
|
- **Disabled state** lives per-workspace in the `disabled_items` DB table, not
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-search
|
|
3
|
+
description: Real-time web search with two gears. Fast (default, Amazon Nova grounding, ~3s, cheap) — current events, news, prices, any routine lookup. Deep (--deep, OpenAI GPT-5.6 web_search via Bedrock Mantle, ~20-40s, token-expensive) — multi-round retrieval with first-hand sources and per-claim citations, for verifying facts behind important decisions or double-checking weak/contradictory fast results. Default to fast; escalate to deep only when the question deserves depth.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
# Web Search
|
|
7
|
+
|
|
8
|
+
One skill, two gears:
|
|
9
|
+
|
|
10
|
+
| Gear | Engine | Speed / cost | Use for |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| **fast** (default) | Amazon Nova 2 Lite `nova_grounding` | ~3 s, ~500 tokens | News, quotes, prices, routine lookups — the everyday default |
|
|
13
|
+
| **deep** (`--deep`) | OpenAI GPT-5.6 `web_search` (Bedrock Mantle) | ~20-40 s, 10-40k tokens | Fact verification before important conclusions; second opinion when fast results look weak, self-contradictory, or conflict with data you hold |
|
|
14
|
+
|
|
15
|
+
Deep gear does multi-round real retrieval (bilingual query rewriting, `site:`-scoped
|
|
16
|
+
searches against official sites), leans toward authoritative first-hand sources
|
|
17
|
+
(official announcements, news agencies), and attaches a citation to every claim —
|
|
18
|
+
**expensive and slow, but thorough and it doesn't make things up**.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# fast (default) — everyday lookups
|
|
24
|
+
python3 ~/.halo/global/skills/web-search/search.py "What are the latest news about the China A-share market today?"
|
|
25
|
+
|
|
26
|
+
# deep — verify facts that must be right
|
|
27
|
+
python3 ~/.halo/global/skills/web-search/search.py --deep "What did the US Federal Reserve announce most recently about interest rates?"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- Output: answer text + a `Sources:` URL list (gear/model/token usage on stderr).
|
|
31
|
+
- Deep gear default model is `luna` (reasoning effort=max — retrieval planning
|
|
32
|
+
maxed out). `--deep --model sol` is carpet-bombing research (auto-paired with
|
|
33
|
+
effort=xhigh; ~3x more search queries, roughly double the tokens) — reserve it
|
|
34
|
+
for "profile one stock / one topic exhaustively" jobs.
|
|
35
|
+
- Full natural-language questions beat keyword stubs in both gears
|
|
36
|
+
(`"RTX 5080"` ✗ → `"Tell me about the NVIDIA RTX 5080, including specs and price."` ✓).
|
|
37
|
+
English works best; Chinese works too — deep gear rewrites queries bilingually.
|
|
38
|
+
- Search output is still a lead, not gospel: for critical figures, `web_fetch`
|
|
39
|
+
the cited source URL and confirm before acting on it.
|
|
40
|
+
- Auth is automatic in both gears (machine AWS credentials — no API key to
|
|
41
|
+
configure or rotate). Deep gear needs the `aws-bedrock-token-generator` pip
|
|
42
|
+
package (one-time: `pip3 install --user aws-bedrock-token-generator`).
|
|
43
|
+
- A deep call can take 40 s — be patient in shell_exec. One clear question per
|
|
44
|
+
call; don't fan out parallel calls.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Web search with two gears.
|
|
3
|
+
|
|
4
|
+
fast (default): Amazon Nova 2 Lite + nova_grounding — ~3 s, ~500 tokens.
|
|
5
|
+
deep (--deep): OpenAI GPT-5.6 on Bedrock Mantle + server-side web_search —
|
|
6
|
+
multi-round retrieval, first-hand sources, ~20-40 s, 10-40k tokens.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 search.py "your question"
|
|
10
|
+
python3 search.py --deep "your question" [--model sol|luna]
|
|
11
|
+
|
|
12
|
+
Auth is automatic in both gears (machine AWS credentials): fast uses boto3's
|
|
13
|
+
default credential chain; deep mints a 12-hour bearer token on every call via
|
|
14
|
+
aws-bedrock-token-generator — no API key to configure or rotate.
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
import urllib.error
|
|
19
|
+
import urllib.request
|
|
20
|
+
|
|
21
|
+
MANTLE_URL = "https://bedrock-mantle.us-east-2.api.aws/openai/v1/responses"
|
|
22
|
+
MODELS = {"luna": "openai.gpt-5.6-luna", "sol": "openai.gpt-5.6-sol"}
|
|
23
|
+
# luna (default) runs effort=max to squeeze out its retrieval planning; sol's
|
|
24
|
+
# retrieval is already exhaustive at medium (measured 48 search queries on one
|
|
25
|
+
# question), so it gets xhigh to avoid pointless token doubling — max over
|
|
26
|
+
# xhigh measured only ~6% more reasoning tokens.
|
|
27
|
+
EFFORTS = {"openai.gpt-5.6-luna": "max", "openai.gpt-5.6-sol": "xhigh"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def fast_search(question: str) -> None:
|
|
31
|
+
import boto3
|
|
32
|
+
from botocore.config import Config
|
|
33
|
+
|
|
34
|
+
bedrock = boto3.client(
|
|
35
|
+
"bedrock-runtime", region_name="us-east-1", config=Config(read_timeout=60)
|
|
36
|
+
)
|
|
37
|
+
response = bedrock.converse(
|
|
38
|
+
modelId="us.amazon.nova-2-lite-v1:0",
|
|
39
|
+
messages=[{"role": "user", "content": [{"text": question}]}],
|
|
40
|
+
toolConfig={"tools": [{"systemTool": {"name": "nova_grounding"}}]},
|
|
41
|
+
)
|
|
42
|
+
answer = ""
|
|
43
|
+
sources: list[str] = []
|
|
44
|
+
for content in response["output"]["message"]["content"]:
|
|
45
|
+
if "text" in content:
|
|
46
|
+
answer += content["text"]
|
|
47
|
+
elif "citationsContent" in content:
|
|
48
|
+
for c in content["citationsContent"].get("citations", []):
|
|
49
|
+
url = c.get("location", {}).get("web", {}).get("url", "")
|
|
50
|
+
if url and url not in sources:
|
|
51
|
+
sources.append(url)
|
|
52
|
+
print(answer)
|
|
53
|
+
if sources:
|
|
54
|
+
print("\nSources:")
|
|
55
|
+
for u in sources:
|
|
56
|
+
print(" -", u)
|
|
57
|
+
usage = response.get("usage", {})
|
|
58
|
+
print(f"\n[gear=fast model=nova-2-lite total_tokens={usage.get('totalTokens')}]", file=sys.stderr)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def deep_search(question: str, model: str) -> None:
|
|
62
|
+
from aws_bedrock_token_generator import provide_token
|
|
63
|
+
|
|
64
|
+
token = provide_token(region="us-east-2")
|
|
65
|
+
body = json.dumps(
|
|
66
|
+
{
|
|
67
|
+
"model": model,
|
|
68
|
+
"input": question,
|
|
69
|
+
"tools": [{"type": "web_search"}],
|
|
70
|
+
"reasoning": {"effort": EFFORTS[model]},
|
|
71
|
+
}
|
|
72
|
+
).encode()
|
|
73
|
+
req = urllib.request.Request(
|
|
74
|
+
MANTLE_URL,
|
|
75
|
+
data=body,
|
|
76
|
+
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
|
77
|
+
method="POST",
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
with urllib.request.urlopen(req, timeout=180) as resp:
|
|
81
|
+
data = json.loads(resp.read())
|
|
82
|
+
except urllib.error.HTTPError as e:
|
|
83
|
+
print(f"HTTP {e.code}: {e.read().decode(errors='replace')[:2000]}", file=sys.stderr)
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
|
|
86
|
+
texts: list[str] = []
|
|
87
|
+
sources: list[str] = []
|
|
88
|
+
for item in data.get("output", []):
|
|
89
|
+
if item.get("type") == "message":
|
|
90
|
+
for c in item.get("content", []):
|
|
91
|
+
if c.get("type") == "output_text":
|
|
92
|
+
texts.append(c.get("text", ""))
|
|
93
|
+
for a in c.get("annotations", []):
|
|
94
|
+
if a.get("type") == "url_citation":
|
|
95
|
+
u = a.get("url", "")
|
|
96
|
+
if u and u not in sources:
|
|
97
|
+
sources.append(u)
|
|
98
|
+
print("\n".join(texts))
|
|
99
|
+
if sources:
|
|
100
|
+
print("\nSources:")
|
|
101
|
+
for u in sources:
|
|
102
|
+
print(" -", u)
|
|
103
|
+
usage = data.get("usage", {})
|
|
104
|
+
print(f"\n[gear=deep model={model} total_tokens={usage.get('total_tokens')}]", file=sys.stderr)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> None:
|
|
108
|
+
args = sys.argv[1:]
|
|
109
|
+
deep = False
|
|
110
|
+
model = MODELS["luna"]
|
|
111
|
+
if "--deep" in args:
|
|
112
|
+
deep = True
|
|
113
|
+
args.remove("--deep")
|
|
114
|
+
if "--model" in args:
|
|
115
|
+
i = args.index("--model")
|
|
116
|
+
try:
|
|
117
|
+
model = MODELS[args[i + 1]]
|
|
118
|
+
except (IndexError, KeyError):
|
|
119
|
+
print("--model must be 'luna' or 'sol'", file=sys.stderr)
|
|
120
|
+
sys.exit(2)
|
|
121
|
+
del args[i : i + 2]
|
|
122
|
+
if not deep:
|
|
123
|
+
print("--model only applies with --deep", file=sys.stderr)
|
|
124
|
+
sys.exit(2)
|
|
125
|
+
if not args:
|
|
126
|
+
print('usage: search.py [--deep] "<question>" [--model sol|luna]', file=sys.stderr)
|
|
127
|
+
sys.exit(2)
|
|
128
|
+
question = " ".join(args)
|
|
129
|
+
|
|
130
|
+
if deep:
|
|
131
|
+
deep_search(question, model)
|
|
132
|
+
else:
|
|
133
|
+
fast_search(question)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|
|
@@ -53,7 +53,7 @@ Agents (2):
|
|
|
53
53
|
Skills (3):
|
|
54
54
|
- repo-conventions (workspace)
|
|
55
55
|
- aws-knowledge (global)
|
|
56
|
-
-
|
|
56
|
+
- web-search (global)
|
|
57
57
|
|
|
58
58
|
Instructions: .halo/INSTRUCTIONS.md (global fallback)
|
|
59
59
|
INDEX.md → 2 docs followed: docs/architecture/auth.md, docs/dev/api.md
|
|
@@ -74,7 +74,7 @@ BUILTIN_AGENT_IDS = {
|
|
|
74
74
|
}
|
|
75
75
|
BUILTIN_SKILL_IDS = {
|
|
76
76
|
"agent", "skill", "workspace", "cron", "send-file", "acp",
|
|
77
|
-
"aws-knowledge", "
|
|
77
|
+
"aws-knowledge", "web-search", "self",
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
# --- helpers ---------------------------------------------------------------
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: nova-web-search
|
|
3
|
-
description: Real-time web search using Amazon Nova 2 Lite's built-in nova_grounding system tool. Use this skill when the user asks about current events, news, latest updates, prices, or any real-time information. This replaces the web_search tool.
|
|
4
|
-
user-invocable: false
|
|
5
|
-
---
|
|
6
|
-
# Nova Web Search
|
|
7
|
-
|
|
8
|
-
Perform real-time web search using Amazon Nova 2 Lite's built-in `nova_grounding` system tool. Returns a synthesized answer with source URLs.
|
|
9
|
-
|
|
10
|
-
## How to Use
|
|
11
|
-
|
|
12
|
-
Run the following Python script via `shell_exec`:
|
|
13
|
-
|
|
14
|
-
```python
|
|
15
|
-
import boto3
|
|
16
|
-
from botocore.config import Config
|
|
17
|
-
|
|
18
|
-
bedrock = boto3.client("bedrock-runtime", region_name="us-east-1", config=Config(read_timeout=60))
|
|
19
|
-
|
|
20
|
-
query = "YOUR_QUERY_HERE" # Replace with the actual search query
|
|
21
|
-
|
|
22
|
-
response = bedrock.converse(
|
|
23
|
-
modelId="us.amazon.nova-2-lite-v1:0",
|
|
24
|
-
messages=[{"role": "user", "content": [{"text": query}]}],
|
|
25
|
-
toolConfig={"tools": [{"systemTool": {"name": "nova_grounding"}}]}
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
answer = ""
|
|
29
|
-
sources = []
|
|
30
|
-
for content in response["output"]["message"]["content"]:
|
|
31
|
-
if "text" in content:
|
|
32
|
-
answer += content["text"]
|
|
33
|
-
elif "citationsContent" in content:
|
|
34
|
-
for c in content["citationsContent"].get("citations", []):
|
|
35
|
-
url = c.get("location", {}).get("web", {}).get("url", "")
|
|
36
|
-
if url and url not in sources:
|
|
37
|
-
sources.append(url)
|
|
38
|
-
|
|
39
|
-
print(answer)
|
|
40
|
-
if sources:
|
|
41
|
-
print("\nSources:", sources)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Query Formulation
|
|
45
|
-
|
|
46
|
-
Nova grounds better against full natural-language questions than against
|
|
47
|
-
keyword stubs — raw keywords return more loosely-related sources, while
|
|
48
|
-
a question gives Nova enough context to filter.
|
|
49
|
-
|
|
50
|
-
| User Intent | Query Template |
|
|
51
|
-
|---|---|
|
|
52
|
-
| News / updates / latest developments | `"What are the latest news about X?"` |
|
|
53
|
-
| Product / specs / concept lookup | `"Tell me about X, including specs, price, and latest updates."` |
|
|
54
|
-
| General search | `"Search for information about X."` |
|
|
55
|
-
|
|
56
|
-
**Examples:**
|
|
57
|
-
- ✅ `"What are the latest news about the Middle East conflict?"`
|
|
58
|
-
- ✅ `"Tell me about the NVIDIA RTX 5080, including specs and price."`
|
|
59
|
-
- ✗ `"RTX 5080"` — terse keywords leave Nova guessing at intent
|
|
60
|
-
|
|
61
|
-
## Requirements
|
|
62
|
-
|
|
63
|
-
- Region **must** be `us-east-1`
|
|
64
|
-
- English queries yield better results than other languages
|
|
65
|
-
- Model ID: `us.amazon.nova-2-lite-v1:0`
|
/package/admin-out/_next/static/{oDvBPFdNAW2vHdHcxQ0lw → PuVQROsIREQUt1b6EFWJR}/_buildManifest.js
RENAMED
|
File without changes
|
/package/admin-out/_next/static/{oDvBPFdNAW2vHdHcxQ0lw → PuVQROsIREQUt1b6EFWJR}/_ssgManifest.js
RENAMED
|
File without changes
|