@townco/ui 0.1.106 → 0.1.107
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/core/hooks/use-chat-messages.d.ts +1 -0
- package/dist/core/hooks/use-chat-session.js +18 -2
- package/dist/core/hooks/use-tool-calls.d.ts +2 -0
- package/dist/core/hooks/use-tool-calls.js +0 -12
- package/dist/core/schemas/chat.d.ts +2 -0
- package/dist/core/schemas/tool-call.d.ts +2 -0
- package/dist/core/schemas/tool-call.js +6 -0
- package/dist/core/store/chat-store.js +6 -3
- package/dist/core/utils/tool-call-state.js +8 -1
- package/dist/gui/components/MessageContent.js +20 -4
- package/dist/gui/components/SubAgentDetails.js +0 -19
- package/dist/gui/components/ToolOperation.js +13 -7
- package/dist/sdk/schemas/session.d.ts +2 -0
- package/dist/sdk/transports/http.js +15 -2
- package/package.json +3 -3
|
@@ -160,6 +160,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
|
|
|
160
160
|
isStreaming?: boolean | undefined;
|
|
161
161
|
}[] | undefined;
|
|
162
162
|
subagentStreaming?: boolean | undefined;
|
|
163
|
+
subagentCompleted?: boolean | undefined;
|
|
163
164
|
}[] | undefined;
|
|
164
165
|
hookNotifications?: {
|
|
165
166
|
id: string;
|
|
@@ -76,12 +76,28 @@ export function useChatSession(client, initialSessionId) {
|
|
|
76
76
|
// Check if we should append to the last assistant message or create a new one
|
|
77
77
|
const messages = useChatStore.getState().messages;
|
|
78
78
|
const lastMessage = messages[messages.length - 1];
|
|
79
|
+
// Check if this is a replay chunk (not live streaming)
|
|
80
|
+
const isReplayChunk = updateMeta?.isReplay === true;
|
|
79
81
|
if (update.message.role === "assistant" &&
|
|
82
|
+
lastMessage?.role === "assistant" &&
|
|
83
|
+
textContent &&
|
|
84
|
+
isReplayChunk) {
|
|
85
|
+
// During replay, always append to the last assistant message
|
|
86
|
+
// This allows multi-part messages (text, tools, text) to accumulate correctly
|
|
87
|
+
logger.debug("Appending replay text to existing assistant message", {
|
|
88
|
+
existingLength: lastMessage.content.length,
|
|
89
|
+
appendingLength: textContent.length,
|
|
90
|
+
});
|
|
91
|
+
useChatStore.getState().updateMessage(lastMessage.id, {
|
|
92
|
+
content: lastMessage.content + textContent,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else if (update.message.role === "assistant" &&
|
|
80
96
|
lastMessage?.role === "assistant" &&
|
|
81
97
|
lastMessage.content === "" &&
|
|
82
98
|
textContent) {
|
|
83
|
-
//
|
|
84
|
-
logger.debug("Appending text to existing assistant message");
|
|
99
|
+
// Non-replay: append text to existing empty assistant message (created by tool call)
|
|
100
|
+
logger.debug("Appending text to existing empty assistant message");
|
|
85
101
|
useChatStore.getState().updateMessage(lastMessage.id, {
|
|
86
102
|
content: lastMessage.content + textContent,
|
|
87
103
|
});
|
|
@@ -157,6 +157,7 @@ export declare function useToolCalls(client: AcpClient | null): {
|
|
|
157
157
|
isStreaming?: boolean | undefined;
|
|
158
158
|
}[] | undefined;
|
|
159
159
|
subagentStreaming?: boolean | undefined;
|
|
160
|
+
subagentCompleted?: boolean | undefined;
|
|
160
161
|
}[]>;
|
|
161
162
|
getToolCallsForSession: (sessionId: string) => {
|
|
162
163
|
id: string;
|
|
@@ -307,5 +308,6 @@ export declare function useToolCalls(client: AcpClient | null): {
|
|
|
307
308
|
isStreaming?: boolean | undefined;
|
|
308
309
|
}[] | undefined;
|
|
309
310
|
subagentStreaming?: boolean | undefined;
|
|
311
|
+
subagentCompleted?: boolean | undefined;
|
|
310
312
|
}[];
|
|
311
313
|
};
|
|
@@ -28,22 +28,10 @@ export function useToolCalls(client) {
|
|
|
28
28
|
addToolCallToCurrentMessage(update.toolCall);
|
|
29
29
|
}
|
|
30
30
|
else if (update.type === "tool_call_update") {
|
|
31
|
-
// Tool call update notification
|
|
32
|
-
_logger.info("[SUBAGENT] Frontend received tool_call_update", {
|
|
33
|
-
sessionId: update.sessionId,
|
|
34
|
-
toolCallId: update.toolCallUpdate.id,
|
|
35
|
-
status: update.toolCallUpdate.status,
|
|
36
|
-
hasSubagentMessages: !!update.toolCallUpdate.subagentMessages,
|
|
37
|
-
subagentMessageCount: update.toolCallUpdate.subagentMessages?.length || 0,
|
|
38
|
-
});
|
|
39
31
|
// Update session-level tool calls (for sidebar)
|
|
40
32
|
updateToolCall(update.sessionId, update.toolCallUpdate);
|
|
41
33
|
// Also update in current assistant message (for inline display)
|
|
42
34
|
updateToolCallInCurrentMessage(update.toolCallUpdate);
|
|
43
|
-
_logger.info("[SUBAGENT] Successfully updated tool call state", {
|
|
44
|
-
sessionId: update.sessionId,
|
|
45
|
-
toolCallId: update.toolCallUpdate.id,
|
|
46
|
-
});
|
|
47
35
|
}
|
|
48
36
|
});
|
|
49
37
|
return () => {
|
|
@@ -239,6 +239,7 @@ export declare const DisplayMessage: z.ZodObject<{
|
|
|
239
239
|
isStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
240
240
|
}, z.core.$strip>>>;
|
|
241
241
|
subagentStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
242
|
+
subagentCompleted: z.ZodOptional<z.ZodBoolean>;
|
|
242
243
|
}, z.core.$strip>>>;
|
|
243
244
|
hookNotifications: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
244
245
|
id: z.ZodString;
|
|
@@ -503,6 +504,7 @@ export declare const ChatSessionState: z.ZodObject<{
|
|
|
503
504
|
isStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
504
505
|
}, z.core.$strip>>>;
|
|
505
506
|
subagentStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
507
|
+
subagentCompleted: z.ZodOptional<z.ZodBoolean>;
|
|
506
508
|
}, z.core.$strip>>>;
|
|
507
509
|
hookNotifications: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
508
510
|
id: z.ZodString;
|
|
@@ -445,6 +445,7 @@ export declare const ToolCallSchema: z.ZodObject<{
|
|
|
445
445
|
isStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
446
446
|
}, z.core.$strip>>>;
|
|
447
447
|
subagentStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
448
|
+
subagentCompleted: z.ZodOptional<z.ZodBoolean>;
|
|
448
449
|
}, z.core.$strip>;
|
|
449
450
|
export type ToolCall = z.infer<typeof ToolCallSchema>;
|
|
450
451
|
/**
|
|
@@ -596,6 +597,7 @@ export declare const ToolCallUpdateSchema: z.ZodObject<{
|
|
|
596
597
|
}, z.core.$strip>], "type">>>;
|
|
597
598
|
isStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
598
599
|
}, z.core.$strip>>>;
|
|
600
|
+
subagentCompleted: z.ZodOptional<z.ZodBoolean>;
|
|
599
601
|
_meta: z.ZodOptional<z.ZodObject<{
|
|
600
602
|
truncationWarning: z.ZodOptional<z.ZodString>;
|
|
601
603
|
compactionAction: z.ZodOptional<z.ZodEnum<{
|
|
@@ -183,6 +183,8 @@ export const ToolCallSchema = z.object({
|
|
|
183
183
|
subagentMessages: z.array(SubagentMessageSchema).optional(),
|
|
184
184
|
/** Whether the sub-agent is currently streaming */
|
|
185
185
|
subagentStreaming: z.boolean().optional(),
|
|
186
|
+
/** Whether the sub-agent has completed */
|
|
187
|
+
subagentCompleted: z.boolean().optional(),
|
|
186
188
|
});
|
|
187
189
|
/**
|
|
188
190
|
* Partial update for an existing tool call
|
|
@@ -207,6 +209,8 @@ export const ToolCallUpdateSchema = z.object({
|
|
|
207
209
|
subagentSessionId: z.string().optional(),
|
|
208
210
|
/** Sub-agent messages for replay */
|
|
209
211
|
subagentMessages: z.array(SubagentMessageSchema).optional(),
|
|
212
|
+
/** Whether the sub-agent has completed */
|
|
213
|
+
subagentCompleted: z.boolean().optional(),
|
|
210
214
|
/** Internal metadata (e.g., compaction info) */
|
|
211
215
|
_meta: z
|
|
212
216
|
.object({
|
|
@@ -245,6 +249,8 @@ export function mergeToolCallUpdate(existing, update) {
|
|
|
245
249
|
subagentSessionId: update.subagentSessionId ?? existing.subagentSessionId,
|
|
246
250
|
// Sub-agent messages for replay
|
|
247
251
|
subagentMessages: update.subagentMessages ?? existing.subagentMessages,
|
|
252
|
+
// Sub-agent completion status (once true, stays true)
|
|
253
|
+
subagentCompleted: update.subagentCompleted ?? existing.subagentCompleted,
|
|
248
254
|
// Internal metadata (compaction info)
|
|
249
255
|
_meta: update._meta ?? existing._meta,
|
|
250
256
|
};
|
|
@@ -265,10 +265,12 @@ export const useChatStore = create((set) => ({
|
|
|
265
265
|
if (!lastAssistantMsg)
|
|
266
266
|
return state;
|
|
267
267
|
// Track the content position where this tool call was invoked
|
|
268
|
-
|
|
268
|
+
// Set contentPosition to where the tool call was invoked in the content stream
|
|
269
|
+
// This ensures tool calls appear inline at the correct position, even during streaming
|
|
270
|
+
const currentContentLength = lastAssistantMsg.content.length;
|
|
269
271
|
const toolCallWithPosition = {
|
|
270
272
|
...toolCall,
|
|
271
|
-
contentPosition,
|
|
273
|
+
contentPosition: currentContentLength,
|
|
272
274
|
};
|
|
273
275
|
messages[lastAssistantIndex] = {
|
|
274
276
|
...lastAssistantMsg,
|
|
@@ -300,7 +302,8 @@ export const useChatStore = create((set) => ({
|
|
|
300
302
|
if (!existing)
|
|
301
303
|
return state;
|
|
302
304
|
const updatedToolCalls = [...toolCalls];
|
|
303
|
-
|
|
305
|
+
const merged = mergeToolCallUpdate(existing, update);
|
|
306
|
+
updatedToolCalls[existingIndex] = merged;
|
|
304
307
|
messages[lastAssistantIndex] = {
|
|
305
308
|
...lastAssistantMsg,
|
|
306
309
|
toolCalls: updatedToolCalls,
|
|
@@ -14,7 +14,14 @@ export function getToolCallDisplayState(toolCall) {
|
|
|
14
14
|
if (toolCall.status === "failed" || toolCall.error) {
|
|
15
15
|
return "failed";
|
|
16
16
|
}
|
|
17
|
-
//
|
|
17
|
+
// Check if this is a subagent call
|
|
18
|
+
const hasSubagent = !!(toolCall.subagentMessages && toolCall.subagentMessages.length > 0);
|
|
19
|
+
// For subagent calls: completed when subagentCompleted is true (regardless of status)
|
|
20
|
+
// This handles the case where status is "in_progress" but subagent has finished
|
|
21
|
+
if (hasSubagent && toolCall.subagentCompleted) {
|
|
22
|
+
return "completed";
|
|
23
|
+
}
|
|
24
|
+
// Completed state (for non-subagent calls)
|
|
18
25
|
if (toolCall.status === "completed") {
|
|
19
26
|
return "completed";
|
|
20
27
|
}
|
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { cva } from "class-variance-authority";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useChatStore } from "../../core/store/chat-store.js";
|
|
5
|
-
import { isPreliminaryToolCall } from "../../core/utils/tool-call-state.js";
|
|
5
|
+
import { getToolCallDisplayState, isPreliminaryToolCall, } from "../../core/utils/tool-call-state.js";
|
|
6
6
|
import { cn } from "../lib/utils.js";
|
|
7
7
|
import { HookNotification } from "./HookNotification.js";
|
|
8
8
|
import { Reasoning } from "./Reasoning.js";
|
|
@@ -140,6 +140,19 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
140
140
|
};
|
|
141
141
|
// Get tool_response hook notifications to associate with tool calls
|
|
142
142
|
const toolResponseHooks = (message.hookNotifications || []).filter((n) => n.hookType === "tool_response");
|
|
143
|
+
// Helper to check if all non-preliminary tool calls are completed
|
|
144
|
+
const areAllToolCallsCompleted = () => {
|
|
145
|
+
const nonPreliminary = sortedToolCalls.filter((tc) => !isPreliminaryToolCall(tc));
|
|
146
|
+
if (nonPreliminary.length === 0)
|
|
147
|
+
return false;
|
|
148
|
+
return nonPreliminary.every((tc) => getToolCallDisplayState(tc) === "completed");
|
|
149
|
+
};
|
|
150
|
+
// Check if we should show the thinking indicator
|
|
151
|
+
// Show when: all tool calls are complete, message is streaming, and there's minimal content after tool calls
|
|
152
|
+
const shouldShowThinkingIndicator = message.isStreaming &&
|
|
153
|
+
sortedToolCalls.length > 0 &&
|
|
154
|
+
areAllToolCallsCompleted() &&
|
|
155
|
+
message.content.trim().length < 50; // Only show if there's very little text content
|
|
143
156
|
// Helper to render a tool call or group
|
|
144
157
|
const renderToolCallOrGroup = (item, index) => {
|
|
145
158
|
// Batch group (parallel operations)
|
|
@@ -175,9 +188,8 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
175
188
|
// Check if hook notifications have positions for inline rendering
|
|
176
189
|
const hasHookPositions = hookNotifications.some((n) => n.contentPosition !== undefined);
|
|
177
190
|
if (!hasHookPositions) {
|
|
178
|
-
// No positions - render hooks at top, then
|
|
179
|
-
return (_jsxs(_Fragment, { children: [hookNotifications.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-1", children: hookNotifications.map((notification) => (_jsx(HookNotification, { notification: notification }, notification.id))) })), groupedToolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) })), _jsx("div", { children: _jsx(
|
|
180
|
-
] }));
|
|
191
|
+
// No positions - render hooks at top, then content, then tool calls
|
|
192
|
+
return (_jsxs(_Fragment, { children: [hookNotifications.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-1", children: hookNotifications.map((notification) => (_jsx(HookNotification, { notification: notification }, notification.id))) })), _jsx("div", { children: _jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false, sources: message.sources ?? [] }) }), groupedToolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) })), shouldShowThinkingIndicator && (_jsx("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: _jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Thinking..." }) }) }))] }));
|
|
181
193
|
}
|
|
182
194
|
// Hooks have positions - render them inline with content
|
|
183
195
|
const elements = [];
|
|
@@ -336,6 +348,10 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
336
348
|
toolCalls: [preliminaryToolCalls[0]], isGrouped: false }) }, `tool-${preliminaryToolCalls[0]?.id}`));
|
|
337
349
|
}
|
|
338
350
|
}
|
|
351
|
+
// Add thinking indicator if all tool calls are complete but message is still streaming
|
|
352
|
+
if (shouldShowThinkingIndicator) {
|
|
353
|
+
elements.push(_jsx("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: _jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Thinking..." }) }) }, "thinking-indicator"));
|
|
354
|
+
}
|
|
339
355
|
return _jsx(_Fragment, { children: elements });
|
|
340
356
|
})()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: message.images.map((image, imageIndex) => (_jsx("img", { src: `data:${image.mimeType};base64,${image.data}`, alt: `Attachment ${imageIndex + 1}`, className: "max-w-[200px] max-h-[200px] rounded-lg object-cover" }, `image-${image.mimeType}-${image.data.slice(0, 20)}`))) })), message.content && (_jsx("div", { className: "whitespace-pre-wrap", children: message.content }))] }))] }));
|
|
341
357
|
}
|
|
@@ -23,18 +23,6 @@ export function SubAgentDetails({ parentStatus, agentName, query, isExpanded: co
|
|
|
23
23
|
const thinkingContainerRef = useRef(null);
|
|
24
24
|
// Use messages from storedMessages prop (populated by parent via tool_call_update)
|
|
25
25
|
const messages = storedMessages || [];
|
|
26
|
-
// Log when messages are received/updated
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
console.log("[SUBAGENT] SubAgentDetails received messages update", {
|
|
29
|
-
agentName,
|
|
30
|
-
messageCount: messages.length,
|
|
31
|
-
parentStatus,
|
|
32
|
-
hasMessages: messages.length > 0,
|
|
33
|
-
contentPreview: messages[0]?.content?.substring(0, 100),
|
|
34
|
-
toolCallCount: messages[0]?.toolCalls?.length || 0,
|
|
35
|
-
contentBlockCount: messages[0]?.contentBlocks?.length || 0,
|
|
36
|
-
});
|
|
37
|
-
}, [messages, agentName, parentStatus]);
|
|
38
26
|
// Determine if subagent is still running based on parent status
|
|
39
27
|
const isRunning = parentStatus === "in_progress" || parentStatus === "pending";
|
|
40
28
|
// Get the current/latest message
|
|
@@ -44,13 +32,6 @@ export function SubAgentDetails({ parentStatus, agentName, query, isExpanded: co
|
|
|
44
32
|
(currentMessage.toolCalls && currentMessage.toolCalls.length > 0) ||
|
|
45
33
|
(currentMessage.contentBlocks &&
|
|
46
34
|
currentMessage.contentBlocks.length > 0));
|
|
47
|
-
console.log("[SUBAGENT] SubAgentDetails render state", {
|
|
48
|
-
agentName,
|
|
49
|
-
messageCount: messages.length,
|
|
50
|
-
hasContent,
|
|
51
|
-
isRunning,
|
|
52
|
-
parentStatus,
|
|
53
|
-
});
|
|
54
35
|
// Auto-collapse Thinking when completed (so Output is the primary view)
|
|
55
36
|
const prevIsRunningRef = useRef(isRunning);
|
|
56
37
|
useEffect(() => {
|
|
@@ -71,8 +71,8 @@ function formatElapsedTime(seconds) {
|
|
|
71
71
|
/**
|
|
72
72
|
* Component to display running duration for a tool call
|
|
73
73
|
*/
|
|
74
|
-
function RunningDuration({ startTime }) {
|
|
75
|
-
const elapsed = useElapsedTime(startTime,
|
|
74
|
+
function RunningDuration({ startTime, isRunning = true, }) {
|
|
75
|
+
const elapsed = useElapsedTime(startTime, isRunning);
|
|
76
76
|
return (_jsx("span", { className: "text-xs text-text-secondary/70 tabular-nums ml-1", children: formatElapsedTime(elapsed) }));
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
@@ -227,7 +227,7 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
227
227
|
// For preliminary/selecting states, show simple non-expandable text
|
|
228
228
|
if (isSelecting && !isGrouped) {
|
|
229
229
|
return (_jsx("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
230
|
-
_jsx("div", { className: "text-text-secondary/70", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-xs text-text-muted", children: [displayText, singleToolCall?.startedAt && (_jsx(RunningDuration, { startTime: singleToolCall.startedAt }))] })
|
|
230
|
+
_jsx("div", { className: "text-text-secondary/70", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-xs text-text-muted", children: [displayText, singleToolCall?.startedAt && (_jsx(RunningDuration, { startTime: singleToolCall.startedAt, isRunning: !singleToolCall.subagentCompleted }))] })
|
|
231
231
|
] }) }));
|
|
232
232
|
}
|
|
233
233
|
// If it's a grouped preliminary (selecting) state
|
|
@@ -241,9 +241,11 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
241
241
|
return null;
|
|
242
242
|
return Math.min(...startTimes);
|
|
243
243
|
})();
|
|
244
|
+
// Check if all tool calls in the group are completed
|
|
245
|
+
const allCompleted = toolCalls.every((tc) => tc.subagentCompleted);
|
|
244
246
|
return (_jsxs("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: [
|
|
245
247
|
_jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
246
|
-
_jsx("div", { className: "text-text-secondary/70", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Selecting tools" }), selectingStartTime && (_jsx(RunningDuration, { startTime: selectingStartTime })), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })
|
|
248
|
+
_jsx("div", { className: "text-text-secondary/70", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Selecting tools" }), selectingStartTime && (_jsx(RunningDuration, { startTime: selectingStartTime, isRunning: !allCompleted })), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })
|
|
247
249
|
] }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText })
|
|
248
250
|
] }));
|
|
249
251
|
}
|
|
@@ -253,7 +255,7 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
253
255
|
_jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
254
256
|
_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: [isGrouped && _jsx("span", { className: "mr-1", children: "Parallel operation" }), !isGrouped && displayText] }), !isGrouped &&
|
|
255
257
|
singleToolCall?.startedAt &&
|
|
256
|
-
displayState === "executing" && (_jsx(RunningDuration, { startTime: singleToolCall.startedAt })), isGrouped &&
|
|
258
|
+
displayState === "executing" && (_jsx(RunningDuration, { startTime: singleToolCall.startedAt, isRunning: !singleToolCall.subagentCompleted })), isGrouped &&
|
|
257
259
|
displayState === "executing" &&
|
|
258
260
|
(() => {
|
|
259
261
|
const startTimes = toolCalls
|
|
@@ -262,7 +264,10 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
262
264
|
if (startTimes.length === 0)
|
|
263
265
|
return null;
|
|
264
266
|
const earliestStart = Math.min(...startTimes);
|
|
265
|
-
|
|
267
|
+
// Check if all tool calls are completed
|
|
268
|
+
const allCompleted = toolCalls.every((tc) => tc.status === "completed" &&
|
|
269
|
+
(!tc.subagentMessages || tc.subagentCompleted));
|
|
270
|
+
return (_jsx(RunningDuration, { startTime: earliestStart, isRunning: !allCompleted }));
|
|
266
271
|
})(), isGrouped && (_jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })), isFailed && (_jsx("span", { title: isGrouped
|
|
267
272
|
? `${toolCalls.filter((tc) => tc.status === "failed").length} of ${toolCalls.length} operations failed`
|
|
268
273
|
: singleToolCall?.error || "Operation failed", children: _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }) })), isGrouped && groupHasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [
|
|
@@ -338,7 +343,8 @@ function GroupedToolCallItem({ toolCall, hookNotification, }) {
|
|
|
338
343
|
hookNotification?.metadata?.action === "compacted_then_truncated" ||
|
|
339
344
|
toolCall._meta?.compactionAction === "truncated");
|
|
340
345
|
const isFailed = toolCall.status === "failed";
|
|
341
|
-
const isRunning = toolCall.status === "pending" || toolCall.status === "in_progress"
|
|
346
|
+
const isRunning = (toolCall.status === "pending" || toolCall.status === "in_progress") &&
|
|
347
|
+
!toolCall.subagentCompleted;
|
|
342
348
|
if (isSubagentCall) {
|
|
343
349
|
// Render subagent with clickable header and SubAgentDetails component
|
|
344
350
|
return (_jsxs("div", { className: "flex flex-col ml-5", children: [
|
|
@@ -350,6 +350,7 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
350
350
|
isStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
351
351
|
}, z.core.$strip>>>;
|
|
352
352
|
subagentStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
353
|
+
subagentCompleted: z.ZodOptional<z.ZodBoolean>;
|
|
353
354
|
}, z.core.$strip>;
|
|
354
355
|
messageId: z.ZodOptional<z.ZodString>;
|
|
355
356
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -557,6 +558,7 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
557
558
|
}, z.core.$strip>], "type">>>;
|
|
558
559
|
isStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
559
560
|
}, z.core.$strip>>>;
|
|
561
|
+
subagentCompleted: z.ZodOptional<z.ZodBoolean>;
|
|
560
562
|
_meta: z.ZodOptional<z.ZodObject<{
|
|
561
563
|
truncationWarning: z.ZodOptional<z.ZodString>;
|
|
562
564
|
compactionAction: z.ZodOptional<z.ZodEnum<{
|
|
@@ -1169,6 +1169,17 @@ export class HttpTransport {
|
|
|
1169
1169
|
typeof update._meta.originalContentPath === "string"
|
|
1170
1170
|
? update._meta.originalContentPath
|
|
1171
1171
|
: undefined;
|
|
1172
|
+
// Check for subagentCompleted at top level first (sent by adapter),
|
|
1173
|
+
// then fallback to _meta (for backward compatibility)
|
|
1174
|
+
const subagentCompleted = "subagentCompleted" in update &&
|
|
1175
|
+
typeof update.subagentCompleted === "boolean"
|
|
1176
|
+
? update.subagentCompleted
|
|
1177
|
+
: update._meta &&
|
|
1178
|
+
typeof update._meta === "object" &&
|
|
1179
|
+
"subagentCompleted" in update._meta &&
|
|
1180
|
+
typeof update._meta.subagentCompleted === "boolean"
|
|
1181
|
+
? update._meta.subagentCompleted
|
|
1182
|
+
: undefined;
|
|
1172
1183
|
// Tool call update notification
|
|
1173
1184
|
const toolCallUpdate = {
|
|
1174
1185
|
id: update.toolCallId ?? "",
|
|
@@ -1181,6 +1192,7 @@ export class HttpTransport {
|
|
|
1181
1192
|
locations: update.locations,
|
|
1182
1193
|
rawOutput: update.rawOutput,
|
|
1183
1194
|
tokenUsage: update.tokenUsage,
|
|
1195
|
+
subagentCompleted,
|
|
1184
1196
|
content: update.content?.map((c) => {
|
|
1185
1197
|
// Type guard to safely check properties
|
|
1186
1198
|
if (typeof c !== "object" || c === null) {
|
|
@@ -1492,8 +1504,9 @@ export class HttpTransport {
|
|
|
1492
1504
|
isComplete: false,
|
|
1493
1505
|
};
|
|
1494
1506
|
}
|
|
1495
|
-
//
|
|
1496
|
-
|
|
1507
|
+
// Queue chunks for both live streaming AND replay
|
|
1508
|
+
// Replay chunks need to be accumulated just like live chunks
|
|
1509
|
+
if (chunk) {
|
|
1497
1510
|
// Resolve any waiting receive() calls immediately
|
|
1498
1511
|
const resolver = this.chunkResolvers.shift();
|
|
1499
1512
|
if (resolver) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.107",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@radix-ui/react-slot": "^1.2.4",
|
|
50
50
|
"@radix-ui/react-tabs": "^1.1.13",
|
|
51
51
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
52
|
-
"@townco/core": "0.0.
|
|
52
|
+
"@townco/core": "0.0.85",
|
|
53
53
|
"@types/mdast": "^4.0.4",
|
|
54
54
|
"@uiw/react-json-view": "^2.0.0-alpha.39",
|
|
55
55
|
"class-variance-authority": "^0.7.1",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"zustand": "^5.0.8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@townco/tsconfig": "0.1.
|
|
70
|
+
"@townco/tsconfig": "0.1.104",
|
|
71
71
|
"@types/node": "^24.10.0",
|
|
72
72
|
"@types/react": "^19.2.2",
|
|
73
73
|
"@types/unist": "^3.0.3",
|