@alpaca-editor/core 1.0.4192 → 1.0.4194

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 (106) hide show
  1. package/dist/agents-view/AgentCard.js +1 -1
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentsView.js +5 -7
  4. package/dist/agents-view/AgentsView.js.map +1 -1
  5. package/dist/config/config.js +14 -7
  6. package/dist/config/config.js.map +1 -1
  7. package/dist/editor/ItemInfo.js +3 -3
  8. package/dist/editor/ItemInfo.js.map +1 -1
  9. package/dist/editor/QuickItemSwitcher.js +1 -1
  10. package/dist/editor/QuickItemSwitcher.js.map +1 -1
  11. package/dist/editor/ai/AgentTerminal.d.ts +1 -7
  12. package/dist/editor/ai/AgentTerminal.js +382 -256
  13. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  14. package/dist/editor/ai/Agents.js +84 -198
  15. package/dist/editor/ai/Agents.js.map +1 -1
  16. package/dist/editor/ai/AiResponseMessage.d.ts +1 -3
  17. package/dist/editor/ai/AiResponseMessage.js +12 -65
  18. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  19. package/dist/editor/ai/ToolCallDisplay.d.ts +1 -2
  20. package/dist/editor/ai/ToolCallDisplay.js +5 -13
  21. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  22. package/dist/editor/client/EditorShell.js +5 -6
  23. package/dist/editor/client/EditorShell.js.map +1 -1
  24. package/dist/editor/client/hooks/useSocketMessageHandler.js +4 -6
  25. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  26. package/dist/editor/commands/componentCommands.js +0 -2
  27. package/dist/editor/commands/componentCommands.js.map +1 -1
  28. package/dist/editor/control-center/About.js +1 -1
  29. package/dist/editor/control-center/About.js.map +1 -1
  30. package/dist/editor/control-center/AllAgentsPanel.js +1 -1
  31. package/dist/editor/field-types/MultiLineText.js +1 -1
  32. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  33. package/dist/editor/services/aiService.d.ts +0 -1
  34. package/dist/editor/services/aiService.js.map +1 -1
  35. package/dist/editor/sidebar/Validation.js +1 -1
  36. package/dist/editor/sidebar/Validation.js.map +1 -1
  37. package/dist/index.d.ts +0 -3
  38. package/dist/index.js +0 -4
  39. package/dist/index.js.map +1 -1
  40. package/dist/page-wizard/PageWizard.js +3 -3
  41. package/dist/page-wizard/PageWizard.js.map +1 -1
  42. package/dist/page-wizard/WizardSteps.js +1 -1
  43. package/dist/page-wizard/WizardSteps.js.map +1 -1
  44. package/dist/revision.d.ts +2 -2
  45. package/dist/revision.js +2 -2
  46. package/dist/splash-screen/OpenPage.js +6 -10
  47. package/dist/splash-screen/OpenPage.js.map +1 -1
  48. package/dist/splash-screen/RecentPages.js +2 -2
  49. package/dist/splash-screen/RecentPages.js.map +1 -1
  50. package/dist/splash-screen/SplashScreen.js +1 -1
  51. package/dist/splash-screen/SplashScreen.js.map +1 -1
  52. package/dist/styles.css +12 -244
  53. package/package.json +1 -1
  54. package/src/agents-view/AgentCard.tsx +6 -1
  55. package/src/agents-view/AgentsView.tsx +30 -18
  56. package/src/config/config.tsx +17 -8
  57. package/src/editor/ItemInfo.tsx +2 -3
  58. package/src/editor/QuickItemSwitcher.tsx +1 -1
  59. package/src/editor/ai/AgentTerminal.tsx +649 -544
  60. package/src/editor/ai/Agents.tsx +250 -464
  61. package/src/editor/ai/AiResponseMessage.tsx +29 -134
  62. package/src/editor/ai/ToolCallDisplay.tsx +4 -18
  63. package/src/editor/client/EditorShell.tsx +6 -9
  64. package/src/editor/client/hooks/useSocketMessageHandler.ts +7 -6
  65. package/src/editor/commands/componentCommands.tsx +0 -1
  66. package/src/editor/control-center/About.tsx +2 -2
  67. package/src/editor/control-center/AllAgentsPanel.tsx +1 -1
  68. package/src/editor/field-types/MultiLineText.tsx +1 -1
  69. package/src/editor/services/aiService.ts +0 -2
  70. package/src/editor/sidebar/Validation.tsx +1 -1
  71. package/src/index.ts +0 -5
  72. package/src/page-wizard/PageWizard.tsx +3 -3
  73. package/src/page-wizard/WizardSteps.tsx +1 -1
  74. package/src/revision.ts +2 -2
  75. package/src/splash-screen/OpenPage.tsx +4 -12
  76. package/src/splash-screen/RecentPages.tsx +61 -58
  77. package/src/splash-screen/SplashScreen.tsx +1 -1
  78. package/styles.css +0 -20
  79. package/dist/components/ui/PlaceholderInput.d.ts +0 -41
  80. package/dist/components/ui/PlaceholderInput.js +0 -160
  81. package/dist/components/ui/PlaceholderInput.js.map +0 -1
  82. package/dist/components/ui/PlaceholderInputTypes.d.ts +0 -41
  83. package/dist/components/ui/PlaceholderInputTypes.js +0 -48
  84. package/dist/components/ui/PlaceholderInputTypes.js.map +0 -1
  85. package/dist/components/ui/PlaceholderItemSelector.d.ts +0 -7
  86. package/dist/components/ui/PlaceholderItemSelector.js +0 -154
  87. package/dist/components/ui/PlaceholderItemSelector.js.map +0 -1
  88. package/dist/editor/ai/MediaImage.d.ts +0 -6
  89. package/dist/editor/ai/MediaImage.js +0 -38
  90. package/dist/editor/ai/MediaImage.js.map +0 -1
  91. package/dist/splash-screen/ModernSplashScreen.d.ts +0 -8
  92. package/dist/splash-screen/ModernSplashScreen.js +0 -92
  93. package/dist/splash-screen/ModernSplashScreen.js.map +0 -1
  94. package/dist/splash-screen/ParheliaAssistantChat.d.ts +0 -8
  95. package/dist/splash-screen/ParheliaAssistantChat.js +0 -155
  96. package/dist/splash-screen/ParheliaAssistantChat.js.map +0 -1
  97. package/dist/splash-screen/RecentAgents.d.ts +0 -7
  98. package/dist/splash-screen/RecentAgents.js +0 -76
  99. package/dist/splash-screen/RecentAgents.js.map +0 -1
  100. package/src/components/ui/PlaceholderInput.tsx +0 -290
  101. package/src/components/ui/PlaceholderInputTypes.tsx +0 -97
  102. package/src/components/ui/PlaceholderItemSelector.tsx +0 -253
  103. package/src/editor/ai/MediaImage.tsx +0 -75
  104. package/src/splash-screen/ModernSplashScreen.tsx +0 -229
  105. package/src/splash-screen/ParheliaAssistantChat.tsx +0 -273
  106. package/src/splash-screen/RecentAgents.tsx +0 -151
