@markusylisiurunen/tau 0.1.38 → 0.1.39

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/dist/app.js CHANGED
@@ -20,22 +20,16 @@ import { ToolRegistry } from "./tools/registry.js";
20
20
  import { createTaskToolDefinition } from "./tools/task.js";
21
21
  import { createWriteToolDefinition } from "./tools/write.js";
22
22
  import { REASONING_LEVELS, } from "./types.js";
23
- import { AppIntroComponent } from "./ui/app_intro.js";
24
- import { AssistantMessageComponent } from "./ui/assistant_message.js";
25
- import { renderBashAborted, renderBashBlocked, renderBashExecution, renderBashRunning, } from "./ui/bash_execution.js";
23
+ import { buildBashAbortedView, buildBashBlockedView, buildBashExecutionView, buildBashRunningView, } from "./ui/bash_execution.js";
26
24
  import { ChatContainerComponent } from "./ui/chat_container.js";
27
25
  import { CustomEditor } from "./ui/custom_editor.js";
28
- import { renderEditBlocked, renderEditSuccess, renderWriteBlocked, renderWriteSuccess, } from "./ui/file_execution.js";
26
+ import { buildEditBlockedView, buildEditSuccessView, buildWriteBlockedView, buildWriteSuccessView, } from "./ui/file_execution.js";
29
27
  import { FooterComponent } from "./ui/footer.js";
30
28
  import { QueuedMessagesComponent } from "./ui/queued_messages.js";
31
- import { renderGrepBlocked, renderGrepFinished, renderGrepRunning, renderListBlocked, renderListSuccess, renderReadBlocked, renderReadSuccess, } from "./ui/restricted_execution.js";
32
- import { SessionDividerComponent } from "./ui/session_divider.js";
33
- import { SessionSummaryComponent } from "./ui/session_summary.js";
29
+ import { buildGrepBlockedView, buildGrepFinishedView, buildGrepRunningView, buildListBlockedView, buildListSuccessView, buildReadBlockedView, buildReadSuccessView, } from "./ui/restricted_execution.js";
34
30
  import { getFileAutocompleteToken, SlashAutocompleteProvider } from "./ui/slash_autocomplete.js";
35
- import { SystemMessageComponent } from "./ui/system_message.js";
36
- import { renderTaskBlocked, renderTaskFinished, renderTaskRunning } from "./ui/task_execution.js";
31
+ import { buildTaskBlockedView, buildTaskFinishedView, buildTaskRunningView, } from "./ui/task_execution.js";
37
32
  import { createUiTheme } from "./ui/theme.js";
38
- import { UserMessageComponent } from "./ui/user_message.js";
39
33
  import { buildBaseSystemPrompt, buildEnvironmentTag, buildProjectContextBlock, buildSkillsIndexBlock, findAgentsFilesFromCwdToHome, formatRiskLevelChangeNotice, } from "./utils/context.js";
40
34
  import { formatHistoryForCompression } from "./utils/fork.js";
41
35
  import { formatAdaptiveNumber, formatCwd, formatTokenWindow } from "./utils/format.js";
@@ -58,7 +52,6 @@ export class ChatApp {
58
52
  repoRoot;
59
53
  initialUserMessage;
60
54
  config;
61
- assistantComponents = [];
62
55
  engine;
63
56
  runningBashComponents = new Map();
64
57
  runningTaskComponents = new Map();
@@ -142,7 +135,7 @@ export class ChatApp {
142
135
  });
143
136
  this.uiTheme = createUiTheme("ansi");
144
137
  this.ui = new TUI(createAppTerminal());
145
- this.chatContainer = new ChatContainerComponent();
138
+ this.chatContainer = new ChatContainerComponent(this.uiTheme);
146
139
  this.chatContainer.setCompactToolUi(this.compactToolUi);
147
140
  this.footer = new FooterComponent(this.uiTheme, this.ui);
148
141
  this.queuedMessages = new QueuedMessagesComponent(this.uiTheme, this.queuedUserMessages);
