@alpaca-editor/core 1.0.4103 → 1.0.4105

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 (134) hide show
  1. package/dist/components/ui/context-menu.d.ts +1 -1
  2. package/dist/components/ui/context-menu.js +8 -8
  3. package/dist/components/ui/context-menu.js.map +1 -1
  4. package/dist/config/config.js +8 -1
  5. package/dist/config/config.js.map +1 -1
  6. package/dist/config/types.d.ts +1 -0
  7. package/dist/editor/ContentTree.js +15 -16
  8. package/dist/editor/ContentTree.js.map +1 -1
  9. package/dist/editor/ContextMenu.js +37 -6
  10. package/dist/editor/ContextMenu.js.map +1 -1
  11. package/dist/editor/MainLayout.js +1 -1
  12. package/dist/editor/MainLayout.js.map +1 -1
  13. package/dist/editor/Terminal.js +2 -2
  14. package/dist/editor/Terminal.js.map +1 -1
  15. package/dist/editor/ai/AgentHistory.js +1 -1
  16. package/dist/editor/ai/AgentTerminal.js +191 -15
  17. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  18. package/dist/editor/ai/Agents.js +9 -1
  19. package/dist/editor/ai/Agents.js.map +1 -1
  20. package/dist/editor/ai/AiResponseMessage.d.ts +1 -1
  21. package/dist/editor/ai/AiResponseMessage.js +4 -2
  22. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  23. package/dist/editor/ai/types.d.ts +25 -0
  24. package/dist/editor/ai/types.js +2 -0
  25. package/dist/editor/ai/types.js.map +1 -0
  26. package/dist/editor/client/EditorShell.js +31 -0
  27. package/dist/editor/client/EditorShell.js.map +1 -1
  28. package/dist/editor/client/editContext.d.ts +3 -0
  29. package/dist/editor/client/editContext.js.map +1 -1
  30. package/dist/editor/commands/agentCommands.d.ts +9 -0
  31. package/dist/editor/commands/agentCommands.js +30 -0
  32. package/dist/editor/commands/agentCommands.js.map +1 -0
  33. package/dist/editor/commands/itemCommands.js +14 -14
  34. package/dist/editor/commands/itemCommands.js.map +1 -1
  35. package/dist/editor/component-designer/aiContext.d.ts +1 -1
  36. package/dist/editor/context-menu/InsertMenu.d.ts +2 -1
  37. package/dist/editor/context-menu/InsertMenu.js +20 -15
  38. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  39. package/dist/editor/control-center/IndexOverview.js +3 -2
  40. package/dist/editor/control-center/IndexOverview.js.map +1 -1
  41. package/dist/editor/control-center/WebSocketMessages.js +3 -3
  42. package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
  43. package/dist/editor/field-types/NameValueListEditor.d.ts +7 -0
  44. package/dist/editor/field-types/NameValueListEditor.js +99 -0
  45. package/dist/editor/field-types/NameValueListEditor.js.map +1 -0
  46. package/dist/editor/fieldTypes.d.ts +1 -1
  47. package/dist/editor/media-selector/MediaFolderBrowser.js +2 -2
  48. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  49. package/dist/editor/menubar/ActiveUsers.js +3 -2
  50. package/dist/editor/menubar/ActiveUsers.js.map +1 -1
  51. package/dist/editor/page-editor-chrome/FrameMenu.js +47 -10
  52. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  53. package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +3 -1
  54. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +30 -4
  55. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  56. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +44 -5
  57. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  58. package/dist/editor/reviews/Comments.js +96 -35
  59. package/dist/editor/reviews/Comments.js.map +1 -1
  60. package/dist/editor/services/agentService.d.ts +22 -2
  61. package/dist/editor/services/agentService.js +26 -1
  62. package/dist/editor/services/agentService.js.map +1 -1
  63. package/dist/editor/services/aiService.d.ts +2 -1
  64. package/dist/editor/services/aiService.js.map +1 -1
  65. package/dist/editor/sidebar/Completions.js +2 -1
  66. package/dist/editor/sidebar/Completions.js.map +1 -1
  67. package/dist/editor/sidebar/GraphQL.js +47 -90
  68. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  69. package/dist/editor/ui/DragPreview.d.ts +13 -0
  70. package/dist/editor/ui/DragPreview.js +35 -0
  71. package/dist/editor/ui/DragPreview.js.map +1 -0
  72. package/dist/editor/ui/Icons.d.ts +2 -1
  73. package/dist/editor/ui/Icons.js +2 -2
  74. package/dist/editor/ui/Icons.js.map +1 -1
  75. package/dist/editor/ui/ItemNameDialogNew.js +2 -2
  76. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  77. package/dist/editor/ui/PerfectTree.js +3 -15
  78. package/dist/editor/ui/PerfectTree.js.map +1 -1
  79. package/dist/editor/ui/SimpleTable.js +1 -1
  80. package/dist/editor/ui/SimpleTable.js.map +1 -1
  81. package/dist/editor/utils.d.ts +12 -1
  82. package/dist/editor/utils.js +60 -12
  83. package/dist/editor/utils.js.map +1 -1
  84. package/dist/revision.d.ts +2 -2
  85. package/dist/revision.js +2 -2
  86. package/dist/styles.css +37 -9
  87. package/dist/tour/default-tour.js +34 -38
  88. package/dist/tour/default-tour.js.map +1 -1
  89. package/package.json +1 -1
  90. package/src/components/ui/context-menu.tsx +31 -19
  91. package/src/config/config.tsx +9 -1
  92. package/src/config/types.ts +1 -0
  93. package/src/editor/ContentTree.tsx +13 -18
  94. package/src/editor/ContextMenu.tsx +112 -19
  95. package/src/editor/MainLayout.tsx +1 -1
  96. package/src/editor/Terminal.tsx +2 -2
  97. package/src/editor/ai/AgentHistory.tsx +2 -2
  98. package/src/editor/ai/AgentTerminal.tsx +234 -35
  99. package/src/editor/ai/Agents.tsx +14 -2
  100. package/src/editor/ai/AiResponseMessage.tsx +8 -6
  101. package/src/editor/ai/types.ts +27 -0
  102. package/src/editor/client/EditorShell.tsx +33 -0
  103. package/src/editor/client/editContext.ts +12 -1
  104. package/src/editor/commands/agentCommands.tsx +49 -0
  105. package/src/editor/commands/itemCommands.tsx +26 -14
  106. package/src/editor/component-designer/aiContext.ts +1 -1
  107. package/src/editor/context-menu/InsertMenu.tsx +64 -39
  108. package/src/editor/control-center/IndexOverview.tsx +3 -2
  109. package/src/editor/control-center/WebSocketMessages.tsx +3 -5
  110. package/src/editor/field-types/NameValueListEditor.tsx +197 -0
  111. package/src/editor/fieldTypes.ts +1 -1
  112. package/src/editor/media-selector/MediaFolderBrowser.tsx +2 -2
  113. package/src/editor/menubar/ActiveUsers.tsx +4 -3
  114. package/src/editor/page-editor-chrome/FrameMenu.tsx +61 -13
  115. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +82 -20
  116. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +77 -24
  117. package/src/editor/reviews/Comments.tsx +123 -38
  118. package/src/editor/services/agentService.ts +53 -2
  119. package/src/editor/services/aiService.ts +3 -1
  120. package/src/editor/sidebar/Completions.tsx +2 -1
  121. package/src/editor/sidebar/GraphQL.tsx +50 -99
  122. package/src/editor/ui/DragPreview.tsx +44 -0
  123. package/src/editor/ui/Icons.tsx +3 -0
  124. package/src/editor/ui/ItemNameDialogNew.tsx +7 -3
  125. package/src/editor/ui/PerfectTree.tsx +2 -17
  126. package/src/editor/ui/SimpleTable.tsx +2 -2
  127. package/src/editor/utils.ts +73 -15
  128. package/src/revision.ts +2 -2
  129. package/src/tour/default-tour.tsx +34 -46
  130. package/dist/editor/ai/AiTerminal.d.ts +0 -47
  131. package/dist/editor/ai/AiTerminal.js +0 -300
  132. package/dist/editor/ai/AiTerminal.js.map +0 -1
  133. package/src/editor/ai/AiTerminal.tsx +0 -570
  134. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx_ +0 -11
