@alpaca-editor/core 1.0.4135 → 1.0.4140

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 (126) hide show
  1. package/dist/config/config.js +7 -0
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/FieldListField.js +3 -4
  4. package/dist/editor/FieldListField.js.map +1 -1
  5. package/dist/editor/Terminal.js +1 -1
  6. package/dist/editor/Terminal.js.map +1 -1
  7. package/dist/editor/Titlebar.js +0 -1
  8. package/dist/editor/Titlebar.js.map +1 -1
  9. package/dist/editor/ai/AgentCostDisplay.d.ts +3 -1
  10. package/dist/editor/ai/AgentCostDisplay.js +26 -2
  11. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  12. package/dist/editor/ai/AgentStatusBadge.d.ts +26 -0
  13. package/dist/editor/ai/AgentStatusBadge.js +110 -0
  14. package/dist/editor/ai/AgentStatusBadge.js.map +1 -0
  15. package/dist/editor/ai/AgentTerminal.js +289 -198
  16. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  17. package/dist/editor/ai/Agents.d.ts +2 -2
  18. package/dist/editor/ai/Agents.js +115 -19
  19. package/dist/editor/ai/Agents.js.map +1 -1
  20. package/dist/editor/ai/AiResponseMessage.js +259 -45
  21. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  22. package/dist/editor/ai/ContextInfoBar.js +124 -113
  23. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  24. package/dist/editor/ai/ToolCallDisplay.d.ts +1 -0
  25. package/dist/editor/ai/ToolCallDisplay.js +70 -58
  26. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  27. package/dist/editor/ai/useAgentStatus.d.ts +13 -0
  28. package/dist/editor/ai/useAgentStatus.js +101 -0
  29. package/dist/editor/ai/useAgentStatus.js.map +1 -0
  30. package/dist/editor/client/EditorShell.js +23 -8
  31. package/dist/editor/client/EditorShell.js.map +1 -1
  32. package/dist/editor/client/itemsRepository.js.map +1 -1
  33. package/dist/editor/commands/localizeItem/LocalizeItemDialog.js +5 -5
  34. package/dist/editor/commands/localizeItem/LocalizeItemDialog.js.map +1 -1
  35. package/dist/editor/control-center/About.js +1 -1
  36. package/dist/editor/control-center/About.js.map +1 -1
  37. package/dist/editor/control-center/AllAgentsPanel.d.ts +5 -0
  38. package/dist/editor/control-center/AllAgentsPanel.js +126 -0
  39. package/dist/editor/control-center/AllAgentsPanel.js.map +1 -0
  40. package/dist/editor/control-center/WebSocketMessages.js +1 -0
  41. package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
  42. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js +42 -7
  43. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js.map +1 -1
  44. package/dist/editor/media-selector/AiImageSearch.d.ts +1 -1
  45. package/dist/editor/media-selector/AiImageSearch.js +162 -103
  46. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  47. package/dist/editor/media-selector/TreeSelector.js +20 -4
  48. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  49. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -2
  50. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  51. package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
  52. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +7 -5
  53. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  54. package/dist/editor/page-viewer/DeviceToolbar.js +2 -2
  55. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  56. package/dist/editor/page-viewer/PageViewerFrame.js +18 -11
  57. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  58. package/dist/editor/services/agentService.d.ts +53 -48
  59. package/dist/editor/services/agentService.js +137 -79
  60. package/dist/editor/services/agentService.js.map +1 -1
  61. package/dist/editor/services/aiService.d.ts +1 -1
  62. package/dist/editor/services/editService.js +1 -0
  63. package/dist/editor/services/editService.js.map +1 -1
  64. package/dist/editor/sidebar/GraphQL.js +20 -7
  65. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  66. package/dist/editor/sidebar/SEOInfo.js +1 -2
  67. package/dist/editor/sidebar/SEOInfo.js.map +1 -1
  68. package/dist/editor/sidebar/Translations.js +10 -7
  69. package/dist/editor/sidebar/Translations.js.map +1 -1
  70. package/dist/editor/ui/ItemNameDialogNew.js +1 -1
  71. package/dist/editor/ui/ItemSearch.js +10 -4
  72. package/dist/editor/ui/ItemSearch.js.map +1 -1
  73. package/dist/page-wizard/steps/CollectStep.js +2 -2
  74. package/dist/page-wizard/steps/CollectStep.js.map +1 -1
  75. package/dist/page-wizard/steps/FieldEditor.js +2 -2
  76. package/dist/page-wizard/steps/FieldEditor.js.map +1 -1
  77. package/dist/revision.d.ts +2 -2
  78. package/dist/revision.js +2 -2
  79. package/dist/splash-screen/NewPage.js +2 -2
  80. package/dist/splash-screen/NewPage.js.map +1 -1
  81. package/dist/splash-screen/RecentPages.js +1 -1
  82. package/dist/splash-screen/RecentPages.js.map +1 -1
  83. package/dist/styles.css +167 -22
  84. package/dist/tour/Tour.js +15 -11
  85. package/dist/tour/Tour.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/config/config.tsx +7 -0
  88. package/src/editor/FieldListField.tsx +13 -13
  89. package/src/editor/Terminal.tsx +1 -1
  90. package/src/editor/Titlebar.tsx +0 -1
  91. package/src/editor/ai/AgentCostDisplay.tsx +57 -1
  92. package/src/editor/ai/AgentStatusBadge.tsx +144 -0
  93. package/src/editor/ai/AgentTerminal.tsx +345 -219
  94. package/src/editor/ai/Agents.tsx +179 -30
  95. package/src/editor/ai/AiResponseMessage.tsx +411 -114
  96. package/src/editor/ai/ContextInfoBar.tsx +134 -131
  97. package/src/editor/ai/ToolCallDisplay.tsx +217 -176
  98. package/src/editor/ai/useAgentStatus.ts +123 -0
  99. package/src/editor/client/EditorShell.tsx +34 -8
  100. package/src/editor/client/itemsRepository.ts +1 -2
  101. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +5 -5
  102. package/src/editor/control-center/About.tsx +0 -14
  103. package/src/editor/control-center/AllAgentsPanel.tsx +300 -0
  104. package/src/editor/control-center/WebSocketMessages.tsx +1 -0
  105. package/src/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.tsx +49 -8
  106. package/src/editor/media-selector/AiImageSearch.tsx +162 -172
  107. package/src/editor/media-selector/TreeSelector.tsx +137 -116
  108. package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +9 -1
  109. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +7 -4
  110. package/src/editor/page-viewer/DeviceToolbar.tsx +15 -11
  111. package/src/editor/page-viewer/PageViewerFrame.tsx +20 -14
  112. package/src/editor/services/agentService.ts +217 -129
  113. package/src/editor/services/aiService.ts +2 -2
  114. package/src/editor/services/editService.ts +1 -0
  115. package/src/editor/sidebar/GraphQL.tsx +143 -117
  116. package/src/editor/sidebar/SEOInfo.tsx +1 -2
  117. package/src/editor/sidebar/Translations.tsx +14 -12
  118. package/src/editor/ui/ItemNameDialogNew.tsx +1 -1
  119. package/src/editor/ui/ItemSearch.tsx +11 -4
  120. package/src/editor/ui/SimpleTabs.tsx +1 -1
  121. package/src/page-wizard/steps/CollectStep.tsx +2 -2
  122. package/src/page-wizard/steps/FieldEditor.tsx +13 -15
  123. package/src/revision.ts +2 -2
  124. package/src/splash-screen/NewPage.tsx +2 -2
  125. package/src/splash-screen/RecentPages.tsx +1 -1
  126. package/src/tour/Tour.tsx +61 -48
