@turmind/halo 0.2.0 → 0.2.1

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 (45) hide show
  1. package/admin-out/404.html +1 -1
  2. package/admin-out/_next/static/chunks/{298-4fac7d44a7949eec.js → 212-170855bed19f67f3.js} +1 -1
  3. package/admin-out/_next/static/chunks/525.2c10a4fd329d0d24.js +1 -0
  4. package/admin-out/_next/static/chunks/app/layout-c2fa8035998e48d5.js +1 -0
  5. package/admin-out/_next/static/chunks/app/page-31317ef1fb0ba31f.js +1 -0
  6. package/admin-out/_next/static/chunks/{webpack-8bd7ef88a6527feb.js → webpack-47520cdc55be63e7.js} +1 -1
  7. package/admin-out/_next/static/css/66df341f99220f5c.css +3 -0
  8. package/admin-out/index.html +1 -1
  9. package/admin-out/index.txt +21 -20
  10. package/bundled-docs/design/agent.md +1 -1
  11. package/bundled-docs/design/evolution.md +11 -2
  12. package/bundled-docs/design/halo-city.md +31 -0
  13. package/bundled-docs/design/prompt-system.md +3 -3
  14. package/bundled-docs/design/session.md +26 -1
  15. package/bundled-docs/design/storage.md +13 -0
  16. package/bundled-docs/dev/acp-adapter.md +26 -0
  17. package/bundled-docs/dev/api.md +6 -4
  18. package/bundled-docs/dev/desktop-packaging.md +4 -0
  19. package/bundled-docs/dev/env.md +4 -2
  20. package/bundled-docs/dev/previews.md +3 -0
  21. package/bundled-docs/dev/tools.md +3 -1
  22. package/bundled-docs/guide/cli.md +40 -16
  23. package/bundled-docs/requirements/editor.md +1 -1
  24. package/dist/index.js +1925 -1199
  25. package/package.json +1 -2
  26. package/templates/INSTRUCTIONS.md +0 -4
  27. package/templates/agents/__apply_agent__/AGENT.md +41 -19
  28. package/templates/agents/__evo_agent__/AGENT.md +73 -45
  29. package/templates/agents/__evo_agent__/agent.yaml +9 -6
  30. package/templates/agents/__score__/AGENT.md +65 -33
  31. package/templates/agents/deep-executor/AGENT.md +23 -6
  32. package/templates/agents/deep-executor/agent.yaml +4 -1
  33. package/templates/agents/default/AGENT.md +32 -8
  34. package/templates/agents/executor/AGENT.md +19 -1
  35. package/templates/agents/executor/agent.yaml +4 -1
  36. package/templates/models/aws-bedrock-claude-invoke.yaml +27 -0
  37. package/templates/prompts/all/WORKSPACE_CONVENTIONS.md +5 -0
  38. package/templates/prompts/bootstrap/BOOTSTRAP.md +4 -0
  39. package/templates/prompts/root/WORKSPACE_MEMORY.md +2 -0
  40. package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +0 -1
  41. package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +0 -1
  42. package/admin-out/_next/static/chunks/app/page-b12a2e8e48fe4ea0.js +0 -1
  43. package/admin-out/_next/static/css/372e3dbd4a0d1c25.css +0 -3
  44. /package/admin-out/_next/static/{ep7BWneNhTuIPwJfFFh72 → iSrc6429pyTB0lK-EE5oH}/_buildManifest.js +0 -0
  45. /package/admin-out/_next/static/{ep7BWneNhTuIPwJfFFh72 → iSrc6429pyTB0lK-EE5oH}/_ssgManifest.js +0 -0