@@ -1,12 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, ViewTransition, } from "react";
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";
5
- import { getAgent, startAgent, updateAgentSettings, updateAgentCostLimit, cancelAgent, canonicalizeAgentMetadata, } from "../services/agentService";
5
+ import { getAgent, startAgent, updateAgentContext, updateAgentSettings, updateAgentCostLimit, cancelAgent, canonicalizeAgentMetadata, } from "../services/agentService";
6
6
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
7
7
  import { Textarea } from "../../components/ui/textarea";
8
8
  import { Button } from "../../components/ui/button";
9
- import { PlaceholderInput } from "../../components/ui/PlaceholderInput";
10
9
  import { AiResponseMessage } from "./AiResponseMessage";
11
10
  import { AgentCostDisplay } from "./AgentCostDisplay";
12
11
  import { ContextInfoBar } from "./ContextInfoBar";
@@ -440,12 +439,6 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
440
439
  id: agentMessage.id,
441
440
  content: agentMessage.content,
442
441
  formattedContent: agentMessage.content
443
- ?.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>")
444
- ?.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>")
445
- ?.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>")
446
- ?.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>")
447
- ?.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>")
448
- ?.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>")
449
442
  ?.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
450
443
  ?.replace(/\n/g, "<br/>"),
451
444
  name: agentMessage.name,
@@ -475,7 +468,7 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
475
468
  // interface AgentTerminalProps {
476
469
  // agentStub: Agent;
477
470
  // }
478
- export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, hideContext = false, hideBottomControls = false, hideGreeting = false, className, initialPrompt, }) {
471
+ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, }) {
479
472
  const editContext = useEditContext();
480
473
  const fieldsContext = useFieldsEditContext();
481
474
  const [agent, setAgent] = useState(undefined);
@@ -485,7 +478,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
485
478
  const [isLoading, setIsLoading] = useState(false);
486
479
  const [isConnecting, setIsConnecting] = useState(false);
487
480
  const [isSubmitting, setIsSubmitting] = useState(false);
488
- const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
489
481
  const [agentMetadata, setAgentMetadata] = useState(null);
490
482
  // Generate a stable clientSessionId per component instance for stream deduplication
491
483
  const clientSessionIdRef = useRef(null);
@@ -658,12 +650,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
658
650
  setIsVoiceSupported(false);
659
651
  }
660
652
  }, []);
661
- // Auto-focus terminal input on mount
662
- useEffect(() => {
663
- if (textareaRef.current) {
664
- textareaRef.current.focus();
665
- }
666
- }, []);
667
653
  // Start voice recognition
