@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,374 +0,0 @@
|
|
|
1
|
-
# Session & Channel Test Cases
|
|
2
|
-
|
|
3
|
-
Manual smoke + regression checklist before a release. Last refreshed for the 2026-05 schema/refactor wave.
|
|
4
|
-
|
|
5
|
-
**Test environment** (matches `reference_test_env.md` memory):
|
|
6
|
-
- Local URL: `http://localhost:9527/?folder=<absolute-workspace-path>`
|
|
7
|
-
- Password: `HALO_PASSWORD` env var (or the random one printed on first startup)
|
|
8
|
-
- Workspace under test: `/path/to/test-workspace`
|
|
9
|
-
- Required workspace agents: `default`, `sleeper`, `test-agent`
|
|
10
|
-
- **Delegation gating (since the team-driven switch):** the agent that *initiates* a delegation must list the target in its `agent.yaml` `team`. The seed `default`'s team is `[default, executor, deep-executor]`, which does **not** include `sleeper` / `test-agent` — so the delegation/inspection cases (A6 `query_agent sleeper`, D1/D5 `start_session sleeper`, G3 `test-agent → sleeper`) require the initiating agent's `team` to contain `sleeper` (and `test-agent`). Add them to the test agent's `team`, or these cases fail with "not in your team / no session tools".
|
|
11
|
-
- Required env vars for the LLM-touching cases: at least one of `KIMI_API_KEY` / `DEEPSEEK_API_KEY` / AWS credentials, configured via Settings page.
|
|
12
|
-
|
|
13
|
-
**How to use this doc**:
|
|
14
|
-
- Run the **automated regression script first** (covers ~25% of cases below): `HALO_TEST_PASSWORD=... HALO_TEST_PROJECT=/path/to/test-workspace node packages/server/tests/test-session-system.mjs`
|
|
15
|
-
- Then walk the table top-to-bottom for full release validation.
|
|
16
|
-
- 🟢 = automated covers it; 🟡 = automated partial; ⬜ = manual only.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## A. Agent system (REST + UI, no LLM)
|
|
21
|
-
|
|
22
|
-
| # | Coverage | Scenario | Method | Expected |
|
|
23
|
-
|---|---|---|---|---|
|
|
24
|
-
| A1 | 🟢 | Default agent loads from global | `GET /api/agent-configs` | `default` present, `scope=global`, `priority=99` |
|
|
25
|
-
| A2 | 🟢 | Workspace agents visible | `GET /api/agent-configs?projectId=...` | `sleeper` + `test-agent` listed alongside global ones |
|
|
26
|
-
| A3 | 🟢 | Create workspace agent via API | `POST /api/agent-configs` then `GET .../yaml` | YAML round-trips name/description/model |
|
|
27
|
-
| A4 | 🟢 | Delete workspace agent | `DELETE /api/agent-configs/:id?scope=workspace` | 200; gone from list |
|
|
28
|
-
| A6 | 🟢 | `query_agent` tool | Chat: "Use query_agent to inspect 'sleeper'" | Full fields (model/tools/skills) |
|
|
29
|
-
| A8 | ⬜ | Provider switch carries over thinking effort if still valid | UI: change Bedrock → Bedrock other model with same effort presets | Effort label stays highlighted |
|
|
30
|
-
| A9 | ⬜ | Provider switch resets effort when vocabulary differs | UI: Bedrock (effort=medium) → Kimi | Effort resets to Kimi's `defaultEnabled` config |
|
|
31
|
-
| A10 | ⬜ | Provider switch turns prompt-caching default on for Bedrock | UI: switch any provider → Bedrock | Prompt caching toggle on, value = `1h` |
|
|
32
|
-
| A11 | ⬜ | Provider switch clears prompt caching when target doesn't support it | UI: Bedrock (caching=1h) → DeepSeek | Caching toggle off, no stale `promptCaching:` field saved |
|
|
33
|
-
| A12 | ⬜ | Agent form Context fields reflect settings.yaml `general.compact.compress_at` default | New agent without override | `compressAt` placeholder shows `0.8` |
|
|
34
|
-
|
|
35
|
-
## B. Default agent basics (WS + LLM)
|
|
36
|
-
|
|
37
|
-
| # | Coverage | Scenario | Input | Expected |
|
|
38
|
-
|---|---|---|---|---|
|
|
39
|
-
| B1 | 🟢 | Multi-turn context | "Say hello" → "What did I just say?" | Context preserved; recalls |
|
|
40
|
-
| B2 | ⬜ | Stop + resume | "Write a 500 word essay" → Stop → "Continue" | Clean stop; resume produces continuation |
|
|
41
|
-
| B3 | ⬜ | Refresh during tool | "sleep 10, then tell a joke" → refresh mid-sleep | Reconnect shows in-progress; completes |
|
|
42
|
-
| B4 | ⬜ | Stop during tool | "sleep 10, then tell a joke" → Stop mid-sleep | Clean abort, no stray "stopped" badges on unrelated sessions |
|
|
43
|
-
| B5 | ⬜ | New message interrupts | "sleep 10, then tell a joke" → new msg mid-sleep | New msg handled after current tool completes (graceful interrupt) |
|
|
44
|
-
| B6 | ⬜ | Agent selector switch | Agent dropdown → `sleeper` → send msg | Session uses sleeper |
|
|
45
|
-
| B7 | ⬜ | Session lock during conversation | Start chat then try switching agent | Dropdown locked |
|
|
46
|
-
|
|
47
|
-
## C. Agent capabilities (LLM + tools + features)
|
|
48
|
-
|
|
49
|
-
| # | Coverage | Scenario | Input | Expected |
|
|
50
|
-
|---|---|---|---|---|
|
|
51
|
-
| C1 | ⬜ | Tool call basic | "Read /tmp/test.txt" | `file_read` tool fires; result rendered |
|
|
52
|
-
| C2 | ⬜ | Multiple tools in one turn | "List files in /tmp and read /tmp/test.txt" | All tool calls visible |
|
|
53
|
-
| C3 | ⬜ | Long shell output | `find /path/to/halo -name '*.ts' -type f` | Truncation marker shown only past `toolResultMax` |
|
|
54
|
-
| C4 | 🟢 | Prompt caching 5min | Two messages in succession | Second usage event has `cacheReadInputTokens > 0` |
|
|
55
|
-
| C5 | ⬜ | Prompt caching 1h | Agent.yaml `promptCaching: 1h` | First message creates cache, subsequent reads it; `cacheWriteInputTokens` only on write |
|
|
56
|
-
| C6 | ⬜ | Thinking adaptive low | Agent `thinking.effort: low` | Reply contains thinking block; small char count |
|
|
57
|
-
| C7 | ⬜ | Thinking adaptive medium | `thinking.effort: medium` | Larger thinking block |
|
|
58
|
-
| C8 | ⬜ | Thinking adaptive high | `thinking.effort: high` | Largest thinking block |
|
|
59
|
-
| C9 | ⬜ | Thinking manual mode (Haiku 4.5) | Agent on `claude-haiku-4-5` w/ `thinking.budget_tokens: 8192` | Usage badge shows `think 8192` (NOT `think medium`) |
|
|
60
|
-
| C10 | ⬜ | Streaming completes after error | Bad credentials in agent.yaml provider | Red "Error: ..." system message rendered; `isStreaming` reverts to false (no spinner stuck) |
|
|
61
|
-
| C11 | ⬜ | Image input passthrough | Paste image into chat → "describe this" | Image saved under `<workspace>/.halo/assets/web/inbound/`, agent sees and describes |
|
|
62
|
-
| C12 | ⬜ | Image filtered for non-vision provider | Paste image while on DeepSeek | Image dropped with `[图片已保存]` marker; agent still answers |
|
|
63
|
-
|
|
64
|
-
**C4-C5 verification (raw usage):**
|
|
65
|
-
```bash
|
|
66
|
-
python3 -c "
|
|
67
|
-
import json, glob
|
|
68
|
-
files = sorted(glob.glob('/path/to/test-workspace/.halo/sessions/default/*.json'), key=lambda f: -__import__('os').path.getmtime(f))
|
|
69
|
-
if files:
|
|
70
|
-
d = json.load(open(files[0]))
|
|
71
|
-
for msg in d.get('rawMessages', []):
|
|
72
|
-
u = msg.get('usage', {})
|
|
73
|
-
if u.get('cache_read_input_tokens', 0) > 0:
|
|
74
|
-
print(f'Cache hit: read={u[\"cache_read_input_tokens\"]} create={u.get(\"cache_creation_input_tokens\", 0)}')"
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## D. Session tools (sub-agent delegation)
|
|
78
|
-
|
|
79
|
-
| # | Coverage | Scenario | Input | Expected |
|
|
80
|
-
|---|---|---|---|---|
|
|
81
|
-
| D1 | 🟢 | Basic delegation | "Ask sleeper to sleep 10s then tell its height" | start_session → wait → auto-report |
|
|
82
|
-
| D2 | ⬜ | query_session continues | After D1: "Ask it again, what's its weight?" | Same session reused (no second start_session) |
|
|
83
|
-
| D3 | ⬜ | interrupt_session | "Ask sleeper to sleep 30s then tell story" → "Interrupt and ask weight" | interrupt fires, new task runs |
|
|
84
|
-
| D4 | ⬜ | stop_session | "Ask sleeper to sleep 30s" → "Stop it" | clean stop |
|
|
85
|
-
| D5 | ⬜ | New session | After D1: "Start another sleeper" | Fresh start_session (different sessionId) |
|
|
86
|
-
| D6 | ⬜ | Parallel delegation | "Start two sleepers concurrently" | Both auto-report |
|
|
87
|
-
| D7 | 🟢 | session_list inside agent | Agent calls session_list mid-conversation | Returns active children |
|
|
88
|
-
| D8 | ⬜ | get_session_output | After D1: "Read full output from sleeper" | Returns full last-turn text |
|
|
89
|
-
| D9 | ⬜ | archive_session | Agent calls archive_session on a sub-tree | Cascade archives, no longer in session_list |
|
|
90
|
-
| D10 | ⬜ | Tool **only sees agent's declared params** | `test-agent` tries `{{tavily-web-search.params.api_key}}` in shell_exec but doesn't list tavily as a skill | Placeholder kept literal, curl 401 — verifies new namespace whitelist |
|
|
91
|
-
|
|
92
|
-
## E. Session ops (REST + WS lifecycle)
|
|
93
|
-
|
|
94
|
-
| # | Coverage | Scenario | Method | Expected |
|
|
95
|
-
|---|---|---|---|---|
|
|
96
|
-
| E1 | ⬜ | start_session emits sessionId | Chat triggers start_session | UI shows sub-session in tree, sessionId returned |
|
|
97
|
-
| E2 | ⬜ | session_list during running | Agent calls during another's run | Status `running` |
|
|
98
|
-
| E3 | ⬜ | query_session restores idle | Query a completed session | Restored from disk; reply produced |
|
|
99
|
-
| E4 | ⬜ | query_session queues busy | Query a running session | Message queued, handled after current turn |
|
|
100
|
-
| E5 | ⬜ | stop_session sets stoppedAt | Agent stops a peer | SQLite has stoppedAt; UI shows stopped badge |
|
|
101
|
-
| E6 | 🟢 | delete_session cascades | WS `session:delete` | SQLite + JSON files for parent + descendants gone |
|
|
102
|
-
| E7 | 🟢 | delete via REST goes through SessionManager | `DELETE /api/sessions/logs/:id` | Tombstone set; pending in-flight saves don't resurrect file |
|
|
103
|
-
| E8 | ⬜ | Resurrection guard | Active session being subscribed; delete via REST while a `chat:complete` save is pending | File stays gone after delete (regression test for the WS save resurrection bug) |
|
|
104
|
-
| E9 | ⬜ | UI Reset button hidden when value = default | Settings page general field at default | "Reset" link not visible |
|
|
105
|
-
| E10 | ⬜ | UI Reset reverts to schema default | Settings page general field overridden, click Reset | Field shows muted-italic placeholder = schema default; source badge `unset` |
|
|
106
|
-
|
|
107
|
-
**E6-E8 verification:**
|
|
108
|
-
```bash
|
|
109
|
-
# Before delete
|
|
110
|
-
sqlite3 /path/to/test-workspace/.halo/halo.db "SELECT id, agent_id, parent_id FROM agent_sessions ORDER BY created_at DESC LIMIT 5;"
|
|
111
|
-
ls -lt /path/to/test-workspace/.halo/sessions/*/ | head
|
|
112
|
-
|
|
113
|
-
# After delete — both should be empty for the deleted id
|
|
114
|
-
sqlite3 /path/to/test-workspace/.halo/halo.db "SELECT id FROM agent_sessions WHERE id = '<deleted-id>';"
|
|
115
|
-
find /path/to/test-workspace/.halo/sessions -name '<deleted-id>*'
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## F. Background & restore
|
|
119
|
-
|
|
120
|
-
| # | Coverage | Scenario | Input | Expected |
|
|
121
|
-
|---|---|---|---|---|
|
|
122
|
-
| F1 | ⬜ | Detached run | Start sub-agent → `/session new` → switch back later | Sub-agent finishes in background; result visible |
|
|
123
|
-
| F2 | ⬜ | WS disconnect/reconnect | Start sub-agent → close tab → reopen | Sub-agent finishes; reconnect shows report |
|
|
124
|
-
| F3 | ⬜ | Server restart restore | Active session → restart server → reconnect | UIState restored from disk; conversation visible |
|
|
125
|
-
| F4 | ⬜ | UIState flush on release | Start agent, immediately interrupt mid-stream | Disk file `.json` `messages` stays in sync with `rawMessages` (regression for releaseSession flushPersist) |
|
|
126
|
-
|
|
127
|
-
## G. Loop & depth
|
|
128
|
-
|
|
129
|
-
| # | Coverage | Scenario | Input | Expected |
|
|
130
|
-
|---|---|---|---|---|
|
|
131
|
-
| G1 | ⬜ | Ping-pong | "Play ping-pong with sleeper, 3 rounds" | Two-way query_session; LLM stops naturally |
|
|
132
|
-
| G2 | ⬜ | 3-level chain | "Start test-agent, have it start another, depth 3" | 3-level parent chain; reports bubble up |
|
|
133
|
-
| G3 | ⬜ | Cross-agent delegation | "Have test-agent ask sleeper its name" | test-agent → start_session(sleeper) |
|
|
134
|
-
| G4 | ⬜ | Max nesting depth guard | Manually trigger 17-deep delegation chain | start_session returns `code 1` "Maximum nesting depth (16) reached" |
|
|
135
|
-
|
|
136
|
-
## H. Context window & compact
|
|
137
|
-
|
|
138
|
-
| # | Coverage | Scenario | Input | Expected |
|
|
139
|
-
|---|---|---|---|---|
|
|
140
|
-
| H1 | ⬜ | Auto-compact at threshold | Read large files until 80% (default `compress_at: 0.8`) | Auto-compact fires; tokens drop |
|
|
141
|
-
| H2 | ⬜ | `general.compact.compress_at` honored | Settings → set 0.5 → start session, fill | Compact fires earlier (50% instead of 80%) |
|
|
142
|
-
| H3 | 🟢 | Manual `/session compact` | Type `/session compact` or click TokenRing | `compact:progress` → `session:compacted` → `compact:done` |
|
|
143
|
-
| H4 | ⬜ | Compact disabled while streaming | Click TokenRing while agent runs | Button disabled (isStreaming guard) |
|
|
144
|
-
| H5 | ⬜ | Stop during compact | Click Stop during compact | Compact cancelled; messageLog unchanged |
|
|
145
|
-
| H6 | ⬜ | **Message during compact drains after** | Send msg during compact | "Queued" notification → after compact ends, message **immediately runs** (regression for `endCompact` drain fix) |
|
|
146
|
-
| H7 | ⬜ | Refresh after compact | Reload after compact | Message order correct; tool calls render in their turns |
|
|
147
|
-
| H8 | ⬜ | Sub-agent context overflow recovery | Sub-agent reads huge files, hits overflow | Local truncation fallback fires + retry; no crash |
|
|
148
|
-
| H9 | ⬜ | Rate-limit retry with backoff | Hammer fast messages | "Rate limited, retrying in Xs…" system msg; eventually succeeds |
|
|
149
|
-
|
|
150
|
-
## I. Slash commands
|
|
151
|
-
|
|
152
|
-
| # | Coverage | Scenario | Method | Expected |
|
|
153
|
-
|---|---|---|---|---|
|
|
154
|
-
| I1 | 🟢 | `/session new` | WS `session:clear` | Old session saved; new conversation empty; old in sidebar |
|
|
155
|
-
| I2 | ⬜ | `/session new` during sub-agent | `/session new` while child running | Sub-agent continues background; new conversation independent |
|
|
156
|
-
| I3 | 🟢 | `/session compact` | WS `command:compact` | Summary generated; older messages compressed |
|
|
157
|
-
| I4 | ⬜ | `/session context` | UI command | Shows tokens, model, agent's available tools, system prompt |
|
|
158
|
-
| I5 | ⬜ | `/session list` (full access) | UI command on web channel (admin-equivalent) | All workspace sessions visible with tags |
|
|
159
|
-
| I6 | ⬜ | `/session list` (readonly/workspace user) | wechat/tg readonly user `/session list` | **Only own-prefix sessions** (regression for cross-user privacy) |
|
|
160
|
-
| I7 | ⬜ | `/session switch` matches `/session list` indices | After I6, `/session switch 1` for readonly user | Switches to user's first own session |
|
|
161
|
-
| I8 | ⬜ | `/session switch` to non-own blocked | readonly tries `/session switch` to a global admin session via crafted index | Returns `switch.readonly` rejection (defense-in-depth even though list filtered) |
|
|
162
|
-
| I9 | ⬜ | All builtin `/help`/`/workspace`/`/session stop` reachable from each channel | Run each through web/tg/wx | Same response shape |
|
|
163
|
-
| I10 | ⬜ | Server startup descriptor↔dispatch sanity check | Restart server | Throws if a builtin server descriptor is missing a dispatch case |
|
|
164
|
-
|
|
165
|
-
## J. Session viewer & Debug
|
|
166
|
-
|
|
167
|
-
| # | Coverage | Scenario | Input | Expected |
|
|
168
|
-
|---|---|---|---|---|
|
|
169
|
-
| J1 | ⬜ | Debug rendering | Multi-tool conversation | Debug: exchange layout, tool_call cards, usage badges |
|
|
170
|
-
| J2 | ⬜ | Non-Debug filter | Same conversation | Only assistant text + inline tool cards |
|
|
171
|
-
| J3 | ⬜ | Usage fields | After multi-tool | in / out / ctx / cache% / ttft / e2e / model shown |
|
|
172
|
-
| J4 | ⬜ | System prompt view | Click Prompt button | Full system prompt rendered |
|
|
173
|
-
| J5 | ⬜ | Parent/child prompts | Inspect parent then child sub-session | Both have Prompt buttons; each shows its own |
|
|
174
|
-
|
|
175
|
-
## K. Sidebar & cleanup
|
|
176
|
-
|
|
177
|
-
| # | Coverage | Scenario | Expected |
|
|
178
|
-
|---|---|---|---|
|
|
179
|
-
| K1 | ⬜ | Session tree render | Users' sessions with children show hierarchy |
|
|
180
|
-
| K2 | ⬜ | Depth indentation | After G2: 3-level indentation correct |
|
|
181
|
-
| K3 | ⬜ | Cascade delete | Delete main session → all sub-sessions + SQLite + files cleaned |
|
|
182
|
-
| K4 | ⬜ | Sub-session preview | Click sub-session → full agent conversation |
|
|
183
|
-
| K5 | ⬜ | Stopped badge | After D4: stopped session shows StopCircle |
|
|
184
|
-
| K6 | ⬜ | Refresh button shows spinning animation | Click refresh on agent-sessions sidebar | RefreshCw rotates ≥350ms even on fast responses |
|
|
185
|
-
| K7 | ⬜ | No spinner flash on bus-driven refresh | Delete session, observe refresh | Other consumers reconcile silently, no extra spinner stutter |
|
|
186
|
-
|
|
187
|
-
## L. Settings UI (post-2026-05 schema refactor)
|
|
188
|
-
|
|
189
|
-
| # | Coverage | Scenario | Expected |
|
|
190
|
-
|---|---|---|---|
|
|
191
|
-
| L1 | ⬜ | Sections grouped by declarer | Open Settings | "System" / "Model Providers" / "Agents" / "Skills" / "Orphans" navigation |
|
|
192
|
-
| L2 | ⬜ | Provider yaml drives section | Add a new `models/myprov.yaml` with `secrets:` | Section appears for it after server restart |
|
|
193
|
-
| L3 | ⬜ | Skill yaml drives section | Add `skills/X/config.yaml` with `params:` | Section visible |
|
|
194
|
-
| L4 | ⬜ | Agent yaml drives section | Add `agents/X/agent-config.yaml` with `params:` | Section visible |
|
|
195
|
-
| L5 | ⬜ | Field type renders right control | Schema int/float/boolean/enum/string fields | Number / toggle / dropdown / text inputs |
|
|
196
|
-
| L6 | ⬜ | YAML scalar preserved on save | Save `compress_at: 0.8` then read settings.yaml | `compress_at: 0.8` (unquoted, real number) |
|
|
197
|
-
| L7 | ⬜ | Mtime-cached config picks up edits live | Edit settings.yaml externally, send a chat | Server picks new value on next call without restart |
|
|
198
|
-
| L8 | ⬜ | Secret masking on transport | Settings page secret field with real value | API returns `xx****yy`; env-var refs `<<KEY>>` not masked |
|
|
199
|
-
| L9 | ⬜ | Source badges | Workspace scope, value comes from global | Badge "inherited from global" |
|
|
200
|
-
| L10 | ⬜ | Orphan listing + remove | Insert junk namespace into settings.yaml | Appears in Orphans tab; Remove deletes it |
|
|
201
|
-
| L11 | ⬜ | Schema default vs config.ts callsite consistency | Boot server | No `[config] default mismatch for "..."` warnings in log |
|
|
202
|
-
| L12 | ⬜ | Missing env var keeps `<<NAME>>` literal | Set `kimi.secrets.api_key: <<UNSET_VAR>>`, chat with kimi | API call returns 401 with literal `<<UNSET_VAR>>` (regression for "no silent env fallback") |
|
|
203
|
-
|
|
204
|
-
## M. Channel — wechat/telegram/web (multi-user)
|
|
205
|
-
|
|
206
|
-
| # | Coverage | Scenario | Method | Expected |
|
|
207
|
-
|---|---|---|---|---|
|
|
208
|
-
| M1 | ⬜ | Inbound text | Send chat from each channel | Routes to its own prefix session |
|
|
209
|
-
| M2 | ⬜ | Inbound image (telegram) | Send photo | Saved under `assets/telegram/inbound/`; agent describes |
|
|
210
|
-
| M3 | ⬜ | Inbound image (wechat) | Send image via wechat-bot | Same: saved + described |
|
|
211
|
-
| M4 | ⬜ | Inbound voice/audio (web) | Paste audio file | Saved under `assets/web/inbound/`; `[语音已保存]` marker |
|
|
212
|
-
| M5 | ⬜ | `inferImageMime` magic byte detection | Various jpg/png/webp/gif | All correct after the dedupe to shared/media-store |
|
|
213
|
-
| M6 | ⬜ | `resolveAccountWorkspace` after dedupe | Disable a workspace path on disk, send msg | All channels return early, no broken sm calls |
|
|
214
|
-
| M7 | ⬜ | Two wechat users in same workspace, isolation | User A `/session list` doesn't show user B's session titles | Confirms post-fix `/session list` filtering |
|
|
215
|
-
| M8 | ⬜ | Compact-during-tg-message drain | Trigger compact while tg user sends a message | Message processed right after compact ends (regression for endCompact) |
|
|
216
|
-
| M9 | ⬜ | tg compaction status feedback | Send message during compact | "compacting" reply (parity with web/wechat) |
|
|
217
|
-
|
|
218
|
-
## N. CLI / TUI
|
|
219
|
-
|
|
220
|
-
The CLI ships an embedded agent loop and **bypasses the WS server** by design (lifestyle separation: CLI/TUI for offline scripts, WS for browser/admin). Tests here live separately.
|
|
221
|
-
|
|
222
|
-
**Setup**:
|
|
223
|
-
```bash
|
|
224
|
-
# Build CLI (if not done)
|
|
225
|
-
pnpm --filter @turmind/halo-cli build
|
|
226
|
-
|
|
227
|
-
# Smoke
|
|
228
|
-
node packages/cli/bin/halo.js --help
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
| # | Coverage | Scenario | Method | Expected |
|
|
232
|
-
|---|---|---|---|---|
|
|
233
|
-
| N1 | ⬜ | CLI prints version | `halo --version` | Matches package.json |
|
|
234
|
-
| N2 | ⬜ | TUI launch | `halo tui` | Starts, shows agent picker, accepts input |
|
|
235
|
-
| N3 | ⬜ | TUI single turn | `halo tui` → say "what is 2+2" | Reply rendered with usage badge `think medium` (or model default) |
|
|
236
|
-
| N4 | ⬜ | TUI session id has `cli_` prefix | After N3 | `~/.halo/sessions/default/cli_<...>.json` exists |
|
|
237
|
-
| N7 | ⬜ | TUI `/session new` | `/session new` | New session, default-scoped |
|
|
238
|
-
| N8 | ⬜ | TUI `/session stop` | "sleep 30 then say hi" → Ctrl-C or `/session stop` | Clean abort |
|
|
239
|
-
| N9 | ⬜ | TUI `/session compact` | Build up 8+ messages → `/session compact` | Compact runs; ctx tokens drop in next prompt |
|
|
240
|
-
| N10 | ⬜ | TUI tool call rendering | Ask file_read | Tool card renders inline in tui |
|
|
241
|
-
| N11 | ⬜ | TUI thinking display (manual mode) | Switch to Haiku 4.5 agent | Usage badge shows `think 8192` (or whatever budget_tokens), not `think medium` |
|
|
242
|
-
| N12 | ⬜ | TUI session resume across restart | N3 then exit + restart `halo tui --resume` (or whatever the resume cmd is) | History visible |
|
|
243
|
-
| N13 | ⬜ | TUI graceful interrupt on new message | "sleep 10 then say hi" → press Esc + new message | New message handled after current tool, no abort error |
|
|
244
|
-
| N14 | ⬜ | CLI `halo server start` single-instance lock | Start two instances | Second exits with "another instance running" |
|
|
245
|
-
| N15 | ⬜ | CLI `halo setup` rewrites config.yaml | Run with `--password new-pw` | `~/.halo/secrets/config.yaml` has new scrypt hash + new jwt_secret |
|
|
246
|
-
| N16 | ⬜ | TUI sandbox enforced | `tui --access-level readonly` (if flag exists) → try `file_write` | Tool refused / sandbox blocks |
|
|
247
|
-
| N17 | ⬜ | TUI uses **same** SessionManager.persistSessionFile path | Run a session in TUI, then start the WS server pointing at same workspace | UI shows the TUI session in sidebar (single source of truth) |
|
|
248
|
-
|
|
249
|
-
> **Note on TUI commands**: I've listed commands by name above (`/session new`, `/session stop`, `/session compact`, etc.). If your TUI uses different keybindings instead, swap the trigger column accordingly — the **expected behavior** is what we're checking.
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## Detailed steps & verification (selected cases)
|
|
254
|
-
|
|
255
|
-
### D1 — Basic delegation
|
|
256
|
-
|
|
257
|
-
**Steps:**
|
|
258
|
-
1. Send: "Ask sleeper to sleep 10s then tell its height"
|
|
259
|
-
2. Default agent calls `start_session` (agent: sleeper)
|
|
260
|
-
3. Sleeper completes; `[Message from ...]` auto-delivered
|
|
261
|
-
4. Default agent activates, reports
|
|
262
|
-
|
|
263
|
-
**Verify:**
|
|
264
|
-
- [ ] No unnecessary polling (no extra `session_list` / `get_session_output`)
|
|
265
|
-
- [ ] Single final reply with sleeper's height
|
|
266
|
-
- [ ] Sidebar: user session shows 1 sub-session
|
|
267
|
-
|
|
268
|
-
**Data:**
|
|
269
|
-
- [ ] SQLite `agent_sessions`: child row has `parent_id = user's session id`
|
|
270
|
-
- [ ] `.halo/sessions/sleeper/`: JSON file exists
|
|
271
|
-
- [ ] JSON: `parentSessionId` = user's id; `rawMessages` non-empty
|
|
272
|
-
|
|
273
|
-
### D10 — Skill namespace whitelist (regression)
|
|
274
|
-
|
|
275
|
-
**Steps:**
|
|
276
|
-
1. Configure `tavily-web-search` skill with a real `api_key` in Settings.
|
|
277
|
-
2. Edit `test-agent/agent.yaml` to include `skills: [tavily-web-search]` — do NOT include it.
|
|
278
|
-
3. Have the agent run: `shell_exec('curl -H "Authorization: Bearer {{tavily-web-search.params.api_key}}" https://api.tavily.com/search')`
|
|
279
|
-
|
|
280
|
-
**Expected:**
|
|
281
|
-
- Server log: `[workspace-tools] {{tavily-web-search.params.api_key}} rejected — namespace not in allowed list`
|
|
282
|
-
- The literal `{{tavily-web-search.params.api_key}}` is sent to Tavily → 401.
|
|
283
|
-
- Re-add `tavily-web-search` to `skills:` list, retry → real key resolves → 200.
|
|
284
|
-
|
|
285
|
-
### E7 — REST DELETE goes through SessionManager
|
|
286
|
-
|
|
287
|
-
**Steps:**
|
|
288
|
-
1. Open a session in the browser, send a couple of messages.
|
|
289
|
-
2. Without closing the browser tab, run from another shell:
|
|
290
|
-
`curl -b cookie.txt -X DELETE "http://localhost:9527/api/sessions/logs/<sid>?projectId=/path/to/test-workspace"`
|
|
291
|
-
3. Send another message in the browser.
|
|
292
|
-
|
|
293
|
-
**Expected:**
|
|
294
|
-
- Server log: `[SessionManager] Deleted N sessions from SQLite (root: <sid>)`
|
|
295
|
-
- The next `chat:complete`-driven save in WS handler is dropped (tombstone hit).
|
|
296
|
-
- File on disk stays gone (don't see the file size grow back).
|
|
297
|
-
|
|
298
|
-
### H6 — Message during compact
|
|
299
|
-
|
|
300
|
-
**Steps:**
|
|
301
|
-
1. Build a session with 9+ messages.
|
|
302
|
-
2. Trigger `/session compact`.
|
|
303
|
-
3. While `compact:progress` event is in flight, send a new message.
|
|
304
|
-
4. Observe the order of events.
|
|
305
|
-
|
|
306
|
-
**Expected:**
|
|
307
|
-
- WS receives `chat:queued` (message stashed in `pendingUserMessages`).
|
|
308
|
-
- `session:compacted` arrives.
|
|
309
|
-
- **Within 1–2 seconds**, the queued message starts running automatically (no need to send anything else).
|
|
310
|
-
- This is the regression test for the recent `endCompact` drain fix — before the fix, the queued message would sit indefinitely.
|
|
311
|
-
|
|
312
|
-
---
|
|
313
|
-
|
|
314
|
-
## Automated regression script
|
|
315
|
-
|
|
316
|
-
Path: `packages/server/tests/test-session-system.mjs`
|
|
317
|
-
|
|
318
|
-
What it covers (🟢 cells in the tables above): A1–A6 (tool calls), B1, C4, D1, D7, E6, E7, I1, I3.
|
|
319
|
-
|
|
320
|
-
Run:
|
|
321
|
-
```bash
|
|
322
|
-
HALO_TEST_PASSWORD=$HALO_PASSWORD \
|
|
323
|
-
HALO_TEST_PROJECT=/path/to/test-workspace \
|
|
324
|
-
node packages/server/tests/test-session-system.mjs
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
Exits 0 on all-green. The script does **not** start/stop a server — bring your own. It logs in via REST, opens a WS, runs the suite, prints a final pass/fail count.
|
|
328
|
-
|
|
329
|
-
**What's NOT in the regression script** — handled by the manual table above:
|
|
330
|
-
- B2–B7 (UI-driven flows: stop+resume, refresh during tool, agent dropdown lock)
|
|
331
|
-
- C5–C12 (caching variants, thinking effort tiers, image filtering)
|
|
332
|
-
- D2–D6, D8–D10 (continued conversations, parallel delegation, namespace whitelist)
|
|
333
|
-
- E8–E10 (resurrection guard, settings reset visuals)
|
|
334
|
-
- F, G, H (background, depth chains, full compact flow)
|
|
335
|
-
- L (settings UI rendering)
|
|
336
|
-
- M (channel inbound)
|
|
337
|
-
- N (CLI/TUI)
|
|
338
|
-
|
|
339
|
-
If you change session lifecycle code, **at minimum**: re-run the regression script + walk D, E, F, H by hand.
|
|
340
|
-
|
|
341
|
-
---
|
|
342
|
-
|
|
343
|
-
## Quick data-verification pipeline
|
|
344
|
-
|
|
345
|
-
After any test case that should produce / mutate session state:
|
|
346
|
-
|
|
347
|
-
```bash
|
|
348
|
-
PROJECT=/path/to/test-workspace
|
|
349
|
-
|
|
350
|
-
# SQLite — agent_sessions index
|
|
351
|
-
sqlite3 $PROJECT/.halo/halo.db \
|
|
352
|
-
"SELECT id, parent_id, agent_id, stopped_at IS NOT NULL AS stopped, archived_at IS NOT NULL AS archived
|
|
353
|
-
FROM agent_sessions ORDER BY created_at DESC LIMIT 10;"
|
|
354
|
-
|
|
355
|
-
# Session files (all agents)
|
|
356
|
-
for dir in $PROJECT/.halo/sessions/*/; do
|
|
357
|
-
echo "=== $(basename $dir) ==="
|
|
358
|
-
ls -lt "$dir" 2>/dev/null | head -5
|
|
359
|
-
done
|
|
360
|
-
|
|
361
|
-
# parentSessionId integrity check
|
|
362
|
-
for f in $PROJECT/.halo/sessions/*/*.json; do
|
|
363
|
-
python3 -c "
|
|
364
|
-
import json
|
|
365
|
-
d = json.load(open('$f'))
|
|
366
|
-
parent = d.get('parentSessionId', 'NONE')
|
|
367
|
-
print(f\"{d['agentId']}/{d['id']} parent={parent}\")
|
|
368
|
-
" 2>/dev/null
|
|
369
|
-
done
|
|
370
|
-
|
|
371
|
-
# Settings schema sanity
|
|
372
|
-
curl -sb /tmp/halo-cookie.txt http://localhost:9527/api/settings/schema | \
|
|
373
|
-
python3 -c "import sys,json; r=json.load(sys.stdin); print(f'sections={len(r[\"sections\"])} orphans={len(r[\"orphans\"])}')"
|
|
374
|
-
```
|
/package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → ep7BWneNhTuIPwJfFFh72}/_buildManifest.js
RENAMED
|
File without changes
|
/package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → ep7BWneNhTuIPwJfFFh72}/_ssgManifest.js
RENAMED
|
File without changes
|