@@ -81,9 +81,35 @@ To remove a binding: `/acp remove` (deletes the skill directory and points out t
81
81
  | `--token` | yes | Web-channel token from admin UI. `full` access required for multi-workspace use. |
82
82
  | `--workspace` | yes | Absolute server-side path for the workspace this adapter drives. |
83
83
  | `--agent-id` | no | Halo agent profile to use when ACP `session/new` creates a new halo session. Default: `default`. |
84
+ | `--header` | no | Extra HTTP header on every upstream request, `"Name: value"` (like `curl -H`). Repeatable. For auth that sits **in front of** the halo server — see "Upstream auth" below. |
84
85
 
85
86
  One adapter process binds to one workspace. To drive multiple workspaces concurrently from the same token, run multiple adapter processes — see "Multi-workspace" below.
86
87
 
88
+ ## Upstream auth (`--header`)
89
+
90
+ `--token` authenticates the adapter to *halo itself* (it becomes the `x-token` header on every web-channel call). It does **not** cover a proxy sitting in front of the server — an SSO/session-cookie gateway, Cloudflare Access, ALB OIDC, nginx basic-auth. Those reject the request before halo ever sees the token.
91
+
92
+ `--header` is the generic escape hatch: it forwards arbitrary headers on every request the adapter makes (`/api/web/chat`, `/history`, `/stop`), exactly like `curl -H`. The adapter deliberately knows nothing about any specific gateway — you supply whatever that layer wants:
93
+
94
+ ```sh
95
+ # session-cookie gateway
96
+ halo acp --host h --port 9527 --scheme https --token <t> --workspace /ws \
97
+ --header "Cookie: <the gateway's session cookie, verbatim>"
98
+
99
+ # Cloudflare Access (two headers — --header is repeatable)
100
+ halo acp … --header "CF-Access-Client-Id: <id>" --header "CF-Access-Client-Secret: <secret>"
101
+
102
+ # HTTP basic auth
103
+ halo acp … --header "Authorization: Basic <base64 user:pass>"
104
+ ```
105
+
106
+ Notes:
107
+
108
+ - **Repeatable** — pass `--header` as many times as the gateway needs.
109
+ - **Colon-safe** — the value is split on the *first* `:` only, so header values that themselves contain colons (`Cookie: a=b:c`) survive intact.
110
+ - **`x-token` always wins** — `--header` cannot override the adapter's own `x-token` / `content-type`; it only adds headers for the layer in front of halo.
111
+ - The generated `ask-<label>` bindings (halo→halo, via `ask.py`) do **not** yet expose `--header`; upstream auth through a binding isn't wired. `halo acp` direct is the path when a gateway is in play.
112
+
87
113
  ## ACP method coverage
88
114
 
89
115
  | Method | Implemented? | Notes |
@@ -235,10 +235,12 @@ runtime so the frontend can render rooms (workspaces) + characters (sessions).
235
235
 
236
236
  | Method | Path | Purpose |
237
237
  |---|---|---|
238
- | GET | `/api/show/state` | Full-access token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }` |
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 tokens may only read their own workspace. |
238
+ | GET | `/api/show/state` | `full` or `observer` token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }` |
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
- Each `workspace` = `{ path, key, label, counts{running,idle,stopped}, totalSessions, skills[], sessions[] }`; each `session` = `{ id, parentId, depth, agentName, description, status, lastTool, activeSkill, contextTokens, outputTokens, 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.
241
+ 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
+
243
+ **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).
242
244
 
243
245
  ## Agent Configs
244
246
 
@@ -312,7 +314,7 @@ File: `packages/server/src/routes/evolution.ts`. Surfaces the global `evolution_
312
314
  | Method | Path | Purpose |
313
315
  |---|---|---|
314
316
  | GET | `/api/evolution/runs` | List all evolution runs across workspaces. Carries the latest `apply_id`/`apply_status` per run. (Score isn't surfaced in the list — it's read from `score.json` on the detail fetch only.) |
315
- | GET | `/api/evolution/runs/:id` | Detail: db row + `patch.md` + `score.json` + `system-suggestions.md` (optional, evo writes when it has platform-level feedback) + `.skip.md` (when `status='skipped'`) + a snapshot summary (first user message + first assistant reply + message count). |
317
+ | GET | `/api/evolution/runs/:id` | Detail: db row + `patch.md` + `score.json` + `.skip.md` (when `status='skipped'`) + wrapper/sub-cli logs + a snapshot summary (first user message + first assistant reply + message count). Row carries `failureReason` (and `applyFailureReason` from the latest apply) — apply-side values include `phase A': apply cli exited <code>`, `phase A': apply agent aborted: <first line of ABORT.md>`, and `phase A': apply agent didn't produce apply.log`. |
316
318
  | POST | `/api/evolution/runs/:id/approve` `{reviewerHint?}` | Move run from `awaiting_review` → `approved`, insert a pending `evolution_applies` row that the ticker will pick up. |