668
654
  const startVoice = useCallback(() => {
669
655
  try {
@@ -1192,6 +1178,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1192
1178
  const loadAgent = useCallback(async () => {
1193
1179
  try {
1194
1180
  if (agentStub.status === "new") {
1181
+ // Set agent ID immediately for new agents
1182
+ window.currentAgentId = agentStub.id;
1183
+ // Set currentAgentId for new agent
1195
1184
  // Derive initial profile from provided metadata if present
1196
1185
  const initialProfileIdFromMeta = (() => {
1197
1186
  try {
@@ -1376,6 +1365,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1376
1365
  const merged = mergeMessagesById(dbMessages, prevMessages);
1377
1366
  return merged;
1378
1367
  });
1368
+ // Set agent ID for existing agents too
1369
+ window.currentAgentId = agentData.id;
1379
1370
  // Check if cost limit was exceeded (detect from existing messages)
1380
1371
  try {
1381
1372
  const costLimitMessage = (agentData.messages || []).find((msg) => msg.role === "assistant" &&
@@ -1887,19 +1878,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1887
1878
  return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
1888
1879
  }, []);
1889
1880
  // Profiles are provided by parent component (Agents). No local loading here.
1890
- // Select active profile based on agent.profileId or agentStub.profileId
1881
+ // Select active profile based on agent.profileId or default to first
1891
1882
  useEffect(() => {
1892
1883
  if (!profiles || profiles.length === 0)
1893
1884
  return;
1894
- // For new agents, use agentStub.profileId; for loaded agents, use agent.profileId
1895
- const profileIdToUse = agent?.profileId || agentStub.profileId;
1896
- const candidate = profileIdToUse
1897
- ? (profiles.find((p) => p.id === profileIdToUse) ?? profiles[0])
1885
+ const candidate = agent?.profileId
1886
+ ? (profiles.find((p) => p.id === agent?.profileId) ??
1887
+ profiles[0])
1898
1888
  : profiles[0];
1899
1889
  if (candidate && (!activeProfile || activeProfile.id !== candidate.id)) {
1900
1890
  setActiveProfile(candidate);
1901
1891
  }
1902
- }, [profiles, agent?.profileId, agentStub.profileId]);
1892
+ }, [profiles, agent?.profileId]);
1903
1893
  // Update selected model when the active profile or agent model changes
1904
1894
  useEffect(() => {
1905
1895
  if (!activeProfile)
@@ -1976,13 +1966,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1976
1966
  }
1977
1967
  }, [agent?.id]);
1978
1968
  const handleSubmit = async () => {
1979
- if (isSubmitting || !editContext)
1969
+ if (!prompt.trim() || isSubmitting || !editContext)
1980
1970
  return;
1981
1971
  try {
1982
1972
  setIsSubmitting(true);
1983
1973
  setError(null);
1984
- // For new agents, use agentStub.id; for existing agents, use agent.id
1985
- const agentId = agent?.id || agentStub.id;
1974
+ const agentId = agent?.id;
1986
1975
  if (!agentId)
1987
1976
  return;
1988
1977
  // Optional context factory: invoke if configured and available, otherwise continue
@@ -2006,7 +1995,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2006
1995
  // It will be added when we receive the agent:user:message broadcast from the server
2007
1996
  // This ensures all tabs (including the sending tab) have the same messageId from the database
2008
1997
  const request = {
2009
- agentId: agentId,
1998
+ agentId: agent.id,
2010
1999
  message: prompt.trim(),
2011
2000
  sessionId: editContext.sessionId,
2012
2001
  profileId: activeProfile?.id || profiles[0]?.id || "",
@@ -2163,35 +2152,232 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2163
2152
  setIsSubmitting(false);
2164
2153
  }
2165
2154
  };
2166
- // Auto-send initial prompt if provided and agent has no messages yet
2167
- const initialPromptSentRef = useRef(false);
2168
- useEffect(() => {
2169
- if (initialPrompt !== undefined &&
2170
- !isLoading &&
2171
- !initialPromptSentRef.current &&
2172
- messages.length === 0 &&
2173
- agentStub.id &&
2174
- editContext &&
2175
- activeProfile && // MUST have activeProfile set, not just profiles.length
2176
- profiles.length > 0) {
2177
- initialPromptSentRef.current = true;
2178
- // Delay slightly to ensure all state is ready
2179
- setTimeout(() => {
2180
- setPrompt(initialPrompt);
2181
- // Trigger submit programmatically
2182
- handleSubmit();
2183
- }, 200);
2155
+ // Context info bar helpers - must be declared before any early returns to keep hooks order stable
2156
+ const removeContextKey = useCallback(async (key, index) => {
2157
+ if (!agent?.id)
2158
+ return;
2159
+ const current = agentMetadata || {};
2160
+ // Exclude top-level context to avoid duplicate keys when spreading
2161
+ const currentWithoutContext = { ...current };
2162
+ delete currentWithoutContext.context;
2163
+ const next = {
2164
+ ...currentWithoutContext,
2165
+ additionalData: {
2166
+ ...(current.additionalData || {}),
2167
+ context: {
2168
+ ...((current.additionalData &&
2169
+ current.additionalData.context) ||
2170
+ {}),
2171
+ },
2172
+ },
2173
+ };
2174
+ if (next.additionalData && next.additionalData.context) {
2175
+ const ctx = next.additionalData.context;
2176
+ if (key === "items" && typeof index === "number" && ctx.items) {
2177
+ ctx.items.splice(index, 1);
2178
+ if (ctx.items.length === 0)
2179
+ delete ctx.items;
2180
+ }
2181
+ else if (key === "componentIds" &&
2182
+ typeof index === "number" &&
2183
+ ctx.componentIds) {
2184
+ ctx.componentIds.splice(index, 1);
2185
+ if (ctx.componentIds.length === 0)
2186
+ delete ctx.componentIds;
2187
+ }
2188
+ else if (key === "field") {
2189
+ delete ctx.field;
2190
+ }
2191
+ else if (key === "comment") {
2192
+ delete ctx.comment;
2193
+ }
2184
2194
  }
2185
- }, [
2186
- initialPrompt,
2187
- isLoading,
2188
- messages.length,
2189
- agentStub.id,
2190
- editContext,
2191
- activeProfile,
2192
- profiles.length,
2193
- handleSubmit,
2194
- ]);
2195
+ try {
2196
+ // For new (not yet persisted) agents, only update local state
2197
+ if (agent.status === "new") {
2198
+ setAgentMetadata(next);
2199
+ return;
2200
+ }
2201
+ // Persisted agents: update server and local cache
2202
+ await updateAgentContext(agent.id, next);
2203
+ setAgentMetadata(next);
2204
+ setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2205
+ }
2206
+ catch (e) {
2207
+ console.error("Failed to update agent metadata", e);
2208
+ }
2209
+ }, [agent?.id, agentMetadata]);
2210
+ const addPagesToContext = async () => {
2211
+ if (!agent?.id || !editContext?.currentItemDescriptor)
2212
+ return;
2213
+ // Add the current page (currentItemDescriptor represents the current page)
2214
+ // Note: Currently only supports adding the current page. To support multiple pages,
2215
+ // we'd need a page selection mechanism in the UI (e.g., a page picker dialog)
2216
+ const item = editContext.currentItemDescriptor;
2217
+ const pageToAdd = {
2218
+ id: item.id,
2219
+ language: item.language,
2220
+ version: item.version,
2221
+ name: editContext.contentEditorItem?.name,
2222
+ path: undefined,
2223
+ };
2224
+ const current = agentMetadata || {};
2225
+ const currentPages = current.additionalData?.context?.items || [];
2226
+ // Check if this page is already in context
2227
+ const existingPageIds = new Set(currentPages.map((p) => `${p.id}-${p.language}-${p.version}`));
2228
+ if (existingPageIds.has(`${pageToAdd.id}-${pageToAdd.language}-${pageToAdd.version}`)) {
2229
+ return; // Page already exists
2230
+ }
2231
+ // Exclude top-level context to avoid duplicate keys when spreading
2232
+ const currentWithoutContext = { ...current };
2233
+ delete currentWithoutContext.context;
2234
+ const next = {
2235
+ ...currentWithoutContext,
2236
+ additionalData: {
2237
+ ...(current.additionalData || {}),
2238
+ context: {
2239
+ ...((current.additionalData &&
2240
+ current.additionalData.context) ||
2241
+ {}),
2242
+ items: [...currentPages, pageToAdd],
2243
+ },
2244
+ },
2245
+ };
2246
+ try {
2247
+ if (agent.status === "new") {
2248
+ setAgentMetadata(next);
2249
+ return;
2250
+ }
2251
+ await updateAgentContext(agent.id, next);
2252
+ setAgentMetadata(next);
2253
+ setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2254
+ }
2255
+ catch (e) {
2256
+ console.error("Failed to update agent metadata (add pages)", e);
2257
+ }
2258
+ };
2259
+ const addSelectedComponentsToContext = async () => {
2260
+ if (!agent?.id || !editContext?.selection?.length)
2261
+ return;
2262
+ const current = agentMetadata || {};
2263
+ const currentComponentIds = current.additionalData?.context?.componentIds ||
2264
+ [];
2265
+ // Merge with existing components, avoiding duplicates
2266
+ const existingIds = new Set(currentComponentIds);
2267
+ const newComponentIds = editContext.selection.filter((id) => !existingIds.has(id));
2268
+ if (newComponentIds.length === 0)
2269
+ return; // No new components to add
2270
+ // Exclude top-level context to avoid duplicate keys when spreading
2271
+ const currentWithoutContext = { ...current };
2272
+ delete currentWithoutContext.context;
2273
+ const next = {
2274
+ ...currentWithoutContext,
2275
+ additionalData: {
2276
+ ...(current.additionalData || {}),
2277
+ context: {
2278
+ ...((current.additionalData &&
2279
+ current.additionalData.context) ||
2280
+ {}),
2281
+ componentIds: [...currentComponentIds, ...newComponentIds],
2282
+ },
2283
+ },
2284
+ };
2285
+ try {
2286
+ if (agent.status === "new") {
2287
+ setAgentMetadata(next);
2288
+ return;
2289
+ }
2290
+ await updateAgentContext(agent.id, next);
2291
+ setAgentMetadata(next);
2292
+ setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2293
+ }
2294
+ catch (e) {
2295
+ console.error("Failed to update agent metadata (add components)", e);
2296
+ }
2297
+ };
2298
+ const addComponentIdsToContext = async (ids) => {
2299
+ if (!agent?.id || !ids?.length)
2300
+ return;
2301
+ const current = agentMetadata || {};
2302
+ const currentComponentIds = current.additionalData?.context?.componentIds ||
2303
+ [];
2304
+ // Merge with existing components, avoiding duplicates
2305
+ const existingIds = new Set(currentComponentIds);
2306
+ const newComponentIds = ids.filter((id) => !!id && !existingIds.has(id));
2307
+ if (newComponentIds.length === 0)
2308
+ return;
2309
+ // Exclude top-level context to avoid duplicate keys when spreading
2310
+ const currentWithoutContext = { ...current };
2311
+ delete currentWithoutContext.context;
2312
+ const next = {
2313
+ ...currentWithoutContext,
2314
+ additionalData: {
2315
+ ...(current.additionalData || {}),
2316
+ context: {
2317
+ ...((current.additionalData &&
2318
+ current.additionalData.context) ||
2319
+ {}),
2320
+ componentIds: [...currentComponentIds, ...newComponentIds],
2321
+ },
2322
+ },
2323
+ };
2324
+ try {
2325
+ if (agent.status === "new") {
2326
+ setAgentMetadata(next);
2327
+ return;
2328
+ }
2329
+ await updateAgentContext(agent.id, next);
2330
+ setAgentMetadata(next);
2331
+ setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2332
+ }
2333
+ catch (e) {
2334
+ console.error("Failed to update agent metadata (add components)", e);
2335
+ }
2336
+ };
2337
+ const addPagesToContextFromItems = async (items) => {
2338
+ if (!agent?.id || !items?.length)
2339
+ return;
2340
+ const current = agentMetadata || {};
2341
+ const currentPages = current.additionalData?.context?.items || [];
2342
+ const existingPageIds = new Set(currentPages.map((p) => `${p.id}-${p.language}-${p.version}`));
2343
+ const pagesToAdd = items
2344
+ .filter((it) => !!it?.id)
2345
+ .map((it) => ({
2346
+ id: it.id,
2347
+ language: it.language,
2348
+ version: it.version,
2349
+ }))
2350
+ .filter((p) => !existingPageIds.has(`${p.id}-${p.language}-${p.version}`));
2351
+ if (pagesToAdd.length === 0)
2352
+ return;
2353
+ // Exclude top-level context to avoid duplicate keys when spreading
2354
+ const currentWithoutContext = { ...current };
2355
+ delete currentWithoutContext.context;
2356
+ const next = {
2357
+ ...currentWithoutContext,
2358
+ additionalData: {
2359
+ ...(current.additionalData || {}),
2360
+ context: {
2361
+ ...((current.additionalData &&
2362
+ current.additionalData.context) ||
2363
+ {}),
2364
+ items: [...currentPages, ...pagesToAdd],
2365
+ },
2366
+ },
2367
+ };
2368
+ try {
2369
+ if (agent.status === "new") {
2370
+ setAgentMetadata(next);
2371
+ return;
2372
+ }
2373
+ await updateAgentContext(agent.id, next);
2374
+ setAgentMetadata(next);
2375
+ setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2376
+ }
2377
+ catch (e) {
2378
+ console.error("Failed to update agent metadata (add items/pages)", e);
2379
+ }
2380
+ };
2195
2381
  // Resolve display names when metadata or editor state changes
2196
2382
  useEffect(() => {
2197
2383
  const metaCtx = agentMetadata;
@@ -2365,11 +2551,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2365
2551
  }
2366
2552
  }, children: "Extend limit and continue" }) })] }));
