@turmind/halo 1.0.3 → 1.1.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.
Files changed (52) hide show
  1. package/admin-out/404.html +1 -1
  2. package/admin-out/_next/static/chunks/212-394c4fd40216e633.js +1 -0
  3. package/admin-out/_next/static/chunks/220.e5a2532bda77e816.js +1 -0
  4. package/admin-out/_next/static/chunks/442.68ed26e27eb1d84f.js +1 -0
  5. package/admin-out/_next/static/chunks/515.4d98fae302278913.js +1 -0
  6. package/admin-out/_next/static/chunks/896.ed21ec1e0846d20d.js +1 -0
  7. package/admin-out/_next/static/chunks/935.543aeec8456b6061.js +1 -0
  8. package/admin-out/_next/static/chunks/app/page-86cb00582129ae76.js +1 -0
  9. package/admin-out/_next/static/chunks/webpack-fc1574554e7b1108.js +1 -0
  10. package/admin-out/_next/static/css/0d0ca7b3c8cc5562.css +3 -0
  11. package/admin-out/index.html +1 -1
  12. package/admin-out/index.txt +5 -5
  13. package/bundled-docs/design/agent.md +11 -0
  14. package/bundled-docs/design/agentcore.md +10 -0
  15. package/bundled-docs/design/architecture.md +28 -29
  16. package/bundled-docs/design/background-dispatch.md +30 -43
  17. package/bundled-docs/design/cron.md +10 -1
  18. package/bundled-docs/design/evolution.md +18 -2
  19. package/bundled-docs/design/feishu.md +19 -6
  20. package/bundled-docs/design/goal-mode.md +2 -2
  21. package/bundled-docs/design/halo-city.md +7 -0
  22. package/bundled-docs/design/session.md +50 -2
  23. package/bundled-docs/design/slack.md +20 -6
  24. package/bundled-docs/design/storage.md +39 -2
  25. package/bundled-docs/design/telegram.md +20 -8
  26. package/bundled-docs/design/web.md +1 -0
  27. package/bundled-docs/design/wechat.md +13 -17
  28. package/bundled-docs/design/ws.md +35 -9
  29. package/bundled-docs/dev/add-channel.md +49 -37
  30. package/bundled-docs/dev/api.md +121 -35
  31. package/bundled-docs/dev/deploy.md +48 -0
  32. package/bundled-docs/dev/desktop-packaging.md +41 -7
  33. package/bundled-docs/dev/env.md +1 -0
  34. package/bundled-docs/dev/previews.md +58 -3
  35. package/bundled-docs/guide/channels/wechat.md +3 -3
  36. package/bundled-docs/guide/cli.md +24 -5
  37. package/bundled-docs/requirements/chat.md +11 -2
  38. package/bundled-docs/requirements/editor.md +5 -2
  39. package/bundled-docs/requirements/explorer.md +1 -0
  40. package/bundled-docs/requirements/session.md +4 -3
  41. package/bundled-docs/requirements/source-control.md +1 -0
  42. package/dist/index.js +14293 -13212
  43. package/package.json +4 -1
  44. package/templates/schema.sql +8 -10
  45. package/admin-out/_next/static/chunks/212-c5f046048be9c367.js +0 -1
  46. package/admin-out/_next/static/chunks/220.3dc41cd6269b88e6.js +0 -1
  47. package/admin-out/_next/static/chunks/442.f4302087bddb1ec1.js +0 -1
  48. package/admin-out/_next/static/chunks/app/page-5a6cf2b73a12b849.js +0 -1
  49. package/admin-out/_next/static/chunks/webpack-47520cdc55be63e7.js +0 -1
  50. package/admin-out/_next/static/css/62b8bf49e6404ffc.css +0 -3
  51. /package/admin-out/_next/static/{CWrUwTypkebkhwZ5i82_G → lOrGs0dR-jmXz9PgabRBc}/_buildManifest.js +0 -0
  52. /package/admin-out/_next/static/{CWrUwTypkebkhwZ5i82_G → lOrGs0dR-jmXz9PgabRBc}/_ssgManifest.js +0 -0
