@oh-my-pi/pi-coding-agent 4.3.2 → 4.4.5
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/CHANGELOG.md +43 -0
- package/package.json +5 -6
- package/src/core/frontmatter.ts +98 -0
- package/src/core/keybindings.ts +1 -1
- package/src/core/prompt-templates.ts +5 -34
- package/src/core/sdk.ts +3 -0
- package/src/core/skills.ts +3 -3
- package/src/core/slash-commands.ts +14 -5
- package/src/core/tools/calculator.ts +1 -1
- package/src/core/tools/edit.ts +2 -2
- package/src/core/tools/exa/render.ts +23 -11
- package/src/core/tools/index.test.ts +2 -0
- package/src/core/tools/index.ts +3 -0
- package/src/core/tools/jtd-to-json-schema.ts +1 -6
- package/src/core/tools/ls.ts +5 -2
- package/src/core/tools/lsp/config.ts +2 -2
- package/src/core/tools/lsp/render.ts +33 -12
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/output.ts +1 -1
- package/src/core/tools/read.ts +15 -49
- package/src/core/tools/render-utils.ts +61 -24
- package/src/core/tools/renderers.ts +2 -0
- package/src/core/tools/schema-validation.test.ts +501 -0
- package/src/core/tools/task/agents.ts +6 -2
- package/src/core/tools/task/commands.ts +9 -3
- package/src/core/tools/task/discovery.ts +3 -2
- package/src/core/tools/task/render.ts +10 -7
- package/src/core/tools/todo-write.ts +256 -0
- package/src/core/tools/web-fetch.ts +4 -2
- package/src/core/tools/web-scrapers/choosealicense.ts +2 -2
- package/src/core/tools/web-search/render.ts +13 -10
- package/src/core/tools/write.ts +2 -2
- package/src/discovery/builtin.ts +4 -4
- package/src/discovery/cline.ts +4 -3
- package/src/discovery/codex.ts +3 -3
- package/src/discovery/cursor.ts +2 -2
- package/src/discovery/github.ts +3 -2
- package/src/discovery/helpers.test.ts +14 -10
- package/src/discovery/helpers.ts +2 -39
- package/src/discovery/windsurf.ts +3 -3
- package/src/modes/interactive/components/custom-editor.ts +4 -11
- package/src/modes/interactive/components/index.ts +2 -1
- package/src/modes/interactive/components/read-tool-group.ts +118 -0
- package/src/modes/interactive/components/todo-display.ts +112 -0
- package/src/modes/interactive/components/tool-execution.ts +18 -2
- package/src/modes/interactive/controllers/command-controller.ts +2 -2
- package/src/modes/interactive/controllers/event-controller.ts +91 -32
- package/src/modes/interactive/controllers/input-controller.ts +19 -13
- package/src/modes/interactive/interactive-mode.ts +103 -3
- package/src/modes/interactive/theme/theme.ts +4 -0
- package/src/modes/interactive/types.ts +14 -2
- package/src/modes/interactive/utils/ui-helpers.ts +55 -26
- package/src/prompts/system/system-prompt.md +177 -126
- package/src/prompts/tools/todo-write.md +187 -0
|
@@ -60,14 +60,14 @@ export class InputController {
|
|
|
60
60
|
this.ctx.editor.onShiftTab = () => this.cycleThinkingLevel();
|
|
61
61
|
this.ctx.editor.onCtrlP = () => this.cycleRoleModel();
|
|
62
62
|
this.ctx.editor.onShiftCtrlP = () => this.cycleRoleModel({ temporary: true });
|
|
63
|
-
this.ctx.editor.
|
|
63
|
+
this.ctx.editor.onAltP = () => this.ctx.showModelSelector({ temporaryOnly: true });
|
|
64
64
|
|
|
65
65
|
// Global debug handler on TUI (works regardless of focus)
|
|
66
66
|
this.ctx.ui.onDebug = () => this.ctx.handleDebugCommand();
|
|
67
67
|
this.ctx.editor.onCtrlL = () => this.ctx.showModelSelector();
|
|
68
68
|
this.ctx.editor.onCtrlR = () => this.ctx.showHistorySearch();
|
|
69
69
|
this.ctx.editor.onCtrlO = () => this.toggleToolOutputExpansion();
|
|
70
|
-
this.ctx.editor.onCtrlT = () => this.
|
|
70
|
+
this.ctx.editor.onCtrlT = () => this.ctx.toggleTodoExpansion();
|
|
71
71
|
this.ctx.editor.onCtrlG = () => this.openExternalEditor();
|
|
72
72
|
this.ctx.editor.onQuestionMark = () => this.ctx.handleHotkeysCommand();
|
|
73
73
|
this.ctx.editor.onCtrlV = () => this.handleImagePaste();
|
|
@@ -85,28 +85,34 @@ export class InputController {
|
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
this.ctx.editor.onAltEnter = async (text: string) => {
|
|
88
|
-
|
|
89
|
-
if (!text) return;
|
|
88
|
+
const trimmedText = text.trim();
|
|
90
89
|
|
|
91
90
|
// Queue follow-up messages while compaction is running
|
|
92
91
|
if (this.ctx.session.isCompacting) {
|
|
93
|
-
|
|
92
|
+
if (!trimmedText) {
|
|
93
|
+
this.ctx.editor.handleInput("\n");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.ctx.queueCompactionMessage(trimmedText, "followUp");
|
|
94
97
|
return;
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
// Alt+Enter queues a follow-up message
|
|
98
|
-
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
100
|
+
// Alt+Enter queues a follow-up message while streaming
|
|
99
101
|
if (this.ctx.session.isStreaming) {
|
|
100
|
-
|
|
102
|
+
if (!trimmedText) {
|
|
103
|
+
this.ctx.editor.handleInput("\n");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.ctx.editor.addToHistory(trimmedText);
|
|
101
107
|
this.ctx.editor.setText("");
|
|
102
|
-
await this.ctx.session.prompt(
|
|
108
|
+
await this.ctx.session.prompt(trimmedText, { streamingBehavior: "followUp" });
|
|
103
109
|
this.ctx.updatePendingMessagesDisplay();
|
|
104
110
|
this.ctx.ui.requestRender();
|
|
111
|
+
return;
|
|
105
112
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
113
|
+
|
|
114
|
+
// Default behavior: insert a new line
|
|
115
|
+
this.ctx.editor.handleInput("\n");
|
|
110
116
|
};
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
Text,
|
|
17
17
|
TUI,
|
|
18
18
|
} from "@oh-my-pi/pi-tui";
|
|
19
|
+
import chalk from "chalk";
|
|
19
20
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
20
21
|
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
21
22
|
import { HistoryStorage } from "../../core/history-storage";
|
|
@@ -26,6 +27,7 @@ import { getRecentSessions } from "../../core/session-manager";
|
|
|
26
27
|
import type { SettingsManager } from "../../core/settings-manager";
|
|
27
28
|
import { loadSlashCommands } from "../../core/slash-commands";
|
|
28
29
|
import { setTerminalTitle } from "../../core/title-generator";
|
|
30
|
+
import { getArtifactsDir } from "../../core/tools/task/artifacts";
|
|
29
31
|
import { VoiceSupervisor } from "../../core/voice-supervisor";
|
|
30
32
|
import { registerAsyncCleanup } from "../cleanup";
|
|
31
33
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
@@ -36,7 +38,7 @@ import type { HookEditorComponent } from "./components/hook-editor";
|
|
|
36
38
|
import type { HookInputComponent } from "./components/hook-input";
|
|
37
39
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
38
40
|
import { StatusLineComponent } from "./components/status-line";
|
|
39
|
-
import type {
|
|
41
|
+
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
40
42
|
import { WelcomeComponent } from "./components/welcome";
|
|
41
43
|
import { CommandController } from "./controllers/command-controller";
|
|
42
44
|
import { EventController } from "./controllers/event-controller";
|
|
@@ -45,10 +47,12 @@ import { InputController } from "./controllers/input-controller";
|
|
|
45
47
|
import { SelectorController } from "./controllers/selector-controller";
|
|
46
48
|
import type { Theme } from "./theme/theme";
|
|
47
49
|
import { getEditorTheme, getMarkdownTheme, onThemeChange, theme } from "./theme/theme";
|
|
48
|
-
import type { CompactionQueuedMessage, InteractiveModeContext } from "./types";
|
|
50
|
+
import type { CompactionQueuedMessage, InteractiveModeContext, TodoItem } from "./types";
|
|
49
51
|
import { UiHelpers } from "./utils/ui-helpers";
|
|
50
52
|
import { VoiceManager } from "./utils/voice-manager";
|
|
51
53
|
|
|
54
|
+
const TODO_FILE_NAME = "todos.json";
|
|
55
|
+
|
|
52
56
|
/** Options for creating an InteractiveMode instance (for future API use) */
|
|
53
57
|
export interface InteractiveModeOptions {
|
|
54
58
|
/** Providers that were migrated during startup */
|
|
@@ -75,6 +79,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
75
79
|
public chatContainer: Container;
|
|
76
80
|
public pendingMessagesContainer: Container;
|
|
77
81
|
public statusContainer: Container;
|
|
82
|
+
public todoContainer: Container;
|
|
78
83
|
public editor: CustomEditor;
|
|
79
84
|
public editorContainer: Container;
|
|
80
85
|
public statusLine: StatusLineComponent;
|
|
@@ -83,10 +88,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
83
88
|
public isBackgrounded = false;
|
|
84
89
|
public isBashMode = false;
|
|
85
90
|
public toolOutputExpanded = false;
|
|
91
|
+
public todoExpanded = false;
|
|
92
|
+
public todoItems: TodoItem[] = [];
|
|
86
93
|
public hideThinkingBlock = false;
|
|
87
94
|
public pendingImages: ImageContent[] = [];
|
|
88
95
|
public compactionQueuedMessages: CompactionQueuedMessage[] = [];
|
|
89
|
-
public pendingTools = new Map<string,
|
|
96
|
+
public pendingTools = new Map<string, ToolExecutionHandle>();
|
|
90
97
|
public pendingBashComponents: BashExecutionComponent[] = [];
|
|
91
98
|
public bashComponent: BashExecutionComponent | undefined = undefined;
|
|
92
99
|
public streamingComponent: AssistantMessageComponent | undefined = undefined;
|
|
@@ -148,6 +155,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
148
155
|
this.chatContainer = new Container();
|
|
149
156
|
this.pendingMessagesContainer = new Container();
|
|
150
157
|
this.statusContainer = new Container();
|
|
158
|
+
this.todoContainer = new Container();
|
|
151
159
|
this.editor = new CustomEditor(getEditorTheme());
|
|
152
160
|
this.editor.setUseTerminalCursor(true);
|
|
153
161
|
this.editor.onAutocompleteCancel = () => {
|
|
@@ -308,6 +316,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
308
316
|
this.ui.addChild(this.chatContainer);
|
|
309
317
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
310
318
|
this.ui.addChild(this.statusContainer);
|
|
319
|
+
this.ui.addChild(this.todoContainer);
|
|
311
320
|
this.ui.addChild(new Spacer(1));
|
|
312
321
|
this.ui.addChild(this.editorContainer);
|
|
313
322
|
this.ui.addChild(this.statusLine); // Only renders hook statuses (main status in editor border)
|
|
@@ -316,6 +325,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
316
325
|
this.inputController.setupKeyHandlers();
|
|
317
326
|
this.inputController.setupEditorSubmitHandler();
|
|
318
327
|
|
|
328
|
+
// Load initial todos
|
|
329
|
+
await this.loadTodoList();
|
|
330
|
+
|
|
319
331
|
// Start the UI
|
|
320
332
|
this.ui.start();
|
|
321
333
|
this.isInitialized = true;
|
|
@@ -379,6 +391,82 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
379
391
|
this.renderSessionContext(context);
|
|
380
392
|
}
|
|
381
393
|
|
|
394
|
+
private formatTodoLine(todo: TodoItem, prefix: string): string {
|
|
395
|
+
const checkbox = theme.checkbox;
|
|
396
|
+
const label = todo.status === "in_progress" ? todo.activeForm : todo.content;
|
|
397
|
+
switch (todo.status) {
|
|
398
|
+
case "completed":
|
|
399
|
+
return theme.fg("success", `${prefix}${checkbox.checked} ${chalk.strikethrough(todo.content)}`);
|
|
400
|
+
case "in_progress":
|
|
401
|
+
return theme.fg("accent", `${prefix}${checkbox.unchecked} ${label}`);
|
|
402
|
+
default:
|
|
403
|
+
return theme.fg("dim", `${prefix}${checkbox.unchecked} ${label}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private getCollapsedTodos(todos: TodoItem[]): TodoItem[] {
|
|
408
|
+
let startIndex = 0;
|
|
409
|
+
for (let i = todos.length - 1; i >= 0; i -= 1) {
|
|
410
|
+
if (todos[i].status === "completed") {
|
|
411
|
+
startIndex = i;
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return todos.slice(startIndex, startIndex + 5);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private renderTodoList(): void {
|
|
419
|
+
this.todoContainer.clear();
|
|
420
|
+
if (this.todoItems.length === 0) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const visibleTodos = this.todoExpanded ? this.todoItems : this.getCollapsedTodos(this.todoItems);
|
|
425
|
+
const indent = " ";
|
|
426
|
+
const hook = theme.tree.hook;
|
|
427
|
+
const lines = [indent + theme.bold(theme.fg("accent", "Todos"))];
|
|
428
|
+
|
|
429
|
+
visibleTodos.forEach((todo, index) => {
|
|
430
|
+
const prefix = `${indent}${index === 0 ? hook : " "} `;
|
|
431
|
+
lines.push(this.formatTodoLine(todo, prefix));
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
if (!this.todoExpanded && visibleTodos.length < this.todoItems.length) {
|
|
435
|
+
const remaining = this.todoItems.length - visibleTodos.length;
|
|
436
|
+
lines.push(theme.fg("muted", `${indent}${hook} +${remaining} more (Ctrl+T to expand)`));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
this.todoContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private async loadTodoList(): Promise<void> {
|
|
443
|
+
const sessionFile = this.sessionManager.getSessionFile() ?? null;
|
|
444
|
+
if (!sessionFile) {
|
|
445
|
+
this.renderTodoList();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const artifactsDir = getArtifactsDir(sessionFile);
|
|
449
|
+
if (!artifactsDir) {
|
|
450
|
+
this.renderTodoList();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const todoPath = path.join(artifactsDir, TODO_FILE_NAME);
|
|
454
|
+
const file = Bun.file(todoPath);
|
|
455
|
+
if (!(await file.exists())) {
|
|
456
|
+
this.renderTodoList();
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const data = (await file.json()) as { todos?: TodoItem[] };
|
|
461
|
+
if (data?.todos && Array.isArray(data.todos)) {
|
|
462
|
+
this.todoItems = data.todos;
|
|
463
|
+
}
|
|
464
|
+
} catch (error) {
|
|
465
|
+
logger.warn("Failed to load todos", { path: todoPath, error: String(error) });
|
|
466
|
+
}
|
|
467
|
+
this.renderTodoList();
|
|
468
|
+
}
|
|
469
|
+
|
|
382
470
|
stop(): void {
|
|
383
471
|
if (this.loadingAnimation) {
|
|
384
472
|
this.loadingAnimation.stop();
|
|
@@ -636,6 +724,18 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
636
724
|
this.inputController.toggleThinkingBlockVisibility();
|
|
637
725
|
}
|
|
638
726
|
|
|
727
|
+
toggleTodoExpansion(): void {
|
|
728
|
+
this.todoExpanded = !this.todoExpanded;
|
|
729
|
+
this.renderTodoList();
|
|
730
|
+
this.ui.requestRender();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
setTodos(todos: TodoItem[]): void {
|
|
734
|
+
this.todoItems = todos;
|
|
735
|
+
this.renderTodoList();
|
|
736
|
+
this.ui.requestRender();
|
|
737
|
+
}
|
|
738
|
+
|
|
639
739
|
openExternalEditor(): void {
|
|
640
740
|
this.inputController.openExternalEditor();
|
|
641
741
|
}
|
|
@@ -15,7 +15,7 @@ import type { HookEditorComponent } from "./components/hook-editor";
|
|
|
15
15
|
import type { HookInputComponent } from "./components/hook-input";
|
|
16
16
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
17
17
|
import type { StatusLineComponent } from "./components/status-line";
|
|
18
|
-
import type {
|
|
18
|
+
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
19
19
|
import type { Theme } from "./theme/theme";
|
|
20
20
|
|
|
21
21
|
export type CompactionQueuedMessage = {
|
|
@@ -23,12 +23,20 @@ export type CompactionQueuedMessage = {
|
|
|
23
23
|
mode: "steer" | "followUp";
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
export type TodoItem = {
|
|
27
|
+
id: string;
|
|
28
|
+
content: string;
|
|
29
|
+
activeForm: string;
|
|
30
|
+
status: "pending" | "in_progress" | "completed";
|
|
31
|
+
};
|
|
32
|
+
|
|
26
33
|
export interface InteractiveModeContext {
|
|
27
34
|
// UI access
|
|
28
35
|
ui: TUI;
|
|
29
36
|
chatContainer: Container;
|
|
30
37
|
pendingMessagesContainer: Container;
|
|
31
38
|
statusContainer: Container;
|
|
39
|
+
todoContainer: Container;
|
|
32
40
|
editor: CustomEditor;
|
|
33
41
|
editorContainer: Container;
|
|
34
42
|
statusLine: StatusLineComponent;
|
|
@@ -46,10 +54,11 @@ export interface InteractiveModeContext {
|
|
|
46
54
|
isBackgrounded: boolean;
|
|
47
55
|
isBashMode: boolean;
|
|
48
56
|
toolOutputExpanded: boolean;
|
|
57
|
+
todoExpanded: boolean;
|
|
49
58
|
hideThinkingBlock: boolean;
|
|
50
59
|
pendingImages: ImageContent[];
|
|
51
60
|
compactionQueuedMessages: CompactionQueuedMessage[];
|
|
52
|
-
pendingTools: Map<string,
|
|
61
|
+
pendingTools: Map<string, ToolExecutionHandle>;
|
|
53
62
|
pendingBashComponents: BashExecutionComponent[];
|
|
54
63
|
bashComponent: BashExecutionComponent | undefined;
|
|
55
64
|
streamingComponent: AssistantMessageComponent | undefined;
|
|
@@ -74,6 +83,7 @@ export interface InteractiveModeContext {
|
|
|
74
83
|
lastStatusSpacer: Spacer | undefined;
|
|
75
84
|
lastStatusText: Text | undefined;
|
|
76
85
|
fileSlashCommands: Set<string>;
|
|
86
|
+
todoItems: TodoItem[];
|
|
77
87
|
|
|
78
88
|
// Lifecycle
|
|
79
89
|
init(): Promise<void>;
|
|
@@ -110,6 +120,8 @@ export interface InteractiveModeContext {
|
|
|
110
120
|
updateEditorTopBorder(): void;
|
|
111
121
|
updateEditorBorderColor(): void;
|
|
112
122
|
rebuildChatFromMessages(): void;
|
|
123
|
+
setTodos(todos: TodoItem[]): void;
|
|
124
|
+
toggleTodoExpansion(): void;
|
|
113
125
|
|
|
114
126
|
// Command handling
|
|
115
127
|
handleExportCommand(text: string): Promise<void>;
|
|
@@ -10,6 +10,7 @@ import { BranchSummaryMessageComponent } from "../components/branch-summary-mess
|
|
|
10
10
|
import { CompactionSummaryMessageComponent } from "../components/compaction-summary-message";
|
|
11
11
|
import { CustomMessageComponent } from "../components/custom-message";
|
|
12
12
|
import { DynamicBorder } from "../components/dynamic-border";
|
|
13
|
+
import { ReadToolGroupComponent } from "../components/read-tool-group";
|
|
13
14
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
14
15
|
import { UserMessageComponent } from "../components/user-message";
|
|
15
16
|
import { theme } from "../theme/theme";
|
|
@@ -159,47 +160,75 @@ export class UiHelpers {
|
|
|
159
160
|
this.ctx.updateEditorBorderColor();
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
let readGroup: ReadToolGroupComponent | null = null;
|
|
162
164
|
for (const message of sessionContext.messages) {
|
|
163
165
|
// Assistant messages need special handling for tool calls
|
|
164
166
|
if (message.role === "assistant") {
|
|
165
167
|
this.ctx.addMessageToChat(message);
|
|
168
|
+
readGroup = null;
|
|
169
|
+
const hasErrorStop = message.stopReason === "aborted" || message.stopReason === "error";
|
|
170
|
+
const errorMessage = hasErrorStop
|
|
171
|
+
? message.stopReason === "aborted"
|
|
172
|
+
? (() => {
|
|
173
|
+
const retryAttempt = this.ctx.session.retryAttempt;
|
|
174
|
+
return retryAttempt > 0
|
|
175
|
+
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
176
|
+
: "Operation aborted";
|
|
177
|
+
})()
|
|
178
|
+
: message.errorMessage || "Error"
|
|
179
|
+
: null;
|
|
180
|
+
|
|
166
181
|
// Render tool call components
|
|
167
182
|
for (const content of message.content) {
|
|
168
|
-
if (content.type
|
|
169
|
-
const tool = this.ctx.session.getToolByName(content.name);
|
|
170
|
-
const component = new ToolExecutionComponent(
|
|
171
|
-
content.name,
|
|
172
|
-
content.arguments,
|
|
173
|
-
{ showImages: this.ctx.settingsManager.getShowImages() },
|
|
174
|
-
tool,
|
|
175
|
-
this.ctx.ui,
|
|
176
|
-
this.ctx.sessionManager.getCwd(),
|
|
177
|
-
);
|
|
178
|
-
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
179
|
-
this.ctx.chatContainer.addChild(component);
|
|
183
|
+
if (content.type !== "toolCall") continue;
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
185
|
+
if (content.name === "read") {
|
|
186
|
+
if (!readGroup) {
|
|
187
|
+
readGroup = new ReadToolGroupComponent();
|
|
188
|
+
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
|
189
|
+
this.ctx.chatContainer.addChild(readGroup);
|
|
190
|
+
}
|
|
191
|
+
readGroup.updateArgs(content.arguments, content.id);
|
|
192
|
+
if (hasErrorStop && errorMessage) {
|
|
193
|
+
readGroup.updateResult(
|
|
194
|
+
{ content: [{ type: "text", text: errorMessage }], isError: true },
|
|
195
|
+
false,
|
|
196
|
+
content.id,
|
|
197
|
+
);
|
|
193
198
|
} else {
|
|
194
|
-
this.ctx.pendingTools.set(content.id,
|
|
199
|
+
this.ctx.pendingTools.set(content.id, readGroup);
|
|
195
200
|
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
readGroup = null;
|
|
205
|
+
const tool = this.ctx.session.getToolByName(content.name);
|
|
206
|
+
const component = new ToolExecutionComponent(
|
|
207
|
+
content.name,
|
|
208
|
+
content.arguments,
|
|
209
|
+
{ showImages: this.ctx.settingsManager.getShowImages() },
|
|
210
|
+
tool,
|
|
211
|
+
this.ctx.ui,
|
|
212
|
+
this.ctx.sessionManager.getCwd(),
|
|
213
|
+
);
|
|
214
|
+
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
215
|
+
this.ctx.chatContainer.addChild(component);
|
|
216
|
+
|
|
217
|
+
if (hasErrorStop && errorMessage) {
|
|
218
|
+
component.updateResult(
|
|
219
|
+
{ content: [{ type: "text", text: errorMessage }], isError: true },
|
|
220
|
+
false,
|
|
221
|
+
content.id,
|
|
222
|
+
);
|
|
223
|
+
} else {
|
|
224
|
+
this.ctx.pendingTools.set(content.id, component);
|
|
196
225
|
}
|
|
197
226
|
}
|
|
198
227
|
} else if (message.role === "toolResult") {
|
|
199
228
|
// Match tool results to pending tool components
|
|
200
229
|
const component = this.ctx.pendingTools.get(message.toolCallId);
|
|
201
230
|
if (component) {
|
|
202
|
-
component.updateResult(message);
|
|
231
|
+
component.updateResult(message, false, message.toolCallId);
|
|
203
232
|
this.ctx.pendingTools.delete(message.toolCallId);
|
|
204
233
|
}
|
|
205
234
|
} else {
|