317
319
  | POST | `/api/evolution/runs/:id/reject` | Move run from `awaiting_review` → `rejected`. |
318
320
  | POST | `/api/evolution/runs/:id/retry` `{hint}` | Reset a finished run back to `pending` with the supplied (required) hint so the wrapper picks it up again. Rejected with 409 when the run is already `running` / `pending`. |
@@ -56,6 +56,10 @@ done # all five must read the same x.y.z, and match the tag you're about to cu
56
56
  pnpm --filter @turmind/halo-core build
57
57
  pnpm --filter @turmind/halo-server build
58
58
  pnpm --filter @turmind/halo-admin build
59
+ # NOTE: acp-adapter is NOT listed here on purpose — build-bundle.mjs now
60
+ # builds every workspace:* dep (core / server / acp-adapter) itself before
61
+ # esbuild inlines them, so its dist can't go stale. This block only covers
62
+ # artifacts the stage script consumes directly (server dist, admin-out).
59
63
 
60
64
  # 2. stage + package (arm64)
61
65
  cd packages/desktop
@@ -87,7 +87,7 @@ Source: `packages/server/src/config.ts`
87
87
  | Variable | Default | Config path | Description |
88
88
  |---|---|---|---|
89
89
  | `HALO_PORT` | `9527` | `config.yaml server.port` | Hono listen port |
90
- | `HALO_PASSWORD` | (none) | `config.yaml server.password` (scrypt hash) | Plaintext login password. When set, takes precedence over the stored hash and bypasses scrypt entirely — intended for Docker / systemd / CI. The hash is set by `halo setup` for interactive installs. |
90
+ | `HALO_PASSWORD` | (none) | `config.yaml server.password` (scrypt hash) | Plaintext login password. When set, takes precedence over the stored hash and bypasses scrypt entirely — intended for Docker / systemd / CI. It also satisfies the **startup gate**: the server refuses to boot with no password configured, and env plaintext counts as a first-class credential there, so an env-only deployment (`halo setup -y && HALO_PASSWORD=... halo server start`, no stored hash) boots fine. The hash is set by `halo setup` for interactive installs. |
91
91
  | `HALO_CORS_ORIGINS` | empty (reflect any origin) | `config.yaml server.cors_origins` | CORS allowlist (comma-separated). Empty = reflect any incoming Origin so credentials work cross-origin. Set explicit list to enforce strict CORS. |
92
92
  | `HALO_FRONTEND_DIR` | `packages/admin/out` | — | Static frontend dir (resolved as absolute path from project root) |
93
93
  | `HALO_MAX_CONTEXT_TOKENS` | `200000` | — | Model max context |
@@ -117,6 +117,8 @@ settings.yaml only (no env override):
117
117
  - `kimi.secrets.api_key` — Kimi (Moonshot AI)
118
118
  - `deepseek.secrets.api_key` — DeepSeek
119
119
 
