@markusylisiurunen/tau 0.3.75 → 0.3.77

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 (126) hide show
  1. package/dist/core/cli.js +0 -3
  2. package/dist/core/cli.js.map +1 -1
  3. package/dist/core/config/builtin_themes.js +0 -30
  4. package/dist/core/config/builtin_themes.js.map +1 -1
  5. package/dist/core/config/content_loader.js +10 -115
  6. package/dist/core/config/content_loader.js.map +1 -1
  7. package/dist/core/config/index.js +1 -1
  8. package/dist/core/config/index.js.map +1 -1
  9. package/dist/core/config/paths.js +0 -1
  10. package/dist/core/config/paths.js.map +1 -1
  11. package/dist/core/config/runtime.js +1 -1
  12. package/dist/core/config/runtime.js.map +1 -1
  13. package/dist/core/config/runtime_config_snapshot.js +0 -1
  14. package/dist/core/config/runtime_config_snapshot.js.map +1 -1
  15. package/dist/core/config/schema.js +8 -84
  16. package/dist/core/config/schema.js.map +1 -1
  17. package/dist/core/config/skills_loader.js +0 -8
  18. package/dist/core/config/skills_loader.js.map +1 -1
  19. package/dist/core/config/theme_variants.js.map +1 -1
  20. package/dist/core/config/virtual_bundle.js +1 -4
  21. package/dist/core/config/virtual_bundle.js.map +1 -1
  22. package/dist/core/debug.js +0 -2
  23. package/dist/core/debug.js.map +1 -1
  24. package/dist/core/index.js +0 -1
  25. package/dist/core/index.js.map +1 -1
  26. package/dist/core/models/catalog.js +2 -3
  27. package/dist/core/models/catalog.js.map +1 -1
  28. package/dist/core/personas.js +3 -81
  29. package/dist/core/personas.js.map +1 -1
  30. package/dist/core/static/index.js +0 -3
  31. package/dist/core/static/index.js.map +1 -1
  32. package/dist/core/static/tau_docs/config-reference.md +17 -59
  33. package/dist/core/static/tau_docs/configuration.md +5 -10
  34. package/dist/core/static/tau_docs/credentials.md +9 -28
  35. package/dist/core/static/tau_docs/image-generation.md +1 -1
  36. package/dist/core/static/tau_docs/node-sdk.md +1 -1
  37. package/dist/core/static/tau_docs/ownership-and-scope.md +3 -3
  38. package/dist/core/static/tau_docs/pdf-unpacking.md +1 -1
  39. package/dist/core/static/tau_docs/personas.md +3 -1
  40. package/dist/core/static/tau_docs/prompts-and-project-context.md +0 -10
  41. package/dist/core/static/tau_docs/remote-sessions.md +5 -21
  42. package/dist/core/static/tau_docs/session-protocol-methods.md +1 -7
  43. package/dist/core/static/tau_docs/skills.md +0 -22
  44. package/dist/core/static/tau_docs/speech-generation.md +1 -1
  45. package/dist/core/static/tau_docs/telegram.md +15 -17
  46. package/dist/core/static/tau_docs/troubleshooting.md +15 -15
  47. package/dist/core/static/tau_docs/tui.md +9 -24
  48. package/dist/core/telegram/adapter.js +120 -20
  49. package/dist/core/telegram/adapter.js.map +1 -1
  50. package/dist/core/telegram/cli.js +4 -5
  51. package/dist/core/telegram/cli.js.map +1 -1
  52. package/dist/core/telegram/runtime.js +3 -2
  53. package/dist/core/telegram/runtime.js.map +1 -1
  54. package/dist/core/telegram/session_manager.js +14 -0
  55. package/dist/core/telegram/session_manager.js.map +1 -1
  56. package/dist/core/telegram/tts.js +11 -9
  57. package/dist/core/telegram/tts.js.map +1 -1
  58. package/dist/core/tool/image_generate.js +37 -14
  59. package/dist/core/tool/image_generate.js.map +1 -1
  60. package/dist/core/tool/media.js +84 -4
  61. package/dist/core/tool/media.js.map +1 -1
  62. package/dist/core/tool/pdf_unpack.js +17 -10
  63. package/dist/core/tool/pdf_unpack.js.map +1 -1
  64. package/dist/core/tool/speech_generate.js +30 -13
  65. package/dist/core/tool/speech_generate.js.map +1 -1
  66. package/dist/core/utils/gemini_transcription.js +5 -95
  67. package/dist/core/utils/gemini_transcription.js.map +1 -1
  68. package/dist/core/utils/{gemini_speech.js → speech.js} +167 -428
  69. package/dist/core/utils/speech.js.map +1 -0
  70. package/dist/core/utils/speech_to_text.js +24 -52
  71. package/dist/core/utils/speech_to_text.js.map +1 -1
  72. package/dist/core/utils/speech_to_text_keywords.js +65 -0
  73. package/dist/core/utils/speech_to_text_keywords.js.map +1 -1
  74. package/dist/core/version.js +1 -1
  75. package/dist/execution/fly_sprite_execution_environment.js +4 -2
  76. package/dist/execution/fly_sprite_execution_environment.js.map +1 -1
  77. package/dist/main.js +6 -78
  78. package/dist/main.js.map +1 -1
  79. package/dist/protocol/index.d.ts +1 -1
  80. package/dist/protocol/index.js.map +1 -1
  81. package/dist/protocol/session_protocol.d.ts +2 -15
  82. package/dist/protocol/session_protocol.js +1 -20
  83. package/dist/protocol/session_protocol.js.map +1 -1
  84. package/dist/sdk/local_client.js +0 -6
  85. package/dist/sdk/local_client.js.map +1 -1
  86. package/dist/tui/chat_view.js +1 -3
  87. package/dist/tui/chat_view.js.map +1 -1
  88. package/dist/tui/listen_capture.js +7 -26
  89. package/dist/tui/listen_capture.js.map +1 -1
  90. package/dist/tui/session_chat_app.js +3 -19
  91. package/dist/tui/session_chat_app.js.map +1 -1
  92. package/dist/tui/session_chat_controller.js +58 -29
  93. package/dist/tui/session_chat_controller.js.map +1 -1
  94. package/dist/tui/speech_playback.js +10 -8
  95. package/dist/tui/speech_playback.js.map +1 -1
  96. package/dist/tui/ui/pending_messages.js +2 -1
  97. package/dist/tui/ui/pending_messages.js.map +1 -1
  98. package/package.json +3 -3
  99. package/dist/core/install/cli.js +0 -260
  100. package/dist/core/install/cli.js.map +0 -1
  101. package/dist/core/models/tau_model_overrides.js +0 -4
  102. package/dist/core/models/tau_model_overrides.js.map +0 -1
  103. package/dist/core/utils/gemini_speech.js.map +0 -1
  104. package/dist/core/utils/openai_transcription.js +0 -594
  105. package/dist/core/utils/openai_transcription.js.map +0 -1
  106. package/dist/execution/cloudflare_sandbox_execution_environment.js +0 -563
  107. package/dist/execution/cloudflare_sandbox_execution_environment.js.map +0 -1
  108. package/dist/execution/sandbox_tool_helpers.js +0 -30
  109. package/dist/execution/sandbox_tool_helpers.js.map +0 -1
  110. package/dist/starter/prompts/approval-review-branch.md +0 -7
  111. package/dist/starter/prompts/approval-review-current-changes.md +0 -7
  112. package/dist/starter/prompts/approval-review-last-commit.md +0 -7
  113. package/dist/starter/prompts/commit-all.md +0 -9
  114. package/dist/starter/prompts/commit-staged.md +0 -9
  115. package/dist/starter/prompts/guided-review-branch.md +0 -7
  116. package/dist/starter/prompts/guided-review-current-changes.md +0 -7
  117. package/dist/starter/prompts/guided-review-last-commit.md +0 -7
  118. package/dist/starter/prompts/plan.md +0 -7
  119. package/dist/starter/prompts/review-branch.md +0 -7
  120. package/dist/starter/prompts/review-current-changes.md +0 -7
  121. package/dist/starter/prompts/review-last-commit.md +0 -7
  122. package/dist/starter/prompts/rewrite-prompt.md +0 -89
  123. package/dist/starter/skills/approval-review/SKILL.md +0 -219
  124. package/dist/starter/skills/code-review/SKILL.md +0 -102
  125. package/dist/starter/skills/guided-review/SKILL.md +0 -152
  126. package/dist/starter/skills/plan/SKILL.md +0 -53
