@markusylisiurunen/tau 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -21
- package/dist/app.js +135 -31
- package/dist/app.js.map +1 -1
- package/dist/bash_commands.js +37 -16
- package/dist/bash_commands.js.map +1 -1
- package/dist/cli.js +23 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +86 -13
- package/dist/commands.js.map +1 -1
- package/dist/config.js +27 -15
- package/dist/config.js.map +1 -1
- package/dist/content_loader.js +225 -97
- package/dist/content_loader.js.map +1 -1
- package/dist/debug.js +149 -0
- package/dist/debug.js.map +1 -0
- package/dist/main.js +35 -0
- package/dist/main.js.map +1 -1
- package/dist/personas.js +29 -8
- package/dist/personas.js.map +1 -1
- package/dist/session/session_engine.js +3 -10
- package/dist/session/session_engine.js.map +1 -1
- package/dist/subagents/registry.js +4 -1
- package/dist/subagents/registry.js.map +1 -1
- package/dist/subagents/subagent_engine.js +22 -7
- package/dist/subagents/subagent_engine.js.map +1 -1
- package/dist/subagents/web.js +58 -0
- package/dist/subagents/web.js.map +1 -0
- package/dist/tools/bash.js +9 -4
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.js +8 -5
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/parallel_api.js +30 -0
- package/dist/tools/parallel_api.js.map +1 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/task.js +8 -5
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/web_fetch.js +241 -0
- package/dist/tools/web_fetch.js.map +1 -0
- package/dist/tools/web_search.js +229 -0
- package/dist/tools/web_search.js.map +1 -0
- package/dist/tools/write.js +7 -4
- package/dist/tools/write.js.map +1 -1
- package/dist/types.js +5 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/bash_execution.js +26 -0
- package/dist/ui/bash_execution.js.map +1 -1
- package/dist/ui/custom_editor.js +13 -0
- package/dist/ui/custom_editor.js.map +1 -1
- package/dist/ui/session_summary.js +1 -10
- package/dist/ui/session_summary.js.map +1 -1
- package/dist/ui/slash_autocomplete.js +6 -3
- package/dist/ui/slash_autocomplete.js.map +1 -1
- package/dist/ui/task_execution.js +15 -0
- package/dist/ui/task_execution.js.map +1 -1
- package/dist/utils/context.js +31 -0
- package/dist/utils/context.js.map +1 -1
- package/dist/utils/project_files.js +144 -3
- package/dist/utils/project_files.js.map +1 -1
- package/dist/utils/streaming_settings.js +15 -0
- package/dist/utils/streaming_settings.js.map +1 -0
- package/dist/utils/zod.js +9 -0
- package/dist/utils/zod.js.map +1 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# tau
|
|
2
2
|
|
|
3
|
-
a terminal-based AI chat client for working with code. tau gives you access to Claude, GPT, and Gemini models, each equipped with tools to explore, write, and edit files in your project.
|
|
3
|
+
a terminal-based AI chat client for working with code. tau gives you access to Claude, GPT, and Gemini models, each equipped with tools to explore, write, and edit files in your project, plus optional sub-agents for deeper codebase investigation and web research.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
@@ -24,13 +24,16 @@ or store keys in `~/.config/tau/config.json`:
|
|
|
24
24
|
"apiKeys": {
|
|
25
25
|
"anthropic": "sk-ant-...",
|
|
26
26
|
"openai": "sk-...",
|
|
27
|
-
"google": "..."
|
|
27
|
+
"google": "...",
|
|
28
|
+
"parallel": "..."
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
environment variables take precedence over the config file.
|
|
33
34
|
|
|
35
|
+
`parallel` is only needed for the web sub-agent tools (`web_search`/`web_fetch`).
|
|
36
|
+
|
|
34
37
|
## security notice
|
|
35
38
|
|
|
36
39
|
**the risk level system is a UX guardrail, not a hard security boundary.** it helps prevent accidental writes and guides model behavior, but it has significant limitations:
|
|
@@ -86,7 +89,7 @@ tau comes with several built-in personas across different models:
|
|
|
86
89
|
- **GPT-5.2** (OpenAI)
|
|
87
90
|
- **Gemini 3 Pro** and **Gemini 2.5 Flash** (Google)
|
|
88
91
|
|
|
89
|
-
each model has three variants: a general-purpose assistant, a coder variant optimized for software engineering, and a raw variant with minimal prompting.
|
|
92
|
+
each model has three variants: a general-purpose assistant, a coder variant optimized for software engineering, and a raw variant with minimal prompting. basic and coder variants include the `web` sub-agent for web research, and coder variants also include the `explore` sub-agent for multi-turn codebase investigation.
|
|
90
93
|
|
|
91
94
|
switch personas at startup with `--persona` or mid-session with `/persona:<id>`:
|
|
92
95
|
|
|
@@ -94,6 +97,15 @@ switch personas at startup with `--persona` or mid-session with `/persona:<id>`:
|
|
|
94
97
|
tau --persona opus-4.5-coder
|
|
95
98
|
```
|
|
96
99
|
|
|
100
|
+
## sub-agents
|
|
101
|
+
|
|
102
|
+
some personas can run isolated sub-agents via the internal `task` tool:
|
|
103
|
+
|
|
104
|
+
- `explore`: read-only, multi-turn codebase investigation
|
|
105
|
+
- `web`: high-threshold web research using Parallel Search/Extract (`web_search`/`web_fetch`)
|
|
106
|
+
|
|
107
|
+
to use the web sub-agent, set `apiKeys.parallel` in `~/.config/tau/config.json` (see above). tau will only make web calls when needed or when you explicitly ask for web research.
|
|
108
|
+
|
|
97
109
|
## reasoning
|
|
98
110
|
|
|
99
111
|
some models support extended thinking, where they reason through problems before responding. cycle through reasoning levels with `shift+tab`, or set one at startup:
|
|
@@ -114,7 +126,9 @@ you can also pipe content directly:
|
|
|
114
126
|
cat src/app.ts | tau --persona opus-4.5
|
|
115
127
|
```
|
|
116
128
|
|
|
117
|
-
for project-aware sessions, use `--with-context` to inject your AGENTS.md
|
|
129
|
+
for project-aware sessions, use `--with-context` to inject your AGENTS.md into the system prompt. tau searches for this file in the current directory and parent directories up to your home folder.
|
|
130
|
+
|
|
131
|
+
run `tau --help` to see all available options, or `tau --debug` to inspect loaded personas, prompts, skills, and the full system prompt for debugging configuration issues.
|
|
118
132
|
|
|
119
133
|
## memory mode
|
|
120
134
|
|
|
@@ -130,20 +144,20 @@ tau will create or update AGENTS.md at your project root, integrating the new in
|
|
|
130
144
|
|
|
131
145
|
tau supports slash commands for common actions:
|
|
132
146
|
|
|
133
|
-
| command | description
|
|
134
|
-
| ---------------------- |
|
|
135
|
-
| `/help` | show available commands
|
|
136
|
-
| `/new` | clear the session and start fresh
|
|
137
|
-
| `/copy` | copy the last assistant message
|
|
138
|
-
| `/copy:code` | copy just the code blocks
|
|
139
|
-
| `/reload` | reload personas and
|
|
140
|
-
| `/fork:only-summary` | compress history and continue with a summary
|
|
141
|
-
| `/fork:with-last-turn` | compress history but keep the last exchange
|
|
142
|
-
| `/persona:<id>` | switch to a different persona
|
|
143
|
-
| `/prompt:<id>` | insert a saved prompt template
|
|
144
|
-
| `/bash:<id>` | run a saved shell command
|
|
145
|
-
| `/risk:<level>` | change the risk level
|
|
146
|
-
| `!<cmd>` | run a shell command directly
|
|
147
|
+
| command | description |
|
|
148
|
+
| ---------------------- | ---------------------------------------------- |
|
|
149
|
+
| `/help` | show available commands |
|
|
150
|
+
| `/new` | clear the session and start fresh |
|
|
151
|
+
| `/copy` | copy the last assistant message |
|
|
152
|
+
| `/copy:code` | copy just the code blocks |
|
|
153
|
+
| `/reload` | reload personas, prompts, and skills from disk |
|
|
154
|
+
| `/fork:only-summary` | compress history and continue with a summary |
|
|
155
|
+
| `/fork:with-last-turn` | compress history but keep the last exchange |
|
|
156
|
+
| `/persona:<id>` | switch to a different persona |
|
|
157
|
+
| `/prompt:<id>` | insert a saved prompt template |
|
|
158
|
+
| `/bash:<id>` | run a saved shell command |
|
|
159
|
+
| `/risk:<level>` | change the risk level |
|
|
160
|
+
| `!<cmd>` | run a shell command directly |
|
|
147
161
|
|
|
148
162
|
the fork commands are useful when conversations get long. they compress everything into a summary so the model retains context without the overhead of a full history.
|
|
149
163
|
|
|
@@ -155,6 +169,7 @@ the fork commands are useful when conversations get long. they compress everythi
|
|
|
155
169
|
| `ctrl+t` | toggle thinking visibility |
|
|
156
170
|
| `ctrl+o` | toggle compact tool display |
|
|
157
171
|
| `ctrl+f` | expand @file mentions |
|
|
172
|
+
| `alt+up` | pop queued message |
|
|
158
173
|
| `esc` | interrupt generation |
|
|
159
174
|
| `ctrl+c` | exit |
|
|
160
175
|
|
|
@@ -169,7 +184,8 @@ store settings in `~/.config/tau/config.json`:
|
|
|
169
184
|
"apiKeys": {
|
|
170
185
|
"anthropic": "sk-ant-...",
|
|
171
186
|
"openai": "sk-...",
|
|
172
|
-
"google": "..."
|
|
187
|
+
"google": "...",
|
|
188
|
+
"parallel": "..."
|
|
173
189
|
},
|
|
174
190
|
"defaultPersona": "gpt-5.2",
|
|
175
191
|
"defaultRisk": "read-write",
|
|
@@ -222,7 +238,8 @@ you can also set model parameters via optional frontmatter fields:
|
|
|
222
238
|
|
|
223
239
|
- `reasoning`: one of `none`, `minimal`, `low`, `medium`, `high`, `xhigh`
|
|
224
240
|
- `allowedReasoningLevels`: list of reasoning levels shown in the ui
|
|
225
|
-
- `
|
|
241
|
+
- `skills`: list of enabled skill names (matched by `name` in skill frontmatter), or `"*"` to enable all discovered skills
|
|
242
|
+
- `subagents`: enable sub-agents (`explore` for multi-turn codebase investigation, `web` for web research). you can specify as a list (`subagents: [explore]`, `subagents: [web]`, or `subagents: [explore, web]`) to use the main persona's model, or as an object to customize each sub-agent's model and reasoning. example:
|
|
226
243
|
```yaml
|
|
227
244
|
subagents:
|
|
228
245
|
explore:
|
|
@@ -248,7 +265,13 @@ suggest specific improvements with code examples.
|
|
|
248
265
|
|
|
249
266
|
insert them with `/prompt:review`. if a project prompt id conflicts with a user or built-in prompt, the project prompt wins.
|
|
250
267
|
|
|
251
|
-
|
|
268
|
+
### skills
|
|
269
|
+
|
|
270
|
+
skills are optional markdown files discovered at `~/.config/tau/skills/<dir>/SKILL.md` (user-level) and `.tau/skills/<dir>/SKILL.md` (project-level). each `SKILL.md` must contain yaml frontmatter with `name` and `description`.
|
|
271
|
+
|
|
272
|
+
enable skills per persona with the `skills` frontmatter field. you can list specific skill names (matched by `name` in skill frontmatter), or use `"*"` to enable all discovered skills. all non-raw built-in personas (basic and coder variants) have `skills: "*"` by default. tau injects an index of enabled skills into the system prompt containing only each skill's `name`, `description`, and absolute file path.
|
|
273
|
+
|
|
274
|
+
use `/reload` to pick up changes to personas, prompts, and skills without restarting.
|
|
252
275
|
|
|
253
276
|
## how it works
|
|
254
277
|
|
package/dist/app.js
CHANGED
|
@@ -14,9 +14,9 @@ import { createEditToolDefinition } from "./tools/edit.js";
|
|
|
14
14
|
import { ToolRegistry } from "./tools/registry.js";
|
|
15
15
|
import { createTaskToolDefinition } from "./tools/task.js";
|
|
16
16
|
import { createWriteToolDefinition } from "./tools/write.js";
|
|
17
|
-
import { REASONING_LEVELS } from "./types.js";
|
|
17
|
+
import { REASONING_LEVELS, } from "./types.js";
|
|
18
18
|
import { AssistantMessageComponent } from "./ui/assistant_message.js";
|
|
19
|
-
import { renderBashBlocked, renderBashExecution, renderBashRunning } from "./ui/bash_execution.js";
|
|
19
|
+
import { renderBashAborted, renderBashBlocked, renderBashExecution, renderBashRunning, } from "./ui/bash_execution.js";
|
|
20
20
|
import { ChatContainerComponent } from "./ui/chat_container.js";
|
|
21
21
|
import { CustomEditor } from "./ui/custom_editor.js";
|
|
22
22
|
import { renderEditBlocked, renderEditSuccess, renderWriteBlocked, renderWriteSuccess, } from "./ui/file_execution.js";
|
|
@@ -24,17 +24,17 @@ import { FooterComponent } from "./ui/footer.js";
|
|
|
24
24
|
import { QueuedMessagesComponent } from "./ui/queued_messages.js";
|
|
25
25
|
import { SessionDividerComponent } from "./ui/session_divider.js";
|
|
26
26
|
import { SessionSummaryComponent } from "./ui/session_summary.js";
|
|
27
|
-
import { SlashAutocompleteProvider } from "./ui/slash_autocomplete.js";
|
|
27
|
+
import { getFileAutocompleteToken, SlashAutocompleteProvider } from "./ui/slash_autocomplete.js";
|
|
28
28
|
import { SystemMessageComponent } from "./ui/system_message.js";
|
|
29
29
|
import { renderTaskBlocked, renderTaskFinished, renderTaskRunning } from "./ui/task_execution.js";
|
|
30
30
|
import { editorBorderForReasoning, theme } from "./ui/theme.js";
|
|
31
31
|
import { UserMessageComponent } from "./ui/user_message.js";
|
|
32
|
-
import { buildBaseSystemPrompt, buildEnvironmentTag, buildProjectContextBlock, findAgentsFilesFromCwdToHome, formatRiskLevelChangeNotice, } from "./utils/context.js";
|
|
32
|
+
import { buildBaseSystemPrompt, buildEnvironmentTag, buildProjectContextBlock, buildSkillsIndexBlock, findAgentsFilesFromCwdToHome, formatRiskLevelChangeNotice, } from "./utils/context.js";
|
|
33
33
|
import { formatHistoryForCompression } from "./utils/fork.js";
|
|
34
34
|
import { formatAdaptiveNumber, formatCwd, formatTokenWindow } from "./utils/format.js";
|
|
35
35
|
import { getGitRoot } from "./utils/git.js";
|
|
36
36
|
import { extractAllFencedCodeBlocks, extractAssistantText } from "./utils/messages.js";
|
|
37
|
-
import { listProjectFiles } from "./utils/project_files.js";
|
|
37
|
+
import { listProjectFiles, listProjectFilesAsync } from "./utils/project_files.js";
|
|
38
38
|
const { palette } = theme;
|
|
39
39
|
export class ChatApp {
|
|
40
40
|
ui;
|
|
@@ -45,14 +45,15 @@ export class ChatApp {
|
|
|
45
45
|
personas;
|
|
46
46
|
currentPersona;
|
|
47
47
|
prompts;
|
|
48
|
+
skills;
|
|
48
49
|
bashCommands;
|
|
49
50
|
repoRoot;
|
|
50
51
|
initialUserMessage;
|
|
51
52
|
config;
|
|
52
53
|
assistantComponents = [];
|
|
53
54
|
engine;
|
|
54
|
-
runningBashComponents = new Map();
|
|
55
|
-
runningTaskComponents = new Map();
|
|
55
|
+
runningBashComponents = new Map();
|
|
56
|
+
runningTaskComponents = new Map();
|
|
56
57
|
taskEvents = new Map(); // toolCallId -> accumulated events
|
|
57
58
|
subagentCostTotal = 0;
|
|
58
59
|
isStreaming = false;
|
|
@@ -68,7 +69,9 @@ export class ChatApp {
|
|
|
68
69
|
initialRiskLevel;
|
|
69
70
|
environmentTag;
|
|
70
71
|
projectContextBlock;
|
|
71
|
-
projectFiles;
|
|
72
|
+
projectFiles = [];
|
|
73
|
+
isRefreshingProjectFiles = false;
|
|
74
|
+
isInFileAutocomplete = false;
|
|
72
75
|
agentsFiles;
|
|
73
76
|
baseSystemPrompt;
|
|
74
77
|
pendingRiskLevelChange;
|
|
@@ -77,6 +80,7 @@ export class ChatApp {
|
|
|
77
80
|
constructor(options) {
|
|
78
81
|
this.personas = options.personas;
|
|
79
82
|
this.prompts = options.prompts ?? [];
|
|
83
|
+
this.skills = options.skills ?? [];
|
|
80
84
|
this.bashCommands = options.bashCommands ?? [];
|
|
81
85
|
this.repoRoot = getGitRoot(process.cwd()) ?? process.cwd();
|
|
82
86
|
this.initialUserMessage = options.initialUserMessage;
|
|
@@ -105,6 +109,7 @@ export class ChatApp {
|
|
|
105
109
|
this.clampPersonaReasoning(this.currentPersona);
|
|
106
110
|
this.baseSystemPrompt = buildBaseSystemPrompt({
|
|
107
111
|
personaSystemPrompt: this.currentPersona.systemPrompt,
|
|
112
|
+
skillsBlock: this.getSkillsIndexBlockForPersona(this.currentPersona).skillsBlock,
|
|
108
113
|
projectContextBlock: this.projectContextBlock,
|
|
109
114
|
environmentTag: this.environmentTag,
|
|
110
115
|
userPreferences: this.config.userPreferences,
|
|
@@ -138,7 +143,7 @@ export class ChatApp {
|
|
|
138
143
|
this.ui.addChild(this.editor);
|
|
139
144
|
this.ui.addChild(this.footer);
|
|
140
145
|
const headerText = `\n${palette.accent("tau")} ${palette.muted("– terminal chat")}\n\n` +
|
|
141
|
-
palette.muted(buildHelpText(this.agentsFiles));
|
|
146
|
+
palette.muted(buildHelpText(this.agentsFiles, this.skills));
|
|
142
147
|
this.chatContainer.addMessage(new Text(headerText, 1, 0));
|
|
143
148
|
this.ui.setFocus(this.editor);
|
|
144
149
|
this.updateFooter();
|
|
@@ -168,9 +173,15 @@ export class ChatApp {
|
|
|
168
173
|
this.editor.onChange = (text) => {
|
|
169
174
|
const wasBash = this.isBashMode;
|
|
170
175
|
const wasMemory = this.isMemoryMode;
|
|
176
|
+
const wasInFileAutocomplete = this.isInFileAutocomplete;
|
|
171
177
|
const trimmed = text.trimStart();
|
|
172
178
|
this.isBashMode = trimmed.startsWith("!");
|
|
173
179
|
this.isMemoryMode = trimmed.startsWith("#");
|
|
180
|
+
const beforeCursor = this.getEditorTextBeforeCursor();
|
|
181
|
+
this.isInFileAutocomplete = Boolean(getFileAutocompleteToken(beforeCursor));
|
|
182
|
+
if (!wasInFileAutocomplete && this.isInFileAutocomplete) {
|
|
183
|
+
this.refreshProjectFilesInBackground();
|
|
184
|
+
}
|
|
174
185
|
if (wasBash !== this.isBashMode || wasMemory !== this.isMemoryMode) {
|
|
175
186
|
this.updateEditorBorderColor();
|
|
176
187
|
}
|
|
@@ -181,6 +192,27 @@ export class ChatApp {
|
|
|
181
192
|
})), () => this.projectFiles));
|
|
182
193
|
this.editor.onSubmit = (text) => this.handleSubmit(text);
|
|
183
194
|
}
|
|
195
|
+
getEditorTextBeforeCursor() {
|
|
196
|
+
const { line, col } = this.editor.getCursor();
|
|
197
|
+
const lines = this.editor.getLines();
|
|
198
|
+
const current = lines[line] ?? "";
|
|
199
|
+
return current.slice(0, col);
|
|
200
|
+
}
|
|
201
|
+
refreshProjectFilesInBackground() {
|
|
202
|
+
if (this.isRefreshingProjectFiles)
|
|
203
|
+
return;
|
|
204
|
+
this.isRefreshingProjectFiles = true;
|
|
205
|
+
void listProjectFilesAsync(process.cwd())
|
|
206
|
+
.then((files) => {
|
|
207
|
+
this.projectFiles = files;
|
|
208
|
+
})
|
|
209
|
+
.catch(() => {
|
|
210
|
+
// Ignore refresh errors; autocomplete will keep using the existing cache.
|
|
211
|
+
})
|
|
212
|
+
.finally(() => {
|
|
213
|
+
this.isRefreshingProjectFiles = false;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
184
216
|
async start() {
|
|
185
217
|
this.ui.start();
|
|
186
218
|
if (this.initialUserMessage) {
|
|
@@ -332,6 +364,34 @@ export class ChatApp {
|
|
|
332
364
|
persona.settings.reasoning = allowed[0];
|
|
333
365
|
}
|
|
334
366
|
}
|
|
367
|
+
getSkillsIndexBlockForPersona(persona) {
|
|
368
|
+
const enabled = persona.skills;
|
|
369
|
+
if (!enabled) {
|
|
370
|
+
return { unknown: [] };
|
|
371
|
+
}
|
|
372
|
+
const skillsByName = new Map();
|
|
373
|
+
for (const skill of this.skills) {
|
|
374
|
+
skillsByName.set(skill.name.toLowerCase(), skill);
|
|
375
|
+
}
|
|
376
|
+
const selected = [];
|
|
377
|
+
const unknown = [];
|
|
378
|
+
const seen = new Set();
|
|
379
|
+
const enabledArray = enabled === "*" ? Array.from(skillsByName.keys()) : enabled;
|
|
380
|
+
for (const name of enabledArray) {
|
|
381
|
+
const key = name.toLowerCase();
|
|
382
|
+
if (seen.has(key))
|
|
383
|
+
continue;
|
|
384
|
+
seen.add(key);
|
|
385
|
+
const skill = skillsByName.get(key);
|
|
386
|
+
if (skill) {
|
|
387
|
+
selected.push(skill);
|
|
388
|
+
}
|
|
389
|
+
else if (enabled !== "*") {
|
|
390
|
+
unknown.push(name);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return { skillsBlock: buildSkillsIndexBlock(selected), unknown };
|
|
394
|
+
}
|
|
335
395
|
// User Actions ----------------------------------------------------------------------------------
|
|
336
396
|
toggleThinkingVisibility() {
|
|
337
397
|
this.showThinking = !this.showThinking;
|
|
@@ -549,7 +609,7 @@ export class ChatApp {
|
|
|
549
609
|
}
|
|
550
610
|
}
|
|
551
611
|
showHelp() {
|
|
552
|
-
this.addSystemMessage(buildHelpText(this.agentsFiles), palette.muted);
|
|
612
|
+
this.addSystemMessage(buildHelpText(this.agentsFiles, this.skills), palette.muted);
|
|
553
613
|
}
|
|
554
614
|
async copyLastAssistantMessage() {
|
|
555
615
|
const lastAssistant = this.getLastAssistantMessage();
|
|
@@ -672,6 +732,7 @@ export class ChatApp {
|
|
|
672
732
|
});
|
|
673
733
|
this.baseSystemPrompt = buildBaseSystemPrompt({
|
|
674
734
|
personaSystemPrompt: this.currentPersona.systemPrompt,
|
|
735
|
+
skillsBlock: this.getSkillsIndexBlockForPersona(this.currentPersona).skillsBlock,
|
|
675
736
|
projectContextBlock: this.projectContextBlock,
|
|
676
737
|
environmentTag: this.environmentTag,
|
|
677
738
|
previousSessionSummary,
|
|
@@ -823,8 +884,10 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
823
884
|
}
|
|
824
885
|
this.currentPersona = persona;
|
|
825
886
|
this.clampPersonaReasoning(this.currentPersona);
|
|
887
|
+
const skillsContext = this.getSkillsIndexBlockForPersona(this.currentPersona);
|
|
826
888
|
this.baseSystemPrompt = buildBaseSystemPrompt({
|
|
827
889
|
personaSystemPrompt: this.currentPersona.systemPrompt,
|
|
890
|
+
skillsBlock: skillsContext.skillsBlock,
|
|
828
891
|
projectContextBlock: this.projectContextBlock,
|
|
829
892
|
environmentTag: this.environmentTag,
|
|
830
893
|
previousSessionSummary: this.previousSessionSummary,
|
|
@@ -833,6 +896,9 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
833
896
|
this.engine.setPersona(this.currentPersona, this.baseSystemPrompt);
|
|
834
897
|
this.updateFooter();
|
|
835
898
|
this.updateEditorBorderColor();
|
|
899
|
+
if (skillsContext.unknown.length > 0) {
|
|
900
|
+
this.addSystemMessage(`warning: unknown skills enabled: ${skillsContext.unknown.join(", ")}`, palette.noticeWarn);
|
|
901
|
+
}
|
|
836
902
|
this.addSystemMessage(`switched to ${persona.label} (${persona.model.id})`, palette.noticeSuccess);
|
|
837
903
|
}
|
|
838
904
|
insertPrompt(id) {
|
|
@@ -859,12 +925,13 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
859
925
|
}
|
|
860
926
|
try {
|
|
861
927
|
const result = await loadAllContent();
|
|
862
|
-
const { personas, prompts, errors } = result;
|
|
928
|
+
const { personas, prompts, skills, errors } = result;
|
|
863
929
|
const bashResult = loadBashCommands(process.cwd());
|
|
864
930
|
this.bashCommands = bashResult.commands;
|
|
865
931
|
// Update the personas and prompts lists
|
|
866
932
|
this.personas = personas;
|
|
867
933
|
this.prompts = prompts;
|
|
934
|
+
this.skills = skills;
|
|
868
935
|
// Try to preserve the current persona; fall back to first if not found
|
|
869
936
|
const currentPersonaId = this.currentPersona.id.toLowerCase();
|
|
870
937
|
const updatedPersona = personas.find((p) => p.id.toLowerCase() === currentPersonaId);
|
|
@@ -879,25 +946,31 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
879
946
|
this.addSystemMessage(`previous persona no longer available; switched to ${this.currentPersona.label || this.currentPersona.id}.`, palette.noticeWarn);
|
|
880
947
|
}
|
|
881
948
|
// Rebuild system prompt and update the engine
|
|
949
|
+
const skillsContext = this.getSkillsIndexBlockForPersona(this.currentPersona);
|
|
882
950
|
this.baseSystemPrompt = buildBaseSystemPrompt({
|
|
883
951
|
personaSystemPrompt: this.currentPersona.systemPrompt,
|
|
952
|
+
skillsBlock: skillsContext.skillsBlock,
|
|
884
953
|
projectContextBlock: this.projectContextBlock,
|
|
885
954
|
environmentTag: this.environmentTag,
|
|
886
955
|
previousSessionSummary: this.previousSessionSummary,
|
|
887
956
|
userPreferences: this.config.userPreferences,
|
|
888
957
|
});
|
|
889
958
|
this.engine.setPersona(this.currentPersona, this.baseSystemPrompt);
|
|
959
|
+
if (skillsContext.unknown.length > 0) {
|
|
960
|
+
this.addSystemMessage(`warning: unknown skills enabled: ${skillsContext.unknown.join(", ")}`, palette.noticeWarn);
|
|
961
|
+
}
|
|
890
962
|
// Update UI
|
|
891
963
|
this.updateFooter();
|
|
892
964
|
this.updateEditorBorderColor();
|
|
893
965
|
// Display summary
|
|
894
966
|
const personaCount = personas.length;
|
|
895
967
|
const promptCount = prompts.length;
|
|
968
|
+
const skillCount = skills.length;
|
|
896
969
|
const bashCount = bashResult.commands.length;
|
|
897
970
|
const errorCount = errors.length + bashResult.errors.length;
|
|
898
971
|
const summary = errorCount > 0
|
|
899
|
-
? `reloaded: ${personaCount} personas, ${promptCount} prompts, ${bashCount} bash commands (${errorCount} errors).`
|
|
900
|
-
: `reloaded: ${personaCount} personas, ${promptCount} prompts, ${bashCount} bash commands.`;
|
|
972
|
+
? `reloaded: ${personaCount} personas, ${promptCount} prompts, ${skillCount} skills, ${bashCount} bash commands (${errorCount} errors).`
|
|
973
|
+
: `reloaded: ${personaCount} personas, ${promptCount} prompts, ${skillCount} skills, ${bashCount} bash commands.`;
|
|
901
974
|
this.addSystemMessage(summary, palette.noticeSuccess);
|
|
902
975
|
this.ui.requestRender();
|
|
903
976
|
}
|
|
@@ -973,15 +1046,18 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
973
1046
|
if (uiEvent.type === "bash_started") {
|
|
974
1047
|
// Create and add the running component, storing its index
|
|
975
1048
|
const index = this.chatContainer.addToolMessage((compact) => renderBashRunning(uiEvent.command, compact));
|
|
976
|
-
this.runningBashComponents.set(uiEvent.toolCallId,
|
|
1049
|
+
this.runningBashComponents.set(uiEvent.toolCallId, {
|
|
1050
|
+
index,
|
|
1051
|
+
command: uiEvent.command,
|
|
1052
|
+
});
|
|
977
1053
|
this.ui.requestRender();
|
|
978
1054
|
}
|
|
979
1055
|
else if (uiEvent.type === "bash_execution") {
|
|
980
1056
|
// Check if we have a running component for this toolCallId
|
|
981
|
-
const
|
|
982
|
-
if (
|
|
1057
|
+
const running = this.runningBashComponents.get(uiEvent.toolCallId);
|
|
1058
|
+
if (running) {
|
|
983
1059
|
// Replace the running component with the finished execution component
|
|
984
|
-
this.chatContainer.replaceToolMessageAtIndex(
|
|
1060
|
+
this.chatContainer.replaceToolMessageAtIndex(running.index, (compact) => renderBashExecution(uiEvent.command, uiEvent.exitCode, uiEvent.truncationInfo, compact));
|
|
985
1061
|
this.runningBashComponents.delete(uiEvent.toolCallId);
|
|
986
1062
|
}
|
|
987
1063
|
else {
|
|
@@ -993,10 +1069,10 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
993
1069
|
else if (uiEvent.type === "bash_blocked") {
|
|
994
1070
|
// Check if this is a post-acceptance failure that has a running card
|
|
995
1071
|
if (uiEvent.toolCallId) {
|
|
996
|
-
const
|
|
997
|
-
if (
|
|
1072
|
+
const running = this.runningBashComponents.get(uiEvent.toolCallId);
|
|
1073
|
+
if (running) {
|
|
998
1074
|
// Replace the running component with the blocked component
|
|
999
|
-
this.chatContainer.replaceToolMessageAtIndex(
|
|
1075
|
+
this.chatContainer.replaceToolMessageAtIndex(running.index, (compact) => renderBashBlocked(uiEvent.command, uiEvent.reason, compact));
|
|
1000
1076
|
this.runningBashComponents.delete(uiEvent.toolCallId);
|
|
1001
1077
|
}
|
|
1002
1078
|
else {
|
|
@@ -1015,7 +1091,14 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
1015
1091
|
this.taskEvents.set(uiEvent.toolCallId, []);
|
|
1016
1092
|
}
|
|
1017
1093
|
const index = this.chatContainer.addToolMessage((compact) => renderTaskRunning(uiEvent.title, [], 0, 0, 0, compact, uiEvent.name));
|
|
1018
|
-
this.runningTaskComponents.set(uiEvent.toolCallId,
|
|
1094
|
+
this.runningTaskComponents.set(uiEvent.toolCallId, {
|
|
1095
|
+
index,
|
|
1096
|
+
name: uiEvent.name,
|
|
1097
|
+
title: uiEvent.title,
|
|
1098
|
+
costTotal: 0,
|
|
1099
|
+
turns: 0,
|
|
1100
|
+
toolCalls: 0,
|
|
1101
|
+
});
|
|
1019
1102
|
this.ui.requestRender();
|
|
1020
1103
|
}
|
|
1021
1104
|
else if (uiEvent.type === "task_progress") {
|
|
@@ -1026,20 +1109,32 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
1026
1109
|
this.taskEvents.set(uiEvent.toolCallId, events);
|
|
1027
1110
|
}
|
|
1028
1111
|
events.push(uiEvent.event);
|
|
1029
|
-
const
|
|
1030
|
-
if (
|
|
1031
|
-
|
|
1112
|
+
const running = this.runningTaskComponents.get(uiEvent.toolCallId);
|
|
1113
|
+
if (running) {
|
|
1114
|
+
running.name = uiEvent.name;
|
|
1115
|
+
running.title = uiEvent.title;
|
|
1116
|
+
running.costTotal = uiEvent.costTotal;
|
|
1117
|
+
running.turns = uiEvent.turns;
|
|
1118
|
+
running.toolCalls = uiEvent.toolCalls;
|
|
1119
|
+
this.chatContainer.replaceToolMessageAtIndex(running.index, (compact) => renderTaskRunning(uiEvent.title, events, uiEvent.costTotal, uiEvent.turns, uiEvent.toolCalls, compact, uiEvent.name));
|
|
1032
1120
|
}
|
|
1033
1121
|
else {
|
|
1034
1122
|
const index = this.chatContainer.addToolMessage((compact) => renderTaskRunning(uiEvent.title, events, uiEvent.costTotal, uiEvent.turns, uiEvent.toolCalls, compact, uiEvent.name));
|
|
1035
|
-
this.runningTaskComponents.set(uiEvent.toolCallId,
|
|
1123
|
+
this.runningTaskComponents.set(uiEvent.toolCallId, {
|
|
1124
|
+
index,
|
|
1125
|
+
name: uiEvent.name,
|
|
1126
|
+
title: uiEvent.title,
|
|
1127
|
+
costTotal: uiEvent.costTotal,
|
|
1128
|
+
turns: uiEvent.turns,
|
|
1129
|
+
toolCalls: uiEvent.toolCalls,
|
|
1130
|
+
});
|
|
1036
1131
|
}
|
|
1037
1132
|
this.ui.requestRender();
|
|
1038
1133
|
}
|
|
1039
1134
|
else if (uiEvent.type === "task_finished") {
|
|
1040
|
-
const
|
|
1041
|
-
if (
|
|
1042
|
-
this.chatContainer.replaceToolMessageAtIndex(
|
|
1135
|
+
const running = this.runningTaskComponents.get(uiEvent.toolCallId);
|
|
1136
|
+
if (running) {
|
|
1137
|
+
this.chatContainer.replaceToolMessageAtIndex(running.index, (compact) => renderTaskFinished(uiEvent.title, uiEvent.costTotal, uiEvent.turns, uiEvent.toolCalls, uiEvent.status, uiEvent.finalOutput, compact, uiEvent.name));
|
|
1043
1138
|
this.runningTaskComponents.delete(uiEvent.toolCallId);
|
|
1044
1139
|
}
|
|
1045
1140
|
else {
|
|
@@ -1051,9 +1146,9 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
1051
1146
|
this.ui.requestRender();
|
|
1052
1147
|
}
|
|
1053
1148
|
else if (uiEvent.type === "task_blocked") {
|
|
1054
|
-
const
|
|
1055
|
-
if (
|
|
1056
|
-
this.chatContainer.replaceToolMessageAtIndex(
|
|
1149
|
+
const running = this.runningTaskComponents.get(uiEvent.toolCallId);
|
|
1150
|
+
if (running) {
|
|
1151
|
+
this.chatContainer.replaceToolMessageAtIndex(running.index, (compact) => renderTaskBlocked(uiEvent.title, uiEvent.reason, compact, uiEvent.name));
|
|
1057
1152
|
this.runningTaskComponents.delete(uiEvent.toolCallId);
|
|
1058
1153
|
}
|
|
1059
1154
|
else {
|
|
@@ -1098,6 +1193,15 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
|
|
|
1098
1193
|
this.addSystemMessage(`error: ${err.message}`, palette.noticeError);
|
|
1099
1194
|
}
|
|
1100
1195
|
finally {
|
|
1196
|
+
const wasAborted = this.currentTurnAbort?.signal.aborted ?? false;
|
|
1197
|
+
const reason = wasAborted ? "aborted" : "interrupted";
|
|
1198
|
+
for (const running of this.runningBashComponents.values()) {
|
|
1199
|
+
this.chatContainer.replaceToolMessageAtIndex(running.index, (compact) => renderBashAborted(running.command, reason, compact));
|
|
1200
|
+
}
|
|
1201
|
+
const taskStatus = wasAborted ? "aborted" : "error";
|
|
1202
|
+
for (const running of this.runningTaskComponents.values()) {
|
|
1203
|
+
this.chatContainer.replaceToolMessageAtIndex(running.index, (compact) => renderTaskFinished(running.title, running.costTotal, running.turns, running.toolCalls, taskStatus, reason, compact, running.name));
|
|
1204
|
+
}
|
|
1101
1205
|
this.footer.stop();
|
|
1102
1206
|
this.isStreaming = false;
|
|
1103
1207
|
this.currentTurnAbort = undefined;
|