120
+ When a provider manifest declares a `default: <<NAME>>` for a secret, `halo setup` offers a "Use env $NAME" action. Picking it **writes the literal `<<NAME>>` placeholder into settings.yaml**, which the standard env-var interpolation ([storage.md](../design/storage.md#env-var-interpolation-env_name)) expands against `process.env` at read time. There is no separate runtime env fallback — the placeholder in the file is the only mechanism (an unset env var leaves the literal `<<NAME>>` visible in the request, failing loudly).
121
+
120
122
  `<skill-id>.params.*` (referenceable from skills via `{{<skill-id>.params.<key>}}`, or short form `{{params.<key>}}` inside SKILL.md; declared in `skills/<id>/config.yaml`):
121
123
  - `tavily-search.params.api_key` — example only; declared by whichever skill needs it
122
124
 
@@ -136,7 +138,7 @@ settings.yaml-driven `general.limits.*` (no env override):
136
138
  Two ways to authenticate:
137
139
 
138
140
  1. **Interactive** — run `halo setup`, pick "Set / change password". The value is scrypt-hashed and written to `~/.halo/secrets/config.yaml server.password`. Best for personal installs.
139
- 2. **Env** — export `HALO_PASSWORD=...` before launching the server. The plaintext value is compared directly (no hashing) and takes precedence over any stored hash. Best for Docker / systemd / CI where a secrets manager already protects the env.
141
+ 2. **Env** — export `HALO_PASSWORD=...` before launching the server. The plaintext value is compared directly (no hashing) and takes precedence over any stored hash. A stored hash isn't required at all — env plaintext alone satisfies the startup gate, so env-only deployments are legal. Best for Docker / systemd / CI where a secrets manager already protects the env.
140
142
 
141
143
  ## Verify
142
144
 
@@ -27,6 +27,9 @@ previews/
27
27
  ├── docx.tsx / docx-view.tsx
28
28
  ├── xlsx.tsx / xlsx-view.tsx
29
29
  ├── pptx.tsx / pptx-view.tsx
30
+ ├── pptx-notes.ts pptx zip helpers: speaker-notes extraction (jszip +
31
+ │ DOMParser, play order via sldIdLst) + Content_Types
32
+ │ repair for decks with dangling Override parts
30
33
  └── media.tsx / media-view.tsx
31
34
  ```
32
35
 
@@ -181,6 +181,8 @@ Start a sub-agent session asynchronously. When the sub-agent finishes, its **wra
181
181
  - Depth exceeded: `{"code": 1, "error": "Maximum nesting depth (N) reached..."}`
182
182
  - Working dir invalid: `{"code": 1, "error": "working_dir \"...\" is outside the workspace"}` (or does-not-exist / not-a-directory)
183
183
 
184
+ UI note: the `agent_start` event this emits carries both a 200-char `text` preview (parent-side rendering) and the full un-truncated brief as `fullText`, which seeds the sub-session log's opening user message — so viewing the child session shows the complete task brief (`system_prompt_context` + `message`), not a cut-off preview. See [session.md → Event routing](../design/session.md#event-routing).
185
+
184
186
  ### session_list
185
187
 
186
188
  List direct child sessions of the current session and their status.
@@ -293,7 +295,7 @@ Returns: full SKILL.md content (body + resource files list). For progressive dis
293
295
  |---|---|---|---|
294
296
  | `content` | string | yes | The complete draft answer. Required and must be non-empty |
295
297
 
296
- The tool **description is deliberately plain** — "Submit a draft answer for self-review; returns a checklist that critiques the draft." It does NOT say *when* to call it (no "call this for complex questions / when unsure"). An earlier version that did embed such self-referential guidance made some models (notably GPT-5.x) thrash. Steering toward *using* draft belongs in the agent's `system_prompt` (e.g. the bundled `default` agents say "hold your answers to a high standard… don't reply off the cuff"), not in the tool description. Empty `content` is rejected with an error **without consuming a draft round**.
298
+ The tool **description is deliberately plain** — "Submit a draft answer for self-review; returns a checklist that critiques the draft." It does NOT say *when* to call it (no "call this for complex questions / when unsure"). An earlier version that did embed such self-referential guidance made some models (notably GPT-5.x) thrash. Steering toward *using* draft belongs in the agent's prompt files (the bundled `default` agent's AGENT.md says "hold your answers to a high standard… don't reply off the cuff" — it lives in AGENT.md because the yaml `system_prompt` is only a fallback when the MD layer is empty), not in the tool description. Empty `content` is rejected with an error **without consuming a draft round**.
297
299
 
298
300
  **Why it exists:** the agent loop only makes another model call when the model emits a `tool_use` block. A plain-text answer (`end_turn`) is single-pass — `thinking` runs *before* the answer in the same call, so the model never gets to look at its *finished* answer and revise it. `draft` closes that gap without touching the loop: the model writes its answer into `content` (materialised into the conversation as a `tool_use` block — uncapped, never echoed back), and the tool_result hands back an adversarial review checklist (framed as a hostile reviewer: list every factual claim and tag its source, verify the unverified ones with tools *now*, check directness/tone, flag gaps). The next model call then critiques that now-concrete draft and either revises (calls `draft` again) or writes the final answer.
299
301
 
@@ -46,12 +46,14 @@ End users then install with `npm install -g @turmind/halo`.
46
46
  Run a one-shot prompt and exit:
47
47
 
48
48
  ```bash
49
- halo "review this PR"
50
- halo -w /path/to/workspace "analyze code"
51
- echo "review the changes" | halo
52
- cat prompt.txt | halo -w /path/to/review-ws
49
+ halo cli "review this PR"
50
+ halo cli -w /path/to/workspace "analyze code"
51
+ echo "review the changes" | halo cli
52
+ cat prompt.txt | halo cli -w /path/to/review-ws
53
53
  ```
54
54
 
55
+ To list agents or sessions without running a prompt, use the dedicated subcommands `halo agents` / `halo sessions` (same `-w` flag).
56
+
55
57
  ### Options
56
58
 
57
59
  | Flag | Short | Default | Description |
@@ -59,11 +61,10 @@ cat prompt.txt | halo -w /path/to/review-ws
59
61
  | `--workspace` | `-w` | cwd | Workspace path |
60
62
  | `--agent` | `-a` | `default` | Agent ID |
61
63
  | `--session` | `-s` | auto | Resume session by ID |
64
+ | `--new` | `-n` | off | Always start a new session |
62
65
  | `--format` | `-f` | `text` | Output format: `text` or `json` |
63
66
  | `--verbose` | `-v` | off | Show thinking, tool calls, usage on stderr |
64
67
  | `--access` | | `full` | Access level: `full`, `workspace`, `readonly` |
65
- | `--agents` | | | List available agents and exit |
66
- | `--sessions` | | | List recent sessions and exit |
67
68
  | `--lang` | | `en` | Language: `en` or `zh` |
68
69
 
69
70
  ### Output
@@ -91,21 +92,22 @@ cat prompt.txt | halo -w /path/to/review-ws
91
92
 
92
93
  ```bash
93
94
  # Code review in CI
94
- halo -w /path/to/review-ws "review the diff" --format json | jq '.text'
95
+ halo cli -w /path/to/review-ws "review the diff" --format json | jq '.text'
95
96
 
96
97
  # Pipe content in
97
- git diff HEAD~1 | halo -w /path/to/review-ws "review this diff"
98
+ git diff HEAD~1 | halo cli -w /path/to/review-ws "review this diff"
98
99
 
99
100
  # Exit code: 0 = success, 1 = error, 130 = SIGINT
100
- halo "check for issues" && echo "OK" || echo "FAILED"
101
+ halo cli "check for issues" && echo "OK" || echo "FAILED"
101
102
  ```
102
103
 
103
104
  ## TUI Mode (interactive)
104
105
 
105
106
  ```bash
106
- halo -i
107
- halo -i -w /path/to/workspace
108
- halo -i -s sid_abc123 # resume session
107
+ halo tui
108
+ halo tui -w /path/to/workspace
109
+ halo tui -s sid_abc123 # resume session
110
+ halo tui -n # always start a new session
109
111
  ```
110
112
 
111
113
  Multi-turn conversation. Supports all standard Halo slash commands:
@@ -130,6 +132,16 @@ Multi-turn conversation. Supports all standard Halo slash commands:
130
132
  | `/evo [hint]` | Queue a self-evolution run on this session (full access only) |
131
133
  | `/quit` | Exit |
132
134
 
135
+ TUI-only client commands (handled locally, also visible in the completion popup):
136
+
137
+ | Command | Description |
138
+ |---|---|
139
+ | `/clear` | Start a new session (alias for `/session new`) |
140
+ | `/retry` | Resend the last user message |
141
+ | `/verbose` | Toggle verbose tool output (args + truncated results) at runtime — same as launching with `-v`; the status bar shows a `v` badge while on |
142
+ | `/log` | Browse the session tree and view a session log (same as `Ctrl+O`) |
143
+ | `/exit` | Alias for `/quit` |
144
+
133
145
  Bare `/<obj>` (or `/<obj> help`) lists the verbs you may run. Verbs gated above your access level are hidden.
134
146
 
135
147
  ### Keybindings
@@ -139,9 +151,15 @@ Bare `/<obj>` (or `/<obj> help`) lists the verbs you may run. Verbs gated above
139
151
  | `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`. |
140
152
  | `Ctrl+C` | Graceful exit; press twice to force |
141
153
  | `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). |
142
- | `↑` / `↓` | Walk input history (when no popup is open) |
154
+ | `↑` / `↓` | Walk input history (when no popup is open). History persists across restarts in `~/.halo/global/tui-history.json` (last 100 entries, shared across workspaces) |
155
+ | `←` / `→`, `Home` / `End` | Move the cursor within the input; text is inserted/deleted at the cursor (CJK and emoji safe) |
156
+ | `Ctrl+A` / `Ctrl+E` | Jump to start / end of input |
157
+ | `Ctrl+W` / `Alt+Backspace` | Delete the word before the cursor |
158
+ | `Ctrl+U` / `Ctrl+K` | Delete to start / to end of input |
159
+
160
+ On resume (`-s <id>`, or the default latest session), the TUI replays the session's prior conversation on screen so you see where you left off. `shell_exec` output is shown inline by default (other tools' output needs `-v`). Tool lines show a short argument summary and duration, e.g. `⚙ file_read hello.txt 4ms`.
143
161
 
