@myrialabs/clopen 0.1.2 → 0.1.3
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/backend/lib/engine/adapters/opencode/message-converter.ts +53 -2
- package/backend/lib/engine/adapters/opencode/stream.ts +89 -5
- package/backend/lib/project/status-manager.ts +221 -181
- package/frontend/lib/components/chat/message/ChatMessages.svelte +16 -4
- package/frontend/lib/components/chat/tools/AgentTool.svelte +12 -11
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +3 -3
- package/frontend/lib/components/chat/tools/BashTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +3 -1
- package/frontend/lib/components/chat/tools/EditTool.svelte +6 -6
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/GlobTool.svelte +12 -12
- package/frontend/lib/components/chat/tools/GrepTool.svelte +5 -5
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +6 -6
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +2 -2
- package/frontend/lib/components/chat/tools/ReadTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/TaskStopTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/TaskTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/WriteTool.svelte +3 -3
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +3 -3
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +2 -2
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +1 -1
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +2 -2
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +1 -1
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +5 -5
- package/frontend/lib/components/common/Button.svelte +1 -1
- package/frontend/lib/components/common/Card.svelte +3 -3
- package/frontend/lib/components/common/Input.svelte +3 -3
- package/frontend/lib/components/common/LoadingSpinner.svelte +1 -1
- package/frontend/lib/components/common/Select.svelte +6 -6
- package/frontend/lib/components/common/Textarea.svelte +3 -3
- package/frontend/lib/components/files/FileViewer.svelte +1 -1
- package/frontend/lib/components/git/ChangesSection.svelte +2 -4
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +9 -29
- package/frontend/lib/components/preview/browser/components/Container.svelte +17 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +2 -2
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +0 -6
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +15 -15
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +2 -2
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +380 -383
- package/frontend/lib/components/workspace/MobileNavigator.svelte +391 -395
- package/frontend/lib/components/workspace/PanelHeader.svelte +115 -4
- package/frontend/lib/components/workspace/ViewMenu.svelte +9 -25
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +29 -4
- package/frontend/lib/services/notification/global-stream-monitor.ts +77 -86
- package/frontend/lib/services/project/status.service.ts +160 -159
- package/frontend/lib/stores/ui/workspace.svelte.ts +326 -283
- package/package.json +1 -1
|
@@ -1,182 +1,222 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Status Data Service
|
|
3
|
-
* Shared logic for getting project status data
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { streamManager } from '../chat/stream-manager.js';
|
|
7
|
-
import { ws } from '../utils/ws.js';
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Project Status Data Service
|
|
3
|
+
* Shared logic for getting project status data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { streamManager, type StreamState } from '../chat/stream-manager.js';
|
|
7
|
+
import { ws } from '../utils/ws.js';
|
|
8
|
+
|
|
9
|
+
// Interactive tools that block the stream waiting for user input
|
|
10
|
+
const INTERACTIVE_TOOLS = new Set(['AskUserQuestion']);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if an active stream is waiting for user input.
|
|
14
|
+
* Scans stream messages for unanswered interactive tool_use blocks.
|
|
15
|
+
* This is the backend single source of truth — works even when the
|
|
16
|
+
* user is on a different project and not receiving chat events.
|
|
17
|
+
*/
|
|
18
|
+
function detectStreamWaitingInput(stream: StreamState): boolean {
|
|
19
|
+
if (stream.status !== 'active') return false;
|
|
20
|
+
|
|
21
|
+
const answeredToolIds = new Set<string>();
|
|
22
|
+
for (const event of stream.messages) {
|
|
23
|
+
const msg = event.message;
|
|
24
|
+
if (!msg || (msg as any).type !== 'user' || !(msg as any).content) continue;
|
|
25
|
+
const content = Array.isArray((msg as any).content) ? (msg as any).content : [];
|
|
26
|
+
for (const item of content) {
|
|
27
|
+
if (item.type === 'tool_result' && item.tool_use_id) {
|
|
28
|
+
answeredToolIds.add(item.tool_use_id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const event of stream.messages) {
|
|
34
|
+
const msg = event.message;
|
|
35
|
+
if (!msg || (msg as any).type !== 'assistant' || !(msg as any).content) continue;
|
|
36
|
+
const content = Array.isArray((msg as any).content) ? (msg as any).content : [];
|
|
37
|
+
if (content.some((item: any) =>
|
|
38
|
+
item.type === 'tool_use' && INTERACTIVE_TOOLS.has(item.name) && item.id && !answeredToolIds.has(item.id)
|
|
39
|
+
)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Store active users per project (shared with main endpoint)
|
|
48
|
+
const projectUsers = new Map<string, Set<{ userId: string; userName: string; lastSeen: number }>>();
|
|
49
|
+
|
|
50
|
+
// Cleanup inactive users after 5 minutes
|
|
51
|
+
const USER_TIMEOUT = 5 * 60 * 1000;
|
|
52
|
+
|
|
53
|
+
function cleanupInactiveUsers() {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
projectUsers.forEach((users, projectId) => {
|
|
56
|
+
const activeUsers = new Set([...users].filter(user => now - user.lastSeen < USER_TIMEOUT));
|
|
57
|
+
if (activeUsers.size === 0) {
|
|
58
|
+
projectUsers.delete(projectId);
|
|
59
|
+
} else {
|
|
60
|
+
projectUsers.set(projectId, activeUsers);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get project status data
|
|
66
|
+
export async function getProjectStatusData(projectId?: string) {
|
|
67
|
+
cleanupInactiveUsers();
|
|
68
|
+
|
|
69
|
+
if (projectId) {
|
|
70
|
+
// Get status for specific project
|
|
71
|
+
const allProjectStreams = streamManager.getProjectStreams(projectId);
|
|
72
|
+
const users = projectUsers.get(projectId);
|
|
73
|
+
|
|
74
|
+
// Filter to only count active streams
|
|
75
|
+
const activeStreams = allProjectStreams.filter(s => s.status === 'active');
|
|
76
|
+
|
|
77
|
+
// Get per-chat-session user presence from WS rooms
|
|
78
|
+
const chatSessionUsers = ws.getProjectChatSessions(projectId);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
projectId,
|
|
82
|
+
hasActiveStreams: activeStreams.length > 0,
|
|
83
|
+
activeStreamCount: activeStreams.length,
|
|
84
|
+
activeUsers: users ? [...users].map(u => ({
|
|
85
|
+
userId: u.userId,
|
|
86
|
+
userName: u.userName
|
|
87
|
+
})) : [],
|
|
88
|
+
streams: allProjectStreams.map(s => ({
|
|
89
|
+
streamId: s.streamId,
|
|
90
|
+
chatSessionId: s.chatSessionId,
|
|
91
|
+
status: s.status,
|
|
92
|
+
startedAt: s.startedAt,
|
|
93
|
+
messagesCount: s.messages.length,
|
|
94
|
+
isWaitingInput: detectStreamWaitingInput(s)
|
|
95
|
+
})),
|
|
96
|
+
chatSessionUsers: Object.fromEntries(
|
|
97
|
+
Array.from(chatSessionUsers.entries()).map(([csId, csUsers]) => [
|
|
98
|
+
csId,
|
|
99
|
+
csUsers.map(u => {
|
|
100
|
+
// Resolve userName from projectUsers
|
|
101
|
+
const projectUser = users ? [...users].find(pu => pu.userId === u.userId) : undefined;
|
|
102
|
+
return { userId: u.userId, userName: projectUser?.userName || u.userId };
|
|
103
|
+
})
|
|
104
|
+
])
|
|
105
|
+
)
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
// Get status for all projects
|
|
109
|
+
const allProjects = new Map<string, any>();
|
|
110
|
+
|
|
111
|
+
// Get all active streams grouped by project
|
|
112
|
+
const allStreams = streamManager.getAllStreams();
|
|
113
|
+
allStreams.forEach(stream => {
|
|
114
|
+
if (stream.projectId) {
|
|
115
|
+
if (!allProjects.has(stream.projectId)) {
|
|
116
|
+
allProjects.set(stream.projectId, {
|
|
117
|
+
projectId: stream.projectId,
|
|
118
|
+
hasActiveStreams: false,
|
|
119
|
+
activeStreamCount: 0,
|
|
120
|
+
activeUsers: [],
|
|
121
|
+
streams: []
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const projectData = allProjects.get(stream.projectId);
|
|
126
|
+
if (stream.status === 'active') {
|
|
127
|
+
projectData.hasActiveStreams = true;
|
|
128
|
+
projectData.activeStreamCount++;
|
|
129
|
+
}
|
|
130
|
+
projectData.streams.push({
|
|
131
|
+
streamId: stream.streamId,
|
|
132
|
+
chatSessionId: stream.chatSessionId,
|
|
133
|
+
status: stream.status,
|
|
134
|
+
startedAt: stream.startedAt,
|
|
135
|
+
messagesCount: stream.messages.length,
|
|
136
|
+
isWaitingInput: detectStreamWaitingInput(stream)
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Add active users to each project
|
|
142
|
+
projectUsers.forEach((users, projectId) => {
|
|
143
|
+
if (!allProjects.has(projectId)) {
|
|
144
|
+
allProjects.set(projectId, {
|
|
145
|
+
projectId,
|
|
146
|
+
hasActiveStreams: false,
|
|
147
|
+
activeStreamCount: 0,
|
|
148
|
+
activeUsers: [],
|
|
149
|
+
streams: [],
|
|
150
|
+
chatSessionUsers: {}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const projectData = allProjects.get(projectId);
|
|
155
|
+
projectData.activeUsers = [...users].map(u => ({
|
|
156
|
+
userId: u.userId,
|
|
157
|
+
userName: u.userName
|
|
158
|
+
}));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Add per-chat-session user presence to each project
|
|
162
|
+
for (const [projectId, projectData] of allProjects) {
|
|
163
|
+
const chatSessionUsers = ws.getProjectChatSessions(projectId);
|
|
164
|
+
const users = projectUsers.get(projectId);
|
|
165
|
+
projectData.chatSessionUsers = Object.fromEntries(
|
|
166
|
+
Array.from(chatSessionUsers.entries()).map(([csId, csUsers]) => [
|
|
167
|
+
csId,
|
|
168
|
+
csUsers.map(u => {
|
|
169
|
+
const projectUser = users ? [...users].find(pu => pu.userId === u.userId) : undefined;
|
|
170
|
+
return { userId: u.userId, userName: projectUser?.userName || u.userId };
|
|
171
|
+
})
|
|
172
|
+
])
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return [...allProjects.values()];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Update user presence
|
|
181
|
+
export function updateUserPresence(projectId: string, userId: string, userName: string, action: string) {
|
|
182
|
+
cleanupInactiveUsers();
|
|
183
|
+
|
|
184
|
+
if (action === 'leave') {
|
|
185
|
+
// Remove user from project
|
|
186
|
+
const users = projectUsers.get(projectId);
|
|
187
|
+
if (users) {
|
|
188
|
+
const updatedUsers = new Set([...users].filter(u => u.userId !== userId));
|
|
189
|
+
if (updatedUsers.size === 0) {
|
|
190
|
+
projectUsers.delete(projectId);
|
|
191
|
+
} else {
|
|
192
|
+
projectUsers.set(projectId, updatedUsers);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// Add or update user presence
|
|
197
|
+
if (!projectUsers.has(projectId)) {
|
|
198
|
+
projectUsers.set(projectId, new Set());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const users = projectUsers.get(projectId)!;
|
|
202
|
+
// Remove old entry if exists
|
|
203
|
+
const updatedUsers = new Set([...users].filter(u => u.userId !== userId));
|
|
204
|
+
// Add new entry with updated timestamp
|
|
205
|
+
updatedUsers.add({
|
|
206
|
+
userId,
|
|
207
|
+
userName,
|
|
208
|
+
lastSeen: Date.now()
|
|
209
|
+
});
|
|
210
|
+
projectUsers.set(projectId, updatedUsers);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Return current users for the project
|
|
214
|
+
const currentUsers = projectUsers.get(projectId);
|
|
215
|
+
return {
|
|
216
|
+
projectId,
|
|
217
|
+
activeUsers: currentUsers ? [...currentUsers].map(u => ({
|
|
218
|
+
userId: u.userId,
|
|
219
|
+
userName: u.userName
|
|
220
|
+
})) : []
|
|
221
|
+
};
|
|
182
222
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
let hasInitiallyScrolled = $state(false);
|
|
29
29
|
let isContentReady = $state(false);
|
|
30
30
|
let lastToolResultsHash = $state('');
|
|
31
|
+
let lastSubAgentHash = $state('');
|
|
31
32
|
// Prevent scroll events from overriding isUserAtBottom during programmatic scroll
|
|
32
33
|
let scrollLockUntil = 0;
|
|
33
34
|
|
|
@@ -254,8 +255,10 @@
|
|
|
254
255
|
// Check for tool result updates ($result additions) across all messages
|
|
255
256
|
let toolResultChanged = false;
|
|
256
257
|
let currentToolResultsHash = '';
|
|
258
|
+
let subAgentChanged = false;
|
|
259
|
+
let currentSubAgentHash = '';
|
|
257
260
|
|
|
258
|
-
// Build a hash of all tool results to detect changes
|
|
261
|
+
// Build a hash of all tool results and sub-agent activities to detect changes
|
|
259
262
|
for (const message of filteredMessages) {
|
|
260
263
|
if ('message' in message) {
|
|
261
264
|
const messageContent = (message as any).message?.content;
|
|
@@ -264,6 +267,9 @@
|
|
|
264
267
|
if (item?.type === 'tool_use' && item.$result) {
|
|
265
268
|
currentToolResultsHash += `${item.id}:${JSON.stringify(item.$result).length}|`;
|
|
266
269
|
}
|
|
270
|
+
if (item?.type === 'tool_use' && item.$subMessages) {
|
|
271
|
+
currentSubAgentHash += `${item.id}:${item.$subMessages.length}|`;
|
|
272
|
+
}
|
|
267
273
|
}
|
|
268
274
|
}
|
|
269
275
|
}
|
|
@@ -275,6 +281,12 @@
|
|
|
275
281
|
lastToolResultsHash = currentToolResultsHash;
|
|
276
282
|
}
|
|
277
283
|
|
|
284
|
+
// Check if sub-agent activities have changed
|
|
285
|
+
if (currentSubAgentHash !== lastSubAgentHash) {
|
|
286
|
+
subAgentChanged = true;
|
|
287
|
+
lastSubAgentHash = currentSubAgentHash;
|
|
288
|
+
}
|
|
289
|
+
|
|
278
290
|
// When message count decreases (e.g. after undo/reload), scroll to bottom
|
|
279
291
|
if (isMessageCountDecreased && hasInitiallyScrolled) {
|
|
280
292
|
requestAnimationFrame(() => {
|
|
@@ -284,14 +296,14 @@
|
|
|
284
296
|
return;
|
|
285
297
|
}
|
|
286
298
|
|
|
287
|
-
// Scroll on new message, partial text changes,
|
|
288
|
-
if ((isNewMessage || partialTextChanged || toolResultChanged) && isUserAtBottom) {
|
|
299
|
+
// Scroll on new message, partial text changes, tool result changes, or sub-agent updates
|
|
300
|
+
if ((isNewMessage || partialTextChanged || toolResultChanged || subAgentChanged) && isUserAtBottom) {
|
|
289
301
|
requestAnimationFrame(() => {
|
|
290
302
|
if (editModeState.isEditing) return;
|
|
291
303
|
|
|
292
304
|
// During streaming, scroll immediately (instant via scrollMessagesToBottom)
|
|
293
305
|
// For non-streaming changes, add a small delay for smooth UX
|
|
294
|
-
if (partialTextChanged || hasStreamingMessage) {
|
|
306
|
+
if (partialTextChanged || hasStreamingMessage || subAgentChanged) {
|
|
295
307
|
scrollMessagesToBottom(false);
|
|
296
308
|
} else {
|
|
297
309
|
const delay = toolResultChanged ? 50 : 100;
|
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
const { toolInput }: { toolInput: AgentToolInput } = $props();
|
|
8
8
|
|
|
9
|
-
const description = toolInput.input.description || '';
|
|
10
|
-
const subagentType = toolInput.input.subagent_type || 'general-purpose';
|
|
11
|
-
const subMessages = (toolInput
|
|
12
|
-
const toolUseCount = subMessages?.filter(a => a.type === 'tool_use').length ?? 0;
|
|
9
|
+
const description = $derived(toolInput.input.description || '');
|
|
10
|
+
const subagentType = $derived(toolInput.input.subagent_type || 'general-purpose');
|
|
11
|
+
const subMessages = $derived(toolInput.$subMessages);
|
|
12
|
+
const toolUseCount = $derived(subMessages?.filter(a => a.type === 'tool_use').length ?? 0);
|
|
13
|
+
const result = $derived(toolInput.$result);
|
|
13
14
|
|
|
14
15
|
let scrollContainer: HTMLDivElement | undefined = $state();
|
|
15
16
|
|
|
16
17
|
// Auto-scroll to bottom when new activities arrive
|
|
17
18
|
$effect(() => {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
19
|
+
const len = subMessages?.length ?? 0;
|
|
20
|
+
if (len > 0 && scrollContainer) {
|
|
20
21
|
tick().then(() => {
|
|
21
22
|
if (scrollContainer) {
|
|
22
23
|
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
</script>
|
|
43
44
|
|
|
44
45
|
<!-- Header card -->
|
|
45
|
-
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
46
|
+
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3 mb-2">
|
|
46
47
|
<div class="space-y-1">
|
|
47
48
|
<InfoLine icon="lucide:search" text={description} />
|
|
48
49
|
<InfoLine icon="lucide:bot" text="Using {subagentType} agent" />
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
<div class="text-xs text-slate-500 dark:text-slate-400 mb-2">
|
|
56
57
|
{toolUseCount} tool {toolUseCount === 1 ? 'call' : 'calls'}:
|
|
57
58
|
</div>
|
|
58
|
-
<div bind:this={scrollContainer} class="max-h-64 overflow-y-auto">
|
|
59
|
+
<div bind:this={scrollContainer} class="max-h-64 overflow-y-auto wrap-break-word">
|
|
59
60
|
<ul class="list-disc pl-5 space-y-0.5">
|
|
60
61
|
{#each subMessages as activity}
|
|
61
62
|
{#if activity.type === 'tool_use'}
|
|
@@ -77,8 +78,8 @@
|
|
|
77
78
|
{/if}
|
|
78
79
|
|
|
79
80
|
<!-- Tool Result -->
|
|
80
|
-
{#if
|
|
81
|
-
{@const resultContent =
|
|
81
|
+
<!-- {#if result}
|
|
82
|
+
{@const resultContent = result.content as any}
|
|
82
83
|
<div class="mt-4">
|
|
83
84
|
{#if typeof resultContent === 'string'}
|
|
84
85
|
<TextMessage content={resultContent} />
|
|
@@ -92,4 +93,4 @@
|
|
|
92
93
|
<TextMessage content={JSON.stringify(resultContent)} />
|
|
93
94
|
{/if}
|
|
94
95
|
</div>
|
|
95
|
-
{/if}
|
|
96
|
+
{/if} -->
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: BashOutputToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const taskId = toolInput.input.task_id;
|
|
9
|
-
const block = toolInput.input.block;
|
|
10
|
-
const timeout = toolInput.input.timeout;
|
|
8
|
+
const taskId = $derived(toolInput.input.task_id);
|
|
9
|
+
const block = $derived(toolInput.input.block);
|
|
10
|
+
const timeout = $derived(toolInput.input.timeout);
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
const { toolInput }: { toolInput: BashToolInput } = $props();
|
|
13
13
|
|
|
14
|
-
const command = toolInput.input.command || '';
|
|
15
|
-
const description = toolInput.input.description;
|
|
16
|
-
const timeout = toolInput.input.timeout;
|
|
17
|
-
const isBackground = toolInput.input.run_in_background;
|
|
14
|
+
const command = $derived(toolInput.input.command || '');
|
|
15
|
+
const description = $derived(toolInput.input.description);
|
|
16
|
+
const timeout = $derived(toolInput.input.timeout);
|
|
17
|
+
const isBackground = $derived(toolInput.input.run_in_background);
|
|
18
18
|
|
|
19
19
|
function parseBashOutputToolOutput(content: string): ParsedBashOutput {
|
|
20
20
|
const statusMatch = content.match(/<status>(.*?)<\/status>/);
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const parsedToolName = $derived(parseMcpToolName(toolInput.name));
|
|
29
|
+
const server = $derived(parsedToolName.server);
|
|
30
|
+
const tool = $derived(parsedToolName.tool);
|
|
29
31
|
|
|
30
32
|
// Format server name for display (e.g., "weather-service" -> "Weather Service")
|
|
31
33
|
const serverDisplayName = $derived.by(() => {
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: EditToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const filePath = toolInput.input.file_path || '';
|
|
9
|
-
const fileName = filePath.split(/[/\\]/).pop() || filePath || 'unknown';
|
|
10
|
-
const oldString = toolInput.input.old_string || '';
|
|
11
|
-
const newString = toolInput.input.new_string || '';
|
|
12
|
-
const replaceAll = toolInput.input.replace_all || false;
|
|
8
|
+
const filePath = $derived(toolInput.input.file_path || '');
|
|
9
|
+
const fileName = $derived(filePath.split(/[/\\]/).pop() || filePath || 'unknown');
|
|
10
|
+
const oldString = $derived(toolInput.input.old_string || '');
|
|
11
|
+
const newString = $derived(toolInput.input.new_string || '');
|
|
12
|
+
const replaceAll = $derived(toolInput.input.replace_all || false);
|
|
13
13
|
|
|
14
|
-
const badges = replaceAll ? [{ text: 'Replace All', color: 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-300' }] : [];
|
|
14
|
+
const badges = $derived(replaceAll ? [{ text: 'Replace All', color: 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-300' }] : []);
|
|
15
15
|
</script>
|
|
16
16
|
|
|
17
17
|
<FileHeader
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: ExitPlanModeToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const plan = (toolInput.input as any).plan as string || '';
|
|
8
|
+
const plan = $derived((toolInput.input as any).plan as string || '');
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3 mb-4">
|
|
@@ -6,26 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
const { toolInput }: { toolInput: GlobToolInput } = $props();
|
|
8
8
|
|
|
9
|
-
const pattern = toolInput.input.pattern || '';
|
|
10
|
-
const searchPath = toolInput.input.path || 'current directory';
|
|
9
|
+
const pattern = $derived(toolInput.input.pattern || '');
|
|
10
|
+
const searchPath = $derived(toolInput.input.path || 'current directory');
|
|
11
11
|
|
|
12
12
|
// Parse the pattern to get a readable description
|
|
13
|
-
function getPatternDescription(
|
|
14
|
-
if (
|
|
15
|
-
const ext =
|
|
13
|
+
function getPatternDescription(p: string): string {
|
|
14
|
+
if (p.includes('**/*.')) {
|
|
15
|
+
const ext = p.split('**/*.')[1];
|
|
16
16
|
return `all .${ext} files`;
|
|
17
|
-
} else if (
|
|
18
|
-
const rest =
|
|
17
|
+
} else if (p.includes('**/')) {
|
|
18
|
+
const rest = p.split('**/')[1];
|
|
19
19
|
return `all ${rest} in subdirectories`;
|
|
20
|
-
} else if (
|
|
21
|
-
const ext =
|
|
20
|
+
} else if (p.includes('*.')) {
|
|
21
|
+
const ext = p.split('*.')[1];
|
|
22
22
|
return `.${ext} files`;
|
|
23
23
|
}
|
|
24
|
-
return
|
|
24
|
+
return p;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const patternDescription = getPatternDescription(pattern);
|
|
28
|
-
const formattedPath = formatPath(searchPath);
|
|
27
|
+
const patternDescription = $derived(getPatternDescription(pattern));
|
|
28
|
+
const formattedPath = $derived(formatPath(searchPath));
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
31
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|