@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.
Files changed (50) hide show
  1. package/backend/lib/engine/adapters/opencode/message-converter.ts +53 -2
  2. package/backend/lib/engine/adapters/opencode/stream.ts +89 -5
  3. package/backend/lib/project/status-manager.ts +221 -181
  4. package/frontend/lib/components/chat/message/ChatMessages.svelte +16 -4
  5. package/frontend/lib/components/chat/tools/AgentTool.svelte +12 -11
  6. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +3 -3
  7. package/frontend/lib/components/chat/tools/BashTool.svelte +4 -4
  8. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +3 -1
  9. package/frontend/lib/components/chat/tools/EditTool.svelte +6 -6
  10. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +1 -1
  11. package/frontend/lib/components/chat/tools/GlobTool.svelte +12 -12
  12. package/frontend/lib/components/chat/tools/GrepTool.svelte +5 -5
  13. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +1 -1
  14. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +6 -6
  15. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +2 -2
  16. package/frontend/lib/components/chat/tools/ReadTool.svelte +4 -4
  17. package/frontend/lib/components/chat/tools/TaskStopTool.svelte +1 -1
  18. package/frontend/lib/components/chat/tools/TaskTool.svelte +1 -1
  19. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +4 -4
  20. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +1 -1
  21. package/frontend/lib/components/chat/tools/WriteTool.svelte +3 -3
  22. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +3 -3
  23. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +2 -2
  24. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +1 -1
  25. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +2 -2
  26. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +1 -1
  27. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +5 -5
  28. package/frontend/lib/components/common/Button.svelte +1 -1
  29. package/frontend/lib/components/common/Card.svelte +3 -3
  30. package/frontend/lib/components/common/Input.svelte +3 -3
  31. package/frontend/lib/components/common/LoadingSpinner.svelte +1 -1
  32. package/frontend/lib/components/common/Select.svelte +6 -6
  33. package/frontend/lib/components/common/Textarea.svelte +3 -3
  34. package/frontend/lib/components/files/FileViewer.svelte +1 -1
  35. package/frontend/lib/components/git/ChangesSection.svelte +2 -4
  36. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +9 -29
  37. package/frontend/lib/components/preview/browser/components/Container.svelte +17 -0
  38. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +2 -2
  39. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +0 -6
  40. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +15 -15
  41. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +2 -2
  42. package/frontend/lib/components/workspace/DesktopNavigator.svelte +380 -383
  43. package/frontend/lib/components/workspace/MobileNavigator.svelte +391 -395
  44. package/frontend/lib/components/workspace/PanelHeader.svelte +115 -4
  45. package/frontend/lib/components/workspace/ViewMenu.svelte +9 -25
  46. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +29 -4
  47. package/frontend/lib/services/notification/global-stream-monitor.ts +77 -86
  48. package/frontend/lib/services/project/status.service.ts +160 -159
  49. package/frontend/lib/stores/ui/workspace.svelte.ts +326 -283
  50. 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