@@ -29,10 +29,11 @@ import {
29
29
  StartAgentRequest,
30
30
  Agent,
31
31
  AgentDetails,
32
- updateAgentMetadata,
32
+ updateAgentContext,
33
33
  AgentMetadata,
34
34
  updateAgentSettings,
35
35
  updateAgentCostLimit,
36
+ cancelAgent,
36
37
  } from "../services/agentService";
37
38
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
38
39
  import { Textarea } from "../../components/ui/textarea";
@@ -678,6 +679,14 @@ export function AgentTerminal({
678
679
  const current = messagesRef.current || [];
679
680
  return current.some((m) => !m.isCompleted && m.messageType === "streaming");
680
681
  }, []);
682
+ const hasPendingApprovals = useCallback(() => {
683
+ const current = messagesRef.current || [];
684
+ return current.some((m) =>
685
+ (m.toolCalls || []).some((tc) =>
686
+ (tc.functionName || "").includes("(pending approval)"),
687
+ ),
688
+ );
689
+ }, []);
681
690
  const resetDotsTimer = useCallback(() => {
682
691
  if (dotsTimeoutRef.current) {
683
692
  clearTimeout(dotsTimeoutRef.current);
@@ -685,18 +694,26 @@ export function AgentTerminal({
685
694
  }
686
695
  const waiting = isWaitingRef.current;
687
696
  const streaming = hasActiveStreaming();
697
+ const pendingApprovals = hasPendingApprovals();
698
+ // Don't show dots if there are pending approvals (waiting for user action)
688
699
  if (!waiting && !streaming) {
689
700
  setShowDots(false);
690
701
  return;
691
702
  }
692
- setShowDots(false);
703
+ if (pendingApprovals) {
704
+ setShowDots(false);
705
+ return;
706
+ }
707
+ // Show dots immediately when waiting or streaming
708
+ setShowDots(true);
693
709
  dotsTimeoutRef.current = setTimeout(() => {
694
- // Re-check conditions after 1s of inactivity
710
+ // Re-check conditions after a brief delay to avoid flicker
695
711
  const stillWaiting = isWaitingRef.current;
696
712
  const stillStreaming = hasActiveStreaming();
697
- setShowDots(stillWaiting || stillStreaming);
698
- }, 500);
699
- }, [hasActiveStreaming]);
713
+ const stillPendingApprovals = hasPendingApprovals();
714
+ setShowDots((stillWaiting || stillStreaming) && !stillPendingApprovals);
715
+ }, 100);
716
+ }, [hasActiveStreaming, hasPendingApprovals]);
700
717
  const [resolvedPageName, setResolvedPageName] = useState<string | undefined>(
701
718
  undefined,
702
719
  );
