@turmind/halo 1.6.5 → 1.6.6
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/README.md +1 -0
- 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/a2a.md +1 -1
- package/bundled-docs/design/cron.md +1 -1
- package/bundled-docs/design/feishu.md +2 -0
- package/bundled-docs/design/halo-city.md +4 -0
- package/bundled-docs/design/slack.md +2 -2
- package/bundled-docs/design/wecom.md +1 -1
- package/bundled-docs/dev/add-model-provider.md +1 -1
- package/bundled-docs/dev/api.md +3 -3
- package/dist/index.js +172 -71
- package/package.json +1 -1
- package/templates/models/mimo-token-plan-china.yaml +3 -55
- /package/admin-out/_next/static/{8v1hCXbUqJhyMuK7LVeet → Yc05ORrVk5IE9P1Gd8Ojh}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{8v1hCXbUqJhyMuK7LVeet → Yc05ORrVk5IE9P1Gd8Ojh}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -199,6 +199,7 @@ Halo is young — treat it as an early-stage project:
|
|
|
199
199
|
- **The sandbox guards writes and credentials, not the network.** In `workspace` / `readonly` sessions, bubblewrap (Linux) or Seatbelt (macOS) limits writes to the workspace and temp dirs (`readonly`: temp dirs only) and hides `~/.halo/secrets`, `~/.ssh`, `~/.aws` and similar paths; the rest of the host stays readable and outbound connections work. The threat model is accidental damage and path escape by a trusted agent — not containing a malicious skill or extension, or a prompt-injected session sending out what it can read.
|
|
200
200
|
- **Windows hosts have no sandbox.** There a `workspace` session is effectively `full`, and `readonly` can read any path the halo process can — don't hand out non-`full` channel tokens on a Windows host expecting isolation ([dev/tools.md](.halo/docs/dev/tools.md)).
|
|
201
201
|
- **The admin is single-tenant, on plain HTTP, on all interfaces.** One password (5 failed logins lock that IP out for 15 minutes); whoever holds the admin cookie controls the server — every workspace, shell as the server user, every channel credential. Keep port 9527 behind a firewall, VPN or SSH tunnel, or a reverse proxy with TLS ([deploy.md §10](.halo/docs/dev/deploy.md#10-optional-nginx-reverse-proxy)), and give other people channel accounts with their own access levels instead of the admin login.
|
|
202
|
+
- **No MCP support.** Agents can't use MCP servers as tools, and Halo doesn't serve MCP itself. To reach an MCP server, wrap it in a skill whose script calls it — the bundled `aws-knowledge` skill does this for the AWS Knowledge MCP server.
|
|
202
203
|
- **Tests are unit/regression-level.** CI runs them on every push and PR to `main`; there's no end-to-end or integration suite yet.
|
|
203
204
|
- **APIs and on-disk formats may still change between versions.** Expect rough edges while things stabilize.
|
|
204
205
|
|
package/admin-out/404.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--Yc05ORrVk5IE9P1Gd8Ojh--><html lang="en" class="dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/e4af272ccee01ff0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/7b736202f0b1dbfd.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/e890aa0d13e94290.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-74f577c1b094f942.js"/><script src="/_next/static/chunks/1b86ba38-c29611e086b2ce25.js" async=""></script><script src="/_next/static/chunks/698-19872069fed27164.js" async=""></script><script src="/_next/static/chunks/main-app-340a5b58ed229c4d.js" async=""></script><script src="/_next/static/chunks/989-066a874961bd519f.js" async=""></script><script src="/_next/static/chunks/app/layout-ea161287d934ba96.js" async=""></script><meta name="robots" content="noindex"/><meta name="next-size-adjust" content=""/><title>404: This page could not be found.</title><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_d8ebd1 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,"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-74f577c1b094f942.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[58321,[\"989\",\"static/chunks/989-066a874961bd519f.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-066a874961bd519f.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[23632,[],\"OutletBoundary\"]\n8:I[86889,[],\"AsyncMetadataOutlet\"]\na:I[23632,[],\"ViewportBoundary\"]\nc:I[23632,[],\"MetadataBoundary\"]\nd:\"$Sreact.suspense\"\nf:I[1935,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/7b736202f0b1dbfd.css\",\"style\"]\n:HL[\"/_next/static/css/e890aa0d13e94290.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"Yc05ORrVk5IE9P1Gd8Ojh\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/7b736202f0b1dbfd.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"link\",\"1\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/e890aa0d13e94290.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark\",\"suppressHydrationWarning\":true,\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}\"}}]}],[\"$\",\"body\",null,{\"className\":\"__variable_d8ebd1 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L6\",null,{\"children\":[\"$L7\",[\"$\",\"$L8\",null,{\"promise\":\"$@9\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$La\",null,{\"children\":\"$Lb\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lc\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$d\",null,{\"fallback\":null,\"children\":\"$Le\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$f\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n7:null\n"])</script><script>self.__next_f.push([1,"10:I[88809,[],\"IconMark\"]\n9:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Halo - Multi-Agent Workspace\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Multi-agent collaboration workspace for complex project delivery\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon.svg?3a6863548daca964\",\"type\":\"image/svg+xml\",\"sizes\":\"any\"}],[\"$\",\"$L10\",\"4\",{}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"e:\"$9:metadata\"\n"])</script></body></html>
|
package/admin-out/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--Yc05ORrVk5IE9P1Gd8Ojh--><html lang="en" class="dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/e4af272ccee01ff0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/7b736202f0b1dbfd.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/e890aa0d13e94290.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/659eccb5db697b76.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-74f577c1b094f942.js"/><script src="/_next/static/chunks/1b86ba38-c29611e086b2ce25.js" async=""></script><script src="/_next/static/chunks/698-19872069fed27164.js" async=""></script><script src="/_next/static/chunks/main-app-340a5b58ed229c4d.js" async=""></script><script src="/_next/static/chunks/989-066a874961bd519f.js" async=""></script><script src="/_next/static/chunks/app/layout-ea161287d934ba96.js" async=""></script><script src="/_next/static/chunks/1d2d5650-d8d130c85727fdbb.js" async=""></script><script src="/_next/static/chunks/87-6a2716c98a819c4e.js" async=""></script><script src="/_next/static/chunks/app/page-812bcbb46db628b6.js" async=""></script><meta name="next-size-adjust" content=""/><title>Halo - Multi-Agent Workspace</title><meta name="description" content="Multi-agent collaboration workspace for complex project delivery"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link rel="icon" href="/icon.svg?3a6863548daca964" type="image/svg+xml" sizes="any"/><script>try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_d8ebd1 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><div class="flex h-screen w-screen items-center justify-center bg-[var(--background)]"><div class="text-sm text-[var(--muted-foreground)]">Loading...</div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-74f577c1b094f942.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[58321,[\"989\",\"static/chunks/989-066a874961bd519f.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"ThemeProvider\"]\n3:I[24554,[\"989\",\"static/chunks/989-066a874961bd519f.js\",\"177\",\"static/chunks/app/layout-ea161287d934ba96.js\"],\"AppI18nProvider\"]\n4:I[63693,[],\"\"]\n5:I[74713,[],\"\"]\n6:I[5444,[],\"ClientPageRoot\"]\n7:I[3124,[\"560\",\"static/chunks/1d2d5650-d8d130c85727fdbb.js\",\"87\",\"static/chunks/87-6a2716c98a819c4e.js\",\"989\",\"static/chunks/989-066a874961bd519f.js\",\"974\",\"static/chunks/app/page-812bcbb46db628b6.js\"],\"default\"]\na:I[23632,[],\"OutletBoundary\"]\nc:I[86889,[],\"AsyncMetadataOutlet\"]\ne:I[23632,[],\"ViewportBoundary\"]\n10:I[23632,[],\"MetadataBoundary\"]\n11:\"$Sreact.suspense\"\n13:I[1935,[],\"\"]\n:HL[\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/7b736202f0b1dbfd.css\",\"style\"]\n:HL[\"/_next/static/css/e890aa0d13e94290.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"Yc05ORrVk5IE9P1Gd8Ojh\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/7b736202f0b1dbfd.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"link\",\"1\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/e890aa0d13e94290.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark\",\"suppressHydrationWarning\":true,\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}\"}}]}],[\"$\",\"body\",null,{\"className\":\"__variable_d8ebd1 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L6\",null,{\"Component\":\"$7\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@8\",\"$@9\"]}],[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/659eccb5db697b76.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"$La\",null,{\"children\":[\"$Lb\",[\"$\",\"$Lc\",null,{\"promise\":\"$@d\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Le\",null,{\"children\":\"$Lf\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$L10\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$11\",null,{\"fallback\":null,\"children\":\"$L12\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$13\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"8:{}\n9:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"f:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\nb:null\n"])</script><script>self.__next_f.push([1,"14:I[88809,[],\"IconMark\"]\nd:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Halo - Multi-Agent Workspace\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Multi-agent collaboration workspace for complex project delivery\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon.svg?3a6863548daca964\",\"type\":\"image/svg+xml\",\"sizes\":\"any\"}],[\"$\",\"$L14\",\"4\",{}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"12:\"$d:metadata\"\n"])</script></body></html>
|
package/admin-out/index.txt
CHANGED
|
@@ -15,7 +15,7 @@ e:I[23632,[],"ViewportBoundary"]
|
|
|
15
15
|
:HL["/_next/static/css/7b736202f0b1dbfd.css","style"]
|
|
16
16
|
:HL["/_next/static/css/e890aa0d13e94290.css","style"]
|
|
17
17
|
:HL["/_next/static/css/659eccb5db697b76.css","style"]
|
|
18
|
-
0:{"P":null,"b":"
|
|
18
|
+
0:{"P":null,"b":"Yc05ORrVk5IE9P1Gd8Ojh","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7b736202f0b1dbfd.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/e890aa0d13e94290.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"dark","suppressHydrationWarning":true,"children":[["$","head",null,{"children":["$","script",null,{"dangerouslySetInnerHTML":{"__html":"try{var t=localStorage.getItem('halo_theme');if(t==='light'||t==='midnight'||t==='warm')document.documentElement.dataset.theme=t}catch(e){}"}}]}],["$","body",null,{"className":"__variable_d8ebd1 font-sans antialiased","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L6",null,{"Component":"$7","searchParams":{},"params":{},"promises":["$@8","$@9"]}],[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/659eccb5db697b76.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","$La",null,{"children":["$Lb",["$","$Lc",null,{"promise":"$@d"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Le",null,{"children":"$Lf"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L10",null,{"children":["$","div",null,{"hidden":true,"children":["$","$11",null,{"fallback":null,"children":"$L12"}]}]}]]}],false]],"m":"$undefined","G":["$13",[]],"s":false,"S":true}
|
|
19
19
|
8:{}
|
|
20
20
|
9:"$0:f:0:1:2:children:1:props:children:0:props:params"
|
|
21
21
|
f:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
|
@@ -42,7 +42,7 @@ The URL is the workspace path relative to the base. `/home/u/A/B` becomes:
|
|
|
42
42
|
- `defaultInputModes` / `defaultOutputModes`: `text/plain` + the four image types.
|
|
43
43
|
- `version`: the user's, else `HALO_VERSION`.
|
|
44
44
|
|
|
45
|
-
Served with `Cache-Control: private, max-age=300` and an ETag (304 on match). An invalid or incomplete card file → 500 to the authenticated caller and `[A2A] invalid agent-card.json …` in the log.
|
|
45
|
+
Served with `Cache-Control: private, max-age=300` and an ETag (304 on match). The ETag is weak (`W/"…"`) because the gzipped and plain bodies share it; a 200 (gzipped or not) and its 304 carry the same tag, and If-None-Match is compared weakly (RFC 9110). An invalid or incomplete card file → 500 to the authenticated caller and `[A2A] invalid agent-card.json …` in the log.
|
|
46
46
|
|
|
47
47
|
**Interface URL:** `general.a2a.public_url` (env `HALO_A2A_PUBLIC_URL` overrides) + `/a2a/<rel>/`. Unset → derived from the request (`Host`, plus `X-Forwarded-Proto` when `general.server.trust_proxy` is on).
|
|
48
48
|
|
|
@@ -114,7 +114,7 @@ interface CronChannelDispatcher {
|
|
|
114
114
|
- no `supportsMedia` (telegram, feishu, wecom) → the **original text with `MEDIA:` lines intact** — the attachment degrades to a visible path instead of silently vanishing.
|
|
115
115
|
- `CronMedia.workspacePath` is the **job's** workspace (where the run produced its files), not the target account's — it's the sandbox root for `isMediaPathAllowed` (paths outside workspace/OS-tmp are rejected with a failed result row). Cron passes no access level, so the `full`-account relaxation the chat handlers get does not apply here — the job workspace + OS tmp rule stays.
|
|
116
116
|
- Wiring telegram/feishu up for real requires refactoring first, not copying: telegram's file send is an inline `sendPhoto/…/sendDocument` switch in `handler.ts`'s responder; feishu's `sendFeishuMedia` is anchored to an inbound `messageId` a cron run doesn't have. Extract the send-to-chat half in the channel module, then flip `supportsMedia: true` (details in each `cron-dispatcher.ts` header).
|
|
117
|
-
- **Per-channel size limits are the dispatcher's job**, not the runner's: wechat splits the text at `WECHAT_TEXT_LIMIT` (3500 chars, shared `splitText` from `channels/shared/chunk.ts`) and sends the chunks in order — the ilink gateway rejects any single `sendmessage` over 16 KB with `ret=-2`. A failure on chunk *i* of *n* is recorded as `chunk i/n: <error>` in the target's `DispatchResult`, so the admin run row shows how much of the report already landed (a `failed` row no longer means "nothing arrived").
|
|
117
|
+
- **Per-channel size limits are the dispatcher's job**, not the runner's: wechat splits the text at `WECHAT_TEXT_LIMIT` (3500 chars, shared `splitText` from `channels/shared/chunk.ts`) and sends the chunks in order — the ilink gateway rejects any single `sendmessage` over 16 KB with `ret=-2`. A failure on chunk *i* of *n* is recorded as `chunk i/n: <error>` in the target's `DispatchResult`, so the admin run row shows how much of the report already landed (a `failed` row no longer means "nothing arrived"). All five chunked dispatchers cut at their channel's chat-responder limit, exported from that channel's `event-adapter.ts` so cron and chat replies stay aligned: telegram `TELEGRAM_TEXT_LIMIT` (4000 chars — the Bot API caps a message at 4096), slack `SLACK_TEXT_LIMIT` (35 000 — Slack caps a body at ~40k), feishu `FEISHU_TEXT_LIMIT` (4500), wecom `WECOM_TEXT_LIMIT` (5000 chars — under the 20480-byte cap at 4 bytes/char), with the same sequential send and `chunk i/n: <error>` failure row (later chunks are not sent). Slack and feishu also run each chunk through the responder's formatter, in the responder's order (cut the raw text first, then format each chunk): `formatForSlack` (CommonMark → mrkdwn) and `formatForFeishu` (strip markup — the text msg type shows `**` literally), both in `channels/shared/markdown.ts`. Telegram, wechat and wecom send the chunks as-is. On slack a failed text chunk still lets the `MEDIA:` uploads run.
|
|
118
118
|
- **`chatId` semantics**: when a job is created from inside a chat, the target pins to that conversation (`chatId` set). When unset, the channel's own default-recipient logic decides: WeChat falls back to the QR-bind owner, then the last active chat; Telegram/Slack/Feishu/WeCom require an explicit `chatId` and throw without one. WeCom is the one dispatcher with **no HTTP send path**: it borrows the handler's live long-connect `WSClient` (`liveClients`), so a stopped / kicked / auth-exhausted account fails the target loudly (`wecom long-connect not active`) instead of queueing.
|
|
119
119
|
- `listTargets()` aggregates across all dispatchers (`listAllCronTargets`) to feed the admin create-form dropdown without the routes knowing any channel.
|
|
120
120
|
|
|
@@ -62,6 +62,8 @@ The feishu cron-dispatcher (`channels/feishu/cron-dispatcher.ts`) requires an ex
|
|
|
62
62
|
1. **Explicit `chatId` on the target** — set when the cron was created from inside a Feishu chat (e.g. cron created from a group thread auto-pins to that thread). Sends only to that chat (top-level message, not nested in the thread — Feishu's open API v1 doesn't support thread-targeting in sendMessage).
|
|
63
63
|
2. **Admin-UI cron without explicit target** — runs silently if no chatId is provided.
|
|
64
64
|
|
|
65
|
+
The report goes through the same steps as a chat reply. It is split with `splitText(text, FEISHU_TEXT_LIMIT)` (4500 chars, exported from `event-adapter.ts`), each chunk runs through `formatForFeishu`, and the chunks are sent in order as `text` messages. A failed chunk stops the send and is recorded as `chunk i/n: <error>`; the chunks before it were already delivered. Same contract as the other channels (see [cron.md](cron.md#dispatch-model)).
|
|
66
|
+
|
|
65
67
|
## Modules
|
|
66
68
|
|
|
67
69
|
Files: `packages/server/src/channels/feishu/`
|
|
@@ -19,6 +19,10 @@ Halo 服务器运行态的**街景式**像素可视化:每个 **workspace 是一
|
|
|
19
19
|
hub [README.md](https://github.com/turmind/halo-hub/blob/main/extensions/halo-city/README.md#和-halo-同源)。
|
|
20
20
|
- `/api/show/state` — 无活跃 UIState 的会话(idle/stopped/重启后)token
|
|
21
21
|
从会话文件头读取(按会话 `updatedAt` 缓存,未变化时不随轮询重读),不显示 0;含 `messageCount` 字段
|
|
22
|
+
- `/api/show/state` 带弱 `ETag` + `Cache-Control: private, no-cache`:哈希不含
|
|
23
|
+
`serverTime`,`uptime` 按分钟计入,所以服务器空闲时 7 秒一次的轮询绝大多数回
|
|
24
|
+
空 body 的 304,每分钟最多一次 200。浏览器 `fetch` 默认缓存模式自动带
|
|
25
|
+
`If-None-Match`,扩展不用改;鉴权仍在最前,不同 scope 的调用方 ETag 不同
|
|
22
26
|
|
|
23
27
|
> **`messageCount` 与 admin 的 `exchangeCount` 是两个语义,不要互相对齐**:
|
|
24
28
|
> 这里读的是会话文件里的 `messageCount`(活跃 UI 日志的长度),城市只把它
|
|
@@ -161,8 +161,8 @@ Slack cron-dispatcher plugs into the unified cron/dispatcher.ts registry. At fir
|
|
|
161
161
|
|
|
162
162
|
1. Retrieve the Slack account (must be enabled)
|
|
163
163
|
2. Parse the target's `chatId` (format: `D…`, `C…`, or `C…:thread_ts`)
|
|
164
|
-
3.
|
|
165
|
-
4. Return dispatch result (ok / error)
|
|
164
|
+
3. Split the text with `splitText(text, SLACK_TEXT_LIMIT)` (35 000 chars, exported from `event-adapter.ts`, the same limit as chat replies), run each chunk through `formatForSlack`, and call `postMessage()` for each in order with the chatId (and threadTs if present). A failed chunk stops the text send.
|
|
165
|
+
4. Return dispatch result: one row for the text (`ok`, or `chunk i/n: <error>`), then one row per `MEDIA:` upload. Uploads still run after a text failure.
|
|
166
166
|
|
|
167
167
|
If no explicit chatId is provided, dispatch fails with a clear message. This prevents silent failures in a shared-bot scenario where the "last messager" might be unrelated to whoever set up the cron.
|
|
168
168
|
|
|
@@ -65,7 +65,7 @@ The wecom cron-dispatcher (`channels/wecom/cron-dispatcher.ts`) requires an expl
|
|
|
65
65
|
1. **Explicit `chatId` on the target** — set when the cron was created from inside a WeCom chat (auto-pinned via `CommandContext.channel.chatId`). Shape: `<userid>` for a single chat, `<chatid>` for a group — exactly what `pickConversation` cached as `chatKey`. `sendMessage(chatId, { msgtype: 'markdown', … })` omits `chat_type`; the server's compat mode (`chat_type = 0`) resolves single vs group from the id.
|
|
66
66
|
2. **Admin-UI cron without explicit target** — throws `wecom cron target requires an explicit chatId …` and the run fails visibly. There is no `/search` route (WeCom exposes no directory API on this channel), so the target must be typed as `wecom:<accountId>:<chatId>`.
|
|
67
67
|
|
|
68
|
-
Because the push rides the handler's socket, the dispatcher borrows it through the exported `liveClients: Map<accountId, WSClient>` (set on `authenticated`, cleared on stop / kick / auth exhaustion). No live client → `{ ok: false, error: 'wecom long-connect not active' }` rather than queueing. WeCom additionally refuses pushes to a user / group that has never messaged the bot, and rate-limits each conversation to 30 msgs/min · 1000/hour (replies and pushes combined).
|
|
68
|
+
Because the push rides the handler's socket, the dispatcher borrows it through the exported `liveClients: Map<accountId, WSClient>` (set on `authenticated`, cleared on stop / kick / auth exhaustion). No live client → `{ ok: false, error: 'wecom long-connect not active' }` rather than queueing. The report is split with `splitText(text, WECOM_TEXT_LIMIT)` (5000 chars, exported from `event-adapter.ts`, the same limit as chat replies) and the chunks are sent in order as `markdown` messages, with no formatter. A failed chunk stops the send and is recorded as `chunk i/n: <error>`. WeCom additionally refuses pushes to a user / group that has never messaged the bot, and rate-limits each conversation to 30 msgs/min · 1000/hour (replies and pushes combined).
|
|
69
69
|
|
|
70
70
|
**`MEDIA:` is not implemented for cron** — the dispatcher declares no `supportsMedia`, so `dispatchToTargets` hands it the original text with `MEDIA:` lines intact (the path degrades to visible text, same caveat as telegram / feishu in [cron.md](cron.md#dispatch-model)). The realtime uploader (`sendWecomMedia` in `handler.ts`) already falls back to `sendMediaMessage(chatid, …)` for a route without a `req_id` (restored at startup); wiring cron up means calling it with `{ reqId: '', chatId }` and flipping `supportsMedia: true`.
|
|
71
71
|
|
|
@@ -33,7 +33,7 @@ Runtime names (`runtime:` value → class): `bedrock-invoke` → `BedrockAgent`,
|
|
|
33
33
|
| `gemini` | `gemini` | `GeminiAgent` | `gemini-agent.ts` | Google Gemini 3.x on the Gemini API's OpenAI-compatible surface (`generativelanguage.googleapis.com/v1beta/openai`, Bearer key), streaming via `fetchChatCompletionStream`. **Thought signatures**: the first tool call of a reply carries `extra_content.google.thought_signature`, and a follow-up that replays a current-turn call without it 400s. The runtime takes the signatures from the raw frames (the `onChunk` hook) and re-attaches them by tool-call id. A call it never saw (rebuilt instance) gets Google's placeholder `skip_thought_signature_validator`. Tool-call replies end with `finish_reason: "stop"`, so they're folded as `tool_calls`. Thinking can't be turned off on Gemini 3. The level goes as `extra_body.google.thinking_config.thinking_level` with `include_thoughts` (this 400s next to `reasoning_effort`). The summary streams inline as `<thought>…</thought>` at the start of `content` and is split out into thinking. Levels per model: 3.8-flash low / medium / high, 3.5-flash-lite minimal…high, xhigh / max invalid everywhere. Usage: `completion_tokens` excludes thought tokens, so output = `total_tokens − prompt_tokens`. Implicit caching via `prompt_tokens_details.cached_tokens` (included in `prompt_tokens`, subtracted). Vision via `image_url` data URLs (probed 2026-10-12) |
|
|
34
34
|
| `openai` | `openai-chat` | `OpenAIAgent` | `openai-agent.ts` | **Generic OpenAI-compatible**. Use for native OpenAI or any third-party OpenAI gateway (incl. Ollama / llama.cpp). Ships a short Bedrock Mantle list callable with a Bedrock API key (`openai.gpt-5.5` default, `openai.gpt-5.4`; `defaultEndpoint` Mantle us-east-1 `/openai/v1`, presets us-east-1 / us-west-2 / api.openai.com — GPT-6.x / 5.6 reject `max_tokens`, so they stay on the Responses-API providers) plus the legacy `gpt-4o` placeholder (official API only); any other endpoint + model id can be typed, and capabilities for unlisted ids are openly editable in the form. Streaming via `fetchChatCompletionStream` — always sends `stream: true` + `stream_options: { include_usage: true }` (OpenAI's own field; Ollama ≥ 0.3 / vLLM / LM Studio accept it too). A gateway that rejects unknown fields would 400 — accepted trade-off, no non-streaming fallback. Forgiving about cache field names (`cachedPromptTokens`: first value > 0 of `prompt_tokens_details.cached_tokens`, top-level `cached_tokens`, `prompt_cache_hit_tokens`, `cache_read_tokens`) and reasoning field names (`reasoning_content` for OpenAI o-series/DeepSeek, `reasoning` for Ollama/llama.cpp). |
|
|
35
35
|
| `anthropic` | `anthropic-messages` | `AnthropicAgent` | `anthropic-agent.ts` | **Generic Anthropic Messages**. Use for api.anthropic.com or any Anthropic-compatible gateway. Ships a short Bedrock Mantle list callable with a Bedrock API key as `x-api-key` (`anthropic.claude-opus-5-5` default, `anthropic.claude-haiku-4-5`; `defaultEndpoint` Mantle us-east-1 `/anthropic`, presets us-east-1 / us-west-2 / api.anthropic.com) plus the legacy `claude-sonnet-4-6` placeholder (official API only); any other endpoint + model id can be typed. Streaming via `fetchAnthropicStream` (the gateway must support `stream: true` SSE — the reference Anthropic behaviour). Standard `cache_control: ephemeral` with optional `ttl: '1h'`. |
|
|
36
|
-
| `mimo-token-plan-china` | `anthropic-messages` | `AnthropicAgent` | (reuses `anthropic-agent.ts`) | Xiaomi MiMo (UI name "Xiaomi MiMo"; the id keeps its historical name so existing configs don't break) — Anthropic-compatible gateway, streams like `anthropic` (gateway built for Claude Code, which always streams; not live-probed); no dedicated class, just `runtime: anthropic-messages` in its yaml (`defaultEndpoint` `token-plan-cn.xiaomimimo.com/anthropic`). The key type picks the endpoint: Token Plan `tp-`/`ttp-` keys only work on `token-plan-{cn,sgp,ams}.xiaomimimo.com/anthropic`, pay-as-you-go `sk-` keys only on `api.xiaomimimo.com/anthropic` — all four ship as `endpointPresets` (pay-as-you-go live-probed 2026-09-24; sgp / ams from the docs only). V2.6 (`mimo-v2.6-pro` / `-flash`, both vision, 1M / 131072) is
|
|
36
|
+
| `mimo-token-plan-china` | `anthropic-messages` | `AnthropicAgent` | (reuses `anthropic-agent.ts`) | Xiaomi MiMo (UI name "Xiaomi MiMo"; the id keeps its historical name so existing configs don't break) — Anthropic-compatible gateway, streams like `anthropic` (gateway built for Claude Code, which always streams; not live-probed); no dedicated class, just `runtime: anthropic-messages` in its yaml (`defaultEndpoint` `token-plan-cn.xiaomimimo.com/anthropic`). The key type picks the endpoint: Token Plan `tp-`/`ttp-` keys only work on `token-plan-{cn,sgp,ams}.xiaomimimo.com/anthropic`, pay-as-you-go `sk-` keys only on `api.xiaomimimo.com/anthropic` — all four ship as `endpointPresets` (pay-as-you-go live-probed 2026-09-24; sgp / ams from the docs only). V2.6 (`mimo-v2.6-pro` / `-flash`, both vision, 1M / 131072) is the only generation listed; `mimo-v2.5(-pro)` (officially retired 2026-10-21) were dropped from the template in revision 2026101401 |
|
|
37
37
|
| `aws-bedrock-mantle` | `bedrock-mantle` | `MantleAgent` | `mantle-agent.ts` | OpenAI GPT-6.1 Sol, GPT-6 (Astra / Sol / Luna) and GPT-5.6 (Sol / Terra / Luna) on Amazon Bedrock via the **OpenAI Responses API** (not Completions/Converse/InvokeModel). Vision, reasoning via `reasoning.effort` (low/med/high/xhigh/max, no budget_tokens; on 5.6 `max` is accepted live but absent from the model's own validation list — measured ~6% more reasoning_tokens than xhigh; on Astra it's on the official list), `text.verbosity` (low/medium/high, same on both generations). **Region split** (live-probed 2026-09-24): us-east-1 serves GPT-6.1 Sol + GPT-6 Sol / Luna + every 5.6 model (404s Astra; GPT-6.1 Sol probed 2026-09-30, us-east-1 only); us-east-2 serves only the 5.6 trio; us-west-2 serves Astra + 5.6 Terra / Luna. Template ships all three as `endpointPresets` (us-east-1 / us-west-2 / us-east-2, default us-east-1, `openai.gpt-6-sol` default model) and pins each model's regions with per-model `endpoints:` (Astra us-west-2; GPT-6.1 Sol / GPT-6 Sol / Luna us-east-1; GPT-5.6 Sol us-east-1 + us-east-2; Terra / Luna none = all presets), so the form only offers — and on a model switch moves to — a region that serves the model; `extractRegionFromHost` falls back to us-east-1. GPT-6 Astra / Sol / Luna: 1,050,000 context / 128,000 max output; GPT-6.1 Sol 1M / 131,072 (card; Mantle accepts any `max_output_tokens` ≥ 16, so the cap isn't enforced server-side); same effort (none/low/medium/high/xhigh/max) + verbosity surface. Input beyond 272K tokens bills at 2x, so every entry ships `contextWindow: 272000` (what a new agent gets) with the 1M-class figure as `maxContextWindow`; raise `context.maxTokens` past 272K only on purpose. Never surfaces reasoning summaries — `output[].reasoning.summary` stays empty even with `reasoning.summary=detailed` while `reasoning_tokens` counts up (verified live), so no thinking text in the UI is upstream behavior, not a parsing gap. Streams (`stream: true`, Responses API SSE folded by `readResponsesStream` in the same file): `response.output_text.delta` frames go out live, the terminal `response.completed` / `response.incomplete` frame carries the full final response object; this host sends **no** `data: [DONE]` (live-probed 2026-09-29). |
|
|
38
38
|
| `aws-bedrock-openai` | `bedrock-mantle` | `MantleAgent` | (reuses `mantle-agent.ts`) | Frontier models on Bedrock via the **bedrock-runtime** host's OpenAI Responses surface (`https://bedrock-runtime.<region>.amazonaws.com/openai/v1` — AWS's recommended endpoint for new apps; Mantle is the compatibility one). Same wire format, so its yaml just says `runtime: bedrock-mantle` + `extractRegionFromHost` accepts both hostnames (SigV4 signs with the host's region — a mismatched region 401s "Credential should be scoped to a valid region"). Rules that differ from Mantle: model ids must be cross-Region inference profiles (`global.*` / `us.*`; bare ids 400 "on-demand throughput isn't supported"); **no region split** — live-probed 2026-09-24, all nine `global.*` ids answer 200 from us-east-1 / us-west-2 / eu-west-1 / ap-northeast-1 alike; **no server-side tools** — `tools:[{type:"web_search"}]` 400s "web search is not supported for this request", which is why the web-search skill's deep gear stays on Mantle. Ships the same seven **GPT-6.1 / 6 / 5.6** entries as the Mantle template (`global.openai.gpt-6.1-sol`, `global.openai.gpt-6-{astra,sol,luna}`, `global.openai.gpt-5.6-{sol,terra,luna}`; identical effort none/low/medium/high/xhigh/max + verbosity surface per each model's own validation error — except Astra and GPT-6.1 Sol 400 on `none`, which the runtime never sends), plus **Grok 4.6** (500K ctx, 2x input price past 200K, max output undisclosed → 64000 carry-over, effort **only** low/medium/high/xhigh — `none`/`max` 400, so no Max preset; vision OK; $2/$6) and **Kimi K3** (1M ctx / 131072 out, effort none…max all accepted, ladder advisory on short prompts; vision OK; $3/$15). All: implicit prompt caching verified (run 1 `cache_write_tokens`, runs 2+ `cached_tokens` ≈ prefix), no reasoning summaries. Streams like Mantle (same event vocabulary) but this host **does** append a trailing `data: [DONE]`, which `readSseJson` skips; grok-4.6 emits a `reasoning` output item (no summary text deltas) before the `message` item. |
|
|
39
39
|
|
package/bundled-docs/dev/api.md
CHANGED
|
@@ -6,7 +6,7 @@ Auth: most `/api/*` routes require a valid JWT cookie (`halo_token`). Exceptions
|
|
|
6
6
|
|
|
7
7
|
**Trust model.** The admin cookie is a single-tenant, all-or-nothing credential: there is one password (`~/.halo/secrets/config.yaml`), one JWT secret, and no roles. A valid `halo_token` equals full control of the server process — every workspace's files (`/api/files/*` project-scoped, `/api/fs/browse` unrestricted), shell via the WS terminal, channel credentials, git credentials, cron jobs, and the ability to mint web-channel tokens at any access level. Routes under the cookie therefore don't re-check "who" beyond the cookie itself; per-user isolation is the job of channel accounts (`x-token` with `readonly` / `workspace` / `full`), not of the admin. Consequences worth knowing when adding routes: (1) never put a cookie-gated route in `PUBLIC_PATHS` to "make it easier for a client" — that's the entire boundary; (2) a route that accepts a path or id from the client still needs traversal / shape checks (`isSafeIdSegment`, `assertPathAllowed`), because the *web-channel* surface reuses the same helpers and is multi-user; (3) if multi-user admin ever lands, the first change is a role claim in the JWT plus per-route checks, and this paragraph becomes the checklist.
|
|
8
8
|
|
|
9
|
-
Compression: every response negotiates gzip/deflate via `hono/compress
|
|
9
|
+
Compression: every response negotiates gzip/deflate via `compressResponses` (`middleware/compress.ts`, wrapping `hono/compress`; `threshold: 1024` — bodies under 1 KiB pass through uncompressed). The threshold holds for every compressible body: one without `Content-Length` (`c.json` / `c.text`) is read up to 1 KiB first and, if it ends there, sent with a `Content-Length`. A 304 carries `Vary: Accept-Encoding`, merged into any existing `Vary`. Mounted after CORS so it covers both `/api/*` JSON and the served admin static assets. `text/event-stream` (SSE) is excluded, so `/api/web/chat`'s streamed reply is unaffected; WS upgrades never enter the Hono fetch pipeline either.
|
|
10
10
|
|
|
11
11
|
## Health
|
|
12
12
|
|
|
@@ -334,7 +334,7 @@ Files: `packages/server/src/a2a/routes.ts` (JSON-RPC handler), `exposure.ts` (pa
|
|
|
334
334
|
| Method | Path | Purpose |
|
|
335
335
|
|---|---|---|
|
|
336
336
|
| POST | `/a2a/<rel>` (trailing `/` ok) | JSON-RPC 2.0 for the workspace `<A2A base>/<rel>` |
|
|
337
|
-
| GET | `/a2a/<rel>/.well-known/agent-card.json` | The agent card. `Cache-Control: private, max-age=300` + `ETag` (304 on `If-None-Match`); invalid `.halo/agent-card.json` → 500 |
|
|
337
|
+
| GET | `/a2a/<rel>/.well-known/agent-card.json` | The agent card. `Cache-Control: private, max-age=300` + weak `ETag` (`W/"…"`, the same on a 200 gzipped or not and on its 304; 304 on a weak `If-None-Match` match); invalid `.halo/agent-card.json` → 500 |
|
|
338
338
|
| POST | `/a2a-push/:pushId` | Webhook receiver for remotes this server dispatched to (`a2a_send`) |
|
|
339
339
|
|
|
340
340
|
**Exposure**: `<rel>` is the workspace path relative to the base dir (`HALO_A2A_ROOT`, default the user's home), each segment percent-encoded. Only a workspace containing `.halo/agent-card.json` is served; empty / `.` / `..` segments, separators inside a segment, and symlinks leaving the base are refused. The card's interface URL is `general.a2a.public_url` (env `HALO_A2A_PUBLIC_URL`) + `/a2a/<rel>/`, else derived from the request host.
|
|
@@ -393,7 +393,7 @@ runtime so the frontend can render rooms (workspaces) + characters (sessions).
|
|
|
393
393
|
|
|
394
394
|
| Method | Path | Purpose |
|
|
395
395
|
|---|---|---|
|
|
396
|
-
| GET | `/api/show/state` | Admin cookie, `full` or `observer` token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }` |
|
|
396
|
+
| GET | `/api/show/state` | Admin cookie, `full` or `observer` token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }`. Sends a weak `ETag` (over `accessLevel` / `skills` / `workspaces` plus `uptime` by the minute — never `serverTime`) and `Cache-Control: private, no-cache`; a weakly-matching `If-None-Match` (list or `*`) gets an empty 304 after auth, so an idle server answers 200 at most once a minute. On a 304 the cached copy's `serverTime` / `uptime` are those of the last 200. |
|
|
397
397
|
| GET | `/api/show/session?ws=&id=` | Inspector-panel detail for a single session. Trimmed message log (last 40, content/tool I/O capped) plus `totalMessages` / `contextTokens` / `outputTokens` / `maxContextTokens` / `isRunning`. Tokens other than `full`/`observer` may only read their own workspace, and within it only sessions their own account minted (`web_<accountId>_*`); anything else is 403. The admin cookie reads any workspace. |
|
|
398
398
|
|
|
399
399
|
`observer` is more than an aggregate-counts role: past `/show/state`, it can also call `/show/session` to read **any workspace's** session transcript (last 40 messages, content truncated to 600 chars, tool input to 200 chars). Mint it knowing it grants cross-workspace transcript read access, not just dashboard counters.
|
package/dist/index.js
CHANGED
|
@@ -4149,7 +4149,7 @@ var init_init = __esm({
|
|
|
4149
4149
|
path14.resolve(__dirname, "..", "bundled-docs"),
|
|
4150
4150
|
path14.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
4151
4151
|
];
|
|
4152
|
-
TEMPLATE_VERSION =
|
|
4152
|
+
TEMPLATE_VERSION = 103;
|
|
4153
4153
|
VERSION_FILE = ".template-version";
|
|
4154
4154
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
4155
4155
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -14303,7 +14303,7 @@ async function initObservability(haloProcess = "server") {
|
|
|
14303
14303
|
return true;
|
|
14304
14304
|
}
|
|
14305
14305
|
const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
|
|
14306
|
-
providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "1.6.
|
|
14306
|
+
providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "1.6.6", haloProcess);
|
|
14307
14307
|
console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, halo.process=${haloProcess}, capture_content=${config.observability.captureContent})`);
|
|
14308
14308
|
return true;
|
|
14309
14309
|
}
|
|
@@ -25556,10 +25556,27 @@ var init_session_types = __esm({
|
|
|
25556
25556
|
}
|
|
25557
25557
|
});
|
|
25558
25558
|
|
|
25559
|
+
// ../server/dist/routes/etag.js
|
|
25560
|
+
function etagMatches(header, etag) {
|
|
25561
|
+
if (!header)
|
|
25562
|
+
return false;
|
|
25563
|
+
const bare = etag.replace(/^W\//, "");
|
|
25564
|
+
return header.split(",").some((t2) => {
|
|
25565
|
+
const tag = t2.trim();
|
|
25566
|
+
return tag === "*" || tag.replace(/^W\//, "") === bare;
|
|
25567
|
+
});
|
|
25568
|
+
}
|
|
25569
|
+
var init_etag = __esm({
|
|
25570
|
+
"../server/dist/routes/etag.js"() {
|
|
25571
|
+
"use strict";
|
|
25572
|
+
}
|
|
25573
|
+
});
|
|
25574
|
+
|
|
25559
25575
|
// ../server/dist/routes/halo-city.js
|
|
25560
25576
|
import { Hono as Hono12 } from "hono";
|
|
25561
25577
|
import fs59 from "node:fs";
|
|
25562
25578
|
import path67 from "node:path";
|
|
25579
|
+
import crypto6 from "node:crypto";
|
|
25563
25580
|
import Database7 from "better-sqlite3";
|
|
25564
25581
|
function scanSkills2(dir) {
|
|
25565
25582
|
let names;
|
|
@@ -25843,6 +25860,10 @@ function discoverWorkspaces(registry2) {
|
|
|
25843
25860
|
}
|
|
25844
25861
|
return out;
|
|
25845
25862
|
}
|
|
25863
|
+
function stateEtag(accessLevel, skills, workspaces, uptime) {
|
|
25864
|
+
const json = JSON.stringify({ accessLevel, skills, workspaces, uptimeMin: Math.floor(uptime / 60) });
|
|
25865
|
+
return `W/"${crypto6.createHash("sha1").update(json).digest("base64url")}"`;
|
|
25866
|
+
}
|
|
25846
25867
|
function createShowRoutes(registry2) {
|
|
25847
25868
|
const app2 = new Hono12();
|
|
25848
25869
|
function auth(c) {
|
|
@@ -25875,11 +25896,18 @@ function createShowRoutes(registry2) {
|
|
|
25875
25896
|
}
|
|
25876
25897
|
}
|
|
25877
25898
|
workspaces.sort((x, y) => workspaceBirth(x.path) - workspaceBirth(y.path) || x.key.localeCompare(y.key));
|
|
25899
|
+
const uptime = process.uptime();
|
|
25900
|
+
const skills = scanSkills2(GLOBAL_SKILLS_DIR);
|
|
25901
|
+
const etag = stateEtag(a.accessLevel, skills, workspaces, uptime);
|
|
25902
|
+
c.header("ETag", etag);
|
|
25903
|
+
c.header("Cache-Control", "private, no-cache");
|
|
25904
|
+
if (etagMatches(c.req.header("if-none-match"), etag))
|
|
25905
|
+
return c.body(null, 304);
|
|
25878
25906
|
return c.json({
|
|
25879
25907
|
serverTime: Date.now(),
|
|
25880
|
-
uptime
|
|
25908
|
+
uptime,
|
|
25881
25909
|
accessLevel: a.accessLevel,
|
|
25882
|
-
skills
|
|
25910
|
+
skills,
|
|
25883
25911
|
workspaces
|
|
25884
25912
|
});
|
|
25885
25913
|
});
|
|
@@ -25957,6 +25985,7 @@ var init_halo_city = __esm({
|
|
|
25957
25985
|
init_session_store();
|
|
25958
25986
|
init_session_types();
|
|
25959
25987
|
init_mtime_cache();
|
|
25988
|
+
init_etag();
|
|
25960
25989
|
SESSIONS_PER_WS = 80;
|
|
25961
25990
|
_skillCache = createMtimeCache();
|
|
25962
25991
|
_metaCache = /* @__PURE__ */ new Map();
|
|
@@ -28876,7 +28905,7 @@ CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
|
|
|
28876
28905
|
// ../server/dist/a2a/exposure.js
|
|
28877
28906
|
import fs65 from "node:fs";
|
|
28878
28907
|
import path74 from "node:path";
|
|
28879
|
-
import
|
|
28908
|
+
import crypto7 from "node:crypto";
|
|
28880
28909
|
function samePath(a, b, platform = process.platform) {
|
|
28881
28910
|
if (platform !== "win32")
|
|
28882
28911
|
return a === b;
|
|
@@ -28942,7 +28971,7 @@ function buildCard(workspace, interfaceUrl, caps) {
|
|
|
28942
28971
|
return {
|
|
28943
28972
|
name: raw.name,
|
|
28944
28973
|
description: raw.description,
|
|
28945
|
-
version: typeof raw.version === "string" ? raw.version : "1.6.
|
|
28974
|
+
version: typeof raw.version === "string" ? raw.version : "1.6.6",
|
|
28946
28975
|
supportedInterfaces: [{ url: interfaceUrl, protocolBinding: "JSONRPC", protocolVersion: A2A_VERSION }],
|
|
28947
28976
|
capabilities: { streaming: caps.streaming, pushNotifications: true, extendedAgentCard: false },
|
|
28948
28977
|
...caps.tokenAuth === false ? {} : {
|
|
@@ -28958,7 +28987,7 @@ function buildCard(workspace, interfaceUrl, caps) {
|
|
|
28958
28987
|
};
|
|
28959
28988
|
}
|
|
28960
28989
|
function etagOf(body) {
|
|
28961
|
-
return `"${
|
|
28990
|
+
return `W/"${crypto7.createHash("sha1").update(body).digest("base64url")}"`;
|
|
28962
28991
|
}
|
|
28963
28992
|
var CARD_FILE, IO_MODES, baseCache, homeStrategies;
|
|
28964
28993
|
var init_exposure = __esm({
|
|
@@ -29001,7 +29030,7 @@ var init_exposure = __esm({
|
|
|
29001
29030
|
// ../server/dist/a2a/routes.js
|
|
29002
29031
|
import { Hono as Hono16 } from "hono";
|
|
29003
29032
|
import { streamSSE } from "hono/streaming";
|
|
29004
|
-
import
|
|
29033
|
+
import crypto8 from "node:crypto";
|
|
29005
29034
|
function str(v) {
|
|
29006
29035
|
return typeof v === "string" ? v : "";
|
|
29007
29036
|
}
|
|
@@ -29111,7 +29140,7 @@ async function dispatchMessage(x, p) {
|
|
|
29111
29140
|
}
|
|
29112
29141
|
const newSession = !sessionId;
|
|
29113
29142
|
if (newSession)
|
|
29114
|
-
sessionId = `${sessionPrefixFor(x.caller)}${Date.now().toString(36)}${
|
|
29143
|
+
sessionId = `${sessionPrefixFor(x.caller)}${Date.now().toString(36)}${crypto8.randomBytes(3).toString("hex")}`;
|
|
29115
29144
|
const followUp = task !== null;
|
|
29116
29145
|
if (!task) {
|
|
29117
29146
|
try {
|
|
@@ -29315,7 +29344,7 @@ function createA2ARoutes(deps) {
|
|
|
29315
29344
|
const etag = etagOf(body);
|
|
29316
29345
|
c.header("Cache-Control", "private, max-age=300");
|
|
29317
29346
|
c.header("ETag", etag);
|
|
29318
|
-
if (c.req.header("if-none-match")
|
|
29347
|
+
if (etagMatches(c.req.header("if-none-match"), etag))
|
|
29319
29348
|
return c.body(null, 304);
|
|
29320
29349
|
return c.body(body, 200, { "Content-Type": "application/json" });
|
|
29321
29350
|
});
|
|
@@ -29390,6 +29419,7 @@ var init_routes = __esm({
|
|
|
29390
29419
|
init_commands2();
|
|
29391
29420
|
init_accounts();
|
|
29392
29421
|
init_session_prefix();
|
|
29422
|
+
init_etag();
|
|
29393
29423
|
init_url_policy();
|
|
29394
29424
|
init_files();
|
|
29395
29425
|
init_exposure();
|
|
@@ -29428,7 +29458,7 @@ var init_routes = __esm({
|
|
|
29428
29458
|
import fs66 from "node:fs";
|
|
29429
29459
|
import os10 from "node:os";
|
|
29430
29460
|
import path75 from "node:path";
|
|
29431
|
-
import
|
|
29461
|
+
import crypto9 from "node:crypto";
|
|
29432
29462
|
import { Hono as Hono17 } from "hono";
|
|
29433
29463
|
function readLease(file) {
|
|
29434
29464
|
let raw;
|
|
@@ -29611,7 +29641,7 @@ var init_agentcore = __esm({
|
|
|
29611
29641
|
WorkspaceLease = class {
|
|
29612
29642
|
file;
|
|
29613
29643
|
hooks;
|
|
29614
|
-
owner =
|
|
29644
|
+
owner = crypto9.randomUUID();
|
|
29615
29645
|
_held = false;
|
|
29616
29646
|
timer = null;
|
|
29617
29647
|
waitLogged = false;
|
|
@@ -32720,7 +32750,7 @@ var init_descriptor = __esm({
|
|
|
32720
32750
|
});
|
|
32721
32751
|
|
|
32722
32752
|
// ../server/dist/channels/wechat/api.js
|
|
32723
|
-
import
|
|
32753
|
+
import crypto10 from "node:crypto";
|
|
32724
32754
|
function buildClientVersion(version) {
|
|
32725
32755
|
const [ma, mi, pa] = version.split(".").map((p) => parseInt(p, 10));
|
|
32726
32756
|
return (ma & 255) << 16 | (mi & 255) << 8 | pa & 255;
|
|
@@ -32729,7 +32759,7 @@ function buildBaseInfo() {
|
|
|
32729
32759
|
return { channel_version: CHANNEL_VERSION };
|
|
32730
32760
|
}
|
|
32731
32761
|
function randomWechatUin() {
|
|
32732
|
-
const n =
|
|
32762
|
+
const n = crypto10.randomBytes(4).readUInt32BE(0);
|
|
32733
32763
|
return Buffer.from(String(n), "utf-8").toString("base64");
|
|
32734
32764
|
}
|
|
32735
32765
|
function commonHeaders() {
|
|
@@ -34106,7 +34136,7 @@ var init_descriptor2 = __esm({
|
|
|
34106
34136
|
// ../server/dist/routes/web.js
|
|
34107
34137
|
import { Hono as Hono22 } from "hono";
|
|
34108
34138
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
34109
|
-
import
|
|
34139
|
+
import crypto11 from "node:crypto";
|
|
34110
34140
|
import fs77 from "node:fs";
|
|
34111
34141
|
function streamWithKeepalive(c, chunks) {
|
|
34112
34142
|
return streamSSE2(c, async (stream) => {
|
|
@@ -34144,8 +34174,8 @@ function createWebRoutes(deps) {
|
|
|
34144
34174
|
const bodyError = accountBodyError(body, ACCOUNT_ACCESS_LEVELS);
|
|
34145
34175
|
if (bodyError)
|
|
34146
34176
|
return c.json({ error: bodyError }, 400);
|
|
34147
|
-
const accountId =
|
|
34148
|
-
const token =
|
|
34177
|
+
const accountId = crypto11.randomUUID().slice(0, 8);
|
|
34178
|
+
const token = crypto11.randomBytes(24).toString("base64url");
|
|
34149
34179
|
insertAccount2(db3, {
|
|
34150
34180
|
accountId,
|
|
34151
34181
|
token,
|
|
@@ -34483,7 +34513,7 @@ function formatForFeishu(input) {
|
|
|
34483
34513
|
inFence = !inFence;
|
|
34484
34514
|
continue;
|
|
34485
34515
|
}
|
|
34486
|
-
if (inFence) {
|
|
34516
|
+
if (inFence || /^MEDIA:/.test(raw)) {
|
|
34487
34517
|
out.push(raw);
|
|
34488
34518
|
continue;
|
|
34489
34519
|
}
|
|
@@ -34508,16 +34538,16 @@ var init_markdown = __esm({
|
|
|
34508
34538
|
});
|
|
34509
34539
|
|
|
34510
34540
|
// ../server/dist/channels/slack/event-adapter.js
|
|
34511
|
-
var
|
|
34541
|
+
var SLACK_TEXT_LIMIT, SlackResponder;
|
|
34512
34542
|
var init_event_adapter3 = __esm({
|
|
34513
34543
|
"../server/dist/channels/slack/event-adapter.js"() {
|
|
34514
34544
|
"use strict";
|
|
34515
34545
|
init_markdown();
|
|
34516
34546
|
init_responder();
|
|
34517
|
-
|
|
34547
|
+
SLACK_TEXT_LIMIT = 35e3;
|
|
34518
34548
|
SlackResponder = class extends ChunkedResponder {
|
|
34519
34549
|
constructor(deps) {
|
|
34520
|
-
super(deps, { limit:
|
|
34550
|
+
super(deps, { limit: SLACK_TEXT_LIMIT, logTag: "Slack", format: formatForSlack });
|
|
34521
34551
|
}
|
|
34522
34552
|
};
|
|
34523
34553
|
}
|
|
@@ -35253,18 +35283,17 @@ async function dispatch3(accountId, text6, explicitChatId, media) {
|
|
|
35253
35283
|
const { channel, threadTs } = parseChatId(explicitChatId);
|
|
35254
35284
|
const out = [];
|
|
35255
35285
|
if (text6) {
|
|
35256
|
-
|
|
35257
|
-
|
|
35258
|
-
|
|
35259
|
-
|
|
35260
|
-
|
|
35261
|
-
|
|
35262
|
-
|
|
35263
|
-
|
|
35264
|
-
|
|
35265
|
-
error: err instanceof Error ? err.message : String(err)
|
|
35266
|
-
});
|
|
35286
|
+
const chunks = splitText(text6, SLACK_TEXT_LIMIT);
|
|
35287
|
+
let failed;
|
|
35288
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
35289
|
+
try {
|
|
35290
|
+
await postMessage({ botToken: acct.botToken, channel, threadTs, text: formatForSlack(chunk) });
|
|
35291
|
+
} catch (err) {
|
|
35292
|
+
failed = `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`;
|
|
35293
|
+
break;
|
|
35294
|
+
}
|
|
35267
35295
|
}
|
|
35296
|
+
out.push(failed ? { channelType: "slack", accountId, chatId: explicitChatId, ok: false, error: failed } : { channelType: "slack", accountId, chatId: explicitChatId, ok: true });
|
|
35268
35297
|
}
|
|
35269
35298
|
for (const filePath of media?.paths ?? []) {
|
|
35270
35299
|
if (!isMediaPathAllowed(filePath, media.workspacePath)) {
|
|
@@ -35303,6 +35332,9 @@ var init_cron_dispatcher3 = __esm({
|
|
|
35303
35332
|
init_channel_db();
|
|
35304
35333
|
init_accounts5();
|
|
35305
35334
|
init_api2();
|
|
35335
|
+
init_event_adapter3();
|
|
35336
|
+
init_chunk();
|
|
35337
|
+
init_markdown();
|
|
35306
35338
|
init_media();
|
|
35307
35339
|
init_dispatcher();
|
|
35308
35340
|
}
|
|
@@ -35380,16 +35412,16 @@ var init_accounts6 = __esm({
|
|
|
35380
35412
|
});
|
|
35381
35413
|
|
|
35382
35414
|
// ../server/dist/channels/feishu/event-adapter.js
|
|
35383
|
-
var
|
|
35415
|
+
var FEISHU_TEXT_LIMIT, FeishuResponder;
|
|
35384
35416
|
var init_event_adapter4 = __esm({
|
|
35385
35417
|
"../server/dist/channels/feishu/event-adapter.js"() {
|
|
35386
35418
|
"use strict";
|
|
35387
35419
|
init_markdown();
|
|
35388
35420
|
init_responder();
|
|
35389
|
-
|
|
35421
|
+
FEISHU_TEXT_LIMIT = 4500;
|
|
35390
35422
|
FeishuResponder = class extends ChunkedResponder {
|
|
35391
35423
|
constructor(deps) {
|
|
35392
|
-
super(deps, { limit:
|
|
35424
|
+
super(deps, { limit: FEISHU_TEXT_LIMIT, logTag: "Feishu", format: formatForFeishu });
|
|
35393
35425
|
}
|
|
35394
35426
|
};
|
|
35395
35427
|
}
|
|
@@ -36211,25 +36243,28 @@ async function dispatch4(accountId, text6, explicitChatId) {
|
|
|
36211
36243
|
throw new Error('feishu cron target requires an explicit chatId (e.g. "oc_abcd1234"). Create the cron from inside a feishu chat to auto-pin, or pass --targets feishu:<accountId>:<chatId>.');
|
|
36212
36244
|
}
|
|
36213
36245
|
const { chatId } = parseChatId2(explicitChatId);
|
|
36214
|
-
|
|
36215
|
-
|
|
36216
|
-
|
|
36217
|
-
|
|
36218
|
-
|
|
36219
|
-
|
|
36220
|
-
|
|
36221
|
-
|
|
36222
|
-
|
|
36223
|
-
|
|
36224
|
-
|
|
36225
|
-
|
|
36226
|
-
|
|
36227
|
-
|
|
36228
|
-
|
|
36229
|
-
|
|
36230
|
-
|
|
36231
|
-
|
|
36246
|
+
const chunks = splitText(text6, FEISHU_TEXT_LIMIT);
|
|
36247
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
36248
|
+
try {
|
|
36249
|
+
await sendMessage3({
|
|
36250
|
+
appId: acct.appId,
|
|
36251
|
+
appSecret: acct.appSecret,
|
|
36252
|
+
receiveIdType: "chat_id",
|
|
36253
|
+
receiveId: chatId,
|
|
36254
|
+
msgType: "text",
|
|
36255
|
+
content: { text: formatForFeishu(chunk) }
|
|
36256
|
+
});
|
|
36257
|
+
} catch (err) {
|
|
36258
|
+
return [{
|
|
36259
|
+
channelType: "feishu",
|
|
36260
|
+
accountId,
|
|
36261
|
+
chatId: explicitChatId,
|
|
36262
|
+
ok: false,
|
|
36263
|
+
error: `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`
|
|
36264
|
+
}];
|
|
36265
|
+
}
|
|
36232
36266
|
}
|
|
36267
|
+
return [{ channelType: "feishu", accountId, chatId: explicitChatId, ok: true }];
|
|
36233
36268
|
}
|
|
36234
36269
|
function listTargets4() {
|
|
36235
36270
|
const cdb = getChannelDb();
|
|
@@ -36251,6 +36286,9 @@ var init_cron_dispatcher4 = __esm({
|
|
|
36251
36286
|
init_channel_db();
|
|
36252
36287
|
init_accounts6();
|
|
36253
36288
|
init_api3();
|
|
36289
|
+
init_event_adapter4();
|
|
36290
|
+
init_chunk();
|
|
36291
|
+
init_markdown();
|
|
36254
36292
|
init_dispatcher();
|
|
36255
36293
|
}
|
|
36256
36294
|
});
|
|
@@ -36324,15 +36362,15 @@ var init_accounts7 = __esm({
|
|
|
36324
36362
|
});
|
|
36325
36363
|
|
|
36326
36364
|
// ../server/dist/channels/wecom/event-adapter.js
|
|
36327
|
-
var
|
|
36365
|
+
var WECOM_TEXT_LIMIT, WecomResponder;
|
|
36328
36366
|
var init_event_adapter5 = __esm({
|
|
36329
36367
|
"../server/dist/channels/wecom/event-adapter.js"() {
|
|
36330
36368
|
"use strict";
|
|
36331
36369
|
init_responder();
|
|
36332
|
-
|
|
36370
|
+
WECOM_TEXT_LIMIT = 5e3;
|
|
36333
36371
|
WecomResponder = class extends ChunkedResponder {
|
|
36334
36372
|
constructor(deps) {
|
|
36335
|
-
super(deps, { limit:
|
|
36373
|
+
super(deps, { limit: WECOM_TEXT_LIMIT, logTag: "WeCom" });
|
|
36336
36374
|
}
|
|
36337
36375
|
};
|
|
36338
36376
|
}
|
|
@@ -36838,18 +36876,21 @@ async function dispatch5(accountId, text6, explicitChatId) {
|
|
|
36838
36876
|
if (!client) {
|
|
36839
36877
|
return [{ channelType: "wecom", accountId, chatId: explicitChatId, ok: false, error: "wecom long-connect not active" }];
|
|
36840
36878
|
}
|
|
36841
|
-
|
|
36842
|
-
|
|
36843
|
-
|
|
36844
|
-
|
|
36845
|
-
|
|
36846
|
-
|
|
36847
|
-
|
|
36848
|
-
|
|
36849
|
-
|
|
36850
|
-
|
|
36851
|
-
|
|
36879
|
+
const chunks = splitText(text6, WECOM_TEXT_LIMIT);
|
|
36880
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
36881
|
+
try {
|
|
36882
|
+
await client.sendMessage(explicitChatId, { msgtype: "markdown", markdown: { content: chunk } });
|
|
36883
|
+
} catch (err) {
|
|
36884
|
+
return [{
|
|
36885
|
+
channelType: "wecom",
|
|
36886
|
+
accountId,
|
|
36887
|
+
chatId: explicitChatId,
|
|
36888
|
+
ok: false,
|
|
36889
|
+
error: `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`
|
|
36890
|
+
}];
|
|
36891
|
+
}
|
|
36852
36892
|
}
|
|
36893
|
+
return [{ channelType: "wecom", accountId, chatId: explicitChatId, ok: true }];
|
|
36853
36894
|
}
|
|
36854
36895
|
function listTargets5() {
|
|
36855
36896
|
const cdb = getChannelDb();
|
|
@@ -36871,6 +36912,8 @@ var init_cron_dispatcher5 = __esm({
|
|
|
36871
36912
|
init_channel_db();
|
|
36872
36913
|
init_accounts7();
|
|
36873
36914
|
init_handler7();
|
|
36915
|
+
init_event_adapter5();
|
|
36916
|
+
init_chunk();
|
|
36874
36917
|
init_dispatcher();
|
|
36875
36918
|
}
|
|
36876
36919
|
});
|
|
@@ -36915,6 +36958,64 @@ var init_descriptors = __esm({
|
|
|
36915
36958
|
}
|
|
36916
36959
|
});
|
|
36917
36960
|
|
|
36961
|
+
// ../server/dist/middleware/compress.js
|
|
36962
|
+
import { compress, COMPRESSIBLE_CONTENT_TYPE_REGEX } from "hono/compress";
|
|
36963
|
+
function compressResponses(threshold) {
|
|
36964
|
+
const inner = compress({ threshold });
|
|
36965
|
+
return (c, next) => inner(c, async () => {
|
|
36966
|
+
await next();
|
|
36967
|
+
if (c.res.status === 304)
|
|
36968
|
+
varyAcceptEncoding(c);
|
|
36969
|
+
else
|
|
36970
|
+
await setProbedLength(c, threshold);
|
|
36971
|
+
});
|
|
36972
|
+
}
|
|
36973
|
+
function varyAcceptEncoding(c) {
|
|
36974
|
+
const vary = c.res.headers.get("Vary");
|
|
36975
|
+
if (vary === "*" || vary && VARY_ACCEPT_ENCODING_RE.test(vary))
|
|
36976
|
+
return;
|
|
36977
|
+
c.header("Vary", vary ? `${vary}, Accept-Encoding` : "Accept-Encoding");
|
|
36978
|
+
}
|
|
36979
|
+
async function setProbedLength(c, threshold) {
|
|
36980
|
+
const res = c.res;
|
|
36981
|
+
const type = res.headers.get("Content-Type");
|
|
36982
|
+
if (res.headers.has("Content-Length") || res.headers.has("Content-Encoding") || res.headers.has("Transfer-Encoding") || c.req.method === "HEAD" || res.status === 204 || res.status === 206 || !res.body || !type || !COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type))
|
|
36983
|
+
return;
|
|
36984
|
+
const reader = res.body.getReader();
|
|
36985
|
+
const head = [];
|
|
36986
|
+
let size = 0;
|
|
36987
|
+
while (size < threshold) {
|
|
36988
|
+
const { done, value } = await reader.read();
|
|
36989
|
+
if (done) {
|
|
36990
|
+
c.res = new Response(Buffer.concat(head, size), res);
|
|
36991
|
+
c.res.headers.set("Content-Length", String(size));
|
|
36992
|
+
return;
|
|
36993
|
+
}
|
|
36994
|
+
head.push(value);
|
|
36995
|
+
size += value.byteLength;
|
|
36996
|
+
}
|
|
36997
|
+
c.res = new Response(new ReadableStream({
|
|
36998
|
+
async pull(ctrl) {
|
|
36999
|
+
const chunk = head.shift();
|
|
37000
|
+
if (chunk)
|
|
37001
|
+
return ctrl.enqueue(chunk);
|
|
37002
|
+
const { done, value } = await reader.read();
|
|
37003
|
+
if (done)
|
|
37004
|
+
ctrl.close();
|
|
37005
|
+
else
|
|
37006
|
+
ctrl.enqueue(value);
|
|
37007
|
+
},
|
|
37008
|
+
cancel: (reason) => reader.cancel(reason)
|
|
37009
|
+
}), res);
|
|
37010
|
+
}
|
|
37011
|
+
var VARY_ACCEPT_ENCODING_RE;
|
|
37012
|
+
var init_compress = __esm({
|
|
37013
|
+
"../server/dist/middleware/compress.js"() {
|
|
37014
|
+
"use strict";
|
|
37015
|
+
VARY_ACCEPT_ENCODING_RE = /(?:^|,)\s*accept-encoding\s*(?:,|$)/i;
|
|
37016
|
+
}
|
|
37017
|
+
});
|
|
37018
|
+
|
|
36918
37019
|
// ../server/dist/listen-banner.js
|
|
36919
37020
|
function formatListenBanner(address, port, version) {
|
|
36920
37021
|
if (address === "::" || address === "0.0.0.0") {
|
|
@@ -36936,7 +37037,6 @@ var init_listen_banner = __esm({
|
|
|
36936
37037
|
var dist_exports = {};
|
|
36937
37038
|
import { Hono as Hono26 } from "hono";
|
|
36938
37039
|
import { cors } from "hono/cors";
|
|
36939
|
-
import { compress } from "hono/compress";
|
|
36940
37040
|
import { serve } from "@hono/node-server";
|
|
36941
37041
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
36942
37042
|
import { WebSocketServer as WebSocketServer4 } from "ws";
|
|
@@ -37128,6 +37228,7 @@ var init_dist2 = __esm({
|
|
|
37128
37228
|
init_registry4();
|
|
37129
37229
|
init_descriptors();
|
|
37130
37230
|
init_auth();
|
|
37231
|
+
init_compress();
|
|
37131
37232
|
init_logger();
|
|
37132
37233
|
init_listen_banner();
|
|
37133
37234
|
init_otel();
|
|
@@ -37137,7 +37238,7 @@ var init_dist2 = __esm({
|
|
|
37137
37238
|
init_git_ssh();
|
|
37138
37239
|
__dirname3 = path87.dirname(fileURLToPath3(import.meta.url));
|
|
37139
37240
|
PORT = config.server.port;
|
|
37140
|
-
HALO_VERSION = "1.6.
|
|
37241
|
+
HALO_VERSION = "1.6.6";
|
|
37141
37242
|
PROJECT_ROOT = findProjectRoot();
|
|
37142
37243
|
HALO_HOME4 = path87.join(homedir25(), ".halo");
|
|
37143
37244
|
GIT_SHA = readGitSha();
|
|
@@ -37181,7 +37282,7 @@ var init_dist2 = __esm({
|
|
|
37181
37282
|
maxAge: 86400,
|
|
37182
37283
|
credentials: allowlist.length > 0
|
|
37183
37284
|
}));
|
|
37184
|
-
app.use("/*",
|
|
37285
|
+
app.use("/*", compressResponses(1024));
|
|
37185
37286
|
jupyter = AGENTCORE ? null : createJupyterManager();
|
|
37186
37287
|
if (!AGENTCORE) {
|
|
37187
37288
|
app.use("/api/*", authMiddleware());
|
|
@@ -38747,7 +38848,7 @@ function installUserUnit(opts, run = runCommand) {
|
|
|
38747
38848
|
}
|
|
38748
38849
|
|
|
38749
38850
|
// src/index.ts
|
|
38750
|
-
var VERSION = "1.6.
|
|
38851
|
+
var VERSION = "1.6.6";
|
|
38751
38852
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
38752
38853
|
|
|
38753
38854
|
Commands:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: mimo-token-plan-china
|
|
2
2
|
runtime: anthropic-messages
|
|
3
|
-
revision:
|
|
3
|
+
revision: 2026101401
|
|
4
4
|
displayName: Xiaomi MiMo
|
|
5
5
|
displayName_zh: 小米 MiMo
|
|
6
6
|
description: Xiaomi MiMo via its Anthropic-compatible gateway — Token Plan (China / Singapore / Europe clusters) or pay-as-you-go. Supports 1M context window.
|
|
@@ -28,9 +28,8 @@ secrets:
|
|
|
28
28
|
# gives `mimo-v2.6-pro` / `mimo-v2.6-flash` 1M context, 128K (131072) max
|
|
29
29
|
# output, full-modal understanding (image input on both — V2.5 only had it on
|
|
30
30
|
# the non-pro tier). `mimo-v2.6-pro-ultraspeed` is "contact us" only, not
|
|
31
|
-
# listed. `mimo-v2.5-pro` / `mimo-v2.5`
|
|
32
|
-
#
|
|
33
|
-
# capabilities until the cutoff; drop both after that date. Thinking shape unchanged from V2.5 — Anthropic-protocol
|
|
31
|
+
# listed. `mimo-v2.5-pro` / `mimo-v2.5` (officially retired 2026-10-21) were
|
|
32
|
+
# dropped from this list on 2026-10-14. Thinking shape unchanged from V2.5 — Anthropic-protocol
|
|
34
33
|
# `thinking.type` enabled/disabled, on by default; the docs say effort levels
|
|
35
34
|
# are not differentiated yet, so the adaptive presets below are a UI knob the
|
|
36
35
|
# gateway accepts, not a real intensity dial. Not live-probed (no valid key at
|
|
@@ -85,54 +84,3 @@ models:
|
|
|
85
84
|
- { value: medium, label: Medium }
|
|
86
85
|
- { value: high, label: High }
|
|
87
86
|
- { value: max, label: Max }
|
|
88
|
-
|
|
89
|
-
# --- deprecated 2026-10-21 10:00 CST (official notice) — remove after cutoff ---
|
|
90
|
-
- id: mimo-v2.5-pro
|
|
91
|
-
displayName: MiMo v2.5 Pro (deprecated 2026-10-21)
|
|
92
|
-
maxOutputTokens: 131072
|
|
93
|
-
contextWindow: 272000
|
|
94
|
-
maxContextWindow: 1000000
|
|
95
|
-
compressAt: 0.9
|
|
96
|
-
capabilities:
|
|
97
|
-
image: false
|
|
98
|
-
video: false
|
|
99
|
-
audio: false
|
|
100
|
-
promptCaching:
|
|
101
|
-
default: 1h
|
|
102
|
-
ttlPresets:
|
|
103
|
-
- { value: 5m, label: 5min }
|
|
104
|
-
- { value: 1h, label: 1hour }
|
|
105
|
-
thinking:
|
|
106
|
-
mode: adaptive
|
|
107
|
-
defaultEnabled: true
|
|
108
|
-
default: high
|
|
109
|
-
effortPresets:
|
|
110
|
-
- { value: low, label: Low }
|
|
111
|
-
- { value: medium, label: Medium }
|
|
112
|
-
- { value: high, label: High }
|
|
113
|
-
- { value: max, label: Max }
|
|
114
|
-
|
|
115
|
-
- id: mimo-v2.5
|
|
116
|
-
displayName: MiMo v2.5 (deprecated 2026-10-21)
|
|
117
|
-
maxOutputTokens: 131072
|
|
118
|
-
contextWindow: 272000
|
|
119
|
-
maxContextWindow: 1000000
|
|
120
|
-
compressAt: 0.9
|
|
121
|
-
capabilities:
|
|
122
|
-
image: true
|
|
123
|
-
video: false
|
|
124
|
-
audio: false
|
|
125
|
-
promptCaching:
|
|
126
|
-
default: 1h
|
|
127
|
-
ttlPresets:
|
|
128
|
-
- { value: 5m, label: 5min }
|
|
129
|
-
- { value: 1h, label: 1hour }
|
|
130
|
-
thinking:
|
|
131
|
-
mode: adaptive
|
|
132
|
-
defaultEnabled: true
|
|
133
|
-
default: high
|
|
134
|
-
effortPresets:
|
|
135
|
-
- { value: low, label: Low }
|
|
136
|
-
- { value: medium, label: Medium }
|
|
137
|
-
- { value: high, label: High }
|
|
138
|
-
- { value: max, label: Max }
|
/package/admin-out/_next/static/{8v1hCXbUqJhyMuK7LVeet → Yc05ORrVk5IE9P1Gd8Ojh}/_buildManifest.js
RENAMED
|
File without changes
|
/package/admin-out/_next/static/{8v1hCXbUqJhyMuK7LVeet → Yc05ORrVk5IE9P1Gd8Ojh}/_ssgManifest.js
RENAMED
|
File without changes
|