@@ -64,16 +64,6 @@ After recording succeeds, the picker becomes non-actionable and shows the prompt
64
64
 
65
65
  Only the newest picker in a chat is active. Pickers are bound to the session that opened them and expire after selection, session replacement, or runner restart. Duplicate taps do not record the same picker twice. Prompt selection does not consume pending attachments or group context.
66
66
 
67
- ### Install starter prompts
68
-
69
- Use `tau install` to copy Tau's starter content:
70
-
71
- ```bash
72
- tau install --prompt commit-staged
73
- ```
74
-
75
- The default target is `.tau/prompts/` under the current directory. Add `--global` for `~/.config/tau/prompts/`. Existing prompt files are skipped unless `--force` is supplied. Running `tau install` without `--prompt` or `--skill` installs all starter prompts and skills.
76
-
77
67
  ## `AGENTS.md` provides standing context
78
68
 
79
69
  `AGENTS.md` is project context injected into the effective system prompt. It is suitable for repository conventions, architectural boundaries, verification commands, and instructions that should apply to every relevant request.
@@ -91,7 +91,7 @@ Remote `--new` supplies the conventional creation attribute `source: "tui"`. It
91
91
 
92
92
  ## Select an execution environment
93
93
 
94
- A session can use a local host directory, an already-provisioned Cloudflare Sandbox, or an already-provisioned Fly Sprite. In all cases, the execution cwd is an absolute path inside that environment.
94
+ A session can use a local host directory or an already-provisioned Fly Sprite. In all cases, the execution cwd is an absolute path inside that environment.
95
95
 
96
96
  ### Host-local directory
97
97
 
@@ -103,22 +103,6 @@ tau attach --new --cwd /srv/workspaces/tau ws://host.example:8787
103
103
 
104
104
  The host process executes agent-visible filesystem and command operations on its own machine. Tau does not clone, pull, or provision a repository.
105
105
 
106
- ### Cloudflare Sandbox
107
-
108
- The host must already have a named bridge in `cloudflareSandbox.bridges`, and the sandbox must already exist:
109
-
110
- ```sh
111
- tau attach \
112
- --new \
113
- --execution-kind cloudflare-sandbox \
114
- --cloudflare-bridge production \
115
- --cloudflare-sandbox sandbox-42 \
116
- --cwd /workspace/tau \
117
- wss://tau.example.com
118
- ```
119
-
120
- `--cloudflare-bridge` identifies host configuration. `--cloudflare-sandbox` identifies an existing sandbox reachable through that bridge. Tau does not create the sandbox or copy a repository into it.
121
-
122
106
  ### Fly Sprite
123
107
 
124
108
  The host must already have a named API target in `flySprites.apis`, and the Sprite must already exist:
@@ -166,7 +150,7 @@ An attached session spans three logical machines even when two happen to share o
166
150
  - command execution, platform, PATH, and runtime dependencies
167
151
  - automatic-compaction archives and other target-side temporary files
168
152
 
169
- This is why changing a host theme does not affect a remote TUI, why `!git status` runs against the execution environment, and why a custom diff application opens on the attaching machine. [Ownership and scope](ownership-and-scope.md) applies the same model across Tau.
153
+ This is why changing a host’s theme selection does not affect a remote TUI, why `!git status` runs against the execution environment, and why a custom diff application opens on the attaching machine. [Ownership and scope](ownership-and-scope.md) applies the same model across Tau.
170
154
 
171
155
  ## Reload or restart the correct process
172
156
 
@@ -178,7 +162,7 @@ Different changes have different owners:
178
162
  | Effective model `apiKeys` in execution-environment or session configuration | Wait for idle, then run `/reload`; new sessions also resolve the current values. |
179
163
  | Managed Codex auth changed with `tau auth` | No host restart; auth storage is read again on later credential resolutions. |
180
164
  | Attaching themes, diff launcher, speech config, or configured client tools | Restart `tau attach`. |