@@ -724,8 +741,8 @@ export function AgentTerminal({
724
741
  const [selectedModelId, setSelectedModelId] = useState<string | undefined>(
725
742
  undefined,
726
743
  );
727
- type AgentMode = "agent" | "ask" | "restricted";
728
- const [mode, setMode] = useState<AgentMode>("restricted");
744
+ type AgentMode = "autonomous" | "read-only" | "supervised";
745
+ const [mode, setMode] = useState<AgentMode>("supervised");
729
746
 
730
747
  // Read deterministic flags from query string once
731
748
  const deterministicFlags = React.useMemo(() => {
@@ -807,7 +824,7 @@ export function AgentTerminal({
807
824
  // Cache mode/model changes made while the agent is still "new" (not yet persisted)
808
825
  const pendingSettingsRef = useRef<{
809
826
  modelName?: string | null;
810
- mode?: "agent" | "ask";
827
+ mode?: "autonomous" | "read-only" | "supervised";
811
828
  } | null>(null);
812
829
 
813
830
  // Auto-scroll to bottom when new messages arrive
@@ -993,10 +1010,28 @@ export function AgentTerminal({
993
1010
 
994
1011
  const handleContentChunk = useCallback(
995
1012
  (message: AgentStreamMessage, agentData?: AgentDetails) => {
996
- const messageId = message.data?.messageId;
1013
+ // Get messageId from data, or generate one from agent ID (for backward compatibility)
1014
+ // If no messageId is provided, we'll use the last assistant message or create a new one
1015
+ let messageId = message.data?.messageId;
1016
+
1017
+ if (!messageId && agentData?.id) {
1018
+ // For backward compatibility: if no messageId, find or create the current streaming message
1019
+ // This handles cases where the backend doesn't send messageId
1020
+ const currentMessages = messagesRef.current;
1021
+ const lastStreamingMessage = [...currentMessages]
1022
+ .reverse()
1023
+ .find((m) => m.role === "assistant" && !m.isCompleted);
1024
+
1025
+ if (lastStreamingMessage) {
1026
+ messageId = lastStreamingMessage.id;
1027
+ } else {
1028
+ // Create a new message ID based on timestamp
1029
+ messageId = crypto.randomUUID();
1030
+ }
1031
+ }
997
1032
 
998
1033
  if (!messageId) {
999
- console.error("No messageId found in message data!");
1034
+ console.error("Unable to determine messageId for content chunk!");
1000
1035
  return;
1001
1036
  }
1002
1037
 
@@ -1005,6 +1040,24 @@ export function AgentTerminal({
1005
1040
  // Any content chunk is an incremental update -> reset idle timer
1006
1041
  resetDotsTimer();
1007
1042
 
1043
+ // Extract cost/token data from content chunk if present
1044
+ const data = message.data as any;
1045
+ if (
1046
+ data &&
1047
+ (data.totalCost !== undefined || data.totalTokens !== undefined)
1048
+ ) {
1049
+ setLiveTotals({
1050
+ input: Number(data.totalInputTokens) || 0,
1051
+ output: Number(data.totalOutputTokens) || 0,
1052
+ cached: Number(data.totalCachedTokens) || 0,
1053
+ inputCost: Number(data.totalInputTokenCost) || 0,
1054
+ outputCost: Number(data.totalOutputTokenCost) || 0,
1055
+ cachedCost: Number(data.totalCachedTokenCost) || 0,
1056
+ totalCost: Number(data.totalCost) || 0,
1057
+ currency: data.currency || "USD",
1058
+ });
1059
+ }
1060
+
1008
1061
  // Always call setMessages and handle all logic in the callback with latest messages
1009
1062
  setMessages((prev) => {
1010
1063
  // Find existing message by messageId in the latest messages
@@ -1133,6 +1186,7 @@ export function AgentTerminal({
1133
1186
  const toolCall = {
1134
1187
  id: toolCallId,
1135
1188
  messageId: toolCallMessageId,
1189
+ dbMessageId: message.data.messageId as string | undefined, // Database message ID for approval/rejection
1136
1190
  toolCallId: toolCallId,
1137
1191
  functionName: functionName,
1138
1192
  functionArguments:
@@ -1197,6 +1251,24 @@ export function AgentTerminal({
1197
1251
  }
1198
1252
  }
1199
1253
 
1254
+ // Extract cost/token data from tool result if present
1255
+ const data = message.data as any;
1256
+ if (
1257
+ data &&
1258
+ (data.totalCost !== undefined || data.totalTokens !== undefined)
1259
+ ) {
1260
+ setLiveTotals({
1261
+ input: Number(data.totalInputTokens) || 0,
1262
+ output: Number(data.totalOutputTokens) || 0,
1263
+ cached: Number(data.totalCachedTokens) || 0,
1264
+ inputCost: Number(data.totalInputTokenCost) || 0,
1265
+ outputCost: Number(data.totalOutputTokenCost) || 0,
1266
+ cachedCost: Number(data.totalCachedTokenCost) || 0,
1267
+ totalCost: Number(data.totalCost) || 0,
1268
+ currency: data.currency || "USD",
1269
+ });
1270
+ }
1271
+
1200
1272
  // Update tool result directly in the messages array
1201
1273
  if (!resultMessageId) {
1202
1274
  return;
@@ -1346,6 +1418,7 @@ export function AgentTerminal({
1346
1418
  if (kind === "tokenUsage") {
1347
1419
  const totals = (message as any)?.data?.totals;
1348
1420
  if (totals) {
1421
+ const totalCost = Number(totals.totalCost) || 0;
1349
1422
  setLiveTotals({
1350
1423
  input: Number(totals.totalInputTokens) || 0,
1351
1424
  output: Number(totals.totalOutputTokens) || 0,
@@ -1354,9 +1427,21 @@ export function AgentTerminal({
1354
1427
  outputCost: Number(totals.totalOutputTokenCost) || 0,
1355
1428
  cachedCost:
1356
1429
  Number(totals.totalCachedInputTokenCost) || 0,
1357
- totalCost: Number(totals.totalCost) || 0,
1430
+ totalCost: totalCost,
1358
1431
  currency: totals.currency,
1359
1432
  });
1433
+
1434
+ // Check if cost limit exceeded
1435
+ if (agent?.costLimit && totalCost > agent.costLimit) {
1436
+ setCostLimitExceeded({
1437
+ totalCost: totalCost,
1438
+ costLimit: agent.costLimit,
1439
+ initialCostLimit: agent.costLimit,
1440
+ });
1441
+ setIsWaitingForResponse(false);
1442
+ shouldCreateNewMessage.current = false;
1443
+ }
1444
+
1360
1445
  // Force a re-render to update cost display immediately
1361
1446
  setMessages((prev) => [...prev]);
1362
1447
  }
@@ -1423,7 +1508,8 @@ export function AgentTerminal({
1423
1508
  setAgentMetadata((prev) => {
1424
1509
  const current = (prev || {}) as AgentMetadata;
1425
1510
  // Exclude top-level context to avoid duplicate keys when spreading
1426
- const { context: _, ...currentWithoutContext } = current;
1511
+ const currentWithoutContext = { ...current };
1512
+ delete (currentWithoutContext as any).context;
1427
1513
  const next: AgentMetadata = {
1428
1514
  ...currentWithoutContext,
1429
1515
  additionalData: {
@@ -1439,9 +1525,9 @@ export function AgentTerminal({
1439
1525
  try {
1440
1526
  const currentMeta: AgentMetadata | null = (() => {
1441
1527
  try {
1442
- return prevAgent.metadata
1528
+ return prevAgent.agentContext
1443
1529
  ? (JSON.parse(
1444
- prevAgent.metadata,
1530
+ prevAgent.agentContext,
1445
1531
  ) as AgentMetadata)
1446
1532
  : null;
1447
1533
  } catch {
@@ -1450,15 +1536,11 @@ export function AgentTerminal({
1450
1536
  })();
1451
1537
  const nextMeta: AgentMetadata = {
1452
1538
  ...(currentMeta || ({} as AgentMetadata)),
1453
- additionalData: {
1454
- ...(((currentMeta as any)?.additionalData as any) ||
1455
- {}),
1456
- context: nextContext,
1457
- },
1539
+ ...nextContext,
1458
1540
  } as AgentMetadata;
1459
1541
  return {
1460
1542
  ...prevAgent,
1461
- metadata: JSON.stringify(nextMeta),
1543
+ agentContext: JSON.stringify(nextMeta),
1462
1544
  };
1463
1545
  } catch {
1464
1546
  return prevAgent;
@@ -1639,25 +1721,37 @@ export function AgentTerminal({
1639
1721
  resetDotsTimer();
1640
1722
  break;
1641
1723
 
1642
- case "error":
1643
- // Detect cost limit exceeded
1724
+ case "contextUpdate":
1725
+ // Update agent context when backend sends context update
1644
1726
  try {
1645
- const data: any = (message as any).data;
1646
- if (
1647
- message.error === "COST_LIMIT_EXCEEDED" ||
1648
- (data && data.kind === "costLimitExceeded")
1649
- ) {
1650
- setCostLimitExceeded({
1651
- totalCost: Number(data?.totalCost) || 0,
1652
- costLimit: Number(data?.costLimit) || 0,
1653
- initialCostLimit: Number(data?.initialCostLimit) || 0,
1727
+ const updatedContext = message.data as AgentMetadata;
1728
+ if (updatedContext) {
1729
+ // Update local metadata state
1730
+ setAgentMetadata((prev) => ({
1731
+ ...prev,
1732
+ ...updatedContext,
1733
+ }));
1734
+
1735
+ // Update agent state with new context
1736
+ setAgent((prevAgent) => {
1737
+ if (!prevAgent) return prevAgent;
1738
+ return {
1739
+ ...prevAgent,
1740
+ agentContext: JSON.stringify(updatedContext),
1741
+ };
1654
1742
  });
1655
- setIsWaitingForResponse(false);
1656
- shouldCreateNewMessage.current = false;
1657
- resetDotsTimer();
1658
- break;
1743
+
1744
+ console.log(
1745
+ "✅ Context updated from backend:",
1746
+ updatedContext,
1747
+ );
1659
1748
  }
1660
- } catch {}
1749
+ } catch (err) {
1750
+ console.error("Error handling context update:", err);
1751
+ }
1752
+ break;
1753
+
1754
+ case "error":
1661
1755
  console.error("❌ Stream error:", message.error);
1662
1756
  setError(message.error || "Stream error occurred");
1663
1757
  setIsWaitingForResponse(false);
@@ -1813,10 +1907,6 @@ export function AgentTerminal({
1813
1907
  name: agentStub.name,
1814
1908
  model: "",
1815
1909
  currency: "USD",
1816
- itemId: "",
1817
- itemPath: "",
1818
- language: "",
1819
- version: 0,
1820
1910
  profileId: initialProfileIdFromMeta || "",
1821
1911
  profileName: initialProfileNameFromMeta || "",
1822
1912
  totalTokensUsed: 0,
@@ -1866,95 +1956,77 @@ export function AgentTerminal({
1866
1956
  return true;
1867
1957
  }
1868
1958
  })();
1959
+ // Create context with top-level properties (what ContextInfoBar expects)
1869
1960
  const localCtx: AgentMetadata | null =
1870
1961
  item && shouldSeedContext
1871
1962
  ? {
1872
- additionalData: {
1873
- context: {
1874
- pages: [
1875
- {
1876
- id: item.id,
1877
- language: item.language,
1878
- version: item.version,
1879
- name: editContext?.contentEditorItem?.name,
1880
- },
1881
- ],
1882
- componentIds: editContext?.selection?.length
1883
- ? (editContext.selection as any)
1884
- : undefined,
1885
- field:
1886
- fieldsContext?.focusedField?.fieldId &&
1887
- (fieldsContext.focusedField as any)?.item?.id
1888
- ? {
1889
- fieldId: fieldsContext.focusedField.fieldId,
1890
- itemId: (fieldsContext.focusedField as any).item.id,
1891
- name: (fieldsContext.focusedField as any).fieldName,
1892
- }
1893
- : undefined,
1963
+ pages: [
1964
+ {
1965
+ id: item.id,
1966
+ language: item.language,
1967
+ version: item.version,
1968
+ name: editContext?.contentEditorItem?.name,
1894
1969
  },
1895
- },
1970
+ ],
1971
+ componentIds: editContext?.selection?.length
1972
+ ? (editContext.selection as any)
1973
+ : undefined,
1974
+ field:
1975
+ fieldsContext?.focusedField?.fieldId &&
1976
+ (fieldsContext.focusedField as any)?.item?.id
1977
+ ? {
1978
+ fieldId: fieldsContext.focusedField.fieldId,
1979
+ itemId: (fieldsContext.focusedField as any).item.id,
1980
+ name: (fieldsContext.focusedField as any).fieldName,
1981
+ }
1982
+ : undefined,
1896
1983
  }
1897
1984
  : null;
1898
1985
 
1899
1986
  let nextMetadata: AgentMetadata | null = null;
1900
1987
  if (initialMetadata) {
1901
- // Merge initial metadata (e.g., profile) with local context
1902
- const base: AgentMetadata = {
1903
- ...(initialMetadata as any),
1904
- additionalData: {
1905
- ...((initialMetadata as any)?.additionalData || {}),
1906
- context: {
1907
- ...(((initialMetadata as any)?.additionalData
1908
- ?.context as any) || {}),
1909
- },
1910
- },
1911
- } as AgentMetadata;
1988
+ // Merge initial metadata with local context (using top-level structure)
1989
+ const base: AgentMetadata = { ...(initialMetadata as any) };
1912
1990
 
1913
- if (localCtx?.additionalData?.context) {
1914
- const ctx = base.additionalData!.context as any;
1915
- const local = localCtx.additionalData!.context as any;
1991
+ if (localCtx) {
1916
1992
  // Normalize existing shape: migrate items -> pages if needed
1917
- if (!ctx.pages && ctx.items) {
1918
- ctx.pages = ctx.items;
1919
- delete ctx.items;
1993
+ if (!base.pages && (base as any).items) {
1994
+ base.pages = (base as any).items;
1995
+ delete (base as any).items;
1920
1996
  }
1921
1997
  // Merge pages (avoid duplicates)
1922
- if (
1923
- (local.pages && local.pages.length) ||
1924
- (local.items && local.items.length)
1925
- ) {
1926
- const targetPages: any[] = Array.isArray(ctx.pages)
1927
- ? [...ctx.pages]
1998
+ if (localCtx.pages && localCtx.pages.length) {
1999
+ const targetPages: any[] = Array.isArray(base.pages)
2000
+ ? [...base.pages]
1928
2001
  : [];
1929
2002
  const existingKeys = new Set(
1930
2003
  targetPages.map(
1931
2004
  (p: any) => `${p.id}-${p.language || ""}-${p.version || ""}`,
1932
2005
  ),
1933
2006
  );
1934
- const incoming = (local.pages || local.items) as any[];
1935
- const additions = incoming.filter(
2007
+ const additions = localCtx.pages.filter(
1936
2008
  (p: any) =>
1937
2009
  !existingKeys.has(
1938
2010
  `${p.id}-${p.language || ""}-${p.version || ""}`,
1939
2011
  ),
1940
2012
  );
1941
- if (additions.length) ctx.pages = [...targetPages, ...additions];
2013
+ if (additions.length) base.pages = [...targetPages, ...additions];
1942
2014
  }
1943
2015
  // Merge componentIds (avoid duplicates)
1944
- if (local.componentIds && local.componentIds.length) {
1945
- const currentIds: string[] = Array.isArray(ctx.componentIds)
1946
- ? ctx.componentIds
2016
+ if (localCtx.componentIds && localCtx.componentIds.length) {
2017
+ const currentIds: string[] = Array.isArray(base.componentIds)
2018
+ ? base.componentIds
1947
2019
  : [];
1948
2020
  const existingIds = new Set(currentIds);
1949
- const additions = (local.componentIds as string[]).filter(
2021
+ const additions = (localCtx.componentIds as string[]).filter(
1950
2022
  (id) => !!id && !existingIds.has(id),
1951
2023
  );
1952
2024
  if (additions.length)
1953
- ctx.componentIds = [...currentIds, ...additions];
2025
+ base.componentIds = [...currentIds, ...additions];
1954
2026
  }
1955
2027
  // Set field if missing
1956
- if (!ctx.field && local.field) {
1957
- ctx.field = local.field;
2028
+ if (!base.field && localCtx.field) {
2029
+ base.field = localCtx.field;
1958
2030
  }
1959
2031
  }
1960
2032
 
@@ -1988,17 +2060,61 @@ export function AgentTerminal({
1988
2060
  // Set agent ID for existing agents too
1989
2061
  (window as any).currentAgentId = agentData.id;
1990
2062
 
2063
+ // Check if cost limit was exceeded (detect from existing messages)
2064
+ try {
2065
+ const costLimitMessage = (agentData.messages || []).find(
2066
+ (msg: AgentChatMessage) =>
2067
+ msg.role === "assistant" &&
2068
+ msg.content &&
2069
+ msg.content.startsWith("⚠️") &&
2070
+ msg.content.includes("Cost limit"),
2071
+ );
2072
+ if (
2073
+ costLimitMessage &&
2074
+ costLimitMessage.content &&
2075
+ agentData.costLimit
2076
+ ) {
2077
+ // Extract cost from the message if possible
2078
+ const match = costLimitMessage.content.match(
2079
+ /Current cost: \$([0-9.]+)/,
2080
+ );
2081
+ const totalCost =
2082
+ match && match[1] ? parseFloat(match[1]) : agentData.totalCost || 0;
2083
+ setCostLimitExceeded({
2084
+ totalCost: totalCost,
2085
+ costLimit: agentData.costLimit,
2086
+ initialCostLimit: agentData.costLimit,
2087
+ });
2088
+ } else if (
2089
+ agentData.costLimit &&
2090
+ agentData.totalCost &&
2091
+ agentData.totalCost > agentData.costLimit
2092
+ ) {
2093
+ // Fallback: check if current cost exceeds limit
2094
+ setCostLimitExceeded({
2095
+ totalCost: agentData.totalCost,
2096
+ costLimit: agentData.costLimit,
2097
+ initialCostLimit: agentData.costLimit,
2098
+ });
2099
+ }
2100
+ } catch (e) {
2101
+ console.error("Failed to check cost limit on load:", e);
2102
+ }
2103
+
1991
2104
  // Parse metadata from DB if present (do not seed for existing agents)
1992
2105
  const parsedMeta: AgentMetadata | null = (() => {
1993
2106
  try {
1994
- if (!agentData.metadata) return null;
1995
- const meta = JSON.parse(agentData.metadata) as AgentMetadata;
1996
- // Clean up: remove top-level context if present (should only be in additionalData)
1997
- if (meta && meta.context) {
1998
- const { context: _, ...cleanMeta } = meta;
1999
- return cleanMeta as AgentMetadata;
2107
+ const contextJson = agentData.agentContext;
2108
+ if (!contextJson) return null;
2109
+
2110
+ const parsedContext = JSON.parse(contextJson);
2111
+
2112
+ // Context is stored as flat structure with top-level properties
2113
+ if (parsedContext && typeof parsedContext === "object") {
2114
+ return parsedContext as AgentMetadata;
2000
2115
  }
2001
- return meta;
2116
+
2117
+ return null;
2002
2118
  } catch {
2003
2119
  return null;
2004
2120
  }
@@ -2007,37 +2123,48 @@ export function AgentTerminal({
2007
2123
  // For existing agents, use database metadata or none
2008
2124
  setAgentMetadata(parsedMeta);
2009
2125
 
2010
- // Connect to stream if agent is running (handle both string and numeric status)
2126
+ // Connect to stream if agent is running
2127
+ // For agents waiting for approval, DON'T auto-reconnect on load - let the user click approve first
2011
2128
  const isRunning =
2012
2129
  agentData.status === "running" || (agentData.status as any) === 1;
2130
+ const isWaitingForApproval =
2131
+ agentData.status === "waitingForApproval" ||
2132
+ (agentData.status as any) === 2;
2133
+
2013
2134
  if (isRunning) {
2014
- // Use setTimeout to ensure state updates are processed first
2135
+ // Only auto-reconnect for running agents, not waiting for approval
2015
2136
  setTimeout(async () => {
2016
- // Check if we're already connecting to avoid duplicate connections
2017
2137
  if (abortControllerRef.current) {
2018
2138
  return;
2019
2139
  }
2020
2140
 
2021
- // Reset streaming state for reconnection
2022
2141
  shouldCreateNewMessage.current = false;
2023
2142
 
2024
- // If there are pending approvals in current messages, skip reconnect for now
2025
2143
  try {
2026
- const hasPending = (agentData.messages || []).some((m: any) =>
2027
- (m.toolCalls || []).some(
2028
- (tc: any) =>
2029
- typeof tc?.functionName === "string" &&
2030
- tc.functionName.includes("(pending approval)"),
2031
- ),
2032
- );
2033
- if (hasPending) {
2034
- return;
2144
+ if (editContext?.sessionId) {
2145
+ await startAgent({
2146
+ agentId: agentData.id,
2147
+ message: "",
2148
+ sessionId: editContext.sessionId,
2149
+ profileId: agentData.profileId || "",
2150
+ model: agentData.model,
2151
+ });
2035
2152
  }
2036
- } catch {}
2153
+ } catch (startError: any) {
2154
+ console.warn(
2155
+ "Failed to call startAgent during reconnection:",
2156
+ startError,
2157
+ );
2158
+ }
2037
2159
 
2038
- // Use the existing connectToStream function with the loaded agent data
2039
2160
  await connectToStream(agentData);
2040
2161
  }, 100);
2162
+ } else if (isWaitingForApproval) {
2163
+ // For agents waiting for approval, just show the UI
2164
+ // The approval button handler will restart the agent if needed
2165
+ console.log(
2166
+ "Agent is waiting for approval - showing approval UI without auto-reconnect",
2167
+ );
2041
2168
  }
2042
2169
  } catch (err: any) {
2043
2170
  console.error("❌ Failed to load agent:", err);
@@ -2133,14 +2260,14 @@ export function AgentTerminal({
2133
2260
  useEffect(() => {
2134
2261
  try {
2135
2262
  const metaMode = (agentMetadata as any)?.mode as
2136
- | "agent"
2137
- | "ask"
2138
- | "restricted"
2263
+ | "autonomous"
2264
+ | "read-only"
2265
+ | "supervised"
2139
2266
  | undefined;
2140
2267
  if (
2141
- metaMode === "agent" ||
2142
- metaMode === "ask" ||
2143
- metaMode === "restricted"
2268
+ metaMode === "autonomous" ||
2269
+ metaMode === "read-only" ||
2270
+ metaMode === "supervised"
2144
2271
  ) {
2145
2272
  setMode(metaMode);
2146
2273
  return;
@@ -2149,9 +2276,9 @@ export function AgentTerminal({
2149
2276
  try {
2150
2277
  const serverMode = (agent as any)?.mode as string | undefined;
2151
2278
  if (
2152
- serverMode === "agent" ||
2153
- serverMode === "ask" ||
2154
- serverMode === "restricted"
2279
+ serverMode === "autonomous" ||
2280
+ serverMode === "read-only" ||
2281
+ serverMode === "supervised"
2155
2282
  ) {
2156
2283
  setMode(serverMode);
2157
2284
  }
@@ -2175,7 +2302,7 @@ export function AgentTerminal({
2175
2302
  setAgentMetadata(nextMeta);
2176
2303
  return;
2177
2304
  }
2178
- await updateAgentMetadata(agent.id, nextMeta);
2305
+ await updateAgentContext(agent.id, nextMeta);
2179
2306
  setAgentMetadata(nextMeta);
2180
2307
  setAgent((prev) =>
2181
2308
  prev ? { ...prev, metadata: JSON.stringify(nextMeta) } : prev,
@@ -2282,29 +2409,14 @@ export function AgentTerminal({
2282
2409
  agentId: agent.id,
2283
2410
  message: prompt.trim(),
2284
2411
  sessionId: editContext.sessionId,
2285
- profileId: activeProfile?.id || "default",
2286
- profile: activeProfile?.name || "default",
2412
+ profileId: activeProfile?.id || profiles[0]?.id || "",
2413
+ profile: activeProfile?.name || profiles[0]?.name || "",
2287
2414
  model: selectedModelId,
2288
- itemid: editContext.currentItemDescriptor?.id || "",
2289
- language: editContext.currentItemDescriptor?.language || "en",
2290
- version: editContext.currentItemDescriptor?.version || 1,
2291
- selection: effectiveSelection,
2292
- selectedText: selectedTextFromCtx,
2293
2415
  mode: mode as any,
2294
- allowedFunctions:
2295
- mode === "ask"
2296
- ? activeProfile?.askModeTools &&
2297
- activeProfile.askModeTools.length > 0
2298
- ? activeProfile.askModeTools
2299
- : []
2300
- : undefined,
2301
- addSelectedComponents: !!effectiveSelection?.length,
2302
- addContextContent: false,
2303
- addAllContent: false,
2304
- context: (agentMetadata as any)?.additionalData?.context,
2416
+ context: agentMetadata, // Flat structure with top-level properties
2305
2417
  deterministic: deterministicFlags.deterministic,
2306
2418
  seed: deterministicFlags.seed,
2307
- };
2419
+ } as any;
2308
2420
 
2309
2421
  // Starting agent
2310
2422
 
@@ -2469,29 +2581,14 @@ export function AgentTerminal({
2469
2581
  agentId: agent.id,
2470
2582
  message: text.trim(),
2471
2583
  sessionId: editContext.sessionId,
2472
- profileId: activeProfile?.id || "default",
2473
- profile: activeProfile?.name || "default",
2584
+ profileId: activeProfile?.id || profiles[0]?.id || "",
2585
+ profile: activeProfile?.name || profiles[0]?.name || "",
2474
2586
  model: selectedModelId,
2475
- itemid: editContext.currentItemDescriptor?.id || "",
2476
- language: editContext.currentItemDescriptor?.language || "en",
2477
- version: editContext.currentItemDescriptor?.version || 1,
2478
- selection: effectiveSelection,
2479
- selectedText: selectedTextFromCtx,
2480
2587
  mode: mode as any,
2481
- allowedFunctions:
2482
- mode === "ask"
2483
- ? activeProfile?.askModeTools &&
2484
- activeProfile.askModeTools.length > 0
2485
- ? activeProfile.askModeTools
2486
- : []
2487
- : undefined,
2488
- addSelectedComponents: !!effectiveSelection?.length,
2489
- addContextContent: false,
2490
- addAllContent: false,
2491
2588
  context: (agentMetadata as any)?.additionalData?.context,
2492
2589
  deterministic: deterministicFlags.deterministic,
2493
2590
  seed: deterministicFlags.seed,
2494
- };
2591
+ } as any;
2495
2592
 
2496
2593
  setIsWaitingForResponse(true);
2497
2594
  resetDotsTimer();
@@ -2522,7 +2619,8 @@ export function AgentTerminal({
2522
2619
  if (!agent?.id) return;
2523
2620
  const current = agentMetadata || {};
2524
2621
  // Exclude top-level context to avoid duplicate keys when spreading
2525
- const { context: _, ...currentWithoutContext } = current;
2622
+ const currentWithoutContext = { ...current };
2623
+ delete (currentWithoutContext as any).context;
2526
2624
  const next: AgentMetadata = {
2527
2625
  ...currentWithoutContext,
2528
2626
  additionalData: {
@@ -2562,7 +2660,7 @@ export function AgentTerminal({
2562
2660
  }
2563
2661
 
2564
2662
  // Persisted agents: update server and local cache
2565
- await updateAgentMetadata(agent.id, next);
2663
+ await updateAgentContext(agent.id, next);
2566
2664
  setAgentMetadata(next);
2567
2665
  setAgent((prev) =>
2568
2666
  prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
@@ -2586,7 +2684,7 @@ export function AgentTerminal({
2586
2684
  language: item.language,
2587
2685
  version: item.version,
2588
2686
  name: editContext.contentEditorItem?.name,
2589
- path: agent?.itemPath,
2687
+ path: undefined,
2590
2688
  };
2591
2689
 
2592
2690
  const current = agentMetadata || {};
@@ -2607,7 +2705,8 @@ export function AgentTerminal({
2607
2705
  }
2608
2706
 
2609
2707
  // Exclude top-level context to avoid duplicate keys when spreading
2610
- const { context: _, ...currentWithoutContext } = current;
2708
+ const currentWithoutContext = { ...current };
2709
+ delete (currentWithoutContext as any).context;
2611
2710
  const next: AgentMetadata = {
2612
2711
  ...currentWithoutContext,
2613
2712
  additionalData: {
@@ -2626,7 +2725,7 @@ export function AgentTerminal({
2626
2725
  setAgentMetadata(next);
2627
2726
  return;
2628
2727
  }
2629
- await updateAgentMetadata(agent.id, next);
2728
+ await updateAgentContext(agent.id, next);
2630
2729
  setAgentMetadata(next);
2631
2730
  setAgent((prev) =>
2632
2731
  prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
@@ -2653,7 +2752,8 @@ export function AgentTerminal({
2653
2752
  if (newComponentIds.length === 0) return; // No new components to add
2654
2753
 
2655
2754
  // Exclude top-level context to avoid duplicate keys when spreading
2656
- const { context: _, ...currentWithoutContext } = current;
2755
+ const currentWithoutContext = { ...current };
2756
+ delete (currentWithoutContext as any).context;
2657
2757
  const next: AgentMetadata = {
2658
2758
  ...currentWithoutContext,
2659
2759
  additionalData: {
@@ -2672,7 +2772,7 @@ export function AgentTerminal({
2672
2772
  setAgentMetadata(next);
2673
2773
  return;
2674
2774
  }
2675
- await updateAgentMetadata(agent.id, next);
2775
+ await updateAgentContext(agent.id, next);
2676
2776
  setAgentMetadata(next);
2677
2777
  setAgent((prev) =>
2678
2778
  prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
@@ -2696,7 +2796,8 @@ export function AgentTerminal({
2696
2796
  if (newComponentIds.length === 0) return;
2697
2797
 
2698
2798
  // Exclude top-level context to avoid duplicate keys when spreading
2699
- const { context: _, ...currentWithoutContext } = current;
2799
+ const currentWithoutContext = { ...current };
2800
+ delete (currentWithoutContext as any).context;
2700
2801
  const next: AgentMetadata = {
2701
2802
  ...currentWithoutContext,
2702
2803
  additionalData: {
@@ -2715,7 +2816,7 @@ export function AgentTerminal({
2715
2816
  setAgentMetadata(next);
2716
2817
  return;
2717
2818
  }
2718
- await updateAgentMetadata(agent.id, next);
2819
+ await updateAgentContext(agent.id, next);
2719
2820
  setAgentMetadata(next);
2720
2821
  setAgent((prev) =>
2721
2822
  prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
@@ -2750,7 +2851,8 @@ export function AgentTerminal({
2750
2851
  if (pagesToAdd.length === 0) return;
2751
2852
 
2752
2853
  // Exclude top-level context to avoid duplicate keys when spreading
2753
- const { context: _, ...currentWithoutContext } = current;
2854
+ const currentWithoutContext = { ...current };
2855
+ delete (currentWithoutContext as any).context;
2754
2856
  const next: AgentMetadata = {
2755
2857
  ...currentWithoutContext,
2756
2858
  additionalData: {
@@ -2769,7 +2871,7 @@ export function AgentTerminal({
2769
2871
  setAgentMetadata(next);
2770
2872
  return;
2771
2873
  }
2772
- await updateAgentMetadata(agent.id, next);
2874
+ await updateAgentContext(agent.id, next);
2773
2875
  setAgentMetadata(next);
2774
2876
  setAgent((prev) =>
2775
2877
  prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
@@ -2863,7 +2965,8 @@ export function AgentTerminal({
2863
2965
 
2864
2966
  const current = agentMetadata || {};
2865
2967
  // Exclude top-level context to avoid duplicate keys when spreading
2866
- const { context: _, ...currentWithoutContext } = current;
2968
+ const currentWithoutContext = { ...current };
2969
+ delete (currentWithoutContext as any).context;
2867
2970
  const next: AgentMetadata = {
2868
2971
  ...currentWithoutContext,
2869
2972
  additionalData: {
@@ -2891,7 +2994,7 @@ export function AgentTerminal({
2891
2994
  setAgentMetadata(next);
2892
2995
  return;
2893
2996
  }
2894
- await updateAgentMetadata(agent.id, next);
2997
+ await updateAgentContext(agent.id, next);
2895
2998
  setAgentMetadata(next);
2896
2999
  setAgent((prev) =>
2897
3000
  prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
@@ -2933,8 +3036,8 @@ export function AgentTerminal({
2933
3036
  editContext?.contentEditorItem?.name
2934
3037
  ) {
2935
3038
  name = editContext.contentEditorItem.name;
2936
- } else if (!name && agent?.itemPath) {
2937
- name = agent.itemPath.split("/").pop() || undefined;
3039
+ } else if (!name) {
3040
+ name = undefined;
2938
3041
  }
2939
3042
  setResolvedPageName(name);
2940
3043
  } else {
@@ -2979,15 +3082,28 @@ export function AgentTerminal({
2979
3082
  agentMetadata,
2980
3083
  editContext?.page,
2981
3084
  editContext?.contentEditorItem,
2982
- agent?.itemPath,
2983
3085
  editContext?.currentItemDescriptor,
2984
3086
  editContext?.itemsRepository,
2985
3087
  ]);
2986
3088
 
2987
3089
  // Stop current execution/stream safely
2988
- const handleStop = useCallback(() => {
3090
+ const handleStop = useCallback(async () => {
2989
3091
  try {
2990
3092
  setIsWaitingForResponse(false);
3093
+
3094
+ // **CRITICAL FIX**: Actually cancel the agent execution on the backend
3095
+ // Before we were only disconnecting from the stream, letting the agent run
3096
+ if (agentStub?.id) {
3097
+ try {
3098
+ await cancelAgent(agentStub.id);
3099
+ console.log(`Agent ${agentStub.id} cancellation requested`);
3100
+ } catch (err) {
3101
+ console.error("Failed to cancel agent on backend:", err);
3102
+ // Continue with UI cleanup even if backend call fails
3103
+ }
3104
+ }
3105
+
3106
+ // Disconnect from the stream
2991
3107
  if (abortControllerRef.current) {
2992
3108
  abortControllerRef.current.abort();
2993
3109
  abortControllerRef.current = null;
@@ -3009,7 +3125,7 @@ export function AgentTerminal({
3009
3125
  } catch (e) {
3010
3126
  console.error("Failed to stop agent execution", e);
3011
3127
  }
3012
- }, [resetDotsTimer]);
3128
+ }, [agentStub?.id, resetDotsTimer]);
3013
3129
 
3014
3130
  // Ensure waiting state resets when no active work remains
3015
3131
  useEffect(() => {
@@ -3074,29 +3190,21 @@ export function AgentTerminal({
3074
3190
  </span>
3075
3191
  </div>
3076
3192
  <div className="flex gap-2">
3077
- <button
3078
- className="rounded border border-amber-300 bg-white px-2 py-1 hover:bg-amber-100"
3079
- onClick={async () => {
3080
- if (!agent?.id) return;
3081
- try {
3082
- await updateAgentCostLimit(agent.id, "remove");
3083
- setCostLimitExceeded(null);
3084
- // Reconnect to stream for next actions
3085
- await connectToStream(agent);
3086
- } catch (e) {
3087
- console.error("Failed to remove cost limit", e);
3088
- }
3089
- }}
3090
- >
3091
- Continue and remove limit
3092
- </button>
3093
3193
  <button
3094
3194
  className="rounded border border-amber-300 bg-white px-2 py-1 hover:bg-amber-100"
3095
3195
  onClick={async () => {
3096
3196
  if (!agent?.id) return;
3097
3197
  try {
3098
3198
  // Extend by initial cost limit amount
3099
- await updateAgentCostLimit(agent.id, "extend");
3199
+ const result = await updateAgentCostLimit(agent.id, "extend");
3200
+
3201
+ // Update the agent's cost limit in local state
3202
+ if (result.success && result.costLimit !== undefined) {
3203
+ setAgent((prev) =>
3204
+ prev ? { ...prev, costLimit: result.costLimit } : prev,
3205
+ );
3206
+ }
3207
+
3100
3208
  setCostLimitExceeded(null);
3101
3209
  await connectToStream(agent);
3102
3210
  } catch (e) {
@@ -3104,7 +3212,7 @@ export function AgentTerminal({
3104
3212
  }
3105
3213
  }}
3106
3214
  >
3107
- Continue and extend limit
3215
+ Extend limit and continue
3108
3216
  </button>
3109
3217
  </div>
3110
3218
  </div>
@@ -3119,9 +3227,8 @@ export function AgentTerminal({
3119
3227
  className="flex-1 overflow-y-auto"
3120
3228
  onScroll={handleScroll}
3121
3229
  >
3122
- {renderCostLimitBanner()}
3123
3230
  {error && (
3124
- <div className="m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3">
3231
+ <div className="m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text">
3125
3232
  <div className="flex items-start">
3126
3233
  <AlertCircle
3127
3234
  className="mt-0.5 h-5 w-5 text-red-400"
@@ -3171,9 +3278,22 @@ export function AgentTerminal({
3171
3278
  (msg) => !msg.isCompleted && msg.messageType === "streaming",
3172
3279
  );
3173
3280
 
3174
- const convertedMessages = convertAgentMessagesToAiFormat(
3175
- group.messages,
3176
- );
3281
+ // Filter out cost limit error messages (they're shown in the banner instead)
3282
+ const filteredMessages = group.messages.filter((msg) => {
3283
+ const content = msg.content || "";
3284
+ // Skip messages that are cost limit errors (shown in banner instead)
3285
+ return (
3286
+ !content.startsWith("⚠️") || !content.includes("Cost limit")
3287
+ );
3288
+ });
3289
+
3290
+ // If all messages were filtered out, don't render this group
3291
+ if (filteredMessages.length === 0) {
3292
+ return null;
3293
+ }
3294
+
3295
+ const convertedMessages =
3296
+ convertAgentMessagesToAiFormat(filteredMessages);
3177
3297
 
3178
3298
  return (
3179
3299
  <AiResponseMessage
@@ -3223,6 +3343,7 @@ export function AgentTerminal({
3223
3343
  <div className={showDots ? "visible" : "invisible"}>
3224
3344
  <DancingDots />
3225
3345
  </div>
3346
+ {renderCostLimitBanner()}
3226
3347
  <div ref={messagesEndRef} />
3227
3348
  </div>
3228
3349
 
@@ -3259,16 +3380,16 @@ export function AgentTerminal({
3259
3380
  <TooltipTrigger asChild>
3260
3381
  <select
3261
3382
  className={`h-5 rounded border px-1.5 text-[10px] ${
3262
- mode === "ask"
3383
+ mode === "read-only"
3263
3384
  ? "border-green-300 bg-green-50 text-green-700"
3264
- : mode === "restricted"
3385
+ : mode === "supervised"
3265
3386
  ? "border-amber-300 bg-amber-50 text-amber-700"
3266
3387
  : "border-red-300 bg-red-50 text-red-700"
3267
3388
  }`}
3268
3389
  value={mode}
3269
3390
  onChange={async (e) => {
3270
3391
  const nextMode =
3271
- (e.target.value as "agent" | "ask") || "agent";
3392
+ (e.target.value as AgentMode) || "supervised";
3272
3393
  // Optimistic UI update
3273
3394
  setMode(nextMode);
3274
3395
  const current = agentMetadata || ({} as AgentMetadata);
@@ -3301,21 +3422,23 @@ export function AgentTerminal({
3301
3422
  aria-label="Mode"
3302
3423
  data-testid="agent-mode-select"
3303
3424
  >
3304
- <option value="restricted">Restricted</option>
3305
- <option value="agent">Agent</option>
3306
- <option value="ask">Ask</option>
3425
+ <option value="supervised">Supervised</option>
3426
+ <option value="autonomous">Autonomous</option>
3427
+ <option value="read-only">Read-Only</option>
3307
3428
  </select>
3308
3429
  </TooltipTrigger>
3309
3430
  <TooltipContent side="top" sideOffset={6}>
3310
3431
  <div className="max-w-[320px] space-y-1">
3311
3432
  <div>
3312
- <span className="font-semibold text-green-500">Ask</span>:
3313
- Limited tool access as configured by the profile (Ask Mode
3433
+ <span className="font-semibold text-green-500">
3434
+ Read-Only
3435
+ </span>
3436
+ : Limited tool access as configured by the profile (Ask Mode
3314
3437
  Tools).
3315
3438
  </div>
3316
3439
  <div>
3317
3440
  <span className="font-semibold text-amber-500">
3318
- Restricted
3441
+ Supervised
3319
3442
  </span>
3320
3443
  : Full tool access, but writes are limited to pages/items in
3321
3444
  the current context. Creating new items or updating existing
@@ -3323,8 +3446,10 @@ export function AgentTerminal({
3323
3446
  approval.
3324
3447
  </div>
3325
3448
  <div>
3326
- <span className="font-semibold text-red-500">Agent</span>:
3327
- Full tool access; can write across the site/project only
3449
+ <span className="font-semibold text-red-500">
3450
+ Autonomous
3451
+ </span>
3452
+ : Full tool access; can write across the site/project only
3328
3453
  limited by user permissions.
3329
3454
  </div>
3330
3455
  </div>
@@ -3472,6 +3597,7 @@ export function AgentTerminal({
3472
3597
  }
3473
3598
  : totalTokens
3474
3599
  }
3600
+ costLimit={agent?.costLimit}
3475
3601
  />
3476
3602
  {(() => {
3477
3603
  try {