@alpaca-editor/core 1.0.4132 β†’ 1.0.4134

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.
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, } from "react";
3
3
  import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, } from "lucide-react";
4
4
  import { DancingDots } from "./DancingDots";
@@ -18,6 +18,106 @@ import { Tooltip, TooltipTrigger, TooltipContent, } from "../../components/ui/to
18
18
  const UserMessage = ({ message }) => {
19
19
  return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx(User, { className: "h-6 w-6 text-blue-600", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-900", children: "You" }), message.createdDate && (_jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date(message.createdDate)) }))] }), _jsx("div", { className: "prose prose-sm max-w-none text-xs text-gray-700 select-text", children: message.content })] })] }));
20
20
  };
21
+ // Helper to extract todos from potentially incomplete JSON during streaming
22
+ const extractPartialTodos = (jsonText) => {
23
+ // First try to parse complete JSON
24
+ try {
25
+ const parsed = JSON.parse(jsonText);
26
+ return Array.isArray(parsed) ? parsed : parsed?.items || [];
27
+ }
28
+ catch (e) {
29
+ // If JSON is incomplete, try to extract whatever todo items we can find
30
+ const items = [];
31
+ // Look for individual todo objects in the partial JSON
32
+ // Match patterns like: { "text": "...", "done": false, "note": "..." }
33
+ // Handle various field orderings (text can be anywhere in the object)
34
+ const textPattern = /"text"\s*:\s*"([^"]+)"/g;
35
+ const textMatches = [];
36
+ let textMatch;
37
+ while ((textMatch = textPattern.exec(jsonText)) !== null) {
38
+ if (textMatch[1]) {
39
+ textMatches.push({
40
+ text: textMatch[1],
41
+ startIdx: textMatch.index,
42
+ });
43
+ }
44
+ }
45
+ // For each text field found, try to find the enclosing object
46
+ for (const { text, startIdx } of textMatches) {
47
+ // Find the opening brace before this text field
48
+ let openBrace = -1;
49
+ for (let i = startIdx - 1; i >= 0; i--) {
50
+ if (jsonText[i] === "{") {
51
+ openBrace = i;
52
+ break;
53
+ }
54
+ if (jsonText[i] === "}")
55
+ break; // Hit another object's end
56
+ }
57
+ if (openBrace === -1)
58
+ continue;
59
+ // Find the closing brace after this text field
60
+ let closeBrace = -1;
61
+ let depth = 0;
62
+ for (let i = openBrace; i < jsonText.length; i++) {
63
+ if (jsonText[i] === "{")
64
+ depth++;
65
+ if (jsonText[i] === "}") {
66
+ depth--;
67
+ if (depth === 0) {
68
+ closeBrace = i;
69
+ break;
70
+ }
71
+ }
72
+ }
73
+ // Extract the object and try to parse it
74
+ const objStr = closeBrace !== -1
75
+ ? jsonText.substring(openBrace, closeBrace + 1)
76
+ : jsonText.substring(openBrace) + "}"; // Try to close incomplete object
77
+ try {
78
+ const obj = JSON.parse(objStr);
79
+ if (obj.text) {
80
+ items.push({
81
+ text: obj.text,
82
+ done: obj.done === true,
83
+ note: obj.note || undefined,
84
+ });
85
+ }
86
+ }
87
+ catch (e) {
88
+ // Skip malformed objects
89
+ }
90
+ }
91
+ // Also try to extract from partial objects at the end
92
+ // Look for the last opening brace and try to parse up to where we have valid content
93
+ const lines = jsonText.split("\n");
94
+ for (let i = lines.length - 1; i >= 0; i--) {
95
+ const partialJson = lines.slice(0, i + 1).join("\n");
96
+ // Try to close any open braces/brackets
97
+ let testJson = partialJson;
98
+ const openBraces = (testJson.match(/\{/g) || []).length;
99
+ const closeBraces = (testJson.match(/\}/g) || []).length;
100
+ const openBrackets = (testJson.match(/\[/g) || []).length;
101
+ const closeBrackets = (testJson.match(/\]/g) || []).length;
102
+ // Add missing closing characters
103
+ testJson += "]".repeat(openBrackets - closeBrackets);
104
+ testJson += "}".repeat(openBraces - closeBraces);
105
+ try {
106
+ const parsed = JSON.parse(testJson);
107
+ const partialItems = Array.isArray(parsed)
108
+ ? parsed
109
+ : parsed?.items || [];
110
+ if (partialItems.length > items.length) {
111
+ return partialItems;
112
+ }
113
+ }
114
+ catch (e) {
115
+ continue;
116
+ }
117
+ }
118
+ return items;
119
+ }
120
+ };
21
121
  const extractTodosFromMessages = (messages) => {
22
122
  const todos = [];
23
123
  const fencedTodoToken = "```todo_list";
@@ -48,13 +148,20 @@ const extractTodosFromMessages = (messages) => {
48
148
  break;
49
149
  try {
50
150
  let jsonText = "";
151
+ let isComplete = true;
51
152
  if (isFenced) {
52
153
  const afterToken = todoStart + fencedTodoToken.length;
53
154
  const closePos = content.indexOf("```", afterToken);
54
- if (closePos === -1)
55
- break;
56
- jsonText = content.slice(afterToken, closePos).trim();
57
- cursor = closePos + 3;
155
+ if (closePos === -1) {
156
+ // Incomplete fenced block - extract what we have so far
157
+ jsonText = content.slice(afterToken).trim();
158
+ isComplete = false;
159
+ cursor = content.length; // Process till end
160
+ }
161
+ else {
162
+ jsonText = content.slice(afterToken, closePos).trim();
163
+ cursor = closePos + 3;
164
+ }
58
165
  }
59
166
  else {
60
167
  const afterToken = todoStart + plainTodoToken.length;
@@ -74,24 +181,54 @@ const extractTodosFromMessages = (messages) => {
74
181
  }
75
182
  }
76
183
  }
77
- if (braceEnd === -1)
78
- break;
79
- jsonText = content.slice(braceStart, braceEnd + 1).trim();
80
- cursor = braceEnd + 1;
184
+ if (braceEnd === -1) {
185
+ // Incomplete JSON - extract what we have
186
+ jsonText = content.slice(braceStart).trim();
187
+ isComplete = false;
188
+ cursor = content.length;
189
+ }
190
+ else {
191
+ jsonText = content.slice(braceStart, braceEnd + 1).trim();
192
+ cursor = braceEnd + 1;
193
+ }
81
194
  }
82
- const parsed = JSON.parse(jsonText);
83
- const todoItems = Array.isArray(parsed) ? parsed : parsed?.items || [];
84
- const title = Array.isArray(parsed) ? undefined : parsed?.title;
195
+ // Use the partial extraction helper for incomplete JSON
196
+ const todoItems = isComplete
197
+ ? (() => {
198
+ try {
199
+ const parsed = JSON.parse(jsonText);
200
+ return Array.isArray(parsed) ? parsed : parsed?.items || [];
201
+ }
202
+ catch (e) {
203
+ return [];
204
+ }
205
+ })()
206
+ : extractPartialTodos(jsonText);
207
+ const title = (() => {
208
+ try {
209
+ const parsed = JSON.parse(jsonText);
210
+ return Array.isArray(parsed) ? undefined : parsed?.title;
211
+ }
212
+ catch (e) {
213
+ return undefined;
214
+ }
215
+ })();
85
216
  todoItems.forEach((item) => {
86
217
  if (!item)
87
218
  return;
88
- const text = item.text || item.label || String(item.task || item.title || "");
219
+ const text = item.text ||
220
+ item.content ||
221
+ item.label ||
222
+ String(item.task || item.title || "");
89
223
  if (!text)
90
224
  return;
91
225
  todos.push({
92
226
  id: item.id,
93
227
  text,
94
- done: !!(item.done ?? item.completed ?? item.checked),
228
+ done: !!(item.done ??
229
+ item.completed ??
230
+ item.checked ??
231
+ item.status === "completed"),
95
232
  note: item.note || item.description,
96
233
  messageId: message.id,
97
234
  sourceTitle: title,
@@ -107,25 +244,97 @@ const extractTodosFromMessages = (messages) => {
107
244
  return todos;
108
245
  };
109
246
  // TodoListPanel component
110
- const TodoListPanel = ({ messages }) => {
247
+ const TodoListPanel = ({ messages, agentMetadata, }) => {
111
248
  const [isExpanded, setIsExpanded] = useState(true);
112
- const todos = useMemo(() => extractTodosFromMessages(messages), [messages]);
113
- // Check if there's an active streaming message with todo content
249
+ const todos = useMemo(() => {
250
+ // First try to get todos from agent metadata (real-time updates)
251
+ const metadataTodos = (() => {
252
+ try {
253
+ const context = agentMetadata?.additionalData?.context;
254
+ const todoList = context?.todoList;
255
+ if (todoList?.items && Array.isArray(todoList.items)) {
256
+ return todoList.items
257
+ .map((item, idx) => ({
258
+ id: item.id || `metadata-${idx}`,
259
+ text: item.text ||
260
+ item.label ||
261
+ String(item.task || item.title || ""),
262
+ done: !!(item.done ?? item.completed ?? item.checked),
263
+ note: item.note || item.description,
264
+ messageId: undefined,
265
+ sourceTitle: todoList.title,
266
+ }))
267
+ .filter((item) => item.text);
268
+ }
269
+ }
270
+ catch (e) {
271
+ // Fallback to extracting from messages
272
+ }
273
+ return null;
274
+ })();
275
+ // If we have metadata todos, use them; otherwise extract from messages
276
+ if (metadataTodos && metadataTodos.length > 0) {
277
+ return metadataTodos;
278
+ }
279
+ return extractTodosFromMessages(messages);
280
+ }, [messages, agentMetadata]);
281
+ // Check if there's an active streaming message with incomplete todo content
114
282
  const isUpdating = useMemo(() => {
115
283
  return messages.some((msg) => {
116
284
  if (msg.role !== "assistant" || msg.isCompleted)
117
285
  return false;
118
286
  const content = msg.content || "";
119
- return content.includes("```todo_list") || content.includes("todo_list");
287
+ // Check for incomplete fenced todo blocks
288
+ const fencedStart = content.indexOf("```todo_list");
289
+ if (fencedStart !== -1) {
290
+ const afterStart = fencedStart + "```todo_list".length;
291
+ const closePos = content.indexOf("```", afterStart);
292
+ if (closePos === -1) {
293
+ // Incomplete fenced block
294
+ return true;
295
+ }
296
+ }
297
+ // Check for incomplete plain todo blocks
298
+ const plainStart = content.indexOf("todo_list");
299
+ if (plainStart !== -1 && plainStart !== fencedStart) {
300
+ const before = plainStart > 0 ? content[plainStart - 1] : "\n";
301
+ if (before === "\n" || before === "\r" || plainStart === 0) {
302
+ const braceStart = content.indexOf("{", plainStart);
303
+ if (braceStart !== -1) {
304
+ let depth = 0;
305
+ let braceEnd = -1;
306
+ for (let i = braceStart; i < content.length; i++) {
307
+ if (content[i] === "{")
308
+ depth++;
309
+ if (content[i] === "}") {
310
+ depth--;
311
+ if (depth === 0) {
312
+ braceEnd = i;
313
+ break;
314
+ }
315
+ }
316
+ }
317
+ if (braceEnd === -1) {
318
+ // Incomplete plain block
319
+ return true;
320
+ }
321
+ }
322
+ }
323
+ }
324
+ return false;
120
325
  });
121
326
  }, [messages]);
122
327
  if (todos.length === 0 && !isUpdating)
123
328
  return null;
124
329
  const completedCount = todos.filter((t) => t.done).length;
125
330
  const totalCount = todos.length;
126
- return (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ListTodo, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Todo List" }), isUpdating ? (_jsxs("span", { className: "flex items-center gap-1 text-xs text-blue-600", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), "Updating..."] })) : (_jsxs("span", { className: "text-xs text-gray-500", children: [completedCount, "/", totalCount, " completed"] }))] }), isExpanded ? (_jsx(ChevronUp, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDown, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isExpanded && (_jsx("div", { className: "max-h-64 overflow-y-auto px-4 pb-3", children: isUpdating && todos.length === 0 ? (_jsxs("div", { className: "flex items-center justify-center gap-2 py-4 text-xs text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), _jsx("span", { children: "Loading todo list..." })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "space-y-1.5", children: todos.map((todo, idx) => (_jsxs("div", { className: "flex items-start gap-2 rounded bg-white p-2 text-xs", children: [_jsx("div", { className: "flex-shrink-0 pt-0.5", children: todo.done ? (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-green-500 bg-green-500", children: _jsx("svg", { className: "h-3 w-3 text-white", fill: "none", strokeWidth: 2, stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })) : (_jsx("div", { className: "h-4 w-4 rounded border-2 border-gray-300" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `${todo.done
127
- ? "text-gray-500 line-through"
128
- : "text-gray-900"}`, children: todo.text }), todo.note && (_jsx("div", { className: "mt-0.5 text-xs text-gray-500", children: todo.note }))] })] }, todo.id || `${todo.messageId}-${idx}`))) }), isUpdating && todos.length > 0 && (_jsxs("div", { className: "mt-2 flex items-center gap-2 rounded bg-blue-50 px-3 py-2 text-xs text-blue-700", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), _jsx("span", { children: "Updating todo list..." })] }))] })) }))] }));
331
+ return (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ListTodo, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Todo List" }), isUpdating ? (_jsxs("span", { className: "flex items-center gap-1 text-xs text-blue-600", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), "Updating..."] })) : (_jsxs("span", { className: "text-xs text-gray-500", children: [completedCount, "/", totalCount, " completed"] }))] }), isExpanded ? (_jsx(ChevronUp, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDown, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isExpanded && (_jsxs("div", { className: "max-h-64 overflow-y-auto px-4 pb-3", children: [todos.length > 0 && (_jsx("div", { className: "space-y-1.5", children: todos.map((todo, idx) => (_jsxs("div", { className: "flex items-start gap-2 rounded bg-white p-2 text-xs", children: [_jsx("div", { className: "flex-shrink-0 pt-0.5", children: todo.done ? (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-green-500 bg-green-500", children: _jsx("svg", { className: "h-3 w-3 text-white", fill: "none", strokeWidth: 2, stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })) : (_jsx("div", { className: "h-4 w-4 rounded border-2 border-gray-300" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `${todo.done
332
+ ? "text-gray-500 line-through"
333
+ : "text-gray-900"}`, children: todo.text }), todo.note && (_jsx("div", { className: "mt-0.5 text-xs text-gray-500", children: todo.note }))] })] }, todo.id || `${todo.messageId}-${idx}`))) })), isUpdating && (_jsxs("div", { className: `flex items-center gap-2 rounded px-3 py-2 text-xs ${todos.length > 0
334
+ ? "mt-2 bg-blue-50 text-blue-700"
335
+ : "justify-center bg-white text-gray-500"}`, children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), _jsx("span", { children: todos.length > 0
336
+ ? "Updating todo list..."
337
+ : "Loading todo list..." })] }))] }))] }));
129
338
  };
130
339
  const groupConsecutiveMessages = (agentMessages) => {
131
340
  // Work directly with the messages array - streaming messages are identified by their properties
@@ -222,7 +431,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
222
431
  const [agent, setAgent] = useState(undefined);
223
432
  const [messages, setMessages] = useState([]);
224
433
  const [prompt, setPrompt] = useState("");
225
- const [inputPlaceholder, setInputPlaceholder] = useState("Type your message... (Enter to send, Ctrl+Enter for new line)");
434
+ const [inputPlaceholder, setInputPlaceholder] = useState("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
226
435
  const [isLoading, setIsLoading] = useState(false);
227
436
  const [isConnecting, setIsConnecting] = useState(false);
228
437
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -320,6 +529,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
320
529
  }, [messages]);
321
530
  const [error, setError] = useState(null);
322
531
  const [costLimitExceeded, setCostLimitExceeded] = useState(null);
532
+ // Live running totals from backend status updates (tokenUsage)
533
+ const [liveTotals, setLiveTotals] = useState(null);
323
534
  // Flag to track when we should create a new message
324
535
  const shouldCreateNewMessage = useRef(false);
325
536
  // Keep a ref to the current messages for immediate access
@@ -487,7 +698,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
487
698
  });
488
699
  throw new Error("No agent available");
489
700
  }
490
- console.log("βœ… Creating new stream message with agent:", currentAgent.id);
701
+ // Reduced: avoid verbose logging during streaming
491
702
  return {
492
703
  id: messageId,
493
704
  agentId: currentAgent.id,
@@ -594,7 +805,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
594
805
  // Final check before adding to prevent duplicates
595
806
  const finalCheck = prev.find((msg) => msg.id === toolCallMessageId);
596
807
  if (finalCheck) {
597
- console.log("#!# ⚠️ Tool call message already exists in state, skipping:", toolCallMessageId);
598
808
  return prev;
599
809
  }
600
810
  const updated = [...prev, newStreamMessage];
@@ -603,9 +813,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
603
813
  });
604
814
  }
605
815
  }
606
- else {
607
- console.log("πŸ“‹ Adding tool call to existing message:", toolCallMessageId);
608
- }
609
816
  }
610
817
  // Add tool call to the message in the array
611
818
  if (toolCallMessageId && message.data && toolCallId) {
@@ -667,10 +874,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
667
874
  }
668
875
  // Update tool result directly in the messages array
669
876
  if (!resultMessageId) {
670
- console.warn("⚠️ No messageId available for tool result");
671
877
  return;
672
878
  }
673
- console.log("πŸ“‹ Updating tool result in message:", resultMessageId);
674
879
  // Update the message with tool result
675
880
  setMessages((prev) => {
676
881
  const updated = prev.map((msg) => {
@@ -729,7 +934,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
729
934
  };
730
935
  updatedMessage.toolCalls = [...updatedMessage.toolCalls, toolCall];
731
936
  }
732
- console.log("πŸ”§ Updated tool calls count:", updatedMessage.toolCalls.length);
937
+ // Updated tool calls count
733
938
  return updatedMessage;
734
939
  });
735
940
  messagesRef.current = updated;
@@ -763,16 +968,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
763
968
  abortControllerRef.current = abortController;
764
969
  try {
765
970
  setIsConnecting(true);
766
- console.log("πŸ”Œ connectToStream: Starting stream connection");
971
+ // Reduced: minimal logging
767
972
  // Expose agent id globally for approval actions
768
973
  window.currentAgentId = currentAgent.id;
769
- console.log("πŸ”— Setting currentAgentId:", currentAgent.id);
770
- console.log("🌐 Attempting to connect to agent stream for:", currentAgent.id);
974
+ // Expose id for approval actions
975
+ // Connecting to agent stream
771
976
  await connectToAgentStream(currentAgent.id, (message) => {
772
- console.log("πŸ“¨ Received stream message:", {
773
- type: message.type,
774
- data: message.data,
775
- });
776
977
  switch (message.type) {
777
978
  case "contentChunk":
778
979
  handleContentChunk(message, currentAgent);
@@ -786,15 +987,30 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
786
987
  case "statusUpdate":
787
988
  try {
788
989
  const kind = message?.data?.kind;
789
- console.log("πŸ“‘ Received status update:", {
790
- kind,
791
- data: message.data,
792
- });
990
+ // Live token usage totals update from backend
991
+ if (kind === "tokenUsage") {
992
+ const totals = message?.data?.totals;
993
+ if (totals) {
994
+ setLiveTotals({
995
+ input: Number(totals.totalInputTokens) || 0,
996
+ output: Number(totals.totalOutputTokens) || 0,
997
+ cached: Number(totals.totalCachedInputTokens) || 0,
998
+ inputCost: Number(totals.totalInputTokenCost) || 0,
999
+ outputCost: Number(totals.totalOutputTokenCost) || 0,
1000
+ cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
1001
+ totalCost: Number(totals.totalCost) || 0,
1002
+ currency: totals.currency,
1003
+ });
1004
+ // Force a re-render to update cost display immediately
1005
+ setMessages((prev) => [...prev]);
1006
+ }
1007
+ break;
1008
+ }
793
1009
  if (kind === "toolApprovalsRequired") {
794
1010
  const data = message.data || {};
795
1011
  const msgId = data.messageId;
796
1012
  const ids = data.toolCallIds || [];
797
- console.log("⏸️ Approvals required; pausing stream until approval:", { msgId, ids });
1013
+ // Pause stream until approval
798
1014
  // Annotate tool calls with a temporary pending marker so UI can reflect paused state on reload
799
1015
  if (msgId && Array.isArray(ids) && ids.length > 0) {
800
1016
  setMessages((prev) => {
@@ -899,21 +1115,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
899
1115
  const data = message.data || {};
900
1116
  const toolCallId = data.toolCallId;
901
1117
  const msgId = data.messageId;
902
- console.log("πŸ”§ Processing tool approval:", {
903
- kind,
904
- toolCallId,
905
- msgId,
906
- data,
907
- });
1118
+ // Processing tool approval
908
1119
  if (toolCallId && msgId) {
909
1120
  setMessages((prev) => {
910
- console.log("πŸ” Looking for message:", {
911
- targetMsgId: msgId,
912
- availableMessages: prev.map((m) => ({
913
- id: m.id,
914
- toolCallsCount: m.toolCalls?.length || 0,
915
- })),
916
- });
917
1121
  const updated = prev.map((m) => {
918
1122
  if (m.id !== msgId)
919
1123
  return m;
@@ -925,11 +1129,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
925
1129
  ? " (approved)"
926
1130
  : " (rejected)";
927
1131
  const newFunctionName = (tc.functionName || "") + suffix;
928
- console.log("🏷️ Updating function name:", {
929
- toolCallId,
930
- oldName: tc.functionName,
931
- newName: newFunctionName,
932
- });
1132
+ // Update function name with approval suffix
933
1133
  return {
934
1134
  ...tc,
935
1135
  functionName: newFunctionName,
@@ -948,7 +1148,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
948
1148
  break;
949
1149
  case "completed":
950
1150
  const completedMessageId = message.data?.messageId;
951
- console.log("πŸ’Ύ Stream completed for message:", completedMessageId, "messages count:", messages.length);
952
1151
  // If the completed event carries full messages, merge them into state
953
1152
  try {
954
1153
  const completionMessages = message?.data
@@ -1012,6 +1211,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1012
1211
  updatedMessage.tokensUsed =
1013
1212
  (updatedMessage.inputTokens || 0) +
1014
1213
  (updatedMessage.outputTokens || 0);
1214
+ // Update cost data if provided in the completed event
1215
+ if (data.inputTokenCost !== undefined) {
1216
+ updatedMessage.inputTokenCost = data.inputTokenCost;
1217
+ }
1218
+ if (data.outputTokenCost !== undefined) {
1219
+ updatedMessage.outputTokenCost =
1220
+ data.outputTokenCost;
1221
+ }
1222
+ if (data.cachedInputTokenCost !== undefined ||
1223
+ data.cachedTokenCost !== undefined) {
1224
+ updatedMessage.cachedInputTokenCost =
1225
+ data.cachedInputTokenCost ?? data.cachedTokenCost;
1226
+ }
1227
+ if (data.totalCost !== undefined) {
1228
+ updatedMessage.totalCost = data.totalCost;
1229
+ }
1015
1230
  // Handle content that might only be sent in the completed event
1016
1231
  if (data.deltaContent && data.deltaContent.trim()) {
1017
1232
  if (!data.isIncremental) {
@@ -1101,7 +1316,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1101
1316
  }
1102
1317
  }
1103
1318
  finally {
1104
- console.log("πŸ”Œ Stream connection finished, cleaning up");
1105
1319
  setIsConnecting(false);
1106
1320
  // Guard: clear waiting state if connection finished without content
1107
1321
  setIsWaitingForResponse(false);
@@ -1115,18 +1329,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1115
1329
  return;
1116
1330
  // Check if we're already connected
1117
1331
  if (abortControllerRef.current) {
1118
- console.log("πŸ”„ Already connected to stream, skipping reconnect");
1119
1332
  return;
1120
1333
  }
1121
1334
  const msgs = messagesRef.current || [];
1122
1335
  const hasPending = msgs.some((m) => (m.toolCalls || []).some((tc) => (tc.functionName || "").includes("(pending approval)")));
1123
1336
  if (!hasPending) {
1124
- console.log("πŸ”„ No pending approvals; reconnecting stream");
1125
1337
  await connectToStream(currentAgent);
1126
1338
  }
1127
- else {
1128
- console.log("⏸️ Still have pending approvals, not reconnecting yet");
1129
- }
1130
1339
  }
1131
1340
  catch (err) {
1132
1341
  console.error("❌ Error attempting reconnect:", err);
@@ -1142,11 +1351,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1142
1351
  const approved = !!detail.approved;
1143
1352
  if (!messageId || !toolCallId)
1144
1353
  return;
1145
- console.log("πŸ”” Approval resolved:", {
1146
- messageId,
1147
- toolCallId,
1148
- approved,
1149
- });
1354
+ // Approval resolved; update local state
1150
1355
  setMessages((prev) => {
1151
1356
  const updated = prev.map((m) => {
1152
1357
  if (m.id !== messageId)
@@ -1184,10 +1389,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1184
1389
  const loadAgent = useCallback(async () => {
1185
1390
  try {
1186
1391
  if (agentStub.status === "new") {
1187
- console.log("βœ… Setting up new agent", agentStub.id);
1188
1392
  // Set agent ID immediately for new agents
1189
1393
  window.currentAgentId = agentStub.id;
1190
- console.log("πŸ”— Setting currentAgentId for new agent:", agentStub.id);
1394
+ // Set currentAgentId for new agent
1191
1395
  // Derive initial profile from provided metadata if present
1192
1396
  const initialProfileIdFromMeta = (() => {
1193
1397
  try {
@@ -1369,7 +1573,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1369
1573
  setMessages(agentData.messages || []);
1370
1574
  // Set agent ID for existing agents too
1371
1575
  window.currentAgentId = agentData.id;
1372
- console.log("πŸ”— Setting currentAgentId for existing agent:", agentData.id);
1373
1576
  // Parse metadata from DB if present (do not seed for existing agents)
1374
1577
  const parsedMeta = (() => {
1375
1578
  try {
@@ -1392,16 +1595,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1392
1595
  // Connect to stream if agent is running (handle both string and numeric status)
1393
1596
  const isRunning = agentData.status === "running" || agentData.status === 1;
1394
1597
  if (isRunning) {
1395
- console.log("πŸš€ Agent is running, connecting to stream...", {
1396
- agentId: agentData.id,
1397
- status: agentData.status,
1398
- messageCount: agentData.messages?.length || 0,
1399
- });
1400
1598
  // Use setTimeout to ensure state updates are processed first
1401
1599
  setTimeout(async () => {
1402
1600
  // Check if we're already connecting to avoid duplicate connections
1403
1601
  if (abortControllerRef.current) {
1404
- console.log("⚠️ loadAgent: Already connected to stream, skipping duplicate connection");
1405
1602
  return;
1406
1603
  }
1407
1604
  // Reset streaming state for reconnection
@@ -1411,7 +1608,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1411
1608
  const hasPending = (agentData.messages || []).some((m) => (m.toolCalls || []).some((tc) => typeof tc?.functionName === "string" &&
1412
1609
  tc.functionName.includes("(pending approval)")));
1413
1610
  if (hasPending) {
1414
- console.log("⏸️ loadAgent: Pending approvals detected, delaying stream reconnect");
1415
1611
  return;
1416
1612
  }
1417
1613
  }
@@ -1426,7 +1622,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1426
1622
  // For new agents that don't exist yet, this is expected
1427
1623
  if (err?.message?.includes("404") ||
1428
1624
  err?.message?.includes("not found")) {
1429
- console.log("ℹ️ Agent does not exist, treating as new");
1625
+ // Agent does not exist, treat as new
1430
1626
  setAgent(undefined);
1431
1627
  setMessages([]);
1432
1628
  setError(null);
@@ -1495,7 +1691,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1495
1691
  useEffect(() => {
1496
1692
  return () => {
1497
1693
  if (abortControllerRef.current) {
1498
- console.log("Cleaning up stream connection");
1499
1694
  abortControllerRef.current.abort();
1500
1695
  }
1501
1696
  };
@@ -1655,7 +1850,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1655
1850
  deterministic: deterministicFlags.deterministic,
1656
1851
  seed: deterministicFlags.seed,
1657
1852
  };
1658
- console.log("Starting agent:", request);
1853
+ // Starting agent
1659
1854
  // Set waiting state to show dancing dots immediately
1660
1855
  setIsWaitingForResponse(true);
1661
1856
  // Start idle timer; dots appear only if no chunks for >1s
@@ -1690,7 +1885,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1690
1885
  }
1691
1886
  };
1692
1887
  const handleKeyPress = (e) => {
1693
- if (e.key === "Enter" && !e.ctrlKey && !e.metaKey) {
1888
+ // Submit only on plain Enter (no Ctrl/Meta/Shift/Alt)
1889
+ if (e.key === "Enter" &&
1890
+ !e.ctrlKey &&
1891
+ !e.metaKey &&
1892
+ !e.shiftKey &&
1893
+ !e.altKey) {
1694
1894
  e.preventDefault();
1695
1895
  handleSubmit();
1696
1896
  }
@@ -2284,7 +2484,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
2284
2484
  resetDotsTimer,
2285
2485
  ]);
2286
2486
  if (isLoading) {
2287
- return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) }));
2487
+ return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-xs text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) }));
2288
2488
  }
2289
2489
  // Calculate total token usage for cost display
2290
2490
  const totalTokens = calculateTotalTokens(messages);
@@ -2366,7 +2566,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
2366
2566
  sendQuickMessage(text);
2367
2567
  } }, groupIndex));
2368
2568
  }
2369
- }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsx(TodoListPanel, { messages: messages }), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
2569
+ }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
2370
2570
  setPrompt(e.target.value);
2371
2571
  // Reset history index when user starts typing
2372
2572
  if (currentHistoryIndex !== -1) {
@@ -2435,7 +2635,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
2435
2635
  setShowPredefined(false);
2436
2636
  if (textareaRef.current)
2437
2637
  textareaRef.current.focus();
2438
- }, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens: totalTokens }), (() => {
2638
+ }, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens: liveTotals
2639
+ ? {
2640
+ input: liveTotals.input,
2641
+ output: liveTotals.output,
2642
+ cached: liveTotals.cached,
2643
+ inputCost: liveTotals.inputCost,
2644
+ outputCost: liveTotals.outputCost,
2645
+ cachedCost: liveTotals.cachedCost,
2646
+ totalCost: liveTotals.totalCost,
2647
+ }
2648
+ : totalTokens }), (() => {
2439
2649
  try {
2440
2650
  const s = window.__agentContextWindowStatus;
2441
2651
  if (!s || !s.contextWindowTokens)
@@ -2452,7 +2662,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
2452
2662
  };
2453
2663
  if (!pct)
2454
2664
  return null;
2455
- return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "rounded border border-gray-200 bg-gray-50 px-2 py-0.5 cursor-help", children: ["Context: ", pct] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1 text-xs", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Model:" }), " ", s.model, s.normalizedModel && ` (${s.normalizedModel})`] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Context window:" }), " ", formatTokens(s.estimatedInputTokens || 0), " /", " ", formatTokens(s.contextWindowTokens), " tokens"] }), typeof s.maxCompletionTokens === "number" && (_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Max completion:" }), " ", formatTokens(s.maxCompletionTokens), " tokens"] })), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Used:" }), " ", pct] })] }) })] }));
2665
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "cursor-help rounded border border-gray-200 bg-gray-50 px-2 py-0.5", children: ["Context: ", pct] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1 text-xs", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Model:" }), " ", s.model, s.normalizedModel && ` (${s.normalizedModel})`] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Context window:" }), " ", formatTokens(s.estimatedInputTokens || 0), " /", " ", formatTokens(s.contextWindowTokens), " tokens"] }), typeof s.maxCompletionTokens === "number" && (_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Max completion:" }), " ", formatTokens(s.maxCompletionTokens), " tokens"] })), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Used:" }), " ", pct] })] }) })] }));
2456
2666
  }
2457
2667
  catch {
2458
2668
  return null;