@turmind/halo 0.1.9 → 0.2.0
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 -5
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/298-4fac7d44a7949eec.js +1 -0
- package/admin-out/_next/static/chunks/{503-7c6d9935df6e0cea.js → 307-d7a99f9a4a436ec3.js} +1 -1
- package/admin-out/_next/static/chunks/app/page-b12a2e8e48fe4ea0.js +1 -0
- package/admin-out/_next/static/css/{8fce4e1a54dac12a.css → 372e3dbd4a0d1c25.css} +1 -1
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +4 -4
- package/bundled-docs/dev/acp-adapter.md +5 -4
- package/bundled-docs/dev/desktop-packaging.md +94 -8
- package/bundled-docs/guide/getting-started.md +3 -2
- package/bundled-docs/requirements/explorer.md +11 -0
- package/dist/index.js +13373 -13246
- package/package.json +1 -1
- package/templates/agents/executor/agent.yaml +2 -2
- package/templates/models/aws-bedrock-claude-invoke.yaml +26 -0
- package/templates/skills/acp/SKILL.md +8 -1
- package/templates/skills/acp/templates/SKILL.md.tmpl +2 -1
- package/templates/skills/acp/templates/ask.py +2 -1
- package/templates/skills/acp/templates/config.yaml.tmpl +5 -0
- package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +0 -1
- package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +0 -1
- package/bundled-docs/plans/cron-tasks.md +0 -362
- package/bundled-docs/plans/design-suggestions.md +0 -203
- package/bundled-docs/plans/issue-session-deep-nesting.md +0 -208
- package/bundled-docs/plans/launch-plan.md +0 -111
- package/bundled-docs/plans/md-and-agent-refactor.md +0 -197
- package/bundled-docs/plans/remote-mac-worker.md +0 -196
- package/bundled-docs/plans/roadmap.md +0 -40
- package/bundled-docs/plans/self-evolution.md +0 -1379
- package/bundled-docs/plans/todo.md +0 -32
- package/bundled-docs/plans/weixin-channel.md +0 -255
- package/bundled-docs/test/docs-seeding.md +0 -101
- package/bundled-docs/test/session.md +0 -374
- /package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → ep7BWneNhTuIPwJfFFh72}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → ep7BWneNhTuIPwJfFFh72}/_ssgManifest.js +0 -0
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# TODO
|
|
2
|
-
|
|
3
|
-
## Architecture
|
|
4
|
-
|
|
5
|
-
(done)
|
|
6
|
-
|
|
7
|
-
## Deep session nesting
|
|
8
|
-
|
|
9
|
-
(done) — detailed analysis in [issue-session-deep-nesting.md](issue-session-deep-nesting.md)
|
|
10
|
-
|
|
11
|
-
## Other
|
|
12
|
-
|
|
13
|
-
- [ ] `eventListeners` should support multiple listeners (Map<string, handler> → Map<string, handler[]>) so multiple frontends can subscribe to the same session tree simultaneously
|
|
14
|
-
|
|
15
|
-
## Workspace settings.yaml copy in share-workspace
|
|
16
|
-
|
|
17
|
-
share-workspace 当前完全排除 `settings.yaml`(stage.py:63 ALWAYS_EXCLUDED)。
|
|
18
|
-
但 workspace 自己的 `settings.yaml`(如 `<ws>/.halo/settings.yaml`,跟
|
|
19
|
-
`~/.halo/secrets/settings.yaml` 不同)其实包含项目特定的非 secret 偏好,
|
|
20
|
-
应该带走。需要区分:
|
|
21
|
-
|
|
22
|
-
- `~/.halo/secrets/settings.yaml` — 含密码 / API key,**永远不带**
|
|
23
|
-
- `<workspace>/.halo/settings.yaml`(如果存在) — 项目级偏好,**应该带**
|
|
24
|
-
|
|
25
|
-
实施时要看 workspace 是否真的支持 `<ws>/.halo/settings.yaml` 加载;如果
|
|
26
|
-
不支持,这条 todo 是个 feature request 而非修复。
|
|
27
|
-
|
|
28
|
-
## i18n: agent management "Internal" section
|
|
29
|
-
|
|
30
|
-
`agent-management-main.tsx` 新加的 internal scope label 当前是硬编码英文
|
|
31
|
-
"Internal"。其他 scope label 用了 i18n key (`t('common.global')` /
|
|
32
|
-
`t('common.workspace')`)。下次集中补 i18n 时加 `t('common.internal')`。
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
# WeChat Channel Integration Plan
|
|
2
|
-
|
|
3
|
-
**Status**: shipped. Channel implementation lives in [packages/server/src/channels/wechat/](../../../packages/server/src/channels/wechat/) and routes in [packages/server/src/routes/weixin.ts](../../../packages/server/src/routes/weixin.ts). Runtime behavior is documented in [design/wechat.md](../design/wechat.md). This file is kept as the design log.
|
|
4
|
-
|
|
5
|
-
## Goals
|
|
6
|
-
|
|
7
|
-
Let the user talk to Halo from WeChat on their phone, sharing the same workspace + session as the web client.
|
|
8
|
-
On the road, exchange messages / see simple results on WeChat; at the desk, use web for the full conversation / code / files.
|
|
9
|
-
|
|
10
|
-
## Architecture
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
┌── ws/ (web channel) ─┐
|
|
14
|
-
Halo server (9527) ──┤ ├── SessionManager
|
|
15
|
-
└── channels/weixin/ (new) ─┘ (per workspace)
|
|
16
|
-
↕ HTTPS long-poll
|
|
17
|
-
ilinkai.weixin.qq.com
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Two channels are peers; both are SessionManager subscribers + callers.
|
|
21
|
-
|
|
22
|
-
## Data model
|
|
23
|
-
|
|
24
|
-
### Workspace ↔ Bot mapping
|
|
25
|
-
|
|
26
|
-
One bot (= one `ilink_bot_id` from a QR scan) is bound to one workspace. A workspace can bind multiple bots (redundancy or use-case split), though 1:1 is recommended.
|
|
27
|
-
|
|
28
|
-
### Session strategy
|
|
29
|
-
|
|
30
|
-
- **One WeChat user → one long-lived session**
|
|
31
|
-
- Session ID: `wx_<normalizedUserId>` (e.g. `a1b2c3@im.wechat` → `wx_a1b2c3-im-wechat`)
|
|
32
|
-
- Session lives in the bot's bound workspace
|
|
33
|
-
- Uses the `default` agent
|
|
34
|
-
|
|
35
|
-
On the web side the workspace's session list shows this session (labelled `wx: <username or userId>`).
|
|
36
|
-
|
|
37
|
-
### Storage
|
|
38
|
-
|
|
39
|
-
WeChat bot accounts are **cross-workspace** (they record which workspace a bot binds to). Not in a workspace DB; separate file: `~/.halo/global/weixin.db` (new).
|
|
40
|
-
|
|
41
|
-
Table `weixin_accounts`:
|
|
42
|
-
|
|
43
|
-
| Field | Type | Notes |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| account_id | TEXT PK | Normalised ilink_bot_id (e.g. `abc-im-bot`) |
|
|
46
|
-
| bot_token | TEXT | For all subsequent API auth |
|
|
47
|
-
| base_url | TEXT | Regional URL from QR scan |
|
|
48
|
-
| user_id | TEXT | ilink_user_id of the scanner |
|
|
49
|
-
| workspace_path | TEXT | Absolute path of the bound workspace |
|
|
50
|
-
| label | TEXT | User-provided name (for UI) |
|
|
51
|
-
| enabled | INTEGER | 1 = long-poll active; 0 = disabled |
|
|
52
|
-
| created_at | INTEGER | |
|
|
53
|
-
| updated_at | INTEGER | |
|
|
54
|
-
|
|
55
|
-
## Code changes
|
|
56
|
-
|
|
57
|
-
### Phase 1: shared foundation
|
|
58
|
-
|
|
59
|
-
**1.1 SessionManager multi-listener**
|
|
60
|
-
File: `packages/server/src/agents/session-manager.ts`
|
|
61
|
-
|
|
62
|
-
Current: `eventListeners: Map<rootId, handler>` stores a single handler; later callers overwrite earlier ones.
|
|
63
|
-
Target: `Map<rootId, Set<handler>>`; `emitEvent` iterates the Set.
|
|
64
|
-
|
|
65
|
-
API changes:
|
|
66
|
-
- `registerEventListener(rootId, handler)` returns `() => void` (unsubscribe)
|
|
67
|
-
- `unregisterEventListener(rootId)` removes a specific handler (signature preserved for backwards compat)
|
|
68
|
-
|
|
69
|
-
Callers to update:
|
|
70
|
-
- ws/handler.ts three `registerEventListener` sites (lines 228, 287, 373, 408)
|
|
71
|
-
- ws/handler.ts one `unregisterEventListener` site (line 343)
|
|
72
|
-
Use the returned unsubscribe fn instead of clearing by rootId.
|
|
73
|
-
|
|
74
|
-
**1.2 SessionManager registry**
|
|
75
|
-
File: `packages/server/src/agents/session-manager-registry.ts` (new)
|
|
76
|
-
|
|
77
|
-
Extract ws/handler.ts:54's closure-scoped `sessionManagers: Map<string, SessionManager>`:
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
export class SessionManagerRegistry {
|
|
81
|
-
private cache = new Map<string, SessionManager>()
|
|
82
|
-
getOrCreate(workspacePath: string): SessionManager
|
|
83
|
-
get(workspacePath: string): SessionManager | undefined
|
|
84
|
-
list(): Array<{ workspacePath: string; sm: SessionManager }>
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
`index.ts` creates one `registry` shared by ws setup and weixin setup.
|
|
89
|
-
|
|
90
|
-
ws/handler.ts uses the registry (minimal change: `getSessionManager(path)` delegates to the registry).
|
|
91
|
-
|
|
92
|
-
### Phase 2: WeChat channel
|
|
93
|
-
|
|
94
|
-
**2.1 Global DB**
|
|
95
|
-
File: `packages/server/src/db/weixin-db.ts` (new)
|
|
96
|
-
|
|
97
|
-
- `createWeixinDb(globalDir)` → open `~/.halo/global/weixin.db`, create the table
|
|
98
|
-
- Drizzle schema for `weixin_accounts`
|
|
99
|
-
|
|
100
|
-
**2.2 HTTP client**
|
|
101
|
-
File: `packages/server/src/channels/weixin/api.ts` (new)
|
|
102
|
-
|
|
103
|
-
Copy from plugin [api.ts](/tmp/weixin-inspect/plugin/package/src/api/api.ts), keep:
|
|
104
|
-
- `getUpdates` (long-poll)
|
|
105
|
-
- `sendMessage`
|
|
106
|
-
- `notifyStart` / `notifyStop`
|
|
107
|
-
- `getUploadUrl` + CDN upload (media support later; stub for now)
|
|
108
|
-
|
|
109
|
-
Drop:
|
|
110
|
-
- `redactUrl/redactBody` (no redacted logs needed)
|
|
111
|
-
- `loadConfigRouteTag` + `SKRouteTag` header (internal routing)
|
|
112
|
-
- `ilink_appid` reading from package.json → hard-code `"bot"`
|
|
113
|
-
- `iLink-App-ClientVersion` → hard-code `buildClientVersion('2.1.10')`
|
|
114
|
-
|
|
115
|
-
**2.3 QR login**
|
|
116
|
-
File: `packages/server/src/channels/weixin/login.ts` (new)
|
|
117
|
-
|
|
118
|
-
Copy from plugin [login-qr.ts](/tmp/weixin-inspect/plugin/package/src/auth/login-qr.ts), export:
|
|
119
|
-
- `startWeixinLoginWithQr({ sessionKey })` → `{ qrcodeUrl, sessionKey }`
|
|
120
|
-
- `waitForWeixinLogin({ sessionKey })` → `{ connected, botToken, accountId, baseUrl, userId }`
|
|
121
|
-
|
|
122
|
-
Preserve concurrent-login via sessionKey (multiple scans in parallel).
|
|
123
|
-
|
|
124
|
-
**2.4 Account DAL**
|
|
125
|
-
File: `packages/server/src/channels/weixin/accounts.ts` (new)
|
|
126
|
-
|
|
127
|
-
Thin DAL:
|
|
128
|
-
- `insertAccount(row)`
|
|
129
|
-
- `updateAccount(accountId, patch)`
|
|
130
|
-
- `deleteAccount(accountId)`
|
|
131
|
-
- `listAccounts()` / `getAccount(accountId)`
|
|
132
|
-
- `getEnabledAccounts()`
|
|
133
|
-
- `normalizeAccountId(rawId)` — from plugin: `abc@im.bot` → `abc-im-bot`
|
|
134
|
-
|
|
135
|
-
**2.5 Long-poll handler**
|
|
136
|
-
File: `packages/server/src/channels/weixin/handler.ts` (new)
|
|
137
|
-
|
|
138
|
-
Main loop:
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
per enabled account:
|
|
142
|
-
loop {
|
|
143
|
-
resp = await getUpdates({ baseUrl, token, sync_buf })
|
|
144
|
-
for msg in resp.msgs:
|
|
145
|
-
handleInboundMessage(account, msg)
|
|
146
|
-
sync_buf = resp.get_updates_buf
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
`handleInboundMessage(account, msg)`:
|
|
151
|
-
1. Extract `from_user_id` / `text` / `context_token`
|
|
152
|
-
2. sessionId = `wx_` + normalize(from_user_id)
|
|
153
|
-
3. sm = `registry.getOrCreate(account.workspace_path)`
|
|
154
|
-
4. If session doesn't exist: `sm.createSession('default', null, 'WeChat: <userId>', 'default', sessionId)`
|
|
155
|
-
5. Register a one-shot listener (or a long-lived reused one) to collect stream/tool_call/error/complete for this turn
|
|
156
|
-
6. `sm.sendUserMessage(sessionId, text)`
|
|
157
|
-
7. Events → text aggregation (see 2.6) → `api.sendMessage(bot_token, to=from_user_id, text, context_token)`
|
|
158
|
-
|
|
159
|
-
On process start, walk `getEnabledAccounts()` and launch a long-poll per account.
|
|
160
|
-
Expose `startAccount(accountId)` / `stopAccount(accountId)` for the API layer.
|
|
161
|
-
|
|
162
|
-
**2.6 Event adapter**
|
|
163
|
-
File: `packages/server/src/channels/weixin/event-adapter.ts` (new)
|
|
164
|
-
|
|
165
|
-
WeChat `sendMessage` is whole-block; LLM is streaming — we coalesce. Copy plugin's `StreamingMarkdownFilter` strategy:
|
|
166
|
-
|
|
167
|
-
- ≥ 200 chars accumulated → flush
|
|
168
|
-
- 3s silence → flush
|
|
169
|
-
- `complete` → flush remainder
|
|
170
|
-
- Tool calls not forwarded to WeChat (user sees details on web); errors are forwarded
|
|
171
|
-
|
|
172
|
-
Pseudocode:
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
class WeixinResponder {
|
|
176
|
-
constructor(sendFn: (text: string) => Promise<void>)
|
|
177
|
-
handle(event: OrchestratorEvent) {...}
|
|
178
|
-
flush()
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
**2.7 Wire-up**
|
|
183
|
-
File: `packages/server/src/index.ts`
|
|
184
|
-
|
|
185
|
-
- Create `registry = new SessionManagerRegistry()`
|
|
186
|
-
- Open `weixinDb = createWeixinDb(path.join(HALO_HOME, 'global'))`
|
|
187
|
-
- `setupWebSocketHandler({ wss, registry })` (signature change)
|
|
188
|
-
- `startWeixinChannels({ registry, db: weixinDb })`
|
|
189
|
-
- On SIGTERM, call `stopAllWeixinChannels()` (which sends notifyStop)
|
|
190
|
-
|
|
191
|
-
### Phase 3: API + frontend admin UI
|
|
192
|
-
|
|
193
|
-
**3.1 Backend routes**
|
|
194
|
-
File: `packages/server/src/routes/weixin.ts` (new)
|
|
195
|
-
|
|
196
|
-
- `POST /api/weixin/login/start` → `{ qrcodeUrl, sessionKey }`
|
|
197
|
-
- `POST /api/weixin/login/wait` (body: `{ sessionKey, workspacePath, label? }`)
|
|
198
|
-
On success, insertAccount + startAccount immediately
|
|
199
|
-
- `GET /api/weixin/accounts` → list
|
|
200
|
-
- `PATCH /api/weixin/accounts/:id` (label / workspacePath / enabled)
|
|
201
|
-
- `DELETE /api/weixin/accounts/:id` → stop long-poll + delete row
|
|
202
|
-
|
|
203
|
-
Mount after `index.ts:121`.
|
|
204
|
-
|
|
205
|
-
**3.2 Frontend page**
|
|
206
|
-
Directory: `packages/admin/src/features/weixin/` (new)
|
|
207
|
-
|
|
208
|
-
- `weixin-settings.tsx` — adds "WeChat Bot" to Settings menu
|
|
209
|
-
- List each bot: label / workspace / status / [edit] [delete]
|
|
210
|
-
- "Add Bot" button → QR modal → poll wait API → on success let the user pick a workspace + name → save
|
|
211
|
-
- QR: use client-side [qrcode](https://www.npmjs.com/package/qrcode) (pass the qrcodeUrl string)
|
|
212
|
-
|
|
213
|
-
Add the tab inside the settings feature, matching current settings page style.
|
|
214
|
-
|
|
215
|
-
## Execution order
|
|
216
|
-
|
|
217
|
-
1. ✅ Phase 1.1 multi-listener (10 min)
|
|
218
|
-
2. ✅ Phase 1.2 Registry (15 min)
|
|
219
|
-
3. ✅ Phase 2.1 DB + schema (10 min)
|
|
220
|
-
4. ✅ Phase 2.2 HTTP API (30 min)
|
|
221
|
-
5. ✅ Phase 2.3 QR login (20 min)
|
|
222
|
-
6. ✅ Phase 2.4 accounts DAL (15 min)
|
|
223
|
-
7. ✅ Phase 2.6 event adapter (15 min)
|
|
224
|
-
8. ✅ Phase 2.5 handler main loop (45 min)
|
|
225
|
-
9. ✅ Phase 2.7 wire up index.ts (15 min)
|
|
226
|
-
10. ✅ Phase 3.1 API routes (20 min)
|
|
227
|
-
11. ✅ Phase 3.2 frontend page (60 min)
|
|
228
|
-
12. ✅ Compile + manual QR scan test
|
|
229
|
-
|
|
230
|
-
## Test path (manual)
|
|
231
|
-
|
|
232
|
-
1. Deploy, open web, go to Settings → WeChat Bot → Add
|
|
233
|
-
2. Scan the QR on your phone, see the bot friend request
|
|
234
|
-
3. Accept the friend request, pick an existing workspace + enter a name
|
|
235
|
-
4. Send "hi" from WeChat to the bot
|
|
236
|
-
5. Watch the reply (with a ≥ 200 char response, test the chunking)
|
|
237
|
-
6. Open the same workspace on the web, see `wx: xxx` in the session list; click to see the full conversation
|
|
238
|
-
7. Send a message from the web to the same session; confirm it arrives on WeChat
|
|
239
|
-
|
|
240
|
-
## Out of scope (explicit)
|
|
241
|
-
|
|
242
|
-
- Image / voice / video messages (plugin supports them; MVP is text-only)
|
|
243
|
-
- Group chats (plugin is private-chat only anyway)
|
|
244
|
-
- /slash commands (plugin has them; we don't copy)
|
|
245
|
-
- Typing indicators (`sendTyping` uncalled)
|
|
246
|
-
- CDN media upload
|
|
247
|
-
- Complex routing of one bot's messages to multiple sessions in a workspace
|
|
248
|
-
- Inter-bot message isolation (assumption: user knows which bot maps to which workspace)
|
|
249
|
-
|
|
250
|
-
## Risks
|
|
251
|
-
|
|
252
|
-
- **Tencent may cap bots-per-WeChat-account**: test and see; if exceeded, fall back to "1 bot per user"
|
|
253
|
-
- **`bot_type=3` stability**: hard-coded by the plugin; we follow
|
|
254
|
-
- **Long-poll connection count**: each bot opens one 35s-long connection; 10 bots is fine
|
|
255
|
-
- **Event-coalescing edge cases**: the 200-char / 3s cutoffs can split Markdown mid-block. Start with plugin defaults and tune after real usage.
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# Platform Docs Seeding Test Cases
|
|
2
|
-
|
|
3
|
-
Test URL: local server (default `http://localhost:9527/?folder=<absolute-workspace-path>`).
|
|
4
|
-
Password: whatever `HALO_PASSWORD` is set to, or the random password printed by the server on first startup.
|
|
5
|
-
|
|
6
|
-
Prerequisite: `~/.halo/global/` already exists from a prior run. Pick any workspace that is **not** the halo source repo (e.g. `/path/to/test-workspace`).
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Test matrix
|
|
11
|
-
|
|
12
|
-
### A. First-time seed
|
|
13
|
-
|
|
14
|
-
| # | Scenario | Method | Expected |
|
|
15
|
-
|---|---|---|---|
|
|
16
|
-
| A1 | Fresh install simulation | `rm -rf ~/.halo/global/docs && restart server` | Startup log: `[Init] Seeding platform docs from ...`; `~/.halo/global/docs/` has 13 files |
|
|
17
|
-
| A2 | Source detection in monorepo | Start server from `/path/to/halo`, delete docs first | Log: `Seeding platform docs from /path/to/halo/.halo/docs` |
|
|
18
|
-
| A3 | Source detection for packaged install | Create `packages/server/bundled-docs/guide/skills.md` with "FAKE"; delete `~/.halo/global/docs/`; restart | Log says `Seeding platform docs from .../bundled-docs`; `~/.halo/global/docs/guide/skills.md` contains "FAKE" |
|
|
19
|
-
| A4 | No source at all | Move init.js somewhere isolated (no repo parent, no bundled-docs), run | Log: `[Init] No docs source found — skipping platform docs seed`; no crash; `~/.halo/global/docs/` not created |
|
|
20
|
-
| A5 | Allowlist boundary | After A1, check what's in `~/.halo/global/docs/` | Only 13 files: `guide/` (8), `dev/` (3), `requirements/` (2). **No** `INDEX.md`, `design/`, `test/`, `plans/`, or other `dev/`/`requirements/` files (the doc lookup table now lives inline in the `halo` skill) |
|
|
21
|
-
|
|
22
|
-
### B. User edits and upgrades
|
|
23
|
-
|
|
24
|
-
| # | Scenario | Method | Expected |
|
|
25
|
-
|---|---|---|---|
|
|
26
|
-
| B1 | User edit preserved | Edit `~/.halo/global/docs/guide/skills.md` → restart server | Edit intact; no `[Init] Created` log for that file |
|
|
27
|
-
| B2 | Deleted file restored | `rm ~/.halo/global/docs/guide/agents.md` → restart | Log: `[Init] Created .../agents.md`; file restored from source |
|
|
28
|
-
| B3 | Version bump hint | Set `.template-version` to a value below the current `TEMPLATE_VERSION` (see `packages/server/src/init.ts`), restart | Log: `[Init] Seed templates updated: v<old> → v<current>. To refresh a specific file, delete it from ... and restart.` Plus `[Server] Templates outdated (v<old> → v<current>), refreshing ~/.halo/global/` from the startup version-check |
|
|
29
|
-
| B4 | Idempotent second run | Restart the server twice in a row | Second run has **no** `[Init] Created` logs; `.template-version` stays at the current `TEMPLATE_VERSION` |
|
|
30
|
-
|
|
31
|
-
### C. Agent-facing integration
|
|
32
|
-
|
|
33
|
-
| # | Scenario | Input | Expected |
|
|
34
|
-
|---|---|---|---|
|
|
35
|
-
| C1 | `halo` skill carries the doc table | Open any chat with the default agent, Debug mode → first message → Prompt button | System prompt lists the `halo` skill in the available-skills block. Activate it (`activate_skill halo`) and the loaded body contains `### Platform Documentation` with the "User asks … / Read …" lookup table |
|
|
36
|
-
| C2 | Agent knows about bundled docs | Open chat in a non-halo workspace, ask: "what platform docs ship with halo?" | Agent activates the `halo` skill and answers from its lookup table (no `file_read` needed for the overview) |
|
|
37
|
-
| C3 | Answer to "how do I test my skill?" | New chat, ask: "I just made a skill. How do I test it?" | Agent reads `~/.halo/global/docs/guide/testing-agents-and-skills.md` directly (path from system prompt table), answers with the attach-to-agent → Test → trigger flow |
|
|
38
|
-
| C4 | Answer to "where do API keys go?" | New chat, ask: "I want my skill to use an API key. Where do I put it?" | Agent reads `guide/secrets-and-credentials.md`, explains schema in `config.yaml` + value in `settings.yaml` + `<<ENV>>` + skill body using `{{params.<key>}}` short form |
|
|
39
|
-
| C5 | Answer for a question NOT in bundled docs | New chat, ask: "how does the file watcher debounce work?" | Agent looks for it in the docs, doesn't find (bundled docs don't have `design/`), falls back to reading code or admits it doesn't know — **does not hallucinate** |
|
|
40
|
-
| C6 | Works in any workspace | Repeat C3 in a workspace that has **no** `.halo/docs/` of its own (e.g. `/path/to/test-workspace`) | Same answer — proves docs come from `~/.halo/global/`, not the workspace |
|
|
41
|
-
|
|
42
|
-
### D. Negative / edge cases
|
|
43
|
-
|
|
44
|
-
| # | Scenario | Method | Expected |
|
|
45
|
-
|---|---|---|---|
|
|
46
|
-
| D1 | Permissions issue | `chmod -w ~/.halo/global/docs/` → delete one file → restart | `[Init] Failed to read template` or similar error logged; server still starts (seed is best-effort) |
|
|
47
|
-
| D2 | Concurrent starts | Start two server processes at the same time against the same home dir | Only one wins the pid lock; losers exit cleanly (not a seed-specific test, but confirms seed doesn't race) |
|
|
48
|
-
| D3 | Partially-populated docs | Remove half the files in `~/.halo/global/docs/guide/`; restart | Exactly the removed files get `[Init] Created` logs; others untouched |
|
|
49
|
-
| D4 | Garbage `.template-version` | Write `abc` to `~/.halo/global/.template-version`; restart | Treated as 0; seed runs; version is reset to the current `TEMPLATE_VERSION` at the end |
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## Verification helpers
|
|
54
|
-
|
|
55
|
-
### Check seeded file count
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
find ~/.halo/global/docs -type f | wc -l
|
|
59
|
-
# Expected: 13
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Check allowlist boundary (A5)
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
# These should all NOT exist
|
|
66
|
-
ls ~/.halo/global/docs/design/ 2>&1 # No such file
|
|
67
|
-
ls ~/.halo/global/docs/test/ 2>&1 # No such file
|
|
68
|
-
ls ~/.halo/global/docs/plans/ 2>&1 # No such file
|
|
69
|
-
ls ~/.halo/global/docs/requirements/terminal.md 2>&1 # No such file
|
|
70
|
-
ls ~/.halo/global/docs/dev/api.md 2>&1 # No such file
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Check `halo` skill carries the doc table (C1)
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
grep -A 3 "Platform Documentation" ~/.halo/global/skills/halo/SKILL.md
|
|
77
|
-
# Expected: "### Platform Documentation" section with the inline "User asks … / Read …" lookup table
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Check source that was used
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
grep "Seeding platform docs\|No docs source" /tmp/halo.log 2>/dev/null | tail -1
|
|
84
|
-
# Or check server's stdout — the log line is emitted once per startup
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Force a fresh seed
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
rm -rf ~/.halo/global/docs ~/.halo/global/.template-version
|
|
91
|
-
# Restart server
|
|
92
|
-
# Expected: 13 [Init] Created log lines for docs/*
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## Known limitations
|
|
98
|
-
|
|
99
|
-
- **Cross-links to non-bundled docs** (e.g. `guide/skills.md` → `design/prompt-system.md`) will 404 if an agent tries to follow them. Agents should answer from the doc body rather than chase links. Noted in the `halo` skill body.
|
|
100
|
-
- **Version downgrade** not detected: if you set `.template-version` higher than the current `TEMPLATE_VERSION`, nothing happens — no warning, no overwrite. Not a concern in practice.
|
|
101
|
-
- **Seed does not overwrite**. If a user edits a file and a newer Halo version ships a fix to the same doc, the user's edit wins. Workaround: `rm` the file and restart.
|