181
- | Host process environment variables, history target, WebSocket listener, Cloudflare bridge, Fly API target, or host startup flags | Restart `tau serve`. |
165
+ | Host process environment variables, history target, WebSocket listener, Fly API target, or host startup flags | Restart `tau serve`. |
182
166
  | Host Tau package, built-in tools, protocol, session recovery code, or built-in documentation | Upgrade and restart the host. |
183
167
  | TUI package, keybindings, rendering, local speech, or client-tool implementation | Upgrade and restart the attaching client. |
184
168
 
@@ -215,9 +199,9 @@ Avoid downgrading a host that has already written sessions with a newer storage
215
199
  The host lists only stored sessions whose execution-environment kind it can currently restore. Confirm that:
216
200
 
217
201
  - the connection uses the same host user and home directory as the original process
218
- - the Cloudflare bridge id or Fly API id still exists in host configuration
202
+ - the Fly API id still exists in host configuration
219
203
  - host credentials are available to the restarted process
220
- - the target sandbox, Sprite, or local directory still exists
204
+ - the target Sprite or local directory still exists
221
205
  - the session was created on this host rather than another machine with a different store
222
206
 
223
207
  Do not edit the session JSON to change environment identity. Restore the owning configuration or target instead.
@@ -63,12 +63,6 @@ Creates a session in one explicitly selected execution environment.
63
63
  params: {
64
64
  executionEnvironment:
65
65
  | { kind: "local"; cwd: string; env?: Record<string, string> }
66
- | {
67
- kind: "cloudflare-sandbox";
68
- bridgeId: string;
69
- sandboxId: string;
70
- cwd: string;
71
- }
72
66
  | {
73
67
  kind: "fly-sprite";
74
68
  apiId: string;
@@ -83,7 +77,7 @@ params: {
83
77
  result: { sessionId: string }
84
78
  ```
85
79
 
86
- `cwd` must be absolute inside the selected execution environment. Cloudflare sandboxes and Fly Sprites must already exist and be reachable through a host-configured resolver. Tau does not provision a target or repository.
80
+ `cwd` must be absolute inside the selected execution environment. Fly Sprites must already exist and be reachable through a host-configured resolver. Tau does not provision a target or repository.
87
81
 
88
82
  `attributes` is required, including when empty. It accepts at most 32 immutable pairs; keys are 1 to 64 characters and values at most 1,024 characters. Tau stores the supplied strings without inferring missing provenance. Conventional attributes and their use are covered in [sessions](sessions.md) and [history](history.md).
89
83
 
@@ -133,28 +133,6 @@ Skill references compose. If `release-check` instructs the agent to use `@@skill
133
133
 
134
134
  After activation, the agent reads `SKILL.md` from the path in the discovered index. It should load only relevant files from `references/` or `assets/`, and prefer provided scripts when they implement the required workflow. Skills are treated as read-only unless the user explicitly asks to edit them.
135
135
 
136
- ## Installing starter skills
137
-
138
- Tau ships starter prompts and skills that can be copied into a project:
139
-
140
- ```bash
141
- tau install
142
- ```
143
-
144
- By default this installs all starter prompts and skills under the current directory's `.tau/`. To install one skill:
145
-
146
- ```bash
147
- tau install --skill code-review
148
- ```
149
-
150
- To install under `~/.config/tau/` instead:
151
-
152
- ```bash
153
- tau install --global --skill commit
154
- ```
155
-
156
- Existing skill directories are skipped. `--force` replaces the entire same-named target directory, including files that are not present in the starter copy, so use it only when replacement is intended. `--prompt` and `--skill` are mutually exclusive. See [prompts and project context](prompts-and-project-context.md) for the prompt side of `tau install`.
157
-
158
136
  ## Applying changes and checking discovery
159
137
 
160
138
  A running TUI session keeps its current content catalog and prompt context until it reloads. Run:
@@ -152,7 +152,7 @@ The output parent directory must exist. Neither the requested WAV nor `<output>.
152
152
 
153
153
  Stdout on success is one JSON object with absolute `output`, `artifacts`, and the number of `batches`. The manifest contains the script text; keep artifacts private for sensitive scripts.
154
154
 
155
- Failures exit nonzero and report the retained directory. Requests are not automatically retried because a failed request may already have incurred a charge.
155
+ Failures exit nonzero and identify invalid script fields, chunks, or turns before generation, or the failed batch and processing stage afterward. Existing output or artifact paths require a fresh `--output` path; preserve recovery artifacts. Errors after output preparation report the retained directory, which may be incomplete. If final publication fails, copy the completed `assembled.wav` to a fresh path instead of generating again. Requests are not automatically retried because a failed request may already have incurred a charge; another generation request may incur another charge.
156
156
 
157
157
  To recover after a later batch fails, inspect the manifest's `completed` flags and chunk mapping. Generate only the unfinished chunks to a fresh output path, then assemble the completed PCM files from both runs in script order. Request stitching does not carry over between invocations.
158
158
 
@@ -224,25 +224,28 @@ Every Telegram turn starts with hidden `<system>` guidance identifying Telegram
224
224
 
225
225
  Top-level and per-bot `systemMessage` values share a second hidden block after the default, in that order. Audio transcripts receive another warning that they may contain noise or errors. Unlike project context, hidden guidance is persisted with user turns and can appear in history, so never put credentials in configured messages.
226
226
 
227
- The runner's speech-to-text provider is loaded from normal Tau config at runner startup, based on the runner process's startup `cwd`. Restart the runner after changing that provider or its environment.
227
+ The runner's speech credentials and `speech.voiceId` are loaded at startup from its environment and normal Tau config, based on the runner process's startup `cwd`. Restart the runner after changing those settings.
228
228
 
229
229
  ## Chat commands
230
230
 
231
231
  | Command | Behavior |
232
232
  | --- | --- |
233
- | `/use_<projectId>` | Saves the project preference for future `/new` sessions; does not change the active session. |
234
- | `/new` | Closes the current active session, then creates one from the current project preference. |
235
- | `/status` | Reports session state, project, model, reasoning, context usage, cost, and goal state when available. |
236
- | `/effort_low` | Selects low reasoning for later independent turns. |
237
- | `/effort_medium` | Selects medium reasoning for later independent turns. |
238
- | `/effort_high` | Selects high reasoning for later independent turns. |
239
- | `/effort_xhigh` | Selects xhigh reasoning for later independent turns. |
233
+ | `/use_<projectId>` | Selects the project for future `/new` sessions. |
234
+ | `/new` | Replaces the active session using the preferred project. |
235
+ | `/status` | Reports session state, project, model, reasoning, context usage, cost, and goal. |
236
+ | `/persona` | Switches persona while idle without losing history or starting a turn. |
237
+ | `/effort_low` | Selects low reasoning for future turns. |
238
+ | `/effort_medium` | Selects medium reasoning for future turns. |
239
+ | `/effort_high` | Selects high reasoning for future turns. |
240
+ | `/effort_xhigh` | Selects xhigh reasoning for future turns. |
240
241
  | `/prompt` | [Records a saved prompt](prompts-and-project-context.md#telegram-prompt-picker) while idle, without starting a turn. |
241
- | `/compact` | Runs summary-only manual compaction while the session is idle. |
242
+ | `/compact` | Runs summary-only compaction while idle. |
242
243
  | `/interrupt` | Interrupts the active Tau turn. |
243
244
  | `/tts_on` | Enables a Gemini-generated voice note after each final assistant response. |
244
245
  | `/tts_off` | Disables voice responses. |
245
246
 
247
+ [Persona](personas.md) selection is session-local; `/new` uses the project default.
248
+
246
249
  Preferences persist per bot and chat across restarts, projects, and sessions. Project changes apply to `/new`, not the active session.
247
250
 
248
251
  In groups, commands must explicitly mention the bot. Accepted forms include:
@@ -277,14 +280,9 @@ Uploads only queue files; captions are metadata. The next text or voice transcri
277
280
 
278
281
  Files use native temporary directories in the execution environment; OS cleanup can invalidate their paths. Pending queues and group context clear on shutdown. File contents and metadata are untrusted.
279
282
 
280
- OpenAI is the default. Select Gemini with `speechToText.provider`. Runner credentials are:
281
-
282
- - Gemini: `GEMINI_API_KEY`, then `apiKeys.google`
283
- - OpenAI: `OPENAI_API_KEY`, then `apiKeys.openai`
284
-
285
- Gemini extracts spelling hints with Gemini 3.8 Flash and transcribes uploaded audio verbatim with `gemini-3.5-transcribe`, using English (`en-US`) and Finnish (`fi-FI`) hints. It attempts remote file deletion afterward. OpenAI normalizes downloaded audio with runner-side `ffmpeg` and uploads it to `gpt-transcribe`. Missing keys prevent transcription, not file delivery. See [credentials](credentials.md).
283
+ Voice transcription uses Gemini with `GEMINI_API_KEY`, then `apiKeys.google`. Optional hints use GPT-6 Luna (reasoning disabled) and OpenAI credentials. Gemini transcribes audio verbatim with `gemini-3.5-transcribe`, using English (`en-US`) and Finnish (`fi-FI`) hints. It attempts remote file deletion. File delivery needs no Google key. See [credentials](credentials.md).
286
284
 
287
- `/tts_on` uses `gemini-3.8-flash`, `gemini-3.1-flash-tts-preview`, Despina, the Google key, and runner `ffmpeg` with Opus. Source and rewritten text each allow 10,000 Unicode characters; audio allows 32 MiB. Rewrite and job timeouts are one and five minutes. Jobs are ephemeral. Failure sends `voice response failed. please try again.` without affecting text; details stay in logs.
285
+ `/tts_on` rewrites text with `gpt-6-luna` (reasoning disabled) and generates speech with Eleven v4 Turbo. It requires both OpenAI and ElevenLabs credentials and runner `ffmpeg` with Opus. Voice notes use brisk delivery and 1.15× speed. See [`speech` configuration](config-reference.md#speech) for default and custom voice selection. Source and rewritten text each allow 10,000 Unicode characters; audio allows 32 MiB. Rewrite and job timeouts are one and five minutes. Jobs are ephemeral. Failure sends `voice response failed. please try again.` without affecting text; details stay in logs.
288
286
 
289
287
  ## Command client tools
290
288
 
@@ -335,7 +333,7 @@ Do not verify bot tokens, provider keys, Access secrets, transcript contents, or
335
333
 
336
334
  **A persistent-directory session fails recovery.** Restore the configured directory and its original path, or return the project config to the snapshot's durable `cwd`. Tau does not migrate an existing session to a new persistent directory.
337
335
 
338
- **Audio reports a missing key.** Set the credential for the configured speech provider in the runner process and restart it. A workspace-only environment change does not update the runner's startup speech configuration.
336
+ **Audio reports a missing key.** Set the credential for the Gemini in the runner process and restart it. A workspace-only environment change does not update the runner's startup speech configuration.
339
337
 
340
338
  **A message sent during active work changes direction.** Telegram uses steering, not ordinary queueing, while a turn is running. Wait for completion before sending an independent next task, or use `/interrupt` to stop the current run first.
341
339
 
@@ -108,7 +108,7 @@ Providers must be known and model IDs are exact and case-sensitive. Check warnin
108
108
 
109
109
  ### Theme
110
110
 
111
- Check the client `cwd` and home, exact filename ID, colors, `disableBuiltinThemes`, and `defaultTheme`. `/reload` does not reload themes. Restart the TUI, then select `/theme:<id>`. A remote host's themes do not supply an attached client's theme.
111
+ Check the exact built-in theme ID and the client’s `defaultTheme`. Select `/theme:<id>` for the current run, or restart the TUI to apply a new startup default. A remote host’s theme selection does not affect an attached client.
112
112
 
113
113
  ## A tool or subagent is unavailable
114
114
 
@@ -148,13 +148,13 @@ A detached owning client makes its tools unavailable and cancels active calls. R
148
148
 
149
149
  Find the process making the request:
150
150
 
151
- | Operation | Owner |
152
- | ------------------------------------------- | ------------ |
153
- | Model, host `web`, `history`, or `nook` | Host |
154
- | `/listen`, `/speak`, or TUI client tool | Client |
155
- | Telegram bot or transcription | Runner |
156
- | `tau nook`, `tau history`, or PDF unpack | Invoking CLI |
157
- | Cloudflare Sandbox or Fly Sprite resolution | Host startup |
151
+ | Operation | Owner |
152
+ | ---------------------------------------- | ------------ |
153
+ | Model, host `web`, `history`, or `nook` | Host |
154
+ | `/listen`, `/speak`, or TUI client tool | Client |
155
+ | Telegram bot or transcription | Runner |
156
+ | `tau nook`, `tau history`, or PDF unpack | Invoking CLI |
157
+ | Fly Sprite resolution | Host startup |
158
158
 
159
159
  A laptop variable does not update a remote host. Environment changes require an owner restart. Runtime `apiKeys` can reload, but only from an eligible execution-environment level and subject to feature precedence.
160
160
 
@@ -205,11 +205,11 @@ If attachment reports an unsupported protocol version or invalid peer message, u
205
205
 
206
206
  For `tau attach --new`, `--cwd` must be an absolute path inside the selected execution environment. With the default local execution kind it is a host path, not a path on the attaching machine. Tau does not create the directory, clone a repository, or infer remote repository attributes.
207
207
 
208
- For Cloudflare Sandbox or Fly Sprite creation, verify on the host that:
208
+ For Fly Sprite creation, verify on the host that:
209
209
 
210
- - the named bridge or API id exists in startup configuration;
210
+ - the named API id exists in startup configuration;
211
211
  - its credential is available to the host process;
212
- - the named sandbox or Sprite already exists;
212
+ - the named Sprite already exists;
213
213
  - the absolute target `cwd` exists there; and
214
214
  - the configured execution home matches the intended target account.
215
215
 
@@ -219,7 +219,7 @@ Without `--session` or `--new`, attach needs a TTY for its selector. In automati
219
219
 
220
220
  ## A session is missing, interrupted, or will not recover
221
221
 
222
- A selector shows only sessions this host can load and restore. Confirm its machine, OS user, home, Tau version, resolver configuration, and target still match the creator. Another user's `~/.config/tau/sessions` is a different store. Restore missing bridge/API definitions, credentials, local directory, sandbox, or Sprite. Never edit session JSON to substitute a target or `cwd`.
222
+ A selector shows only sessions this host can load and restore. Confirm its machine, OS user, home, Tau version, resolver configuration, and target still match the creator. Another user's `~/.config/tau/sessions` is a different store. Restore missing API definitions, credentials, local directory, or Sprite. Never edit session JSON to substitute a target or `cwd`.
223
223
 
224
224
  Recovery returns idle, aborts unfinished turns, cancels running maintenance, removes live subagents, and blocks an active goal. Review the last assistant and tools, then safely check `!!pwd` and `!!git status --short`. Resume a goal only after understanding the stop. Retry continues current history without rerunning completed tools automatically.
225
225
 
@@ -273,13 +273,13 @@ Recovery needs the same `workspaceRoot`, project definitions, host home, and Tau
273
273
 
274
274
  ## Telegram audio or attachment processing fails
275
275
 
276
- Telegram audio transcription uses the runner's `speechToText.provider`, which defaults to OpenAI. OpenAI needs `OPENAI_API_KEY` or `apiKeys.openai` and runner-side `ffmpeg`; Gemini needs `GEMINI_API_KEY` or `apiKeys.google`. Set the credential for the runner process and restart it after changing the environment or provider.
276
+ Telegram audio transcription uses Gemini and needs `GEMINI_API_KEY` or `apiKeys.google`. Set the credential for the runner process and restart it after changing the environment or configured key.
277
277
 
278
- Distinguish download, materialization, format, and transcription errors. The reply or runner log states which stage failed. Confirm Telegram can deliver the file to the bot, the attachment type is supported, the runner can write its temporary directory, and the selected provider accepts the media type. Do not log media bytes or transcripts merely to prove they exist.
278
+ Distinguish download, materialization, format, and transcription errors. The reply or runner log states which stage failed. Confirm Telegram can deliver the file to the bot, the attachment type is supported, the runner can write its temporary directory, and Gemini accepts the media type. Do not log media bytes or transcripts merely to prove they exist.
279
279
 
280
280
  Successful audio turns echo `transcribed: …` before submission. In groups, non-triggering audio can be buffered for later mentioned context. If that is inappropriate for the chat, narrow `allowedChatIds` or avoid enabling the group.
281
281
 
282
- Outgoing `/tts_on` voice responses always need `GEMINI_API_KEY` or `apiKeys.google`, even when incoming audio uses OpenAI. They also require runner-side `ffmpeg` with Opus support. A generation or delivery failure sends `voice response failed. please try again.` while detailed diagnostics remain in runner logs. The original text response remains delivered.
282
+ Outgoing `/tts_on` voice responses need both `OPENAI_API_KEY` or `apiKeys.openai` for rewriting and `ELEVENLABS_API_KEY` or `apiKeys.elevenlabs` for synthesis. Check [`speech.voiceId`](config-reference.md#speech) if a configured voice is unavailable. They also require runner-side `ffmpeg` with Opus support. A generation or delivery failure sends `voice response failed. please try again.` while detailed diagnostics remain in runner logs. The original text response remains delivered.
283
283
 
284
284
  ## Telegram replies or notifications are delayed or missing
285
285
 
@@ -47,7 +47,7 @@ When Tau is idle, Enter and Ctrl+Enter both start a normal turn. While a turn is
47
47
 
48
48
  - Enter queues the text as a new turn to run when the session becomes idle.
49
49
  - Ctrl+Enter steers the active turn. Tau applies steering at a safe continuation boundary rather than injecting it into a model response or tool execution in progress.
50
- - Alt+Up cancels all pending queued messages and steering that has not yet been applied, then restores their text to the editor. Multiple messages are separated with `---`.
50
+ - Alt+Up cancels all pending queued messages and steering that has not yet been applied, then restores their text to the editor. Multiple messages are separated with `---`. Hidden guidance, such as transcription warnings, stays hidden in the pending list and editor. Restoring messages preserves their guidance in order after any guidance already attached to the draft. Submission places all guidance at the beginning of the combined message. Editing a non-empty draft preserves it; clearing the draft clears it.
51
51
 
52
52
  Pending input is session state shared by attached clients while the host remains alive. It is not durable across host restart or session recovery. A queued turn captures the persona, reasoning, tools, and model settings when that turn actually starts. Steering remains part of the active logical turn and keeps the settings captured when that turn began.
53
53
 
@@ -143,22 +143,7 @@ Direct commands require the session to be idle. In an attached TUI, `!pwd` repor
143
143
 
144
144
  ## Use themes
145
145
 
146
- Themes belong to the TUI process and never become session state. Tau loads built-in themes plus JSON files from these locations:
147
-
148
- - `~/.config/tau/themes/<id>.json` for user themes, when the TUI cwd is under the user’s home.
149
- - `.tau/themes/<id>.json` at discovered project configuration levels, with the nearest project definition winning by id.
150
-
151
- The filename is the theme id. A custom theme is a flat JSON object from semantic palette token names to colors:
152
-
153
- ```json
154
- {
155
- "brandAccent": "#8fb3ff",
156
- "textMuted": "rgb(145, 151, 166)",
157
- "feedbackError": "hsl(354, 70%, 72%)"
158
- }
159
- ```
160
-
161
- Colors accept `#rgb`, `#rrggbb`, `rgb(r, g, b)`, and `hsl(h, s%, l%)`. Unknown tokens, non-string values, and invalid colors are ignored. Missing tokens render without a custom color. Custom themes are single-variant; built-in themes adapt to detected terminal appearance.
146
+ Themes belong to the TUI process and never become session state. Tau’s built-in themes adapt to detected terminal appearance.
162
147
 
163
148
  Set the startup theme with `defaultTheme` in the attaching client’s [configuration](configuration.md), or switch for the current run:
164
149
 
@@ -166,7 +151,7 @@ Set the startup theme with `defaultTheme` in the attaching client’s [configura
166
151
  /theme:gold
167
152
  ```
168
153
 
169
- Theme ids are exact and case-sensitive. `/theme` does not persist the selection. `/reload` refreshes the hosted session, not the attaching client’s loaded theme files, so restart the TUI after adding or changing a theme. In remote use, changing theme files on the host has no effect unless the host and TUI are the same physical machine and the TUI loaded those files itself.
154
+ Theme ids are exact and case-sensitive. `/theme` does not persist the selection. Restart the TUI to apply a new `defaultTheme`. A remote host’s theme selection does not affect an attached client.
170
155
 
171
156
  ## Review a diff
172
157
 
@@ -197,21 +182,21 @@ The TUI also advertises diff review as a client tool unless `--no-client-tools`
197
182
 
198
183
  ## Use speech
199
184
 
200
- `/listen` and Ctrl+Y are currently macOS-only. Recording uses local `ffmpeg` with the AVFoundation audio input and stops when Ctrl+Y is pressed again, when Escape is pressed, or at the provider recording limit: 9 minutes for Gemini and 20 minutes for OpenAI. The Gemini limit leaves time for setup and finalization within its 10-minute live session. Startup fails if the microphone produces no audio within 15 seconds. Stopping manually or reaching the recording limit finishes transcription and inserts the transcript at the cursor for review without submitting it.
185
+ `/listen` and Ctrl+Y are currently macOS-only. Recording uses local `ffmpeg` with the AVFoundation audio input and stops when Ctrl+Y is pressed again, when Escape is pressed, or at the 9-minute recording limit. The Gemini limit leaves time for setup and finalization within its 10-minute live session. Startup fails if the microphone produces no audio within 15 seconds. Stopping manually or reaching the recording limit finishes transcription and inserts the transcript at the cursor for review without submitting it.
201
186
 
202
- Gemini displays a replaceable live preview in muted italic text at the original cursor position. Finalized dictation uses normal text once transcription settles. Its final transcript replaces the preview without duplication. Editing stays disabled from recording startup through finalization, and failure restores the original draft. OpenAI inserts its transcript after finalization. Recording and retries cannot start while another editor update is pending.
187
+ Gemini displays a replaceable live preview in muted italic text at the original cursor position. Finalized dictation uses normal text once transcription settles. Its final transcript replaces the preview without duplication. Submitting a draft containing finalized dictation includes hidden guidance that some or all of the message may have been transcribed from speech and may contain transcription errors. The guidance remains after edits and clears when the draft is emptied. Editing stays disabled from recording startup through finalization, and failure restores the original draft. Recording and retries cannot start while another editor update is pending.
203
188
 
204
- If transcription fails, Tau retains the local WAV and reports its path. Run `/listen retry` to transcribe the same audio again with the current provider and conversation context, or `/listen discard` to delete it. Retained recordings longer than 20 minutes are rejected rather than sent to the provider. A replacement recording deletes the retained file only after the new capture starts producing audio. Tau keeps at most one failed recording, and exiting leaves that file at the reported path for manual recovery. For live transcription, both providers extract spelling hints from recent conversation while recording begins, buffer audio until the transcription session is ready, and then stream subsequent audio. Gemini uses Gemini 3.8 Flash for hint extraction and `gemini-3.5-transcribe-live` in verbatim mode with English (`en-US`) and Finnish (`fi-FI`) language hints; OpenAI uses GPT-6 Luna and `gpt-live-transcribe`. Hint extraction failure does not fail transcription. On retry, Gemini uploads the retained recording for `gemini-3.5-transcribe` verbatim transcription with the same language hints and attempts to delete the remote file afterward; OpenAI uploads it to `gpt-transcribe` with recent context.
189
+ If transcription fails, Tau retains the local WAV and reports its path. Run `/listen retry` to transcribe the same audio again with Gemini and the current conversation context, or `/listen discard` to delete it. Retained recordings longer than 20 minutes are rejected rather than sent to the provider. A replacement recording deletes the retained file only after the new capture starts producing audio. Tau keeps at most one failed recording, and exiting leaves that file at the reported path for manual recovery. For live transcription, GPT-6 Luna extracts spelling hints from recent conversation while recording begins, with reasoning disabled. Tau buffers audio until the transcription session is ready and then streams subsequent audio. Gemini uses `gemini-3.5-transcribe-live` in verbatim mode with English (`en-US`) and Finnish (`fi-FI`) language hints. Missing OpenAI credentials or hint extraction failure does not fail transcription. On retry, Gemini uploads the retained recording for `gemini-3.5-transcribe` verbatim transcription with the same language hints and attempts to delete the remote file afterward.
205
190
 
206
- Install `ffmpeg` and configure a speech-to-text provider:
191
+ Install `ffmpeg`:
207
192
 
208
193
  ```sh
209
194
  brew install ffmpeg
210
195
  ```
211
196
 
212
- OpenAI is the default and needs `OPENAI_API_KEY` or `apiKeys.openai`. Set `speechToText.provider` to `gemini` to use `GEMINI_API_KEY` or `apiKeys.google`. These settings and credentials are read by the TUI process, including during remote attachment.
197
+ Speech transcription uses Gemini and requires `GEMINI_API_KEY` or `apiKeys.google`. Credentials are read by the TUI process, including during remote attachment.
213
198
 
214
- `/speak` is also macOS-only. It rewrites the last assistant response for speech, streams audio from Gemini, and plays it through the local `ffplay` command included with `ffmpeg`. It requires `GEMINI_API_KEY` or `apiKeys.google`, runs only while the session is idle, and can be stopped with Escape. Longer responses are divided into balanced segments targeting at most two minutes of generated speech each. Speech source and rewritten text are limited to 10,000 Unicode characters, and generation stops after 32 MiB of raw audio.
199
+ `/speak` is also macOS-only. It rewrites the last assistant response with GPT-6 Luna (reasoning disabled), streams Eleven v4 Turbo audio, and plays it at 1.15× speed through the local `ffplay` command included with `ffmpeg`. It requires both `OPENAI_API_KEY` or `apiKeys.openai` for rewriting and `ELEVENLABS_API_KEY` or `apiKeys.elevenlabs` for synthesis, runs only while the session is idle, and can be stopped with Escape. Longer responses are divided into balanced segments targeting at most two minutes of generated speech each. Speech source and rewritten text are limited to 10,000 Unicode characters, and generation stops after 32 MiB of raw audio. Voice lookups and synthesis requests have a two-minute deadline. See [`speech` configuration](config-reference.md#speech) for the default Maisie/Caleb lookup and custom voice selection.
215
200
 
216
201
  ## Reload the right component
217
202
 
@@ -777,9 +777,10 @@ class TelegramAdapterImpl {
777
777
  botUsername;
778
778
  pollIntervalMs;
779
779
  requestTimeoutSeconds;
780
- speechToTextProvider;
781
780
  geminiApiKey;
782
- openaiApiKey;
781
+ openAIApiKey;
782
+ elevenLabsApiKey;
783
+ speechVoiceId;
783
784
  speechToTextDeps;
784
785
  sessionManager;
785
786
  projectPreferences;
@@ -794,6 +795,7 @@ class TelegramAdapterImpl {
794
795
  commandHandlers;
795
796
  callbackActionHandlers;
796
797
  abortController = new AbortController();
798
+ personaPickers = new Map();
797
799
  promptPickers = new Map();
798
800
  activeSessionsByChat = new Map();
799
801
  sessionsByChat = new Map();
@@ -832,9 +834,10 @@ class TelegramAdapterImpl {
832
834
  this.botUsername = options.botUsername;
833
835
  this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
834
836
  this.requestTimeoutSeconds = options.requestTimeoutSeconds ?? DEFAULT_REQUEST_TIMEOUT_SECONDS;
835
- this.speechToTextProvider = options.speechToTextProvider ?? "openai";
836
837
  this.geminiApiKey = options.geminiApiKey?.trim() || undefined;
837
- this.openaiApiKey = options.openaiApiKey?.trim() || undefined;
838
+ this.openAIApiKey = options.openAIApiKey?.trim() || undefined;
839
+ this.elevenLabsApiKey = options.elevenLabsApiKey?.trim() || undefined;
840
+ this.speechVoiceId = options.speechVoiceId;
838
841
  this.speechToTextDeps = options.speechToTextDeps;
839
842
  this.sessionManager = options.sessionManager;
840
843
  this.projectPreferences = options.projectPreferences;
@@ -912,6 +915,11 @@ class TelegramAdapterImpl {
912
915
  callbackAction: "status",
913
916
  handler: async (chatId) => this.handleStatus(chatId),
914
917
  },
918
+ {
919
+ command: "/persona",
920
+ description: "switch the active session persona",
921
+ handler: async (chatId, args) => this.handlePersona(chatId, args),
922
+ },
915
923
  {
916
924
  command: "/prompt",
917
925
  description: "add a saved prompt without starting a turn",
@@ -1458,6 +1466,10 @@ class TelegramAdapterImpl {
1458
1466
  await handler(chatId, args, sourceMessageId);
1459
1467
  }
1460
1468
  async handleCallback(chatId, callbackData, messageId) {
1469
+ if (callbackData.startsWith("persona:")) {
1470
+ await this.handlePersonaCallback(chatId, callbackData, messageId);
1471
+ return true;
1472
+ }
1461
1473
  if (callbackData.startsWith("prompt:")) {
1462
1474
  await this.handlePromptCallback(chatId, callbackData, messageId);
1463
1475
  return true;
@@ -1528,6 +1540,99 @@ class TelegramAdapterImpl {
1528
1540
  await this.reply(chatId, `failed to save voice response preference: ${error instanceof Error ? error.message : String(error)}`);
1529
1541
  }
1530
1542
  }
1543
+ async handlePersona(chatId, args) {
1544
+ if (args.length > 0) {
1545
+ await this.reply(chatId, "use /persona to choose a persona.");
1546
+ return;
1547
+ }
1548
+ const session = await this.requireActiveSession(chatId);
1549
+ if (!session)
1550
+ return;
1551
+ try {
1552
+ const snapshot = await this.getSessionManagerForChat(chatId).getSessionSnapshot(session.id);
1553
+ if (!snapshot) {
1554
+ await this.reply(chatId, "the session is still preparing.");
1555
+ return;
1556
+ }
1557
+ const personas = snapshot.catalog.personas;
1558
+ if (personas.length === 0) {
1559
+ await this.reply(chatId, "this session has no personas.");
1560
+ return;
1561
+ }
1562
+ this.personaPickers.set(chatId, {
1563
+ token: randomUUID(),
1564
+ sessionId: session.id,
1565
+ personas,
1566
+ currentPersonaId: snapshot.settings.personaId,
1567
+ });
1568
+ await this.showPersonaPage(chatId, 0);
1569
+ }
1570
+ catch (error) {
1571
+ await this.reply(chatId, this.formatManagerError(error));
1572
+ }
1573
+ }
1574
+ async showPersonaPage(chatId, page, messageId) {
1575
+ const picker = this.personaPickers.get(chatId);
1576
+ if (!picker)
1577
+ return;
1578
+ const start = page * 20;
1579
+ const keyboard = picker.personas.slice(start, start + 20).map((persona, index) => [
1580
+ {
1581
+ text: `${persona.label ?? persona.id}${persona.id === picker.currentPersonaId ? " (current)" : ""}`,
1582
+ callback_data: `persona:${picker.token}:select:${start + index}`,
1583
+ },
1584
+ ]);
1585
+ const navigation = [];
1586
+ if (page > 0)
1587
+ navigation.push({
1588
+ text: "previous",
1589
+ callback_data: `persona:${picker.token}:page:${page - 1}`,
1590
+ });
1591
+ if (start + 20 < picker.personas.length)
1592
+ navigation.push({ text: "next", callback_data: `persona:${picker.token}:page:${page + 1}` });
1593
+ if (navigation.length > 0)
1594
+ keyboard.push(navigation);
1595
+ const text = "choose a persona for this session. switching is available only while Tau is idle.";
1596
+ const replyMarkup = { inline_keyboard: keyboard };
1597
+ if (messageId !== undefined) {
1598
+ await this.sendWithRetry((signal) => this.api.editMessage(chatId, messageId, text, { replyMarkup, signal }));
1599
+ }
1600
+ else {
1601
+ await this.reply(chatId, text, { replyMarkup });
1602
+ }
1603
+ }
1604
+ async handlePersonaCallback(chatId, data, messageId) {
1605
+ const match = /^persona:([^:]+):(select|page):(0|[1-9][0-9]*)$/.exec(data);
1606
+ const picker = this.personaPickers.get(chatId);
1607
+ if (!match ||
1608
+ !picker ||
1609
+ picker.token !== match[1] ||
1610
+ this.getActiveSession(chatId)?.id !== picker.sessionId ||
1611
+ messageId === undefined) {
1612
+ await this.reply(chatId, "this persona picker has expired. use /persona to open a new one.");
1613
+ return;
1614
+ }
1615
+ const index = Number(match[3]);
1616
+ if (match[2] === "page") {
1617
+ if (index * 20 < picker.personas.length)
1618
+ await this.showPersonaPage(chatId, index, messageId);
1619
+ return;
1620
+ }
1621
+ const persona = picker.personas[index];
1622
+ if (!persona)
1623
+ return;
1624
+ let snapshot;
1625
+ try {
1626
+ snapshot = await this.getSessionManagerForChat(chatId).setPersona(picker.sessionId, persona.id);
1627
+ }
1628
+ catch (error) {
1629
+ await this.reply(chatId, this.formatManagerError(error));
1630
+ return;
1631
+ }
1632
+ this.personaPickers.delete(chatId);
1633
+ const text = `switched to ${persona.label ?? persona.id}. this session is using ${snapshot.bootstrap.model.name || snapshot.bootstrap.model.id} with ${snapshot.settings.reasoning ?? "none"} reasoning.`;
1634
+ await this.sendWithRetry((signal) => this.api.editMessage(chatId, messageId, text, { signal }));
1635
+ }
1531
1636
  async handlePrompt(chatId, args) {
1532
1637
  if (args.length > 0) {
1533
1638
  await this.reply(chatId, "use /prompt to choose a saved prompt.");
@@ -1867,20 +1972,10 @@ class TelegramAdapterImpl {
1867
1972
  }
1868
1973
  }
1869
1974
  getSpeechToTextApiKey() {
1870
- switch (this.speechToTextProvider) {
1871
- case "gemini":
1872
- return this.geminiApiKey;
1873
- case "openai":
1874
- return this.openaiApiKey;
1875
- }
1975
+ return this.geminiApiKey;
1876
1976
  }
1877
1977
  getSpeechToTextApiKeyErrorMessage(action) {
1878
- switch (this.speechToTextProvider) {
1879
- case "gemini":
1880
- return `set GEMINI_API_KEY or apiKeys.google to ${action}`;
1881
- case "openai":
1882
- return `set OPENAI_API_KEY or apiKeys.openai to ${action}`;
1883
- }
1978
+ return `set GEMINI_API_KEY or apiKeys.google to ${action}`;
1884
1979
  }
1885
1980
  async transcribeTelegramVoice(chatId, message, options = {}) {
1886
1981
  if (message.durationMs > SPEECH_TO_TEXT_CLIENT_MAX_DURATION_MS) {
@@ -1901,9 +1996,9 @@ class TelegramAdapterImpl {
1901
1996
  let transcript;
1902
1997
  try {
1903
1998
  const transcription = createSpeechToTextTranscription({
1904
- provider: this.speechToTextProvider,
1905
1999
  mode: "file",
1906
2000
  apiKey,
2001
+ openAIApiKey: this.openAIApiKey,
1907
2002
  context: await this.resolveSpeechToTextContext(chatId),
1908
2003
  deps: {
1909
2004
  ...this.speechToTextDeps,
@@ -2234,11 +2329,16 @@ class TelegramAdapterImpl {
2234
2329
  }, TELEGRAM_TTS_JOB_TIMEOUT_MS);
2235
2330
  timeout.unref?.();
2236
2331
  try {
2237
- if (!this.geminiApiKey) {
2238
- throw new Error("missing Google credential for Telegram voice responses");
2332
+ if (!this.openAIApiKey) {
2333
+ throw new Error("missing OpenAI credential for Telegram voice responses");
2334
+ }
2335
+ if (!this.elevenLabsApiKey) {
2336
+ throw new Error("missing ElevenLabs credential for Telegram voice responses");
2239
2337
  }
2240
2338
  const voice = await this.generateVoice({
2241
- apiKey: this.geminiApiKey,
2339
+ openAIApiKey: this.openAIApiKey,
2340
+ elevenLabsApiKey: this.elevenLabsApiKey,
2341
+ voiceId: this.speechVoiceId,
2242
2342
  sourceText,
2243
2343
  fetchImpl: this.fetchImpl,
2244
2344
  signal: AbortSignal.any([this.abortController.signal, timeoutController.signal]),