- // Store active users per project (shared with main endpoint)
10
- const projectUsers = new Map<string, Set<{ userId: string; userName: string; lastSeen: number }>>();
11
-
12
- // Cleanup inactive users after 5 minutes
13
- const USER_TIMEOUT = 5 * 60 * 1000;
14
-
15
- function cleanupInactiveUsers() {
16
- const now = Date.now();
17
- projectUsers.forEach((users, projectId) => {
18
- const activeUsers = new Set([...users].filter(user => now - user.lastSeen < USER_TIMEOUT));
19
- if (activeUsers.size === 0) {
20
- projectUsers.delete(projectId);
21
- } else {
22
- projectUsers.set(projectId, activeUsers);
23
- }
24
- });
25
- }
26
-
27
- // Get project status data
28
- export async function getProjectStatusData(projectId?: string) {
29
- cleanupInactiveUsers();
30
-
31
- if (projectId) {
32
- // Get status for specific project
33
- const allProjectStreams = streamManager.getProjectStreams(projectId);
34
- const users = projectUsers.get(projectId);
35
-
36
- // Filter to only count active streams
37
- const activeStreams = allProjectStreams.filter(s => s.status === 'active');
38
-
39
- // Get per-chat-session user presence from WS rooms
40
- const chatSessionUsers = ws.getProjectChatSessions(projectId);
41
-
42
- return {
43
- projectId,
44
- hasActiveStreams: activeStreams.length > 0,
45
- activeStreamCount: activeStreams.length,
46
- activeUsers: users ? [...users].map(u => ({
47
- userId: u.userId,
48
- userName: u.userName
49
- })) : [],
50
- streams: allProjectStreams.map(s => ({
51
- streamId: s.streamId,
52
- chatSessionId: s.chatSessionId,
53
- status: s.status,
54
- startedAt: s.startedAt,
55
- messagesCount: s.messages.length
56
- })),
57
- chatSessionUsers: Object.fromEntries(
58
- Array.from(chatSessionUsers.entries()).map(([csId, csUsers]) => [
59
- csId,
60
- csUsers.map(u => {
61
- // Resolve userName from projectUsers
62
- const projectUser = users ? [...users].find(pu => pu.userId === u.userId) : undefined;
63
- return { userId: u.userId, userName: projectUser?.userName || u.userId };
64
- })
65
- ])
66
- )
67
- };
68
- } else {
69
- // Get status for all projects
70
- const allProjects = new Map<string, any>();
71
-
72
- // Get all active streams grouped by project
73
- const allStreams = streamManager.getAllStreams();
74
- allStreams.forEach(stream => {
75
- if (stream.projectId) {
76
- if (!allProjects.has(stream.projectId)) {
77
- allProjects.set(stream.projectId, {
78
- projectId: stream.projectId,
79
- hasActiveStreams: false,
80
- activeStreamCount: 0,
81
- activeUsers: [],
82
- streams: []
83
- });
84
- }
85
-
86
- const projectData = allProjects.get(stream.projectId);
87
- if (stream.status === 'active') {
88
- projectData.hasActiveStreams = true;
89
- projectData.activeStreamCount++;
90
- }
91
- projectData.streams.push({
92
- streamId: stream.streamId,
93
- chatSessionId: stream.chatSessionId,
94
- status: stream.status,
95
- startedAt: stream.startedAt,
96
- messagesCount: stream.messages.length
97
- });
98
- }
99
- });
100
-
101
- // Add active users to each project
102
- projectUsers.forEach((users, projectId) => {
103
- if (!allProjects.has(projectId)) {
104
- allProjects.set(projectId, {
105
- projectId,
106
- hasActiveStreams: false,
107
- activeStreamCount: 0,
108
- activeUsers: [],
109
- streams: [],
110
- chatSessionUsers: {}
111
- });
112
- }
113
-
114
- const projectData = allProjects.get(projectId);
115
- projectData.activeUsers = [...users].map(u => ({
116
- userId: u.userId,
117
- userName: u.userName
118
- }));
119
- });
120
-
121
- // Add per-chat-session user presence to each project
122
- for (const [projectId, projectData] of allProjects) {
123
- const chatSessionUsers = ws.getProjectChatSessions(projectId);
124
- const users = projectUsers.get(projectId);
125
- projectData.chatSessionUsers = Object.fromEntries(
126
- Array.from(chatSessionUsers.entries()).map(([csId, csUsers]) => [
127
- csId,
128
- csUsers.map(u => {
129
- const projectUser = users ? [...users].find(pu => pu.userId === u.userId) : undefined;
130
- return { userId: u.userId, userName: projectUser?.userName || u.userId };
131
- })
132
- ])
133
- );
134
- }
135
-
136
- return [...allProjects.values()];
137
- }
138
- }
139
-
140
- // Update user presence
141
- export function updateUserPresence(projectId: string, userId: string, userName: string, action: string) {
142
- cleanupInactiveUsers();
143
-
144
- if (action === 'leave') {
145
- // Remove user from project
146
- const users = projectUsers.get(projectId);
147
- if (users) {
148
- const updatedUsers = new Set([...users].filter(u => u.userId !== userId));
149
- if (updatedUsers.size === 0) {
150
- projectUsers.delete(projectId);
151
- } else {
152
- projectUsers.set(projectId, updatedUsers);
153
- }
154
- }
155
- } else {
156
- // Add or update user presence
157
- if (!projectUsers.has(projectId)) {
158
- projectUsers.set(projectId, new Set());
159
- }
160
-
161
- const users = projectUsers.get(projectId)!;
162
- // Remove old entry if exists
163
- const updatedUsers = new Set([...users].filter(u => u.userId !== userId));
164
- // Add new entry with updated timestamp
165
- updatedUsers.add({
166
- userId,
167
- userName,
168
- lastSeen: Date.now()
169
- });
170
- projectUsers.set(projectId, updatedUsers);
171
- }
172
-
173
- // Return current users for the project
174
- const currentUsers = projectUsers.get(projectId);
175
- return {
176
- projectId,
177
- activeUsers: currentUsers ? [...currentUsers].map(u => ({
178
- userId: u.userId,
179
- userName: u.userName
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, or tool result 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 as any).$subMessages as SubAgentActivity[] | undefined;
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 _len = subMessages?.length ?? 0;
19
- if (_len > 0 && scrollContainer) {
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 toolInput.$result}
81
- {@const resultContent = toolInput.$result.content as any}
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 { server, tool } = parseMcpToolName(toolInput.name);
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(pattern: string): string {
14
- if (pattern.includes('**/*.')) {
15
- const ext = pattern.split('**/*.')[1];
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 (pattern.includes('**/')) {
18
- const rest = pattern.split('**/')[1];
17
+ } else if (p.includes('**/')) {
18
+ const rest = p.split('**/')[1];
19
19
  return `all ${rest} in subdirectories`;
20
- } else if (pattern.includes('*.')) {
21
- const ext = pattern.split('*.')[1];
20
+ } else if (p.includes('*.')) {
21
+ const ext = p.split('*.')[1];
22
22
  return `.${ext} files`;
23
23
  }
24
- return pattern;
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">