@@ -156,7 +149,12 @@ export class ChatApp {
156
149
  this.ui.addChild(this.queuedMessages);
157
150
  this.ui.addChild(this.editor);
158
151
  this.ui.addChild(this.footer);
159
- this.chatContainer.addMessage(new AppIntroComponent(this.uiTheme, "tau", APP_VERSION, buildHelpText(this.agentsFiles, this.skills)));
152
+ this.chatContainer.addMessage({
153
+ type: "app_intro",
154
+ appName: "tau",
155
+ version: APP_VERSION,
156
+ helpText: buildHelpText(this.agentsFiles, this.skills),
157
+ });
160
158
  this.ui.setFocus(this.editor);
161
159
  this.updateFooter();
162
160
  this.updateEditorBorderColor();
@@ -267,16 +265,15 @@ export class ChatApp {
267
265
  this.ui.requestRender();
268
266
  }
269
267
  addSystemMessage(text, kind) {
270
- this.chatContainer.addMessage(new SystemMessageComponent(this.uiTheme, text, kind));
268
+ this.chatContainer.addMessage({ type: "system", text, kind });
271
269
  this.ui.requestRender();
272
270
  }
273
271
  addUserMessage(text, opts) {
274
- this.chatContainer.addMessage(new UserMessageComponent(this.uiTheme, text, opts));
275
- this.ui.requestRender();
276
- }
277
- addAssistantComponent(component) {
278
- this.chatContainer.addMessage(component);
279
- this.assistantComponents.push(component);
272
+ this.chatContainer.addMessage({
273
+ type: "user",
274
+ text,
275
+ isMemoryMode: opts?.isMemoryMode,
276
+ });
280
277
  this.ui.requestRender();
281
278
  }
282
279
  // Context & Cost Tracking -----------------------------------------------------------------------
@@ -442,9 +439,6 @@ export class ChatApp {
442
439
  // User Actions ----------------------------------------------------------------------------------
443
440
  toggleThinkingVisibility() {
444
441
  this.showThinking = !this.showThinking;
445
- this.assistantComponents.forEach((c) => {
446
- c.setThinkingVisibility(this.showThinking);
447
- });
448
442
  this.chatContainer.setThinkingVisibility(this.showThinking);
449
443
  const message = this.showThinking
450
444
  ? "thoughts visible (ctrl+t to hide)"
@@ -699,13 +693,12 @@ export class ChatApp {
699
693
  }
700
694
  clearSession() {
701
695
  this.engine.reset();
702
- this.assistantComponents = [];
703
696
  this.runningBashComponents.clear();
704
697
  this.runningTaskComponents.clear();
705
698
  this.taskEvents.clear();
706
699
  this.subagentCostTotal = 0;
707
700
  this.expandedFilesInCurrentPrompt.clear();
708
- this.chatContainer.addMessage(new SessionDividerComponent(this.uiTheme, "new session"));
701
+ this.chatContainer.addMessage({ type: "session_divider", label: "new session" });
709
702
  this.isBashMode = false;
710
703
  this.isMemoryMode = false;
711
704
  this.previousSessionSummary = undefined;
@@ -835,14 +828,16 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
835
828
  this.previousSessionSummary = previousSessionContext;
836
829
  // Reset the session state but preserve history with divider and summary
837
830
  this.engine.reset();
838
- this.assistantComponents = [];
839
831
  this.runningBashComponents.clear();
840
832
  this.runningTaskComponents.clear();
841
833
  this.taskEvents.clear();
842
834
  this.subagentCostTotal = 0;
843
835
  this.expandedFilesInCurrentPrompt.clear();
844
- this.chatContainer.addMessage(new SessionDividerComponent(this.uiTheme, "new session"));
845
- this.chatContainer.addMessage(new SessionSummaryComponent(this.uiTheme, this.previousSessionSummary));
836
+ this.chatContainer.addMessage({ type: "session_divider", label: "new session" });
837
+ this.chatContainer.addMessage({
838
+ type: "session_summary",
839
+ summary: this.previousSessionSummary,
840
+ });
846
841
  this.isBashMode = false;
847
842
  this.isMemoryMode = false;
848
843
  // Rebuild environment tag and system prompt with the new summary and current risk level
@@ -1045,18 +1040,16 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1045
1040
  if (currentAssistant)
1046
1041
  return currentAssistant;
1047
1042
  currentAssistant = {
1048
- component: new AssistantMessageComponent(this.uiTheme, undefined, this.showThinking),
1049
1043
  inserted: false,
1044
+ model: { type: "assistant_partial", text: "", thinking: "" },
1050
1045
  };
1051
1046
  return currentAssistant;
1052
1047
  };
1053
- const ensureAssistantInserted = () => {
1054
- const state = ensureCurrentAssistant();
1048
+ const ensureAssistantInserted = (state) => {
1055
1049
  if (state.inserted)
1056
1050
  return;
1057
1051
  state.inserted = true;
1058
- this.addAssistantComponent(state.component);
1059
- state.component.setThinkingVisibility(this.showThinking);
1052
+ state.id = this.chatContainer.addMessage(state.model);
1060
1053
  };
1061
1054
  for await (const event of this.engine.processTurn(this.currentTurnAbort.signal)) {
1062
1055
  if (this.currentTurnAbort.signal.aborted)
@@ -1064,34 +1057,39 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1064
1057
  switch (event.type) {
1065
1058
  case "assistant_start":
1066
1059
  currentAssistant = {
1067
- component: new AssistantMessageComponent(this.uiTheme, undefined, this.showThinking),
1068
1060
  inserted: false,
1061
+ model: { type: "assistant_partial", text: "", thinking: "" },
1069
1062
  };
1070
1063
  break;
1071
1064
  case "assistant_partial": {
1072
1065
  const state = ensureCurrentAssistant();
1073
1066
  const { snapshot } = event;
1067
+ const model = {
1068
+ type: "assistant_partial",
1069
+ text: snapshot.hasTextStarted ? snapshot.text : "",
1070
+ thinking: snapshot.thinking,
1071
+ };
1072
+ state.model = model;
1074
1073
  const shouldInsert = snapshot.hasTextStarted || (this.showThinking && snapshot.hasAnyThinking);
1075
1074
  if (shouldInsert && !state.inserted) {
1076
- ensureAssistantInserted();
1075
+ ensureAssistantInserted(state);
1077
1076
  }
1078
- if (state.inserted) {
1079
- // Capture visibility state before update
1080
- const wasVisible = state.component.hasVisibleText;
1081
- state.component.updatePartial(snapshot.hasTextStarted ? snapshot.text : "", snapshot.thinking);
1082
- // If component became visible (e.g. text started after thoughts were hidden),
1083
- // rebuild the container to show it
1084
- if (!wasVisible && state.component.hasVisibleText) {
1085
- this.chatContainer.rebuild();
1086
- }
1077
+ if (state.inserted && state.id) {
1078
+ this.chatContainer.updateMessage(state.id, model);
1087
1079
  this.ui.requestRender();
1088
1080
  }
1089
1081
  break;
1090
1082
  }
1091
1083
  case "assistant_final": {
1092
- ensureAssistantInserted();
1093
- ensureCurrentAssistant().component.updateFromMessage(event.message);
1094
- this.chatContainer.rebuild();
1084
+ const state = ensureCurrentAssistant();
1085
+ const model = { type: "assistant", message: event.message };
1086
+ state.model = model;
1087
+ if (!state.inserted) {
1088
+ ensureAssistantInserted(state);
1089
+ }
1090
+ if (state.id) {
1091
+ this.chatContainer.updateMessage(state.id, model);
1092
+ }
1095
1093
  this.updateFooter();
1096
1094
  this.ui.requestRender();
1097
1095
  currentAssistant = undefined;
@@ -1100,7 +1098,10 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1100
1098
  case "tool_ui": {
1101
1099
  const uiEvent = event.uiEvent;
1102
1100
  if (uiEvent.type === "bash_started") {
1103
- this.chatContainer.addToolMessage((compact) => renderBashRunning(this.uiTheme, uiEvent.command, compact), uiEvent.toolCallId);
1101
+ this.chatContainer.addMessage({
1102
+ type: "tool",
1103
+ view: buildBashRunningView(this.uiTheme, uiEvent.command),
1104
+ }, uiEvent.toolCallId);
1104
1105
  this.runningBashComponents.set(uiEvent.toolCallId, {
1105
1106
  command: uiEvent.command,
1106
1107
  });
@@ -1108,7 +1109,10 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1108
1109
  }
1109
1110
  else if (uiEvent.type === "bash_execution") {
1110
1111
  const running = this.runningBashComponents.get(uiEvent.toolCallId);
1111
- this.chatContainer.replaceToolMessage(uiEvent.toolCallId, (compact) => renderBashExecution(this.uiTheme, uiEvent.command, uiEvent.exitCode, uiEvent.truncationInfo, compact));
1112
+ this.chatContainer.replaceMessage(uiEvent.toolCallId, {
1113
+ type: "tool",
1114
+ view: buildBashExecutionView(this.uiTheme, uiEvent.command, uiEvent.exitCode, uiEvent.truncationInfo),
1115
+ });
1112
1116
  if (running) {
1113
1117
  this.runningBashComponents.delete(uiEvent.toolCallId);
1114
1118
  }
@@ -1117,13 +1121,19 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1117
1121
  else if (uiEvent.type === "bash_blocked") {
1118
1122
  if (uiEvent.toolCallId) {
1119
1123
  const running = this.runningBashComponents.get(uiEvent.toolCallId);
1120
- this.chatContainer.replaceToolMessage(uiEvent.toolCallId, (compact) => renderBashBlocked(this.uiTheme, uiEvent.command, uiEvent.reason, compact));
1124
+ this.chatContainer.replaceMessage(uiEvent.toolCallId, {
1125
+ type: "tool",
1126
+ view: buildBashBlockedView(this.uiTheme, uiEvent.command, uiEvent.reason),
1127
+ });
1121
1128
  if (running) {
1122
1129
  this.runningBashComponents.delete(uiEvent.toolCallId);
1123
1130
  }
1124
1131
  }
1125
1132
  else {
1126
- this.chatContainer.addToolMessage((compact) => renderBashBlocked(this.uiTheme, uiEvent.command, uiEvent.reason, compact));
1133
+ this.chatContainer.addMessage({
1134
+ type: "tool",
1135
+ view: buildBashBlockedView(this.uiTheme, uiEvent.command, uiEvent.reason),
1136
+ });
1127
1137
  }
1128
1138
  this.ui.requestRender();
1129
1139
  }
@@ -1133,10 +1143,13 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1133
1143
  }
1134
1144
  const kind = uiEvent.kind ?? "task";
1135
1145
  const subagentName = uiEvent.name.trim() || undefined;
1136
- this.chatContainer.addToolMessage((compact) => renderTaskRunning(this.uiTheme, uiEvent.title, [], 0, 0, 0, compact, {
1137
- kind,
1138
- subagentName,
1139
- }), uiEvent.toolCallId);
1146
+ this.chatContainer.addMessage({
1147
+ type: "tool",
1148
+ view: buildTaskRunningView(this.uiTheme, uiEvent.title, [], 0, 0, 0, {
1149
+ kind,
1150
+ subagentName,
1151
+ }),
1152
+ }, uiEvent.toolCallId);
1140
1153
  this.runningTaskComponents.set(uiEvent.toolCallId, {
1141
1154
  kind,
1142
1155
  name: subagentName,
@@ -1165,14 +1178,20 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1165
1178
  running.turns = uiEvent.turns;
1166
1179
  running.toolCalls = uiEvent.toolCalls;
1167
1180
  }
1168
- this.chatContainer.replaceToolMessage(uiEvent.toolCallId, (compact) => renderTaskRunning(this.uiTheme, uiEvent.title, events, uiEvent.costTotal, uiEvent.turns, uiEvent.toolCalls, compact, { kind, subagentName }));
1181
+ this.chatContainer.replaceMessage(uiEvent.toolCallId, {
1182
+ type: "tool",
1183
+ view: buildTaskRunningView(this.uiTheme, uiEvent.title, events, uiEvent.costTotal, uiEvent.turns, uiEvent.toolCalls, { kind, subagentName }),
1184
+ });
1169
1185
  this.ui.requestRender();
1170
1186
  }
1171
1187
  else if (uiEvent.type === "task_finished") {
1172
1188
  const running = this.runningTaskComponents.get(uiEvent.toolCallId);
1173
1189
  const kind = uiEvent.kind ?? running?.kind ?? "task";
1174
1190
  const subagentName = uiEvent.name.trim() || undefined;
1175
- this.chatContainer.replaceToolMessage(uiEvent.toolCallId, (compact) => renderTaskFinished(this.uiTheme, uiEvent.title, uiEvent.costTotal, uiEvent.turns, uiEvent.toolCalls, uiEvent.status, uiEvent.finalOutput, compact, { kind, subagentName }));
1191
+ this.chatContainer.replaceMessage(uiEvent.toolCallId, {
1192
+ type: "tool",
1193
+ view: buildTaskFinishedView(this.uiTheme, uiEvent.title, uiEvent.costTotal, uiEvent.turns, uiEvent.toolCalls, uiEvent.status, uiEvent.finalOutput, { kind, subagentName }),
1194
+ });
1176
1195
  this.runningTaskComponents.delete(uiEvent.toolCallId);
1177
1196
  this.taskEvents.delete(uiEvent.toolCallId);
1178
1197
  this.subagentCostTotal += uiEvent.costTotal;
@@ -1184,63 +1203,102 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1184
1203
  const kind = uiEvent.kind ?? running?.kind ?? "task";
1185
1204
  const subagentName = uiEvent.name?.trim() || undefined;
1186
1205
  if (running) {
1187
- this.chatContainer.replaceToolMessage(uiEvent.toolCallId, (compact) => renderTaskBlocked(this.uiTheme, uiEvent.title, uiEvent.reason, compact, {
1188
- kind,
1189
- subagentName,
1190
- }));
1206
+ this.chatContainer.replaceMessage(uiEvent.toolCallId, {
1207
+ type: "tool",
1208
+ view: buildTaskBlockedView(this.uiTheme, uiEvent.title, uiEvent.reason, {
1209
+ kind,
1210
+ subagentName,
1211
+ }),
1212
+ });
1191
1213
  }
1192
1214
  else {
1193
- this.chatContainer.addToolMessage((compact) => renderTaskBlocked(this.uiTheme, uiEvent.title, uiEvent.reason, compact, {
1194
- kind,
1195
- subagentName,
1196
- }), uiEvent.toolCallId);
1215
+ this.chatContainer.addMessage({
1216
+ type: "tool",
1217
+ view: buildTaskBlockedView(this.uiTheme, uiEvent.title, uiEvent.reason, {
1218
+ kind,
1219
+ subagentName,
1220
+ }),
1221
+ }, uiEvent.toolCallId);
1197
1222
  }
1198
1223
  this.runningTaskComponents.delete(uiEvent.toolCallId);
1199
1224
  this.taskEvents.delete(uiEvent.toolCallId);
1200
1225
  this.ui.requestRender();
1201
1226
  }
1202
1227
  else if (uiEvent.type === "write_success") {
1203
- this.chatContainer.addToolMessage((compact) => renderWriteSuccess(this.uiTheme, uiEvent.path, uiEvent.bytes, uiEvent.lines, uiEvent.content, compact));
1228
+ this.chatContainer.addMessage({
1229
+ type: "tool",
1230
+ view: buildWriteSuccessView(this.uiTheme, uiEvent.path, uiEvent.bytes, uiEvent.lines, uiEvent.content),
1231
+ });
1204
1232
  this.ui.requestRender();
1205
1233
  }
1206
1234
  else if (uiEvent.type === "write_blocked") {
1207
- this.chatContainer.addToolMessage((compact) => renderWriteBlocked(this.uiTheme, uiEvent.path, uiEvent.reason, compact));
1235
+ this.chatContainer.addMessage({
1236
+ type: "tool",
1237
+ view: buildWriteBlockedView(this.uiTheme, uiEvent.path, uiEvent.reason),
1238
+ });
1208
1239
  this.ui.requestRender();
1209
1240
  }
1210
1241
  else if (uiEvent.type === "edit_success") {
1211
- this.chatContainer.addToolMessage((compact) => renderEditSuccess(this.uiTheme, uiEvent.path, uiEvent.oldLength, uiEvent.newLength, uiEvent.oldText, uiEvent.newText, compact));
1242
+ this.chatContainer.addMessage({
1243
+ type: "tool",
1244
+ view: buildEditSuccessView(this.uiTheme, uiEvent.path, uiEvent.oldLength, uiEvent.newLength, uiEvent.oldText, uiEvent.newText),
1245
+ });
1212
1246
  this.ui.requestRender();
1213
1247
  }
1214
1248
  else if (uiEvent.type === "edit_blocked") {
1215
- this.chatContainer.addToolMessage((compact) => renderEditBlocked(this.uiTheme, uiEvent.path, uiEvent.reason, compact));
1249
+ this.chatContainer.addMessage({
1250
+ type: "tool",
1251
+ view: buildEditBlockedView(this.uiTheme, uiEvent.path, uiEvent.reason),
1252
+ });
1216
1253
  this.ui.requestRender();
1217
1254
  }
1218
1255
  else if (uiEvent.type === "read_success") {
1219
- this.chatContainer.addToolMessage((compact) => renderReadSuccess(this.uiTheme, uiEvent.path, uiEvent.startLine, uiEvent.endLine, uiEvent.content, uiEvent.modelTruncation, compact));
1256
+ this.chatContainer.addMessage({
1257
+ type: "tool",
1258
+ view: buildReadSuccessView(this.uiTheme, uiEvent.path, uiEvent.startLine, uiEvent.endLine, uiEvent.content, uiEvent.modelTruncation),
1259
+ });
1220
1260
  this.ui.requestRender();
1221
1261
  }
1222
1262
  else if (uiEvent.type === "read_blocked") {
1223
- this.chatContainer.addToolMessage((compact) => renderReadBlocked(this.uiTheme, uiEvent.path, uiEvent.reason, compact));
1263
+ this.chatContainer.addMessage({
1264
+ type: "tool",
1265
+ view: buildReadBlockedView(this.uiTheme, uiEvent.path, uiEvent.reason),
1266
+ });
1224
1267
  this.ui.requestRender();
1225
1268
  }
1226
1269
  else if (uiEvent.type === "list_success") {
1227
- this.chatContainer.addToolMessage((compact) => renderListSuccess(this.uiTheme, uiEvent.path, uiEvent.offset, uiEvent.limit, uiEvent.total, uiEvent.returned, uiEvent.entries, compact));
1270
+ this.chatContainer.addMessage({
1271
+ type: "tool",
1272
+ view: buildListSuccessView(this.uiTheme, uiEvent.path, uiEvent.offset, uiEvent.limit, uiEvent.total, uiEvent.returned, uiEvent.entries),
1273
+ });
1228
1274
  this.ui.requestRender();
1229
1275
  }
1230
1276
  else if (uiEvent.type === "list_blocked") {
1231
- this.chatContainer.addToolMessage((compact) => renderListBlocked(this.uiTheme, uiEvent.path, uiEvent.reason, compact));
1277
+ this.chatContainer.addMessage({
1278
+ type: "tool",
1279
+ view: buildListBlockedView(this.uiTheme, uiEvent.path, uiEvent.reason),
1280
+ });
1232
1281
  this.ui.requestRender();
1233
1282
  }
1234
1283
  else if (uiEvent.type === "grep_started") {
1235
- this.chatContainer.addToolMessage((compact) => renderGrepRunning(this.uiTheme, uiEvent.pattern, compact), uiEvent.toolCallId);
1284
+ this.chatContainer.addMessage({
1285
+ type: "tool",
1286
+ view: buildGrepRunningView(this.uiTheme, uiEvent.pattern),
1287
+ }, uiEvent.toolCallId);
1236
1288
  this.ui.requestRender();
1237
1289
  }
1238
1290
  else if (uiEvent.type === "grep_finished") {
1239
- this.chatContainer.replaceToolMessage(uiEvent.toolCallId, (compact) => renderGrepFinished(this.uiTheme, uiEvent.pattern, uiEvent.status, uiEvent.exitCode, uiEvent.stdout, uiEvent.stderr, uiEvent.captureTruncated, compact));
1291
+ this.chatContainer.replaceMessage(uiEvent.toolCallId, {
1292
+ type: "tool",
1293
+ view: buildGrepFinishedView(this.uiTheme, uiEvent.pattern, uiEvent.status, uiEvent.exitCode, uiEvent.stdout, uiEvent.stderr, uiEvent.captureTruncated),
1294
+ });
1240
1295
  this.ui.requestRender();
1241
1296
  }
1242
1297
  else if (uiEvent.type === "grep_blocked") {
1243
- this.chatContainer.addToolMessage((compact) => renderGrepBlocked(this.uiTheme, uiEvent.pattern, uiEvent.reason, compact), uiEvent.toolCallId);
1298
+ this.chatContainer.addMessage({
1299
+ type: "tool",
1300
+ view: buildGrepBlockedView(this.uiTheme, uiEvent.pattern, uiEvent.reason),
1301
+ }, uiEvent.toolCallId);
1244
1302
  this.ui.requestRender();
1245
1303
  }
1246
1304
  break;
@@ -1262,11 +1320,17 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1262
1320
  const wasAborted = this.currentTurnAbort?.signal.aborted ?? false;
1263
1321
  const reason = wasAborted ? "aborted" : "interrupted";
1264
1322
  for (const [id, running] of this.runningBashComponents.entries()) {
1265
- this.chatContainer.replaceToolMessage(id, (compact) => renderBashAborted(this.uiTheme, running.command, reason, compact));
1323
+ this.chatContainer.replaceMessage(id, {
1324
+ type: "tool",
1325
+ view: buildBashAbortedView(this.uiTheme, running.command, reason),
1326
+ });
1266
1327
  }
1267
1328
  const taskStatus = wasAborted ? "aborted" : "error";
1268
1329
  for (const [id, running] of this.runningTaskComponents.entries()) {
1269
- this.chatContainer.replaceToolMessage(id, (compact) => renderTaskFinished(this.uiTheme, running.title, running.costTotal, running.turns, running.toolCalls, taskStatus, reason, compact, { kind: running.kind, subagentName: running.name }));
1330
+ this.chatContainer.replaceMessage(id, {
1331
+ type: "tool",
1332
+ view: buildTaskFinishedView(this.uiTheme, running.title, running.costTotal, running.turns, running.toolCalls, taskStatus, reason, { kind: running.kind, subagentName: running.name }),
1333
+ });
1270
1334
  }
1271
1335
  this.footer.stop();
1272
1336
  this.isStreaming = false;
@@ -1288,7 +1352,10 @@ Write plain prose, no formatting. Be thorough enough that the reader can resume
1288
1352
  stdout: { maxLines: BASH_USER_MAX_STDOUT_LINES, maxTokens: BASH_USER_MAX_STDOUT_TOKENS },
1289
1353
  stderr: { maxLines: BASH_USER_MAX_STDERR_LINES, maxTokens: BASH_USER_MAX_STDERR_TOKENS },
1290
1354
  });
1291
- this.chatContainer.addMessage(renderBashExecution(this.uiTheme, command, exitCode, truncationInfo, false));
1355
+ this.chatContainer.addMessage({
1356
+ type: "tool",
1357
+ view: buildBashExecutionView(this.uiTheme, command, exitCode, truncationInfo),
1358
+ });
1292
1359
  this.engine.addUserText(formatBashUserMessageText({ command, truncationInfo }));
1293
1360
  this.ui.requestRender();
1294
1361
  }