2367
2553
  };
2368
- return (_jsxs("div", { className: `flex h-full flex-col ${className || ""}`, children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-sm text-red-700", children: error })] })] }) })), messages.length === 0 && !error && !hideGreeting && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsx("div", { className: "max-w-prose text-center", children: !activeProfile ? (_jsx(Loader2, { className: "h-8 w-8 animate-spin text-gray-400 mx-auto" })) : (_jsxs(_Fragment, { children: [activeProfile.svgIcon ? (_jsx("div", { className: "mx-auto mb-4 flex h-24 w-24 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
2369
- __html: activeProfile.svgIcon,
2370
- } })) : (_jsx(SecretAgentIcon, { size: 96, strokeWidth: 1, className: "mx-auto mb-4 text-gray-400" })), activeProfile.greetingMessage ? (_jsx(ViewTransition, { children: _jsx("div", { className: "prose prose-sm mx-auto text-center", dangerouslySetInnerHTML: {
2371
- __html: activeProfile.greetingMessage,
2372
- } }) })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }))] })) }) })), _jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: groupConsecutiveMessages(messages).map((group, groupIndex) => {
2554
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-sm text-red-700", children: error })] })] }) })), messages.length === 0 && !error && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "max-w-prose text-center", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "mx-auto mb-4 flex h-24 w-24 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
2555
+ __html: activeProfile.svgIcon,
2556
+ } })) : (_jsx(SecretAgentIcon, { size: 96, strokeWidth: 1, className: "mx-auto mb-4 text-gray-400" })), activeProfile?.greetingMessage ? (_jsx("div", { className: "prose prose-sm mx-auto text-center", dangerouslySetInnerHTML: {
2557
+ __html: activeProfile.greetingMessage,
2558
+ } })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }))] }) })), _jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: groupConsecutiveMessages(messages).map((group, groupIndex) => {
2373
2559
  if (group.type === "user" && group.messages[0]) {
2374
2560
  // Render user message
2375
2561
  return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
@@ -2389,23 +2575,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2389
2575
  return null;
2390
2576
  }
2391
2577
  const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
2392
- return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.name, onQuickAction: (action) => {
2578
+ return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined, profileSvgIcon: activeProfile?.svgIcon, onQuickAction: (action) => {
2393
2579
  const text = (action.prompt ||
2394
2580
  action.value ||
2395
2581
  action.label ||
2396
2582
  "").trim();
2397
2583
  if (!text)
2398
2584
  return;
2399
- // Check if text contains placeholders ({placeholder} or <placeholder>)
2400
- const hasPlaceholders = /\{([^}]+)\}|<([^>]+)>/.test(text);
2401
- if (hasPlaceholders) {
2402
- // Show placeholder input
2403
- setActivePlaceholderInput({
2404
- text,
2405
- behavior: action.behavior,
2406
- });
2407
- return;
2408
- }
2409
2585
  if (action.behavior === "compose") {
2410
2586
  setPrompt(text);
2411
2587
  setInputPlaceholder(action.placeholder ||
@@ -2431,204 +2607,154 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2431
2607
  sendQuickMessage(text);
2432
2608
  } }, groupIndex));
2433
2609
  }