144
- On resume (`-s <id>`, or the default latest session), the TUI replays the session's prior conversation on screen so you see where you left off. `shell_exec` output is shown inline by default (other tools' output needs `-v`).
162
+ **Multi-line paste**: pasting text with newlines (or >800 chars) collapses into a compact placeholder like `[#1 pasted 3 lines]` in the input; you can keep typing around it, and the original text is expanded back in full on submit. Pasted content is inserted at the cursor position.
145
163
 
146
164
  ## File, Image, and Scope References
147
165
 
@@ -174,10 +192,16 @@ Attach files or images to your message with `@file` and `@image`, or pull a dire
174
192
  ```
175
193
  packages/cli/
176
194
  src/
177
- index.ts — Entry: arg parsing, mode dispatch, SIGINT handling
195
+ index.ts — Entry: subcommand dispatch (tui/cli/agents/sessions/server/setup/acp), SIGINT handling
178
196
  harness.ts — Shared agent harness wrapping SessionManager
179
197
  cli.ts — Non-interactive: stdin/args → agent → stdout → exit
180
- tui.ts — Interactive: readline loop + event display + commands
198
+ tui.tsx — Interactive entry: mounts the ink app
199
+ tui/
200
+ app.tsx — Root component: event reducer, blocks, submit/command handling
201
+ line-editor.ts — Grapheme-aware pure line-editing functions (cursor, insert, delete)
202
+ history.ts — Persistent input history (~/.halo/global/tui-history.json)
203
+ components/ — input-box, text-input, messages, streaming, status-bar,
204
+ slash-suggest, path-suggest, log-navigator, log-viewer, banner
181
205
  format-usage.ts — Usage-line formatter (mirrors admin UsageLine badge order)
182
206
  resolve-refs.ts — @file / @image reference resolution with size limits
183
207
  render-md.ts — Terminal markdown rendering (marked + marked-terminal)
@@ -42,7 +42,7 @@ Non-text files go through `FilePreview`, which looks up a **plugin** in the prev
42
42
  - **PDF** — browser-native iframe
43
43
  - **DOCX / DOC** — `mammoth` → HTML (parsed in a Web Worker so the UI stays responsive)
44
44
  - **XLSX / XLS / CSV** — `xlsx` (SheetJS) → table (parsed in a Web Worker; CSV decoded as UTF-8 with BOM strip)
45
- - **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)
45
+ - **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
46
  - **Images / video / audio** — native `<img>` / `<video>` / `<audio>`, supports HTTP Range for seek-without-full-download
47
47
 
48
48
  All previews share a **standard header** (`PreviewShell`) with filename, Download, Open-as-Text, and an `extraToolbar` slot for plugin-specific buttons (e.g. DOCX Print, XLSX sheet tabs).