@@ -110,6 +110,12 @@ halo tui -s sid_abc123 # resume session
110
110
  halo tui -n # always start a new session
111
111
  ```
112
112
 
113
+ `halo tui` needs an interactive terminal — the keyboard layer can't enter raw
114
+ mode without one. When stdin isn't a TTY (CI, `halo tui < file`,
115
+ `echo x | halo tui`) it exits immediately with code 1 and points you at `halo
116
+ cli "<prompt>"`, rather than failing a couple of seconds later inside the
117
+ rendering layer.
118
+
113
119
  Multi-turn conversation. Supports all standard Halo slash commands:
114
120
 
115
121
  | Command | Description |
@@ -149,9 +155,10 @@ Bare `/<obj>` (or `/<obj> help`) lists the verbs you may run. Verbs gated above
149
155
 
150
156
  | Key | Action |
151
157
  |---|---|
152
- | `Esc` (while running) | Interrupt the current turn immediately — aborts a command mid-execution, then any messages typed while it was running are folded into one follow-up turn. Same as `/session interrupt`. |
153
- | `Ctrl+C` | Graceful exit; press twice to force |
154
- | `Ctrl+O` | Toggle the sub-agent navigator — lists every sub-agent spawned this session, each showing its agent name, task title (same as the session list), and status (`●` running green / `○` idle grey / `✕` stopped red, plus a `▢ archived` marker when the session is archived); `↑↓` to move, `Enter` to view that sub-agent's log, `Esc`/`q` to close. (Was `Shift+Tab`, but Windows terminals consume that as backtab.) The log viewer auto-refreshes while the viewed session is still running (a `● live` hint shows in the header) and follows the bottom as new output lands — unless you've scrolled up to read, in which case it stays put (`G` jumps back to the bottom and resumes following). |
158
+ | `Esc` | **With nothing else open, while a turn is running**: interrupt it immediately — the in-flight tool or command is aborted, then any messages typed while it ran fold into one follow-up turn (same as `/session interrupt`; repeat presses are idempotent, the "interrupting…" notice is throttled). **While the log viewer, the sub-agent navigator, or a completion popup is open**: Esc only closes that surface — a running turn is never touched, so leaving a live log you opened with `Ctrl+O` can't kill it. The popup keeps your typed text when dismissed. Once nothing is open, the next Esc interrupts again. |
159
+ | `Ctrl+C` | Exit — first press arms a confirm (the input hint flips to "press Ctrl+C again to exit" for 1.5s), second press exits. Works even while a modal is open (unlike Esc, nothing swallows it). Exit is graceful: a running turn is stopped, the conversation repaired, and the session flushed to disk. |
160
+ | `Ctrl+O` | Open the session-log navigator (no-op while a log surface is already open) — lists the session tree, the root session plus every sub-agent spawned under it, each showing its agent name, task title (same as the session list), and status (`●` running green / `○` idle grey / `✕` stopped red, plus a `▢ archived` marker when the session is archived); `↑↓` to move, `Enter` to view that session's log, `Esc`/`q` to close. (Was `Shift+Tab`, but Windows terminals consume that as backtab.) Inside the log viewer: `j`/`k` or `↑`/`↓` scroll by line, `d`/`u` (or `Ctrl+F`/`Ctrl+B`, PgDn/PgUp) by half a page, `g`/`G` jump to top/bottom, `q`/`Esc` close. The viewer auto-refreshes while the viewed session is still running (a `● live` hint shows in the header) and follows the bottom as new output lands — unless you've scrolled up to read, in which case it stays put (`G` jumps back to the bottom and resumes following). |
161
+ | `Tab` / `Enter` (popup open) | Complete the highlighted suggestion — slash commands and their verbs while typing `/…`, paths after `@`/`@file`/`@image`/`@scope`. `↑`/`↓` move the highlight; `Esc` dismisses the popup without clearing what you typed (any edit brings it back). A picked directory completes with a trailing `/` and keeps the popup open so you can keep descending; a picked file closes it. With no popup open, Enter submits. |
155
162
  | `↑` / `↓` | Walk input history (when no popup is open). History persists across restarts in `~/.halo/global/tui-history.json` (last 100 entries, shared across workspaces) |
156
163
  | `←` / `→`, `Home` / `End` | Move the cursor within the input; text is inserted/deleted at the cursor (CJK and emoji safe) |
157
164
  | `Ctrl+A` / `Ctrl+E` | Jump to start / end of input |
@@ -176,11 +183,11 @@ Attach files or images to your message with `@file` and `@image`, or pull a dire
176
183
 
177
184
  - `@file path` — reads the file and inlines its content in the message as `<file>` block
178
185
  - `@image path` — reads the image as base64 and sends it via the model's vision input
179
- - `@file photo.png` — image extensions (`.png`, `.jpg`, `.gif`, `.webp`, `.bmp`) are auto-detected as images
186
+ - `@file photo.png` — image extensions (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp`) are auto-detected as images
180
187
  - `@scope dir` — injects the directory-scoped `.halo/INSTRUCTIONS.md` along the path from the workspace root down to `dir` (root level excluded — it's already in the system prompt) into **this turn only**. Use it to bring a sub-directory's conventions into scope for one request. It does not change where tools run.
181
188
  - Paths are relative to the workspace; absolute paths also work
182
189
  - Quoted paths supported for filenames with spaces: `@file "my file.txt"`
183
- - **Tab completion** in TUI mode: type `@file ` (or `@scope `, which lists directories only) then press Tab to browse
190
+ - **Path completion** in TUI mode: the suggestion popup opens by itself as you type after `@file ` / `@image ` (images and directories only) / `@scope ` (directories only) — `Tab` or `Enter` completes the highlighted entry, directories stay open for descending (see Keybindings)
184
191
 
185
192
  ### Limits
186
193
 
@@ -188,6 +195,18 @@ Attach files or images to your message with `@file` and `@image`, or pull a dire
188
195
  - **Images**: skipped if larger than 5MB
189
196
  - If the current model does not support vision, a warning is shown and images are ignored
190
197
 
198
+ ## Server Mode (background daemon)
199
+
200
+ `halo server start|stop|restart|status|logs` manages a local server from the
201
+ same binary. `start -d` runs it in the background **with a bounded respawn
202
+ supervisor**: the detached process supervises the server and brings it back on
203
+ a crash (non-zero exit, or a kill by anything other than SIGTERM/SIGINT), at
204
+ most 5 times per 5 minutes with a 2s pause, logging each decision as
205
+ `[respawn] …` into `~/.halo/global/logs/server.log`. `halo server stop` is
206
+ recognized as intentional and never respawns; `halo server stop|restart|status`
207
+ still target the server's own pid. Full policy in
208
+ [dev/deploy.md](../dev/deploy.md#crash-semantics--why-restarton-failure-is-load-bearing).
209
+
191
210
  ## Architecture
192
211
 
193
212
  ```
@@ -21,12 +21,21 @@ The primary surface for talking to an agent.
21
21
  Hover actions on user-role turns: the blue sticky user bubble, plus the sub-agent-report (green) and compact-summary (purple) callouts — all three share the same Copy/Delete pair. On the callouts, Copy copies the body only (the `(from: session X)` / `[Conversation Summary…]` marker line is stripped); a deleted callout greys out and gains a "deleted" badge like the bubble does:
22
22
  - **Copy** — copies the prompt text to the clipboard
23
23
  - **Show more/less** — the existing clamp toggle, shown when the text overflows
24
- - **Delete** (confirm dialog) — removes the whole exchange (the user turn + all responses up to the next user turn) with **two-layer semantics**: the LLM context (`rawMessages`) drops the turn physically — the model never sees it again, freeing context; the UI keeps the messages, rendered greyed-out with a "deleted" tag, as an audit trail. No undo; a deleted exchange loses its Delete button. Rejected with an error toast while the agent is running or compacting. Root sessions only (sub-session logs don't offer Delete). If the turn was already compacted out of raw context, only the UI marking happens (silent degrade). Design details in [design/session.md](../design/session.md#exchange-deletion-soft-ui--hard-raw), protocol in [design/ws.md](../design/ws.md).
24
+ - **Delete** (confirm dialog) — removes the whole exchange (the user turn + all responses up to the next user turn) with **two-layer semantics**: the LLM context (`rawMessages`) drops the turn physically — the model never sees it again, freeing context; the UI keeps the messages, rendered greyed-out with a "deleted" tag, as an audit trail. No undo; a deleted exchange loses its Delete button. Rejected with an error toast while the agent is running or compacting. Root sessions only (sub-session logs don't offer Delete). If the turn was already compacted out of raw context, only the UI marking happens (silent degrade). Once a session has **archived history** the action is refused for the whole session with a plain explanatory notice (not an `Error:` bubble) — per-turn positions can no longer be mapped once older turns left the active file. Design details in [design/session.md](../design/session.md#exchange-deletion-soft-ui--hard-raw), protocol in [design/ws.md](../design/ws.md).
25
+
26
+ ### Archived history (scroll up to load)
27
+ Long sessions get their older exchanges moved out of the active log on compact, so opening a session stays fast no matter how long it has run. What the user sees:
28
+
29
+ - A **"Load earlier messages"** row appears above the conversation only for sessions that actually have archived history; it loads one archive chunk per click, oldest-newer order preserved, and becomes **"No earlier messages"** once everything is loaded
30
+ - Scrolling to the very top of the message area triggers the same load automatically (re-armed only after scrolling back down, so one gesture doesn't cascade through the whole archive), and the reading position is preserved when the older messages prepend — the view doesn't jump
31
+ - Loaded history sits in a **collapsed** block ("Archived · N segment(s) · M messages") — the user scrolled up for older context, not to have hundreds of exchanges re-flow the view; expanding renders them like normal messages
32
+ - Archived exchanges are **read-only**: Copy still works, Delete is absent (the server refuses it for archived sessions)
33
+ - Chunks already loaded aren't re-fetched; a failed load can simply be retried with the same row
25
34
 
26
35
  ### Session sidebar (right side)
27
36
  A fixed, collapsible list of the workspace's root sessions on the right edge of the chat panel (terminal-list style, ~200px). Replaces the old popup dropdown — the History button (with count badge) left of the composer and the "N previous sessions" link in the empty state both just toggle it now. Open/collapsed state persists globally in `localStorage` (`halo_session_sidebar_open`), default open.
28
37
 
29
- - Each row: 🎯 goal badge, title, `N msgs · time-ago`, model tail; hover reveals **rename** (pencil) and **delete** buttons; infinite scroll pages older sessions; "+ New Session" footer
38
+ - Each row: 🎯 goal badge, title, `N msgs · time-ago`, model tail; hover reveals **rename** (pencil) and **delete** buttons; infinite scroll pages older sessions; "+ New Session" footer. `N` counts the session's user turns **over its whole lifetime** — compacting or archiving history never makes it go backwards
30
39
  - **Inline rename**: pencil → input in place, Enter/blur commits (`PATCH /sessions/logs/:id`), Esc cancels; empty or unchanged title is a no-op. Other session-list consumers refresh via the `session:changed` WS push
31
40
  - **Switch loading**: clicking a row shows a spinner on that row + a "Loading session…" state in the message area, cleared only when the server's `state:snapshot` for that exact sessionId arrives (empty sessions included). Past 30s it degrades to "Slow network — still loading…" plus a Retry button (re-subscribes) — slow ≠ failed, nothing aborts on its own
32
41
 
@@ -34,14 +34,17 @@ When agents write files through tools, the canvas detects it:
34
34
  Open tabs and the active tab live in localStorage; survive refresh.
35
35
 
36
36
  ### Diff view
37
- - Tracked files → `GET /api/files/diff` for git diff
37
+ - Tracked files → `GET /api/git/diff` (returns `{ original, modified }`) for git diff
38
38
  - Monaco left/right compare view
39
39
 
40
40
  ### Binary previews
41
41
  Non-text files go through `FilePreview`, which looks up a **plugin** in the preview registry by file extension. Built-in plugins:
42
42
  - **PDF** — browser-native iframe
43
43
  - **DOCX / DOC** — `mammoth` → HTML (parsed in a Web Worker so the UI stays responsive)
44
- - **XLSX / XLS / CSV** — `xlsx` (SheetJS) → table (parsed in a Web Worker; CSV decoded as UTF-8 with BOM strip)
44
+ - **XLSX / XLS** — `xlsx` (SheetJS) → table, parsed whole in a Web Worker (zip can't stream); rendered with client-side paging (500 rows/page over the in-memory parse — no truncation, just paged)
45
+ - **CSV / TSV** — server-paginated table: each page (100 rows) is parsed and fetched from the server on demand, so large files open instantly. Delimiter is sniffed from the header line (comma/semicolon/tab; `.tsv` forces tab); total row count is a lazy lower bound ("N+") until the last page is reached
46
+ - **Parquet** — server-paginated table (schema + 100-row pages), same pager as CSV/SQLite
47
+ - **SQLite / DB (.db / .sqlite / .sqlite3)** — server-paginated table with a table-selector sidebar (name + row count per table); opened read-only, one page (100 rows) per request
45
48
  - **PPTX / PPT** — `pptx-preview` list mode. Fidelity is approximate — complex animations / SmartArt / embedded fonts may not render perfectly; users can download for the exact source. Flagged `heavy: true` so only the *active* pptx mounts (canvas-based rendering needs the main thread). **Speaker-notes sidebar**: notes are extracted from the pptx zip in presentation (play) order and listed on the left; clicking a note scrolls to the corresponding slide (a domIndex mapping bridges play order to pptx-preview's part-filename render order, so reordered decks still land on the right slide). Collapsible with the state remembered in localStorage (`halo.pptxNotesHidden`); the sidebar doesn't appear at all when no slide has notes. Decks with dangling `[Content_Types].xml` overrides (e.g. some WPS exports) are repaired before rendering; if zero slides render anyway, a real error UI is shown instead of a silent black wrapper
46
49
  - **Images / video / audio** — native `<img>` / `<video>` / `<audio>`, supports HTTP Range for seek-without-full-download
47
50
 
@@ -43,6 +43,7 @@ Visual directory browser opened by the 📁🔍 button:
43
43
  - No max depth limit
44
44
  - Expansion state lives in localStorage; restored on refresh and lazily reloaded
45
45
  - WebSocket `file:changed` events incrementally update loaded branches; unloaded branches are left alone (re-fetched on expand)
46
+ - **Reconnect reconciliation**: because the tree is kept in sync purely by `file:changed` deltas, events missed while the socket was down (laptop lid, network drop) would leave it stale forever. On WS *re*connect (not first connect) the root level is silently refetched; expanded directories self-heal because their fresh nodes come back without children, which re-arms the lazy-load along the persisted expanded-paths spine
46
47
 
47
48
  ### Selection model
48
49
  VS Code-style highlight selection (no checkboxes):
@@ -6,7 +6,7 @@ Session history viewer: hierarchy tree, message playback, debug mode, system pro
6
6
 
7
7
  ### Session tree view
8
8
  - Top-level sessions (no parentSessionId) act as roots; child sessions render indented
9
- - Each row shows: title, agent-name badge, message count, relative time, and status icons (StopCircle for stopped, amber Archive for archived)
9
+ - Each row shows: title, agent-name badge, message count (user turns over the session's lifetime — never shrinks when history is compacted/archived), relative time, and status icons (StopCircle for stopped, amber Archive for archived)
10
10
  - Clicking loads the full message list into SessionChatPanel
11
11
  - Collapse / expand via arrow buttons
12
12
  - Clicking the count badge (e.g. "+3") shows total descendant count
@@ -35,8 +35,8 @@ The Prompt button (FileText icon) shows the system prompt used by this session.
35
35
  - Created on the first `chat` WS message (with a new sessionId)
36
36
  - Auto-saved after every `complete` event
37
37
  - Sessions restore from disk on refresh / WS reconnect (loaded at subscribe time; running sessions reattach from the detached pool)
38
- - Deleting a session removes the JSON file and cascade-deletes all descendants in SQLite
39
- - Assistant messages embed a `toolCalls` array so tool-call cards persist across refresh
38
+ - Deleting a session removes the JSON file (plus any archived history segments) and cascade-deletes all descendants in SQLite
39
+ - Assistant messages persist their `contentBlocks` (text / thinking / tool calls, in the order they happened) so tool-call cards survive a refresh in the right places. Sessions written before content blocks existed carry a flat `toolCalls` array instead and still render, as a fallback
40
40
 
41
41
  ### Non-destructive /session new
42
42
  `/session new` (session:clear) **does not** destroy the old session:
@@ -63,6 +63,7 @@ Unified session logs API (recommended):
63
63
  |---|---|
64
64
  | List | `GET /api/sessions/logs?projectId=...` |
65
65
  | Read | `GET /api/sessions/logs/:id?projectId=...` |
66
+ | Read archived history | `GET /api/sessions/logs/:id/archive/:n?projectId=...` |
66
67
  | Delete | `DELETE /api/sessions/logs/:id?projectId=...` |
67
68
 
68
69
  The List endpoint serves two consumers: this tree view (default — roots + all
@@ -46,6 +46,7 @@ The panel, the history graph, and the Explorer git decorations all refresh on th
46
46
 
47
47
  - **Panel-driven writes**: every git mutation route (stage/unstage/commit/push/pull/init/remote) re-broadcasts `file:changed` (path `.git`) itself, because the workspace file watcher deliberately ignores `.git`. So after a commit the CHANGES list auto-clears with no manual refresh.
48
48
  - **Command-line writes**: a lightweight, **non-recursive** watch on the `.git` directory's top-level `HEAD` / `index` files (separate from the recursive workspace watcher, which excludes `.git` to avoid being overwhelmed by object-store inodes) catches terminal `git commit` / `checkout` / `add` / `reset` and broadcasts the same `file:changed`. `.lock` churn is filtered; the operation's events are debounced into a single refresh. So a commit made in the integrated terminal updates the panel without a manual refresh.
49
+ - **Reconnect reconciliation**: `file:changed` is a fire-and-forget push, so anything that happened while the socket was down (laptop lid, network drop) is simply lost. Panel, history graph and Explorer decorations therefore also refresh once on WS *re*connect (first connect is skipped — the initial load already fetched), which is the only way a terminal commit made during the outage shows up without a manual refresh.
49
50
  - **Two-phase watcher**: `GitDirWatcher` adapts to whether the workspace is a repo yet. A real work-tree watches `.git` directly (the steady state above). A **non-git folder** instead watches the workspace *root* (still non-recursive) for one thing only — `.git` appearing — so a terminal `git init` / `git clone` is noticed and upgrades the watcher onto `.git`, firing once so the [entry auto-surfaces](#entry-visibility-git-gated). The root-watch handler compares the event filename before any disk IO (ordinary file churn costs one string compare), and a real repo never enters this degraded mode.
50
51
 
51
52
  ## Explorer git decorations