2434
- }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), renderCostLimitBanner(), _jsx("div", { ref: messagesEndRef })] }), !hideContext && renderContextInfoBar(), !hideContext && (_jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata })), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [activePlaceholderInput ? (
2435
- // Placeholder Input (from quick actions)
2436
- _jsx(PlaceholderInput, { text: activePlaceholderInput.text, showButtons: false, onComplete: (filledText) => {
2437
- setActivePlaceholderInput(null);
2438
- if (activePlaceholderInput.behavior === "compose") {
2439
- setPrompt(filledText);
2440
- setInputPlaceholder("Review and edit, then press Enter to send");
2441
- if (textareaRef.current) {
2442
- try {
2443
- textareaRef.current.focus();
2444
- const v = textareaRef.current.value || "";
2445
- textareaRef.current.selectionStart = v.length;
2446
- textareaRef.current.selectionEnd = v.length;
2447
- }
2448
- catch { }
2449
- }
2450
- }
2451
- else {
2452
- // Submit behavior or default
2453
- if (isExecuting) {
2454
- try {
2455
- handleStop();
2456
- }
2457
- catch { }
2458
- }
2459
- sendQuickMessage(filledText);
2460
- }
2461
- }, onCancel: () => {
2462
- setActivePlaceholderInput(null);
2463
- } })) : prompt && /\{([^}]+)\}|<([^>]+)>/.test(prompt) ? (
2464
- // Template mode: show PlaceholderInput when prompt contains placeholders
2465
- _jsx(PlaceholderInput, { text: prompt, showButtons: false, onComplete: (filledText) => {
2466
- setPrompt(filledText);
2467
- // Auto-submit after filling placeholders
2468
- if (filledText.trim()) {
2469
- if (isExecuting) {
2470
- try {
2471
- handleStop();
2472
- }
2473
- catch { }
2474
- }
2475
- sendQuickMessage(filledText);
2476
- }
2477
- }, onCancel: () => {
2478
- setPrompt("");
2479
- setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
2480
- } })) : (_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
2610
+ }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), renderCostLimitBanner(), _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) => {
2481
2611
  setPrompt(e.target.value);
2482
2612
  // Reset history index when user starts typing
2483
2613
  if (currentHistoryIndex !== -1) {
2484
2614
  setCurrentHistoryIndex(-1);
2485
2615
  }
2486
- }, onKeyDown: handleKeyPress, placeholder: inputPlaceholder, className: "h-[80px] flex-1 resize-none overflow-y-auto text-xs", "data-testid": "agent-terminal-prompt", disabled: isSubmitting }) })), !hideBottomControls && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-stretch justify-between gap-2", children: [_jsxs("div", { className: "mt-2 flex flex-wrap items-center justify-start gap-2", children: [_jsxs(Tooltip, { delayDuration: 400, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("select", { className: `h-5 rounded border px-1.5 text-[10px] ${mode === "read-only"
2487
- ? "border-green-300 bg-green-50 text-green-700"
2488
- : mode === "supervised"
2489
- ? "border-amber-300 bg-amber-50 text-amber-700"
2490
- : "border-red-300 bg-red-50 text-red-700"}`, value: mode, onChange: async (e) => {
2491
- const nextMode = e.target.value || "supervised";
2492
- // Optimistic UI update
2493
- setMode(nextMode);
2494
- const current = agentMetadata || {};
2495
- const nextMeta = {
2496
- ...current,
2616
+ }, onKeyDown: handleKeyPress, placeholder: inputPlaceholder, className: "h-[80px] flex-1 resize-none overflow-y-auto text-xs", "data-testid": "agent-terminal-prompt", disabled: isSubmitting }) }), _jsxs("div", { className: "flex items-stretch justify-between gap-2", children: [_jsxs("div", { className: "mt-2 flex flex-wrap items-center justify-start gap-2", children: [_jsxs(Tooltip, { delayDuration: 400, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("select", { className: `h-5 rounded border px-1.5 text-[10px] ${mode === "read-only"
2617
+ ? "border-green-300 bg-green-50 text-green-700"
2618
+ : mode === "supervised"
2619
+ ? "border-amber-300 bg-amber-50 text-amber-700"
2620
+ : "border-red-300 bg-red-50 text-red-700"}`, value: mode, onChange: async (e) => {
2621
+ const nextMode = e.target.value || "supervised";
2622
+ // Optimistic UI update
2623
+ setMode(nextMode);
2624
+ const current = agentMetadata || {};
2625
+ const nextMeta = {
2626
+ ...current,
2627
+ mode: nextMode,
2628
+ };
2629
+ try {
2630
+ if (!agent?.id || agent.status === "new") {
2631
+ setAgentMetadata(nextMeta);
2632
+ // Cache until first start when agent is persisted
2633
+ pendingSettingsRef.current = {
2634
+ ...(pendingSettingsRef.current || {}),
2497
2635
  mode: nextMode,
2498
2636
  };
2499
- try {
2500
- if (!agent?.id || agent.status === "new") {
2501
- setAgentMetadata(nextMeta);
2502
- // Cache until first start when agent is persisted
2503
- pendingSettingsRef.current = {
2504
- ...(pendingSettingsRef.current || {}),
2505
- mode: nextMode,
2506
- };
2507
- return;
2508
- }
2509
- await updateAgentSettings(agent.id, {
2510
- mode: nextMode,
2511
- });
2512
- setAgentMetadata(nextMeta);
2513
- setAgent((prev) => prev
2514
- ? { ...prev, metadata: JSON.stringify(nextMeta) }
2515
- : prev);
2516
- }
2517
- catch (e2) {
2518
- console.error("Failed to persist mode change", e2);
2519
- }
2520
- }, title: "Mode", "aria-label": "Mode", "data-testid": "agent-mode-select", children: [_jsx("option", { value: "supervised", children: "Supervised" }), _jsx("option", { value: "autonomous", children: "Autonomous" }), _jsx("option", { value: "read-only", children: "Read-Only" })] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold text-green-500", children: "Read-Only" }), ": Limited tool access as configured by the profile (Ask Mode Tools)."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-amber-500", children: "Supervised" }), ": Full tool access, but writes are limited to pages/items in the current context. Creating new items or updating existing items outside the current context requires explicit approval."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-red-500", children: "Autonomous" }), ": Full tool access; can write across the site/project only limited by user permissions."] })] }) })] }), profiles?.length > 0 && (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: activeProfile?.id || "", onChange: async (e) => {
2521
- const nextProfile = profiles.find((x) => x.id === e.target.value);
2522
- if (!nextProfile)
2523
- return;
2524
- setActiveProfile(nextProfile);
2525
- try {
2526
- if (agent?.id && agent.status !== "new") {
2527
- await updateAgentSettings(agent.id, {
2528
- profileId: nextProfile.id,
2529
- profileName: nextProfile.name,
2530
- });
2531
- }
2532
- else {
2533
- // cache until first start
2534
- pendingSettingsRef.current = {
2535
- ...(pendingSettingsRef.current || {}),
2536
- // we cache profile by updating local metadata
2537
- };
2538
- setAgentMetadata((current) => {
2539
- const next = { ...(current || {}) };
2540
- next.profile = nextProfile.name;
2541
- next.additionalData = {
2542
- ...(next.additionalData || {}),
2543
- profileId: nextProfile.id,
2544
- profileName: nextProfile.name,
2545
- };
2546
- return next;
2547
- });
2548
- }
2549
- // reflect in local agent stub so tabs and titles can use it if needed
2550
- setAgent((prev) => prev
2551
- ? {
2552
- ...prev,
2553
- metadata: JSON.stringify({
2554
- ...(agentMetadata || {}),
2555
- profile: nextProfile.name,
2556
- additionalData: {
2557
- ...(agentMetadata
2558
- ?.additionalData || {}),
2559
- profileId: nextProfile.id,
2560
- profileName: nextProfile.name,
2561
- },
2562
- }),
2637
+ return;
2563
2638
  }
2564
- : prev);
2565
- }
2566
- catch (err) {
2567
- console.error("Failed to persist agent profile", err);
2568
- }
2569
- }, title: "Profile", "aria-label": "Profile", "data-testid": "agent-profile-select", children: profiles.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id))) })), activeProfile?.models?.length ? (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: selectedModelId || "", onChange: async (e) => {
2570
- const nextId = e.target.value;
2571
- setSelectedModelId(nextId);
2572
- const modelName = activeProfile?.models?.find((m) => m.id === nextId)
2573
- ?.name || "";
2574
- // Update local agent state immediately for UX and to reflect in streaming stub
2575
- setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
2576
- // Persist only for existing agents; otherwise cache until first start
2577
- try {
2578
- if (agent?.id && agent.status !== "new") {
2579
- await updateAgentSettings(agent.id, {
2580
- model: modelName,
2581
- });
2639
+ await updateAgentSettings(agent.id, { mode: nextMode });
2640
+ setAgentMetadata(nextMeta);
2641
+ setAgent((prev) => prev
2642
+ ? { ...prev, metadata: JSON.stringify(nextMeta) }
2643
+ : prev);
2582
2644
  }
2583
- else {
2584
- pendingSettingsRef.current = {
2585
- ...(pendingSettingsRef.current || {}),
2586
- modelName,
2587
- };
2645
+ catch (e2) {
2646
+ console.error("Failed to persist mode change", e2);
2588
2647
  }
2648
+ }, title: "Mode", "aria-label": "Mode", "data-testid": "agent-mode-select", children: [_jsx("option", { value: "supervised", children: "Supervised" }), _jsx("option", { value: "autonomous", children: "Autonomous" }), _jsx("option", { value: "read-only", children: "Read-Only" })] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold text-green-500", children: "Read-Only" }), ": Limited tool access as configured by the profile (Ask Mode Tools)."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-amber-500", children: "Supervised" }), ": Full tool access, but writes are limited to pages/items in the current context. Creating new items or updating existing items outside the current context requires explicit approval."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-red-500", children: "Autonomous" }), ": Full tool access; can write across the site/project only limited by user permissions."] })] }) })] }), profiles?.length > 0 && (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: activeProfile?.id || "", onChange: async (e) => {
2649
+ const nextProfile = profiles.find((x) => x.id === e.target.value);
2650
+ if (!nextProfile)
2651
+ return;
2652
+ setActiveProfile(nextProfile);
2653
+ try {
2654
+ if (agent?.id && agent.status !== "new") {
2655
+ await updateAgentSettings(agent.id, {
2656
+ profileId: nextProfile.id,
2657
+ profileName: nextProfile.name,
2658
+ });
2659
+ }
2660
+ else {
2661
+ // cache until first start
2662
+ pendingSettingsRef.current = {
2663
+ ...(pendingSettingsRef.current || {}),
2664
+ // we cache profile by updating local metadata
2665
+ };
2666
+ setAgentMetadata((current) => {
2667
+ const next = { ...(current || {}) };
2668
+ next.profile = nextProfile.name;
2669
+ next.additionalData = {
2670
+ ...(next.additionalData || {}),
2671
+ profileId: nextProfile.id,
2672
+ profileName: nextProfile.name,
2673
+ };
2674
+ return next;
2675
+ });
2676
+ }
2677
+ // reflect in local agent stub so tabs and titles can use it if needed
2678
+ setAgent((prev) => prev
2679
+ ? {
2680
+ ...prev,
2681
+ metadata: JSON.stringify({
2682
+ ...(agentMetadata || {}),
2683
+ profile: nextProfile.name,
2684
+ additionalData: {
2685
+ ...(agentMetadata?.additionalData ||
2686
+ {}),
2687
+ profileId: nextProfile.id,
2688
+ profileName: nextProfile.name,
2689
+ },
2690
+ }),
2589
2691
  }
2590
- catch (err) {
2591
- console.error("Failed to persist agent model", err);
2592
- }
2593
- }, title: "Model", "aria-label": "Model", "data-testid": "agent-model-select", children: activeProfile.models.map((m) => (_jsx("option", { value: m.id, children: m.name }, m.id))) })) : null, activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-xs text-gray-700 hover:bg-gray-100", onClick: () => {
2594
- setPrompt(p.prompt);
2595
- setShowPredefined(false);
2596
- if (textareaRef.current)
2597
- textareaRef.current.focus();
2598
- }, 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
2599
- ? {
2600
- input: liveTotals.input,
2601
- output: liveTotals.output,
2602
- cached: liveTotals.cached,
2603
- cacheWrite: liveTotals.cacheWrite ?? 0,
2604
- inputCost: liveTotals.inputCost,
2605
- outputCost: liveTotals.outputCost,
2606
- cachedCost: liveTotals.cachedCost,
2607
- cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
2608
- totalCost: liveTotals.totalCost,
2692
+ : prev);
2609
2693
  }
2610
- : totalTokens, costLimit: effectiveCostLimit }), (() => {
2611
- try {
2612
- const s = window.__agentContextWindowStatus;
2613
- if (!s || !s.contextWindowTokens)
2614
- return null;
2615
- const pct = typeof s.contextUsedPercent === "number"
2616
- ? `${s.contextUsedPercent.toFixed(1)}%`
2617
- : undefined;
2618
- // Helper function to format tokens as "k"
2619
- const formatTokens = (tokens) => {
2620
- if (tokens >= 1000) {
2621
- return `${(tokens / 1000).toFixed(1)}k`;
2694
+ catch (err) {
2695
+ console.error("Failed to persist agent profile", err);
2696
+ }
2697
+ }, title: "Profile", "aria-label": "Profile", "data-testid": "agent-profile-select", children: profiles.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id))) })), activeProfile?.models?.length ? (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: selectedModelId || "", onChange: async (e) => {
2698
+ const nextId = e.target.value;
2699
+ setSelectedModelId(nextId);
2700
+ const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name ||
2701
+ "";
2702
+ // Update local agent state immediately for UX and to reflect in streaming stub
2703
+ setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
2704
+ // Persist only for existing agents; otherwise cache until first start
2705
+ try {
2706
+ if (agent?.id && agent.status !== "new") {
2707
+ await updateAgentSettings(agent.id, { model: modelName });
2622
2708
  }
2623
- return tokens.toString();
2624
- };
2625
- if (!pct)
2626
- return null;
2627
- 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] })] }) })] }));
2628
- }
2629
- catch {
2630
- return null;
2709
+ else {
2710
+ pendingSettingsRef.current = {
2711
+ ...(pendingSettingsRef.current || {}),
2712
+ modelName,
2713
+ };
2714
+ }
2715
+ }
2716
+ catch (err) {
2717
+ console.error("Failed to persist agent model", err);
2718
+ }
2719
+ }, title: "Model", "aria-label": "Model", "data-testid": "agent-model-select", children: activeProfile.models.map((m) => (_jsx("option", { value: m.id, children: m.name }, m.id))) })) : null, activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-xs text-gray-700 hover:bg-gray-100", onClick: () => {
2720
+ setPrompt(p.prompt);
2721
+ setShowPredefined(false);
2722
+ if (textareaRef.current)
2723
+ textareaRef.current.focus();
2724
+ }, 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
2725
+ ? {
2726
+ input: liveTotals.input,
2727
+ output: liveTotals.output,
2728
+ cached: liveTotals.cached,
2729
+ cacheWrite: liveTotals.cacheWrite ?? 0,
2730
+ inputCost: liveTotals.inputCost,
2731
+ outputCost: liveTotals.outputCost,
2732
+ cachedCost: liveTotals.cachedCost,
2733
+ cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
2734
+ totalCost: liveTotals.totalCost,
2735
+ }
2736
+ : totalTokens, costLimit: effectiveCostLimit }), (() => {
2737
+ try {
2738
+ const s = window.__agentContextWindowStatus;
2739
+ if (!s || !s.contextWindowTokens)
2740
+ return null;
2741
+ const pct = typeof s.contextUsedPercent === "number"
2742
+ ? `${s.contextUsedPercent.toFixed(1)}%`
2743
+ : undefined;
2744
+ // Helper function to format tokens as "k"
2745
+ const formatTokens = (tokens) => {
2746
+ if (tokens >= 1000) {
2747
+ return `${(tokens / 1000).toFixed(1)}k`;
2631
2748
  }
2632
- })()] })] }))] })] }));
2749
+ return tokens.toString();
2750
+ };
2751
+ if (!pct)
2752
+ return null;
2753
+ 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] })] }) })] }));
2754
+ }
2755
+ catch {
2756
+ return null;
2757
+ }
2758
+ })()] })] })] }));
2633
2759
  }
2634
2760
  //# sourceMappingURL=AgentTerminal.js.map