@turmind/halo 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -46
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/app/page-f6e0adceb730c1a7.js +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +2 -2
- package/bundled-docs/design/agentcore.md +97 -0
- package/bundled-docs/design/evolution.md +2 -2
- package/bundled-docs/design/halo-city.md +18 -5
- package/bundled-docs/design/storage.md +4 -4
- package/bundled-docs/design/web.md +10 -8
- package/bundled-docs/dev/api.md +3 -1
- package/bundled-docs/dev/deploy.md +10 -0
- package/bundled-docs/dev/env.md +3 -2
- package/bundled-docs/dev/tools.md +4 -3
- package/bundled-docs/guide/channels/web.md +2 -0
- package/bundled-docs/requirements/explorer.md +1 -0
- package/bundled-docs/requirements/settings.md +10 -3
- package/dist/index.js +986 -341
- package/package.json +1 -1
- package/admin-out/_next/static/chunks/app/page-31317ef1fb0ba31f.js +0 -1
- /package/admin-out/_next/static/{iSrc6429pyTB0lK-EE5oH → NE1IhKMHm2l1nBV-EMjhV}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{iSrc6429pyTB0lK-EE5oH → NE1IhKMHm2l1nBV-EMjhV}/_ssgManifest.js +0 -0
package/admin-out/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--NE1IhKMHm2l1nBV_EMjhV--><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/66df341f99220f5c.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-47520cdc55be63e7.js"/><script src="/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js" async=""></script><script src="/_next/static/chunks/635-3dd0043ed0520b9d.js" async=""></script><script src="/_next/static/chunks/main-app-483b436873410ddc.js" async=""></script><script src="/_next/static/chunks/212-170855bed19f67f3.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/307-d7a99f9a4a436ec3.js" async=""></script><script src="/_next/static/chunks/app/page-f6e0adceb730c1a7.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-47520cdc55be63e7.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-170855bed19f67f3.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"ThemeProvider\"]\n3:I[26967,[\"212\",\"static/chunks/212-170855bed19f67f3.js\",\"177\",\"static/chunks/app/layout-c2fa8035998e48d5.js\"],\"AppI18nProvider\"]\n4:I[66048,[],\"\"]\n5:I[29926,[],\"\"]\n6:I[57917,[],\"ClientPageRoot\"]\n7:I[11704,[\"560\",\"static/chunks/1d2d5650-810a3ca1ae20cab5.js\",\"307\",\"static/chunks/307-d7a99f9a4a436ec3.js\",\"212\",\"static/chunks/212-170855bed19f67f3.js\",\"974\",\"static/chunks/app/page-f6e0adceb730c1a7.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/66df341f99220f5c.css\",\"style\"]\n:HL[\"/_next/static/css/659eccb5db697b76.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"NE1IhKMHm2l1nBV-EMjhV\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/66df341f99220f5c.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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
4:I[66048,[],""]
|
|
5
5
|
5:I[29926,[],""]
|
|
6
6
|
6:I[57917,[],"ClientPageRoot"]
|
|
7
|
-
7:I[11704,["560","static/chunks/1d2d5650-810a3ca1ae20cab5.js","307","static/chunks/307-d7a99f9a4a436ec3.js","212","static/chunks/212-170855bed19f67f3.js","974","static/chunks/app/page-
|
|
7
|
+
7:I[11704,["560","static/chunks/1d2d5650-810a3ca1ae20cab5.js","307","static/chunks/307-d7a99f9a4a436ec3.js","212","static/chunks/212-170855bed19f67f3.js","974","static/chunks/app/page-f6e0adceb730c1a7.js"],"default"]
|
|
8
8
|
a:I[11789,[],"OutletBoundary"]
|
|
9
9
|
c:I[21528,[],"AsyncMetadataOutlet"]
|
|
10
10
|
e:I[11789,[],"ViewportBoundary"]
|
|
@@ -14,7 +14,7 @@ e:I[11789,[],"ViewportBoundary"]
|
|
|
14
14
|
:HL["/_next/static/media/e4af272ccee01ff0-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
|
15
15
|
:HL["/_next/static/css/66df341f99220f5c.css","style"]
|
|
16
16
|
:HL["/_next/static/css/659eccb5db697b76.css","style"]
|
|
17
|
-
0:{"P":null,"b":"
|
|
17
|
+
0:{"P":null,"b":"NE1IhKMHm2l1nBV-EMjhV","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/66df341f99220f5c.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"}]]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# AgentCore Runtime Mode
|
|
2
|
+
|
|
3
|
+
Halo server as an **Amazon Bedrock AgentCore Runtime** container — a fourth
|
|
4
|
+
way to run halo (server / CLI / desktop / AgentCore). One env var flips it:
|
|
5
|
+
`HALO_RUNTIME_MODE=agentcore`. Demo package: `packages/agentcore-demo/`
|
|
6
|
+
(Dockerfile, chat frontend, CDK stack, auth Lambdas — its README carries the
|
|
7
|
+
operational gotchas; this doc covers how the mode works inside the server).
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Browser (static frontend, S3 + CloudFront)
|
|
11
|
+
│ /api/login, /api/verify → CloudFront /api/* behavior → API GW → auth Lambda (DDB users, JWT)
|
|
12
|
+
│ /api/ws-presign → presign Lambda (SigV4-signs the AgentCore WS URL)
|
|
13
|
+
│ wss (presigned URL, 5-min TTL)
|
|
14
|
+
▼
|
|
15
|
+
AgentCore Runtime (per-session microVM, auth terminated here)
|
|
16
|
+
│ X-Amzn-Bedrock-AgentCore-Runtime-Session-Id = user UUID
|
|
17
|
+
▼
|
|
18
|
+
halo server :8080 (HALO_RUNTIME_MODE=agentcore)
|
|
19
|
+
├── GET /ping Healthy | HealthyBusy (any running agent session)
|
|
20
|
+
├── POST /invocations {"input":{"prompt"}} → full assistant message
|
|
21
|
+
└── WS /ws streaming frames (primary path)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What the mode changes (packages/server/src/index.ts)
|
|
25
|
+
|
|
26
|
+
`config.server.runtimeMode === 'agentcore'` (env `HALO_RUNTIME_MODE`) skips:
|
|
27
|
+
|
|
28
|
+
- **Password/JWT gate** — AgentCore terminates auth upstream (SigV4 presign /
|
|
29
|
+
OAuth); the container is only reachable through the runtime.
|
|
30
|
+
- **Single-instance lock** — one microVM per session; many server processes
|
|
31
|
+
coexist by design.
|
|
32
|
+
- **Channels, cron, evolution, archive daemon** — meaningless in an ephemeral
|
|
33
|
+
per-session microVM; sessions are driven only through the AgentCore surface.
|
|
34
|
+
|
|
35
|
+
Everything else (agent loop, tools, skills, sqlite persistence) is the normal
|
|
36
|
+
server. The adapter itself is `packages/server/src/routes/agentcore.ts`,
|
|
37
|
+
mounted at the root (the AgentCore contract paths live outside `/api`).
|
|
38
|
+
|
|
39
|
+
## Per-user workspace isolation
|
|
40
|
+
|
|
41
|
+
`userWorkspace(base, runtimeSessionId)` maps each runtime session id to
|
|
42
|
+
`<HALO_WORKSPACE>/users/<sanitized-id>/` — an isolated workspace with its own
|
|
43
|
+
`.halo/` (sqlite + session files). The id chain:
|
|
44
|
+
|
|
45
|
+
DDB `agentcore-demo-users` UUID → login response → frontend uses it as
|
|
46
|
+
sessionId → presign Lambda signs it into
|
|
47
|
+
`X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` → adapter routes the workspace.
|
|
48
|
+
|
|
49
|
+
So **1 user = 1 runtime session id = 1 workspace**: users never see each
|
|
50
|
+
other's history; reconnecting with the same id resumes the same conversation.
|
|
51
|
+
`HALO_WORKSPACE` points at an EFS mount (`/mnt/efs`) — microVMs are ephemeral,
|
|
52
|
+
EFS makes the workspaces survive session termination and image rollouts.
|
|
53
|
+
|
|
54
|
+
## WS protocol quirks (why /init exists)
|
|
55
|
+
|
|
56
|
+
The AgentCore WS proxy only forwards **client frames containing `inputText`**
|
|
57
|
+
and cannot push server frames on connect. Hence:
|
|
58
|
+
|
|
59
|
+
- Frontend sends `{"inputText":"/init"}` after open; the server special-cases
|
|
60
|
+
it (also accepts `{"type":"init"}`) and replies with a `history` frame —
|
|
61
|
+
a full snapshot, which the frontend renders by rebuild-from-scratch (not
|
|
62
|
+
append), keyed by a signature so identical snapshots skip re-rendering.
|
|
63
|
+
- Frames without `inputText` are silently ignored server-side — the frontend
|
|
64
|
+
uses `{type:'ping'}` every 30s purely to keep the proxy from cutting the
|
|
65
|
+
socket.
|
|
66
|
+
- `/session switch` sends a `{type:'switch'}` frame → frontend clears, then
|
|
67
|
+
the follow-up history frame rebuilds.
|
|
68
|
+
|
|
69
|
+
## Session lifecycle (the part everyone gets wrong)
|
|
70
|
+
|
|
71
|
+
- Idle timeout (`idleRuntimeSessionTimeout`) terminates a session whose
|
|
72
|
+
`/ping` reports `Healthy`; `maxLifetime` (8h) force-terminates even busy
|
|
73
|
+
ones; failed health checks kill immediately.
|
|
74
|
+
- **An open WebSocket = an in-flight invocation** — the session never counts
|
|
75
|
+
as idle while a socket is open. The 30s frontend keepalive therefore keeps
|
|
76
|
+
the whole session warm; effective idle timeout ≈ "after the tab closes".
|
|
77
|
+
- `/ping` returns `HealthyBusy` whenever any agent session is running
|
|
78
|
+
(`registry.list().some(sm => sm.hasRunningSessions())`) — the official
|
|
79
|
+
keep-alive for long tool chains with no open connection.
|
|
80
|
+
- Termination is cheap: data is on EFS, next connect cold-starts (~3s) and
|
|
81
|
+
history reloads. `stop-runtime-session` kills one session on demand; there
|
|
82
|
+
is **no list/get-runtime-sessions API** (observe via CloudWatch `Sessions`
|
|
83
|
+
metric + runtime log filtering).
|
|
84
|
+
|
|
85
|
+
## Ops crib sheet
|
|
86
|
+
|
|
87
|
+
- CloudWatch logs capture container **stdout**; halo's logger drops
|
|
88
|
+
sub-threshold lines before stdout — set `HALO_LOG_LEVEL=info` or the log
|
|
89
|
+
group stays near-empty.
|
|
90
|
+
- `update-agent-runtime` **replaces the whole config** — omit
|
|
91
|
+
`--filesystem-configurations` and the EFS mount silently disappears.
|
|
92
|
+
Fetch-modify-send, and strip `requireServiceS3Endpoint` (rejected on
|
|
93
|
+
newer runtimes).
|
|
94
|
+
- VPC mode has no public IP: private subnets need `0.0.0.0/0 → NAT` or all
|
|
95
|
+
egress (Bedrock included) hangs → opaque 502s.
|
|
96
|
+
- Full operational detail + deploy walkthrough:
|
|
97
|
+
`packages/agentcore-demo/README.md`.
|
|
@@ -109,7 +109,7 @@ Sandbox is whitelist-cp from main workspace (only: `INSTRUCTIONS.md`, `INDEX.md`
|
|
|
109
109
|
### Phase B — Dry-run + Fix Loop
|
|
110
110
|
|
|
111
111
|
```
|
|
112
|
-
timeout
|
|
112
|
+
timeout 1800 halo cli -a <patch.testScenario.agentId> -n -w <runDir>/sandbox \
|
|
113
113
|
--access workspace <patch.testScenario.testMessage>
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -174,7 +174,7 @@ The wrapper clears any stale ABORT.md (from a crashed previous attempt) at the s
|
|
|
174
174
|
For each source_run_id:
|
|
175
175
|
|
|
176
176
|
1. Read `testScenario` from source run's `patch.md`.
|
|
177
|
-
2. Run `timeout
|
|
177
|
+
2. Run `timeout 1800 halo cli -a <agentId> -n -w <applyDir>/sandbox <testMessage>` against merged sandbox.
|
|
178
178
|
3. Spawn `__score__` with regress-mode brief, write `regress/<runId>/score.json`.
|
|
179
179
|
4. Any score with `lint < 50` or `behavior < 50` → regression, abort with `failed`.
|
|
180
180
|
|
|
@@ -34,10 +34,11 @@ Halo 服务器运行态的**街景式**像素可视化:侧视、剖面的像素
|
|
|
34
34
|
| 🪜 楼梯间 | 大楼左侧,真的逐层爬上爬下(没有传送) |
|
|
35
35
|
| 💺 工作层 | 每层 3 张工位(按 agent 名固定)+ 1 个技能站 + 随机小设施(盆栽/小书架/白板/售货机);干活时屏幕滚代码 |
|
|
36
36
|
| 🍵 公共层 | 冰箱(偶尔开门漏光)、水吧(水壶冒汽)、书报架、沙发,外加街机/鱼缸/猫爬架三选一;右侧伸出**阳台**(烟灰桶+盆栽)——抽烟点 |
|
|
37
|
-
| 🚬 习惯 | 每个 session 由 id hash 派生 **3 个固定偏好**(
|
|
38
|
-
| 🧭 就近原则 | 市民**生在自己的归属层**(
|
|
37
|
+
| 🚬 习惯 | 每个 session 由 id hash 派生 **3 个固定偏好**(抽烟/咖啡/泡茶/翻冰箱/看书/街机/看鱼/撸猫/打电话/下楼遛弯/沙发/工位打游戏/工位刷手机),摸鱼时偏好权重 ~4×——"这个会话就爱在阳台抽烟"是稳定人设,不是随机骰子(工位打游戏/工位刷手机不挪窝,就在自己座位上摸鱼;面板文案里"工位刷手机"沿用了"打电话"那条文案) |
|
|
38
|
+
| 🧭 就近原则 | 市民**生在自己的归属层**(自己的工位层;子代理不论嵌套多深都解析到所在会话树的**根会话**工位层,不再掉进大堂)。休息活动以**归属层**为锚点找最近的公共层,不会为了倒杯茶爬通天,**也不会随时间往低层漂**——12 楼的人永远回 F11 休息,不会一路沉到大堂。街机/鱼缸/猫爬架这类稀有设施只在归属层 **±4 层**的漫游带内找最近一个,带外直接放弃改走最近的休息层;`下楼遛弯` 同样按漫游带算——归属层在 F3 及以下才真下街,F4+ 改在带内最近的休息层放风 |
|
|
39
|
+
| 📌 稳定工位 | 每个**根会话**的工位楼层只在**第一次出现**时分配一次(选最低空位),离开释放、回来复用——不会因为 `/api/show/state` 按 `updated_at DESC` 排序每次轮询都重排,否则摸鱼回来就会撞上"工位换人"的错觉 |
|
|
39
40
|
| 💡 灯光 | 有人在的楼层灯亮(running 或 idle 都算——摸鱼也开灯),只有彻底 stopped 的层才熄;天黑后街灯/霓虹/远景窗火全亮 |
|
|
40
|
-
| ↳ 小一号 |
|
|
41
|
+
| ↳ 小一号 | 子代理:生在所在会话树的**根会话**工位层(而不是直接父代理那层),干活时聚在**直接**父代理桌边,不占工位 |
|
|
41
42
|
|
|
42
43
|
进出有戏:新会话直接出现在自己的工位层(从本层楼梯口走到工位,不再从街上爬通天);结束的走出门沿街远去淡出。
|
|
43
44
|
|
|
@@ -51,7 +52,8 @@ Halo 服务器运行态的**街景式**像素可视化:侧视、剖面的像素
|
|
|
51
52
|
- **上下文仪表**:`134k / 200k (67%)` —— 分母是该会话的**真实** `maxContextTokens`
|
|
52
53
|
- **委派链**:父/子会话可点击跳转,每行带 token 数和消息数
|
|
53
54
|
- **点楼层**:该层的会话委派关系树(缩进、可点跳转)——工作层看"谁在这层干活、
|
|
54
|
-
带着哪些子代理",公共层看"谁在摸鱼"
|
|
55
|
+
带着哪些子代理",公共层看"谁在摸鱼";**工位在这层的人即使正外出休息也照样算在这层**
|
|
56
|
+
(按归属工位层统计,不是按人此刻站在哪),点楼层永远能看全常驻名单
|
|
55
57
|
- 点大楼:全员 roster + token 合计;点技能站:谁在用
|
|
56
58
|
|
|
57
59
|
## 街景与载具
|
|
@@ -77,6 +79,9 @@ Halo 服务器运行态的**街景式**像素可视化:侧视、剖面的像素
|
|
|
77
79
|
- **纯净模式(zen)**:淡出所有 HUD(统计/动态/图例/缩放条/面板),只留街景,适合大屏常驻展示
|
|
78
80
|
- **多语言(i18n)**:中文 / English 一键切;`?lang=en`/`?lang=zh` 直达,选择存 localStorage。
|
|
79
81
|
运行时文案走 `t()`,静态 DOM 走 `data-i18n` 属性填充
|
|
82
|
+
- **背景音乐**:🎵/🔇 HUD 按钮(挨着 中/EN),纯 Web Audio 程序生成、零素材,
|
|
83
|
+
默认开、偏好存 localStorage;受浏览器自动播放策略限制,AudioContext 在首次
|
|
84
|
+
用户手势后才创建,关掉时真正 suspend(零 CPU),音频图与画面 rAF 循环完全独立
|
|
80
85
|
|
|
81
86
|
## 调试参数
|
|
82
87
|
|
|
@@ -107,10 +112,18 @@ js/
|
|
|
107
112
|
## 服务端配套
|
|
108
113
|
|
|
109
114
|
- `GET /api/show/session?ws=&id=` — 只读会话详情:裁剪后的消息日志 + 真实
|
|
110
|
-
token 上限。鉴权同 `/api/show/state`(x-token
|
|
115
|
+
token 上限。鉴权同 `/api/show/state`(x-token;`full`/`observer` 可跨
|
|
116
|
+
workspace 读取,其余 accessLevel 只能看自己 workspace)
|
|
111
117
|
- `/api/show/state` — 无活跃 UIState 的会话(idle/stopped/重启后)token
|
|
112
118
|
从会话文件头读取(mtime 缓存,不随轮询刷盘),不显示 0;含 `messageCount` 字段
|
|
113
119
|
|
|
120
|
+
> **`observer` token 的权限面**:除了 `/show/state` 的聚合计数外,`observer`
|
|
121
|
+
> 还能调 `/show/session` 读取**任意 workspace** 任意会话的明细——最近 40 条
|
|
122
|
+
> 消息(每条截断到 600 字,tool input 截到 200 字)。它是"看板/监控"用的
|
|
123
|
+
> 全局只读角色,但这条 transcript 读取能力不止是计数;签发 observer token 时
|
|
124
|
+
> 要知道它相当于给了跨 workspace 的会话内容只读权限,不要把它当成纯粹的
|
|
125
|
+
> "数字仪表盘"凭证随手发放。
|
|
126
|
+
|
|
114
127
|
## 性能设计
|
|
115
128
|
|
|
116
129
|
原则:一个纯只读可视化页面,轮询**不得对服务端会话生命周期产生任何副作用**,
|
|
@@ -296,8 +296,8 @@ general: # built-in declarer (the server itself
|
|
|
296
296
|
max_message_slice: 800
|
|
297
297
|
summarize_timeout_sec: 300
|
|
298
298
|
sandbox:
|
|
299
|
-
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker"
|
|
300
|
-
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig"
|
|
299
|
+
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh"
|
|
300
|
+
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc"
|
|
301
301
|
logging:
|
|
302
302
|
level: warn
|
|
303
303
|
|
|
@@ -352,8 +352,8 @@ A value of the form `<<ENV_NAME>>` is replaced at read time with `process.env.EN
|
|
|
352
352
|
| `general.limits.tool_result_ui_chars` | 65536 | Per-tool-result cap on the content **stored for UI display** (admin/web chat panel). Far larger than the LLM cap so a normal command's full output stays visible, but bounded so a multi-MB `cat` can't bloat the session file / WS payload / browser render; excess truncated with a marker pointing at `file_read` |
|
|
353
353
|
| `general.limits.ws_event_buffer` | 5000 | Events buffered per detached WS session before oldest are dropped on reattach |
|
|
354
354
|
| `general.limits.terminal_scrollback_bytes` | 50000 | Off-screen scrollback bytes retained per detached persistent terminal |
|
|
355
|
-
| `general.sandbox.hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker` | bwrap tmpfs overlay (Linux only) |
|
|
356
|
-
| `general.sandbox.hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig` | bwrap /dev/null bind (Linux only) |
|
|
355
|
+
| `general.sandbox.hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh` | bwrap tmpfs overlay (Linux only) |
|
|
356
|
+
| `general.sandbox.hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc` | bwrap /dev/null bind (Linux only) |
|
|
357
357
|
| `general.logging.level` | warn | `debug` / `info` / `warn` / `error` |
|
|
358
358
|
|
|
359
359
|
Schema source: [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`.
|
|
@@ -170,10 +170,8 @@ node server.js
|
|
|
170
170
|
|
|
171
171
|
### Security model
|
|
172
172
|
|
|
173
|
-
- `HALO_TOKEN` and `HALO_API` are server-side only, never exposed to the browser
|
|
174
|
-
-
|
|
175
|
-
- web-demo server proxies all requests to Halo, injecting the token in `x-token` header
|
|
176
|
-
- Session auth: random hex token stored in `x-session` header + localStorage
|
|
173
|
+
- **Proxy mode (default)**: `HALO_TOKEN` and `HALO_API` are server-side only, never exposed to the browser; frontend uses same-origin relative paths (`/chat`, `/history`, etc.); web-demo server proxies all requests to Halo, injecting the token in `x-token` header. Session auth: HMAC token in `x-session` header + localStorage. All proxy routes including `GET /file` are gated by the auth middleware.
|
|
174
|
+
- **Direct-connect mode (opt-in)**: the gear panel takes a halo server URL + web-channel token; the browser then calls `<server>/api/web/*` directly with `x-token`, bypassing the proxy (no password step). The token is stored in that browser's localStorage by explicit user choice — the UI says so next to the field. One slot; clearing it returns to proxy mode. Works because the server CORS reflects any origin and allowlists `x-token`.
|
|
177
175
|
|
|
178
176
|
### Proxy routes
|
|
179
177
|
|
|
@@ -183,17 +181,21 @@ node server.js
|
|
|
183
181
|
| `POST /stop` | `POST /api/web/stop` |
|
|
184
182
|
| `GET /history` | `GET /api/web/history` |
|
|
185
183
|
| `GET /subscribe` | `GET /api/web/subscribe` |
|
|
184
|
+
| `GET /file` | `GET /api/web/file` |
|
|
186
185
|
|
|
187
186
|
### Features
|
|
188
187
|
|
|
189
|
-
-
|
|
190
|
-
-
|
|
188
|
+
- Markdown rendering for assistant text (marked@12, gfm + breaks) with typewriter streaming; user text stays escaped plain text
|
|
189
|
+
- SSE streaming with collapsible thinking / tool-call step blocks
|
|
190
|
+
- Auto-reconnect: if session is running on page load, subscribes to live stream; history renders rebuild-from-scratch (never append)
|
|
191
191
|
- Message queue: can send while agent is running, messages queue and send sequentially
|
|
192
|
-
- Slash commands (`/help`, `/session`, `/agent`, `/skill`, `/workspace`)
|
|
193
|
-
- Image upload
|
|
192
|
+
- Slash commands (`/help`, `/session`, `/agent`, `/skill`, `/workspace`), session/agent switcher menus
|
|
193
|
+
- Image upload + camera capture (camera button is mobile-only — the `capture` attribute is a no-op on desktop, where it would duplicate the file picker)
|
|
194
194
|
- Voice recording (webm audio saved to workspace, path given to agent)
|
|
195
195
|
- Stop/interrupt button
|
|
196
196
|
- i18n: auto-detects browser language (`navigator.language`), toggle button in header, persists to `localStorage`
|
|
197
|
+
- Mobile-first layout: 100dvh-safe, safe-area insets, ≥44px touch targets, bottom-sheet menus/settings on small widths, visual language shared with `packages/agentcore-demo`
|
|
198
|
+
- Page load needs internet for two CDN deps (tailwindcss, marked) — no longer fully self-contained
|
|
197
199
|
|
|
198
200
|
## Integration guide (custom frontend)
|
|
199
201
|
|
package/bundled-docs/dev/api.md
CHANGED
|
@@ -222,7 +222,7 @@ File: `packages/server/src/routes/web.ts`
|
|
|
222
222
|
| POST | `/api/web/stop` | Stop running task → `{stopped: boolean}` |
|
|
223
223
|
| GET | `/api/web/history` | Active session history → `{sessionId, messages[], running}` |
|
|
224
224
|
| GET | `/api/web/subscribe` | Reconnect SSE to running session |
|
|
225
|
-
| GET | `/api/web/file?path=` | Inline-serve a workspace-relative file (image / video / pdf etc.). Path-traversal-checked against the token's bound workspace. |
|
|
225
|
+
| GET | `/api/web/file?path=` | Inline-serve a workspace-relative file (image / video / pdf etc.). Path-traversal-checked against the token's bound workspace: the lexical check is re-verified against the realpath'd root, so a symlink inside the workspace pointing outside it returns 403; a dangling symlink (target doesn't exist) returns 404. |
|
|
226
226
|
|
|
227
227
|
See [design/web.md](../design/web.md).
|
|
228
228
|
|
|
@@ -238,6 +238,8 @@ runtime so the frontend can render rooms (workspaces) + characters (sessions).
|
|
|
238
238
|
| GET | `/api/show/state` | `full` or `observer` token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }` |
|
|
239
239
|
| GET | `/api/show/session?ws=&id=` | Inspector-panel detail for a single session. Trimmed message log (last 40, content/tool I/O capped) plus `contextTokens` / `outputTokens` / `maxContextTokens` / `isRunning`. Non-`full`/`observer` tokens may only read their own workspace. |
|
|
240
240
|
|
|
241
|
+
`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.
|
|
242
|
+
|
|
241
243
|
Each `workspace` = `{ path, key, label, counts{running,idle,stopped}, totalSessions, skills[], sessions[] }`; each `session` = `{ id, parentId, depth, agentName, description, status, lastTool, activeSkill, contextTokens, outputTokens, messageCount, updatedAt }`. `lastTool` / `activeSkill` come from the live in-memory UI log (empty when the session isn't loaded). Sessions per room are capped (`totalSessions` reports the true total). Frontend: `halo-city/` at repo root.
|
|
242
244
|
|
|
243
245
|
**Read-only by construction — degraded snapshots.** Both endpoints resolve a workspace's runtime via `registry.peek()` — in-memory lookup only, **never** `getOrCreate`: constructing a SessionManager has write side effects (boot orphan-reconciliation batch-stops live sub-session rows; `.halo/` scaffolding), so a pure visualization poll must not trigger it. When no live SessionManager exists in this process, the handler opens that workspace's `.halo/halo.db` **read-only** (cached connection; missing db/tables silently degrade to an empty room) and projects rows / the persisted session file into the same wire shape. Sessions not in memory therefore return a **persisted snapshot**: `status` / token counts / `messageCount` may lag behind the last persistence point, and the live-only signals (`lastTool` / `activeSkill`) are empty by definition. The degraded `/api/show/session` path reports `maxContextTokens: 0` (the real cap needs a live agent config; the city inspector hides the meter when the cap is unknown).
|
|
@@ -152,6 +152,14 @@ This installs the `halo` binary on `$PATH`. Subcommands available:
|
|
|
152
152
|
1. `halo upgrade` — bumps the on-disk npm package
|
|
153
153
|
2. `halo server restart` — server's startup check sees `~/.halo/global/.template-version` is behind the new bundled `TEMPLATE_VERSION`, runs `ensureHaloHome` automatically, then starts. Refreshes `docs/`, built-in agents, built-in skills, system prompts, and the model registry. User-owned files (USER.md, custom agents/skills, INSTRUCTIONS.md overrides) are left alone. See `init.ts` for the per-category overwrite policy.
|
|
154
154
|
|
|
155
|
+
### Release checklist (before `npm publish`)
|
|
156
|
+
|
|
157
|
+
1. **Bump version** in root `package.json` (and any workspace `package.json` that mirrors it).
|
|
158
|
+
2. **Update `CHANGELOG.md`**: rename `[Unreleased]` → `[x.y.z] - YYYY-MM-DD`, add a fresh empty `[Unreleased]` section above it.
|
|
159
|
+
3. **Bump `TEMPLATE_VERSION`** in `packages/server/src/init.ts` if any file under `templates/` was touched.
|
|
160
|
+
4. **Build admin**: `pnpm --filter @turmind/halo-admin build` — verify `admin/out/monaco/vs/loader.js` exists.
|
|
161
|
+
5. Commit, tag `vx.y.z`, push, then publish.
|
|
162
|
+
|
|
155
163
|
The published package contains a single bundled JS entry (~620 KB), all built-in templates (agents / skills / prompts / models), bundled platform docs, and the admin Web UI static export. Total install footprint ≈ 120 MB after npm dedupes shared deps.
|
|
156
164
|
|
|
157
165
|
### Non-interactive (Docker / CI) details
|
|
@@ -196,3 +204,5 @@ server {
|
|
|
196
204
|
}
|
|
197
205
|
}
|
|
198
206
|
```
|
|
207
|
+
|
|
208
|
+
Behind a reverse proxy, set `general.server.trust_proxy: true` in `settings.yaml` (global scope only). Without it, brute-force rate-limiting / lockout resolves the client IP from the direct socket address — which behind a proxy is the proxy's own IP, so every client collapses into one bucket and can't be told apart. Only enable this when the proxy in front is one you control and it rewrites `x-forwarded-for` itself; otherwise a client can forge the header and bypass lockouts. Default is `false` (direct-connect deployments).
|
package/bundled-docs/dev/env.md
CHANGED
|
@@ -108,8 +108,9 @@ settings.yaml only (no env override):
|
|
|
108
108
|
- `general.compact.max_summary_input` (default 15000) — local compaction fallback input cap
|
|
109
109
|
- `general.compact.max_message_slice` (default 800) — local compaction per-message cap
|
|
110
110
|
- `general.compact.summarize_timeout_sec` (default 300) — self-compact timeout
|
|
111
|
-
- `general.
|
|
112
|
-
- `general.sandbox.
|
|
111
|
+
- `general.server.trust_proxy` (default `false`) — trust `x-forwarded-for` for client IP resolution, scope: global (enable only behind a reverse proxy you control)
|
|
112
|
+
- `general.sandbox.hidden_dirs` (default `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh`) — bwrap tmpfs overlays, scope: global
|
|
113
|
+
- `general.sandbox.hidden_files` (default `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc`) — bwrap /dev/null binds, scope: global
|
|
113
114
|
- `general.logging.level` (default `warn`) — log level: debug | info | warn | error
|
|
114
115
|
|
|
115
116
|
`<provider-id>.secrets.*` (server-only, hard-rejected by `{{}}` substitution; declared in `models/<provider-id>.yaml` `secrets:`):
|
|
@@ -137,10 +137,10 @@ Configured in `settings.yaml` under `general.sandbox`:
|
|
|
137
137
|
|
|
138
138
|
| Setting | Default | Method |
|
|
139
139
|
|---|---|---|
|
|
140
|
-
| `hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker` | `--tmpfs` overlay (empty directory) |
|
|
141
|
-
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig` | `--ro-bind /dev/null` (empty file) |
|
|
140
|
+
| `hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh` | `--tmpfs` overlay (empty directory) |
|
|
141
|
+
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc` | `--ro-bind /dev/null` (empty file) |
|
|
142
142
|
|
|
143
|
-
Changes take effect immediately — `config.ts` reads settings.yaml via an mtime-watched lazy cache, so the next `shell_exec` reads the latest values.
|
|
143
|
+
Changes take effect immediately — `config.ts` reads settings.yaml via an mtime-watched lazy cache, so the next `shell_exec` reads the latest values. These keys are `globalOnly` in the schema — a workspace `settings.yaml` cannot override them, since they define the security boundary agents run inside.
|
|
144
144
|
|
|
145
145
|
`/tmp` is not in the hidden list — it receives a standalone `--tmpfs` mount for process isolation (each bwrap invocation gets its own empty `/tmp`), not for hiding secrets.
|
|
146
146
|
|
|
@@ -172,6 +172,7 @@ Start a sub-agent session asynchronously. When the sub-agent finishes, its **wra
|
|
|
172
172
|
| `agent_id` | string | yes | Agent ID (e.g. `"coder"`) |
|
|
173
173
|
| `message` | string | yes | Task description |
|
|
174
174
|
| `system_prompt_context` | string | no | Extra context prepended to the initial message |
|
|
175
|
+
| `title` | string | no | Session title shown in the admin sidebar. When omitted, auto-generated from the task message (first 60 chars of `description`). |
|
|
175
176
|
| `working_dir` | string | no | Sub-agent's focus directory (absolute or workspace-relative; default: project root). It's persistent session identity (stored in the DB, restored on resume), so the directory-chain `.halo/INSTRUCTIONS.md` along the path root→dir is baked into the sub-agent's **system prompt every turn** (it never forgets the directory's rules), and the prompt is tagged with this focus. Does **not** change where tools run. |
|
|
176
177
|
|
|
177
178
|
**Output (JSON string)**
|
|
@@ -134,6 +134,8 @@ Environment variables:
|
|
|
134
134
|
|
|
135
135
|
The demo holds your token server-side and exposes its own password-based session — the browser never sees the halo token. Open `http://localhost:9528` to use it.
|
|
136
136
|
|
|
137
|
+
Alternatively, the gear icon (header / login screen) opens **direct-connect mode**: enter any halo server's URL + a web-channel token and the browser talks to that server's `/api/web/*` directly, skipping the proxy and its password. Handy for pointing one deployed web-demo at several halo servers. The token is kept in that browser's localStorage — only use it on devices you trust; clear it in the same panel to return to proxy mode.
|
|
138
|
+
|
|
137
139
|
## Security notes
|
|
138
140
|
|
|
139
141
|
- **Tokens are unhashed** in `~/.halo/secrets/channels/channels.db`. Restrict that file to the user that runs halo
|
|
@@ -55,6 +55,7 @@ VS Code-style highlight selection (no checkboxes):
|
|
|
55
55
|
- Selected files/folders drag onto a target dir
|
|
56
56
|
- Multi-file drag supported
|
|
57
57
|
- Each file goes through `POST /api/files/rename` with `{oldPath, newPath}`
|
|
58
|
+
- **Spring-loaded expand** (applies to both tree-internal drags and OS-file drops): a collapsed folder does **not** expand the moment a drag passes over it — it unfolds only after the drag hovers on it for ~600ms (VSCode/Finder behaviour; the timer cancels on drag-leave). Dropping into a collapsed folder expands it immediately to show the result
|
|
58
59
|
|
|
59
60
|
### Right-click menu
|
|
60
61
|
|
|
@@ -7,6 +7,8 @@ Global + workspace configuration. The shape mirrors VSCode's `contributes.config
|
|
|
7
7
|
```yaml
|
|
8
8
|
# ~/.halo/secrets/settings.yaml — values only
|
|
9
9
|
general: # built-in declarer (server itself)
|
|
10
|
+
server:
|
|
11
|
+
trust_proxy: false # true only behind a reverse proxy you control — see below
|
|
10
12
|
session:
|
|
11
13
|
max_queue_size: 256
|
|
12
14
|
max_nesting_depth: 16
|
|
@@ -15,8 +17,8 @@ general: # built-in declarer (server itself)
|
|
|
15
17
|
max_summary_input: 15000
|
|
16
18
|
...
|
|
17
19
|
sandbox:
|
|
18
|
-
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker"
|
|
19
|
-
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig"
|
|
20
|
+
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh"
|
|
21
|
+
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc"
|
|
20
22
|
writable_dirs: "" # e.g. ~/.kiro,~/.local/share/kiro-cli — rw bind-mounts in bwrap; ignored for readonly sessions
|
|
21
23
|
logging:
|
|
22
24
|
level: warn
|
|
@@ -91,7 +93,11 @@ secrets: []
|
|
|
91
93
|
|
|
92
94
|
### General — built-in
|
|
93
95
|
|
|
94
|
-
Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `agent.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `evolution.*`, `limits.*`.
|
|
96
|
+
Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `agent.*`, `server.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `evolution.*`, `limits.*`.
|
|
97
|
+
|
|
98
|
+
`server.trust_proxy` (boolean, default `false`, `globalOnly`): whether the brute-force rate limiter (`middleware/brute-force.ts` `getClientIp`) trusts the `x-forwarded-for` header for client IP resolution. Direct-connect deployments leave it `false` and get the socket address. Behind a reverse proxy (nginx / Cloudflare / etc.), set it to `true` so the real client IP is honored instead of the proxy's — but only when that proxy is one you control and rewrites the header itself, otherwise a client can forge XFF to dodge lockouts.
|
|
99
|
+
|
|
100
|
+
`sandbox.hidden_dirs` / `sandbox.hidden_files` / `sandbox.writable_dirs` are `globalOnly` — they define the security boundary agents run inside, so a workspace `settings.yaml` cannot override them (a workspace overriding them could lift its own sandbox constraints).
|
|
95
101
|
|
|
96
102
|
`evolution.*` controls the self-evolution subsystem (see [plans/self-evolution.md](../plans/self-evolution.md)). All `evolution.*` keys are `globalOnly` — they live in `~/.halo/secrets/settings.yaml` only, not workspace settings. Notable knobs: `evolution.level` (`L0` = off, `L1` = human + LLM assist), `evolution.max_concurrent_run` / `max_concurrent_apply` (wrapper concurrency caps), `evolution.run_timeout_minutes` / `apply_timeout_minutes` (heartbeat timeouts), `evolution.max_attempts` (per-row retry cap), `evolution.triggers.pre_compact` (snapshot session before compaction).
|
|
97
103
|
|
|
@@ -106,6 +112,7 @@ Declared in [packages/server/src/settings-schema.ts](../../../packages/server/sr
|
|
|
106
112
|
| `description_zh` | no | Chinese description (UI picks based on lang) |
|
|
107
113
|
| `default` | no | Placeholder shown when the value is unset; supports `<<ENV>>` |
|
|
108
114
|
| `secret` | no | `true` → masked in API responses + password input in UI |
|
|
115
|
+
| `globalOnly` | no | `true` → read from global settings only; workspace overrides are ignored at runtime. UI disables the workspace input and shows a "global only" hint |
|
|
109
116
|
|
|
110
117
|
## Scope: global vs. workspace
|
|
111
118
|
|