@@ -5,22 +5,7 @@ import React, {
5
5
  useCallback,
6
6
  useLayoutEffect,
7
7
  } from "react";
8
- import {
9
- Send,
10
- Bot,
11
- AlertCircle,
12
- Loader2,
13
- User,
14
- X,
15
- FileText,
16
- Puzzle,
17
- Type,
18
- Plus,
19
- MessageSquare,
20
- Wand2,
21
- Square,
22
- ChevronDown,
23
- } from "lucide-react";
8
+ import { Send, AlertCircle, Loader2, User, Wand2, Square } from "lucide-react";
24
9
  import { DancingDots } from "./DancingDots";
25
10
  import {
26
11
  AgentChatMessage,
@@ -33,13 +18,15 @@ import {
33
18
  AgentDetails,
34
19
  updateAgentMetadata,
35
20
  AgentMetadata,
21
+ updateAgentSettings,
22
+ updateAgentCostLimit,
36
23
  } from "../services/agentService";
37
24
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
38
25
  import { Textarea } from "../../components/ui/textarea";
39
26
  import { Button } from "../../components/ui/button";
40
27
  import { AiResponseMessage } from "./AiResponseMessage";
41
28
  import { AgentCostDisplay } from "./AgentCostDisplay";
42
- import { Message } from "./AiTerminal";
29
+ import { Message } from "./types";
43
30
  import { ContextInfoBar } from "./ContextInfoBar";
44
31
  import { getComponentById } from "../componentTreeHelper";
45
32
  import { Comment } from "../../types";
@@ -50,6 +37,8 @@ import {
50
37
  PopoverContent,
51
38
  PopoverTrigger,
52
39
  } from "../../components/ui/popover";
40
+ import { SecretAgentIcon } from "../ui/Icons";
41
+ import { formatTime, formatDateTime } from "../utils";
53
42
 
54
43
  // Simple user message component
55
44
  const UserMessage = ({ message }: { message: AgentChatMessage }) => {
@@ -60,10 +49,10 @@ const UserMessage = ({ message }: { message: AgentChatMessage }) => {
60
49
  </div>
61
50
  <div className="min-w-0 flex-1 select-text">
62
51
  <div className="mb-1 flex items-center gap-2">
63
- <span className="text-sm font-medium text-gray-900">You</span>
52
+ <span className="text-xs font-medium text-gray-900">You</span>
64
53
  {message.createdDate && (
65
54
  <span className="text-xs text-gray-400">
66
- {new Date(message.createdDate).toLocaleTimeString()}
55
+ {formatTime(new Date(message.createdDate))}
67
56
  </span>
68
57
  )}
69
58
  </div>
@@ -316,6 +305,11 @@ export function AgentTerminal({
316
305
  }, [messages]);
317
306
 
318
307
  const [error, setError] = useState<string | null>(null);
308
+ const [costLimitExceeded, setCostLimitExceeded] = useState<{
309
+ totalCost: number;
310
+ costLimit: number;
311
+ initialCostLimit: number;
312
+ } | null>(null);
319
313
 
320
314
  // Flag to track when we should create a new message
321
315
  const shouldCreateNewMessage = useRef(false);
@@ -329,6 +323,12 @@ export function AgentTerminal({
329
323
  const messagesContainerRef = useRef<HTMLDivElement>(null);
330
324
  const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
331
325
 
326
+ // Cache mode/model changes made while the agent is still "new" (not yet persisted)
327
+ const pendingSettingsRef = useRef<{
328
+ modelName?: string | null;
329
+ mode?: "agent" | "ask";
330
+ } | null>(null);
331
+
332
332
  // Auto-scroll to bottom when new messages arrive
333
333
  const scrollToBottom = useCallback(() => {
334
334
  const container = messagesContainerRef.current;
@@ -850,6 +850,24 @@ export function AgentTerminal({
850
850
  break;
851
851
 
852
852
  case "error":
853
+ // Detect cost limit exceeded
854
+ try {
855
+ const data: any = (message as any).data;
856
+ if (
857
+ message.error === "COST_LIMIT_EXCEEDED" ||
858
+ (data && data.kind === "costLimitExceeded")
859
+ ) {
860
+ setCostLimitExceeded({
861
+ totalCost: Number(data?.totalCost) || 0,
862
+ costLimit: Number(data?.costLimit) || 0,
863
+ initialCostLimit: Number(data?.initialCostLimit) || 0,
864
+ });
865
+ setIsWaitingForResponse(false);
866
+ shouldCreateNewMessage.current = false;
867
+ resetDotsTimer();
868
+ break;
869
+ }
870
+ } catch {}
853
871
  console.error("❌ Stream error:", message.error);
854
872
  setError(message.error || "Stream error occurred");
855
873
  setIsWaitingForResponse(false);
@@ -1188,17 +1206,23 @@ export function AgentTerminal({
1188
1206
  }
1189
1207
  }, [profiles, agent?.profileId]);
1190
1208
 
1191
- // Update selected model when the active profile changes
1209
+ // Update selected model when the active profile or agent model changes
1192
1210
  useEffect(() => {
1193
1211
  if (!activeProfile) return;
1194
- const agentModelId = agent?.model;
1195
- const availableModelIds = (activeProfile.models || []).map((m) => m.id);
1196
- const nextModelId =
1197
- agentModelId && availableModelIds.includes(agentModelId)
1198
- ? agentModelId
1199
- : activeProfile.defaultModelId || activeProfile.models?.[0]?.id;
1212
+ const agentModelName = agent?.model; // persisted as model NAME on server
1213
+ const models = activeProfile.models || [];
1214
+ let nextModelId: string | undefined = undefined;
1215
+ if (agentModelName) {
1216
+ const match = models.find(
1217
+ (m) => (m.name || "").toLowerCase() === agentModelName.toLowerCase(),
1218
+ );
1219
+ if (match) nextModelId = match.id;
1220
+ }
1221
+ if (!nextModelId) {
1222
+ nextModelId = activeProfile.defaultModelId || models[0]?.id;
1223
+ }
1200
1224
  setSelectedModelId(nextModelId || undefined);
1201
- }, [activeProfile?.id]);
1225
+ }, [activeProfile?.id, agent?.model]);
1202
1226
 
1203
1227
  // Cleanup stream connection when component unmounts or agent changes
1204
1228
  useEffect(() => {
@@ -1210,7 +1234,7 @@ export function AgentTerminal({
1210
1234
  };
1211
1235
  }, [agent?.id]);
1212
1236
 
1213
- // Initialize mode from metadata when available
1237
+ // Initialize mode from metadata; fall back to agent.Mode from server
1214
1238
  useEffect(() => {
1215
1239
  try {
1216
1240
  const metaMode = (agentMetadata as any)?.mode as
@@ -1219,9 +1243,16 @@ export function AgentTerminal({
1219
1243
  | undefined;
1220
1244
  if (metaMode === "agent" || metaMode === "ask") {
1221
1245
  setMode(metaMode);
1246
+ return;
1222
1247
  }
1223
1248
  } catch {}
1224
- }, [agentMetadata]);
1249
+ try {
1250
+ const serverMode = (agent as any)?.mode as string | undefined;
1251
+ if (serverMode === "agent" || serverMode === "ask") {
1252
+ setMode(serverMode);
1253
+ }
1254
+ } catch {}
1255
+ }, [agentMetadata, (agent as any)?.mode]);
1225
1256
 
1226
1257
  const updateMode = useCallback(
1227
1258
  async (nextMode: "agent" | "ask") => {
@@ -1266,6 +1297,26 @@ export function AgentTerminal({
1266
1297
  }
1267
1298
  }, [showDots, shouldAutoScroll, scrollToBottom]);
1268
1299
 
1300
+ // Persist any pending settings (mode/model) once an agent exists server-side
1301
+ const persistPendingSettingsIfNeeded = useCallback(async () => {
1302
+ try {
1303
+ if (!agent?.id) return;
1304
+ const pending = pendingSettingsRef.current;
1305
+ if (!pending) return;
1306
+ const payload: {
1307
+ model?: string | null;
1308
+ mode?: "agent" | "ask" | string | null;
1309
+ } = {};
1310
+ if (pending.modelName) payload.model = pending.modelName;
1311
+ if (pending.mode) payload.mode = pending.mode;
1312
+ if (Object.keys(payload).length === 0) return;
1313
+ await updateAgentSettings(agent.id, payload);
1314
+ pendingSettingsRef.current = null;
1315
+ } catch (e) {
1316
+ console.error("Failed to persist pending settings", e);
1317
+ }
1318
+ }, [agent?.id]);
1319
+
1269
1320
  const handleSubmit = async () => {
1270
1321
  if (!prompt.trim() || isSubmitting || !editContext) return;
1271
1322
 
@@ -1276,6 +1327,26 @@ export function AgentTerminal({
1276
1327
 
1277
1328
  if (!agentId) return;
1278
1329
 
1330
+ // Optional context factory: invoke if configured and available, otherwise continue
1331
+ const factoryName = (agentMetadata as any)?.additionalData
1332
+ ?.contextFactory as string | undefined;
1333
+ if (factoryName) {
1334
+ const factory = editContext.getContextFactory?.(factoryName);
1335
+ if (factory) {
1336
+ try {
1337
+ await Promise.resolve(factory());
1338
+ } catch (e: any) {
1339
+ console.warn(
1340
+ `Context factory '${factoryName}' failed: ${e?.message || String(e)}`,
1341
+ );
1342
+ }
1343
+ } else {
1344
+ console.warn(
1345
+ `Context factory not found: ${factoryName}. Proceeding without it.`,
1346
+ );
1347
+ }
1348
+ }
1349
+
1279
1350
  // Add user message to local state immediately for better UX
1280
1351
  const userMessage: AgentChatMessage = {
1281
1352
  id: `user-${Date.now()}`,
@@ -1351,6 +1422,9 @@ export function AgentTerminal({
1351
1422
 
1352
1423
  await startAgent(request);
1353
1424
 
1425
+ // If user changed mode/model while the agent was new, persist them now
1426
+ await persistPendingSettingsIfNeeded();
1427
+
1354
1428
  // Save prompt to history
1355
1429
  if (prompt.trim()) {
1356
1430
  setPromptHistory((prev) => [
@@ -1433,6 +1507,26 @@ export function AgentTerminal({
1433
1507
  const agentId = agent?.id;
1434
1508
  if (!agentId) return;
1435
1509
 
1510
+ // Optional context factory: invoke if configured and available, otherwise continue
1511
+ const factoryName = (agentMetadata as any)?.additionalData
1512
+ ?.contextFactory as string | undefined;
1513
+ if (factoryName) {
1514
+ const factory = editContext.getContextFactory?.(factoryName);
1515
+ if (factory) {
1516
+ try {
1517
+ await Promise.resolve(factory());
1518
+ } catch (e: any) {
1519
+ console.warn(
1520
+ `Context factory '${factoryName}' failed: ${e?.message || String(e)}`,
1521
+ );
1522
+ }
1523
+ } else {
1524
+ console.warn(
1525
+ `Context factory not found: ${factoryName}. Proceeding without it.`,
1526
+ );
1527
+ }
1528
+ }
1529
+
1436
1530
  const userMessage: AgentChatMessage = {
1437
1531
  id: `user-${Date.now()}`,
1438
1532
  agentId,
@@ -1499,6 +1593,9 @@ export function AgentTerminal({
1499
1593
 
1500
1594
  await startAgent(request);
1501
1595
 
1596
+ // If user changed mode/model while the agent was new, persist them now
1597
+ await persistPendingSettingsIfNeeded();
1598
+
1502
1599
  await connectToStream();
1503
1600
  } catch (err) {
1504
1601
  console.error("Failed to submit quick message:", err);
@@ -2046,6 +2143,56 @@ export function AgentTerminal({
2046
2143
  />
2047
2144
  );
2048
2145
 
2146
+ const renderCostLimitBanner = () => {
2147
+ if (!costLimitExceeded) return null;
2148
+ const { totalCost, costLimit, initialCostLimit } = costLimitExceeded;
2149
+ return (
2150
+ <div className="m-3 rounded border border-amber-300 bg-amber-50 p-3 text-xs text-amber-900">
2151
+ <div className="mb-2 flex items-center gap-2">
2152
+ <AlertCircle className="h-4 w-4 text-amber-500" strokeWidth={1} />
2153
+ <span>
2154
+ Cost limit exceeded. Spent ${totalCost.toFixed(4)} / $
2155
+ {costLimit.toFixed(4)}.
2156
+ </span>
2157
+ </div>
2158
+ <div className="flex gap-2">
2159
+ <button
2160
+ className="rounded border border-amber-300 bg-white px-2 py-1 hover:bg-amber-100"
2161
+ onClick={async () => {
2162
+ if (!agent?.id) return;
2163
+ try {
2164
+ await updateAgentCostLimit(agent.id, "remove");
2165
+ setCostLimitExceeded(null);
2166
+ // Reconnect to stream for next actions
2167
+ await connectToStream(agent);
2168
+ } catch (e) {
2169
+ console.error("Failed to remove cost limit", e);
2170
+ }
2171
+ }}
2172
+ >
2173
+ Continue and remove limit
2174
+ </button>
2175
+ <button
2176
+ className="rounded border border-amber-300 bg-white px-2 py-1 hover:bg-amber-100"
2177
+ onClick={async () => {
2178
+ if (!agent?.id) return;
2179
+ try {
2180
+ // Extend by initial cost limit amount
2181
+ await updateAgentCostLimit(agent.id, "extend");
2182
+ setCostLimitExceeded(null);
2183
+ await connectToStream(agent);
2184
+ } catch (e) {
2185
+ console.error("Failed to extend cost limit", e);
2186
+ }
2187
+ }}
2188
+ >
2189
+ Continue and extend limit
2190
+ </button>
2191
+ </div>
2192
+ </div>
2193
+ );
2194
+ };
2195
+
2049
2196
  return (
2050
2197
  <div className="flex h-full flex-col">
2051
2198
  {/* Messages */}
@@ -2054,6 +2201,7 @@ export function AgentTerminal({
2054
2201
  className="flex-1 overflow-y-auto"
2055
2202
  onScroll={handleScroll}
2056
2203
  >
2204
+ {renderCostLimitBanner()}
2057
2205
  {error && (
2058
2206
  <div className="m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3">
2059
2207
  <div className="flex items-start">
@@ -2072,9 +2220,10 @@ export function AgentTerminal({
2072
2220
  {messages.length === 0 && !error && (
2073
2221
  <div className="flex h-full items-center justify-center p-8">
2074
2222
  <div className="text-center">
2075
- <Bot
2076
- className="mx-auto mb-4 h-12 w-12 text-gray-400"
2223
+ <SecretAgentIcon
2224
+ size={48}
2077
2225
  strokeWidth={1}
2226
+ className="mx-auto mb-4 text-gray-400"
2078
2227
  />
2079
2228
  <h3 className="mb-2 text-lg font-medium text-gray-900">
2080
2229
  Start a conversation
@@ -2186,9 +2335,36 @@ export function AgentTerminal({
2186
2335
  <select
2187
2336
  className="h-5 rounded border px-1.5 text-[10px] text-gray-500"
2188
2337
  value={mode}
2189
- onChange={(e) =>
2190
- updateMode((e.target.value as "agent" | "ask") || "agent")
2191
- }
2338
+ onChange={async (e) => {
2339
+ const nextMode = (e.target.value as "agent" | "ask") || "agent";
2340
+ // Optimistic UI update
2341
+ setMode(nextMode);
2342
+ const current = agentMetadata || ({} as AgentMetadata);
2343
+ const nextMeta: AgentMetadata = {
2344
+ ...current,
2345
+ mode: nextMode,
2346
+ } as AgentMetadata;
2347
+ try {
2348
+ if (!agent?.id || agent.status === "new") {
2349
+ setAgentMetadata(nextMeta);
2350
+ // Cache until first start when agent is persisted
2351
+ pendingSettingsRef.current = {
2352
+ ...(pendingSettingsRef.current || {}),
2353
+ mode: nextMode,
2354
+ };
2355
+ return;
2356
+ }
2357
+ await updateAgentSettings(agent.id, { mode: nextMode });
2358
+ setAgentMetadata(nextMeta);
2359
+ setAgent((prev) =>
2360
+ prev
2361
+ ? { ...prev, metadata: JSON.stringify(nextMeta) }
2362
+ : prev,
2363
+ );
2364
+ } catch (e2) {
2365
+ console.error("Failed to persist mode change", e2);
2366
+ }
2367
+ }}
2192
2368
  title="Mode"
2193
2369
  aria-label="Mode"
2194
2370
  data-testid="agent-mode-select"
@@ -2219,7 +2395,30 @@ export function AgentTerminal({
2219
2395
  <select
2220
2396
  className="h-5 rounded border px-1.5 text-[10px] text-gray-500"
2221
2397
  value={selectedModelId || ""}
2222
- onChange={(e) => setSelectedModelId(e.target.value)}
2398
+ onChange={async (e) => {
2399
+ const nextId = e.target.value;
2400
+ setSelectedModelId(nextId);
2401
+ const modelName =
2402
+ activeProfile?.models?.find((m) => m.id === nextId)?.name ||
2403
+ "";
2404
+ // Update local agent state immediately for UX and to reflect in streaming stub
2405
+ setAgent((prev) =>
2406
+ prev ? { ...prev, model: modelName } : prev,
2407
+ );
2408
+ // Persist only for existing agents; otherwise cache until first start
2409
+ try {
2410
+ if (agent?.id && agent.status !== "new") {
2411
+ await updateAgentSettings(agent.id, { model: modelName });
2412
+ } else {
2413
+ pendingSettingsRef.current = {
2414
+ ...(pendingSettingsRef.current || {}),
2415
+ modelName,
2416
+ };
2417
+ }
2418
+ } catch (err) {
2419
+ console.error("Failed to persist agent model", err);
2420
+ }
2421
+ }}
2223
2422
  title="Model"
2224
2423
  aria-label="Model"
2225
2424
  data-testid="agent-model-select"
@@ -502,7 +502,6 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
502
502
  return (
503
503
  <div className="flex h-full items-center justify-center">
504
504
  <div className="w-72">
505
-
506
505
  {loadingProfiles ? (
507
506
  <div className="text-center text-xs text-gray-500">
508
507
  Loading profiles...
@@ -670,7 +669,20 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
670
669
  {agent.name}
671
670
  </div>
672
671
  <div className="text-xs text-gray-400">
673
- {new Date(agent.updatedDate).toLocaleString()}
672
+ {(() => {
673
+ try {
674
+ const {
675
+ formatDateTime,
676
+ } = require("../utils");
677
+ return formatDateTime(
678
+ new Date(agent.updatedDate),
679
+ );
680
+ } catch {
681
+ return new Date(
682
+ agent.updatedDate,
683
+ ).toLocaleString();
684
+ }
685
+ })()}
674
686
  </div>
675
687
  </div>
676
688
  <SimpleIconButton
@@ -1,13 +1,15 @@
1
- import React, { useState, useEffect, useMemo } from "react";
1
+ import { useState, useEffect, useMemo } from "react";
2
+ import { formatTime } from "../utils";
2
3
 
3
4
  import { useEditContext } from "../client/editContext";
4
5
  import { EditOperation } from "../../types";
5
- import { Message, ToolCall } from "./AiTerminal";
6
+ import { Message, ToolCall } from "./types";
6
7
  import { ToolCallDisplay } from "./ToolCallDisplay";
7
8
 
8
- import { X, Bot, Loader2 } from "lucide-react";
9
+ import { X } from "lucide-react";
9
10
  import { Button } from "../../components/ui/button";
10
11
  import { Checkbox } from "../../components/ui/checkbox";
12
+ import { SecretAgentIcon } from "../ui/Icons";
11
13
 
12
14
  type QuickAction = {
13
15
  id?: string;
@@ -464,15 +466,15 @@ export function AiResponseMessage({
464
466
  return (
465
467
  <div className="flex gap-3 p-4" data-testid="agent-message">
466
468
  <div className="flex-shrink-0">
467
- <Bot className="h-6 w-6 text-green-600" strokeWidth={1} />
469
+ <SecretAgentIcon size={24} strokeWidth={1} className="text-gray-2" />
468
470
  </div>
469
471
  <div className="min-w-0 flex-1 select-text">
470
472
  <div className="mb-1 flex items-center gap-2">
471
- <span className="text-sm font-medium text-gray-900">Agent</span>
473
+ <span className="text-dark text-xs font-medium">Agent</span>
472
474
 
473
475
  {lastMessage && lastMessage.createdDate && (
474
476
  <span className="text-xs text-gray-400">
475
- {new Date(lastMessage.createdDate).toLocaleTimeString()}
477
+ {formatTime(new Date(lastMessage.createdDate))}
476
478
  </span>
477
479
  )}
478
480
  </div>
@@ -0,0 +1,27 @@
1
+ export type ToolCall = {
2
+ id: string;
3
+ displayName?: string;
4
+ function: {
5
+ name: string;
6
+ arguments: string;
7
+ result?: string;
8
+ error?: string;
9
+ };
10
+ };
11
+
12
+ export type Message = {
13
+ id: string;
14
+ content: string;
15
+ formattedContent?: string;
16
+ name: string;
17
+ role: string;
18
+ tool_calls?: ToolCall[];
19
+ tool_call_id?: string;
20
+ createdDate?: string;
21
+ };
22
+
23
+ export type AiContext = {
24
+ promptData: any;
25
+ endpoint?: string;
26
+ callback?: (response: any) => void;
27
+ };
@@ -1621,6 +1621,16 @@ export function EditorShell({
1621
1621
 
1622
1622
  const editContext = useMemo<EditContextType>(() => {
1623
1623
  // console.log('🔄 EditContext useMemo is being recalculated');
1624
+ // Simple registry for per-view context factories (e.g., GraphQL context)
1625
+ // Stored outside the EditContext object via ref to avoid re-renders
1626
+ if (!(globalThis as any).__editorContextFactoriesRef) {
1627
+ (globalThis as any).__editorContextFactoriesRef = {
1628
+ map: new Map<string, () => Promise<any> | any>(),
1629
+ };
1630
+ }
1631
+ const factoriesRef = (globalThis as any).__editorContextFactoriesRef as {
1632
+ map: Map<string, () => Promise<any> | any>;
1633
+ };
1624
1634
  const context = {
1625
1635
  operations: operationsContext.ops,
1626
1636
  itemsRepository,
@@ -2069,6 +2079,27 @@ export function EditorShell({
2069
2079
  setCurrentWizardId,
2070
2080
  favorites,
2071
2081
  loadFavorites,
2082
+ // Context factory registry methods
2083
+ registerContextFactory: (
2084
+ name: string,
2085
+ factory: () => Promise<any> | any,
2086
+ ) => {
2087
+ try {
2088
+ factoriesRef.map.set(name, factory);
2089
+ } catch {}
2090
+ },
2091
+ unregisterContextFactory: (name: string) => {
2092
+ try {
2093
+ factoriesRef.map.delete(name);
2094
+ } catch {}
2095
+ },
2096
+ getContextFactory: (name: string) => {
2097
+ try {
2098
+ return factoriesRef.map.get(name);
2099
+ } catch {
2100
+ return undefined;
2101
+ }
2102
+ },
2072
2103
  };
2073
2104
 
2074
2105
  return context as unknown as EditContextType;
@@ -2126,6 +2157,7 @@ export function EditorShell({
2126
2157
  revision,
2127
2158
  comments,
2128
2159
  setComments,
2160
+ availableCommentTags,
2129
2161
  selectedComment,
2130
2162
  setSelectedComment,
2131
2163
  loadComments,
@@ -2141,6 +2173,7 @@ export function EditorShell({
2141
2173
  setShowSuggestedEdits,
2142
2174
  showSuggestedEditsDiff,
2143
2175
  setShowSuggestedEditsDiff,
2176
+ showComments,
2144
2177
  showResolvedComments,
2145
2178
  setShowResolvedComments,
2146
2179
  showComponentNavigator,
@@ -176,7 +176,9 @@ export type EditContextType = {
176
176
  showToast: (message: string) => void;
177
177
  sessionId: string;
178
178
  openSplashScreen: () => void;
179
- getComponentCommands: (components: Component[]) => Promise<ComponentCommand[]>;
179
+ getComponentCommands: (
180
+ components: Component[],
181
+ ) => Promise<ComponentCommand[]>;
180
182
  selectMedia: ({
181
183
  selectedIdPath,
182
184
  mode,
@@ -372,6 +374,15 @@ export type EditContextType = {
372
374
  loadFavorites: () => Promise<void>;
373
375
  currentWizardId: string | null;
374
376
  setCurrentWizardId: (wizardId: string | null) => void;
377
+
378
+ // Context factory registry (optional): panels can register named factories
379
+ // that produce structured context objects for agent prompts.
380
+ registerContextFactory?: (
381
+ name: string,
382
+ factory: () => Promise<any> | any,
383
+ ) => void;
384
+ unregisterContextFactory?: (name: string) => void;
385
+ getContextFactory?: (name: string) => (() => Promise<any> | any) | undefined;
375
386
  };
376
387
 
377
388
  const EditContext = React.createContext<EditContextType | undefined>(undefined);
@@ -0,0 +1,49 @@
1
+ import React from "react";
2
+ import { Command, CommandContext } from "./commands";
3
+ import { Wand2 } from "lucide-react";
4
+
5
+ export type CreateAgentCommandData = {
6
+ profileName?: string;
7
+ profileId?: string;
8
+ contextFactory?: string;
9
+ initialPrompt?: string;
10
+ additionalData?: Record<string, any>;
11
+ };
12
+
13
+ export const createAgentCommand: Command<CreateAgentCommandData> = {
14
+ id: "createAgent",
15
+ label: "Start Agent",
16
+ icon: <Wand2 strokeWidth={1} className="text-violet-600" />,
17
+ execute: async (context: CommandContext<CreateAgentCommandData>) => {
18
+ const edit = context.editContext;
19
+ try {
20
+ edit.setShowAgentsPanel?.(true);
21
+
22
+ const {
23
+ profileName,
24
+ profileId,
25
+ contextFactory,
26
+ initialPrompt,
27
+ additionalData,
28
+ } = context.data || {};
29
+
30
+ const metadata = {
31
+ profile: profileName,
32
+ additionalData: {
33
+ ...(additionalData || {}),
34
+ profileName,
35
+ profileId,
36
+ contextFactory,
37
+ initialPrompt,
38
+ },
39
+ } as any;
40
+
41
+ window.dispatchEvent(
42
+ new CustomEvent("editor:addNewAgent", { detail: { metadata } }),
43
+ );
44
+ } catch (e) {
45
+ console.error("Failed to start agent", e);
46
+ }
47
+ },
48
+ disabled: () => false,
49
+ };