@parhelia/core 0.1.12517 → 0.1.12534

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 (75) hide show
  1. package/dist/config/config.js +13 -0
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/config/types.d.ts +9 -0
  4. package/dist/config/types.js.map +1 -1
  5. package/dist/editor/MainLayout.js +2 -1
  6. package/dist/editor/MainLayout.js.map +1 -1
  7. package/dist/editor/ai/AgentTerminal.js +345 -87
  8. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  9. package/dist/editor/ai/Agents.js +4 -1
  10. package/dist/editor/ai/Agents.js.map +1 -1
  11. package/dist/editor/ai/AiResponseMessage.d.ts +9 -1
  12. package/dist/editor/ai/AiResponseMessage.js +2 -2
  13. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  14. package/dist/editor/ai/ToolCallDisplay.d.ts +11 -2
  15. package/dist/editor/ai/ToolCallDisplay.js +161 -7
  16. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  17. package/dist/editor/ai/dialogs/browserBoundCapture.js +18 -3
  18. package/dist/editor/ai/dialogs/browserBoundCapture.js.map +1 -1
  19. package/dist/editor/ai/types.d.ts +1 -1
  20. package/dist/editor/client/editContext.d.ts +1 -0
  21. package/dist/editor/client/editContext.js.map +1 -1
  22. package/dist/editor/client/operations.d.ts +1 -0
  23. package/dist/editor/client/operations.js +18 -0
  24. package/dist/editor/client/operations.js.map +1 -1
  25. package/dist/editor/context-menu/InsertMenu.js +46 -23
  26. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  27. package/dist/editor/hooks/useNavigationPanelLogic.js +6 -1
  28. package/dist/editor/hooks/useNavigationPanelLogic.js.map +1 -1
  29. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +102 -15
  30. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  31. package/dist/editor/page-viewer/EditorForm.js +9 -2
  32. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  33. package/dist/editor/page-viewer/MiniMap.d.ts +1 -1
  34. package/dist/editor/page-viewer/MiniMap.js +18 -6
  35. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  36. package/dist/editor/page-viewer/RenderingParametersSection.d.ts +6 -0
  37. package/dist/editor/page-viewer/RenderingParametersSection.js +147 -0
  38. package/dist/editor/page-viewer/RenderingParametersSection.js.map +1 -0
  39. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +7 -0
  40. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  41. package/dist/editor/pageModel.d.ts +4 -0
  42. package/dist/editor/reviews/SuggestedEdit.js +4 -1
  43. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  44. package/dist/editor/services/agentService.d.ts +24 -0
  45. package/dist/editor/services/agentService.js +34 -0
  46. package/dist/editor/services/agentService.js.map +1 -1
  47. package/dist/editor/services/aiService.d.ts +1 -0
  48. package/dist/editor/services/aiService.js.map +1 -1
  49. package/dist/editor/services/contentService.d.ts +1 -0
  50. package/dist/editor/services/contentService.js.map +1 -1
  51. package/dist/editor/services/editService.d.ts +17 -0
  52. package/dist/editor/services/editService.js +12 -0
  53. package/dist/editor/services/editService.js.map +1 -1
  54. package/dist/editor/sidebar/ComponentTree.js +1 -36
  55. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  56. package/dist/editor/sidebar/NavigationPanelItem.js +5 -4
  57. package/dist/editor/sidebar/NavigationPanelItem.js.map +1 -1
  58. package/dist/editor/sidebar/OperationItem.js +1 -0
  59. package/dist/editor/sidebar/OperationItem.js.map +1 -1
  60. package/dist/editor/sidebar/SidebarPanel.js +10 -3
  61. package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
  62. package/dist/editor/sidebar/Validation.js +22 -12
  63. package/dist/editor/sidebar/Validation.js.map +1 -1
  64. package/dist/editor/ui/Splitter.js +2 -2
  65. package/dist/editor/ui/Splitter.js.map +1 -1
  66. package/dist/revision.d.ts +2 -2
  67. package/dist/revision.js +2 -2
  68. package/dist/task-board/components/TaskAttachmentsSection.js +2 -1
  69. package/dist/task-board/components/TaskAttachmentsSection.js.map +1 -1
  70. package/dist/task-board/components/TaskDetailPanel.js +10 -7
  71. package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
  72. package/dist/task-board/components/WizardTaskDetailsPanel.js +3 -2
  73. package/dist/task-board/components/WizardTaskDetailsPanel.js.map +1 -1
  74. package/dist/types.d.ts +8 -1
  75. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, } from "react";
3
3
  import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ArrowLeft, DollarSign, ExternalLink, Settings2, Target, X, Plus, } from "lucide-react";
4
- import { getAgent, startAgent, claimAgentBrowser, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, releaseAgentBrowser, } from "../services/agentService";
4
+ import { getAgent, startAgent, claimAgentBrowser, assignAgentSkill, persistDraftAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, releaseAgentBrowser, revokeAgentSkill, } from "../services/agentService";
5
5
  import { parseAgentStatus } from "../services/agentStatus";
6
6
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
7
7
  import { localStorageService } from "../services/localStorageService";
@@ -31,14 +31,14 @@ import { Splitter } from "../ui/Splitter";
31
31
  import { ScrollingContentTree } from "../ScrollingContentTree";
32
32
  import { MarkdownDisplay, } from "../../components/MarkdownDisplay";
33
33
  const userMessageMarkdownComponents = {
34
- h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm font-semibold leading-5 text-gray-900" })),
35
- h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] font-semibold leading-5 text-gray-900" })),
36
- h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] font-semibold leading-5 text-gray-900" })),
37
- h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] font-medium leading-5 text-gray-800" })),
38
- p: (props) => _jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" }),
34
+ h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm leading-5 font-semibold text-gray-900" })),
35
+ h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] leading-5 font-semibold text-gray-900" })),
36
+ h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] leading-5 font-semibold text-gray-900" })),
37
+ h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] leading-5 font-medium text-gray-800" })),
38
+ p: (props) => (_jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" })),
39
39
  ul: (props) => (_jsx("ul", { ...props, className: "my-2 ml-5 list-disc space-y-1 text-[12px] leading-5 text-gray-700" })),
40
40
  ol: (props) => (_jsx("ol", { ...props, className: "my-2 ml-5 list-decimal space-y-1 text-[12px] leading-5 text-gray-700" })),
41
- li: (props) => _jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" }),
41
+ li: (props) => (_jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" })),
42
42
  pre: (props) => (_jsx("pre", { ...props, className: "my-2 overflow-auto rounded-md bg-slate-100 px-3 py-2 text-[11px] leading-4 text-slate-700" })),
43
43
  code: ({ inline, className, ...props }) => inline ? (_jsx("code", { ...props, className: "rounded bg-slate-100 px-1 py-0.5 text-[11px] text-slate-700" })) : (_jsx("code", { ...props, className: className })),
44
44
  };
@@ -149,25 +149,36 @@ function toUserFacingAgentErrorMessage(value) {
149
149
  if (!normalizedValue)
150
150
  return "";
151
151
  const trimmed = normalizedValue.trim();
152
- const maybeJson = trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith('"');
152
+ const maybeJson = trimmed.startsWith("{") ||
153
+ trimmed.startsWith("[") ||
154
+ trimmed.startsWith('"');
153
155
  if (maybeJson) {
154
156
  try {
155
157
  const parsed = JSON.parse(trimmed);
156
158
  const structuredMessage = typeof parsed === "string"
157
159
  ? parsed
158
- : typeof parsed?.error === "string"
159
- ? parsed.error
160
- : typeof parsed?.message === "string"
161
- ? parsed.message
162
- : typeof parsed?.detail === "string"
163
- ? parsed.detail
164
- : typeof parsed?.error_description === "string"
165
- ? parsed.error_description
166
- : typeof parsed?.error === "object" &&
167
- parsed?.error &&
168
- typeof parsed.error.message ===
169
- "string"
170
- ? String(parsed.error.message)
160
+ : typeof parsed?.error === "object" && parsed?.error
161
+ ? (() => {
162
+ const errObj = parsed.error;
163
+ // Extract detailed message from metadata.raw (OpenRouter-style nested errors)
164
+ if (typeof errObj.metadata?.raw ===
165
+ "string") {
166
+ return String(errObj.metadata.raw);
167
+ }
168
+ // Fall back to error.message
169
+ if (typeof errObj.message === "string") {
170
+ return errObj.message;
171
+ }
172
+ return "";
173
+ })()
174
+ : typeof parsed?.error === "string"
175
+ ? parsed.error
176
+ : typeof parsed?.message === "string"
177
+ ? parsed.message
178
+ : typeof parsed?.detail === "string"
179
+ ? parsed.detail
180
+ : typeof parsed?.error_description === "string"
181
+ ? parsed.error_description
171
182
  : "";
172
183
  if (structuredMessage.trim()) {
173
184
  value = structuredMessage;
@@ -607,7 +618,8 @@ const groupConsecutiveMessages = (agentMessages) => {
607
618
  // Add user message
608
619
  groups.push({ type: "user", messages: [message] });
609
620
  }
610
- else if (message.messageType === "heartbeat" || message.role === "system") {
621
+ else if (message.messageType === "heartbeat" ||
622
+ message.role === "system") {
611
623
  if (currentAssistantGroup.length > 0) {
612
624
  groups.push({
613
625
  type: "assistant-group",
@@ -720,6 +732,35 @@ const stringifyToolField = (value) => {
720
732
  return String(value);
721
733
  }
722
734
  };
735
+ const parseToolResultValue = (value) => {
736
+ if (value === undefined || value === null) {
737
+ return undefined;
738
+ }
739
+ if (typeof value === "object") {
740
+ return value;
741
+ }
742
+ if (typeof value !== "string") {
743
+ return String(value);
744
+ }
745
+ const trimmed = value.trim();
746
+ if (!trimmed) {
747
+ return undefined;
748
+ }
749
+ try {
750
+ let parsed = JSON.parse(trimmed);
751
+ if (typeof parsed === "string" &&
752
+ (parsed.startsWith("{") || parsed.startsWith("["))) {
753
+ parsed = JSON.parse(parsed);
754
+ }
755
+ if (parsed && typeof parsed === "object") {
756
+ return parsed;
757
+ }
758
+ }
759
+ catch {
760
+ // Fall back to the original string when the payload is plain text.
761
+ }
762
+ return value;
763
+ };
723
764
  const getFirstToolCallEnvelope = (data) => {
724
765
  if (!data || typeof data !== "object")
725
766
  return undefined;
@@ -779,13 +820,14 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
779
820
  ? agentMessage.toolCalls.map((toolCall) => {
780
821
  const isPruned = !!toolCall.isPruned ||
781
822
  /^PRUNED$/i.test(toolCall.functionError || "");
823
+ const displayResult = parseToolResultValue(toolCall.functionResultRichContent) ?? toolCall.functionResult;
782
824
  return {
783
825
  id: toolCall.toolCallId,
784
826
  displayName: toolCall.functionName,
785
827
  function: {
786
828
  name: toolCall.functionName,
787
829
  arguments: toolCall.functionArguments,
788
- result: toolCall.functionResult,
830
+ result: displayResult,
789
831
  error: toolCall.functionError,
790
832
  },
791
833
  // Pass through approval info if present on the tool call
@@ -798,7 +840,7 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
798
840
  // Tool call is streaming if message is not completed and tool call has no result yet
799
841
  isStreaming: !agentMessage.isCompleted &&
800
842
  !toolCall.isCompleted &&
801
- !toolCall.functionResult &&
843
+ !displayResult &&
802
844
  !toolCall.functionError &&
803
845
  !isPruned,
804
846
  // Pass through message IDs for approval/rejection events
@@ -1087,6 +1129,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1087
1129
  const [selectableTemplateIds, setSelectableTemplateIds] = useState([]);
1088
1130
  const [skillsLoading, setSkillsLoading] = useState(false);
1089
1131
  const [skillsError, setSkillsError] = useState(null);
1132
+ const [skillActionError, setSkillActionError] = useState(null);
1090
1133
  const [triggerSubscriptions, setTriggerSubscriptions] = useState([]);
1091
1134
  const [availableTools, setAvailableTools] = useState([]);
1092
1135
  const [operationAllowances, setOperationAllowances] = useState({
@@ -1102,7 +1145,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1102
1145
  const [toolsSectionExpanded, setToolsSectionExpanded] = useState(false);
1103
1146
  const [allowancesSectionExpanded, setAllowancesSectionExpanded] = useState(false);
1104
1147
  const [subscribedTriggersSectionExpanded, setSubscribedTriggersSectionExpanded,] = useState(false);
1105
- const isNewAgent = agent?.status === "new";
1148
+ const isPersistedAgent = !!agent?.userId;
1149
+ const isLocalOnlyDraftAgent = agent?.status === "new" && !isPersistedAgent;
1106
1150
  const hasSpawnedAgents = useMemo(() => {
1107
1151
  if (!agentMetadata)
1108
1152
  return false;
@@ -1289,9 +1333,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1289
1333
  .filter((id) => id.length > 0);
1290
1334
  }, [activeProfile?.preloadSkills]);
1291
1335
  const autoAssignedSkillIds = useMemo(() => {
1292
- const all = isNewAgent
1293
- ? [...preloadedSkillIds, ...backendAssignedSkillIds]
1294
- : backendAssignedSkillIds;
1336
+ const preloadedIdSet = new Set(preloadedSkillIds.map((id) => id.toLowerCase()));
1337
+ const all = isLocalOnlyDraftAgent
1338
+ ? preloadedSkillIds
1339
+ : backendAssignedSkillIds.filter((id) => preloadedIdSet.has(id.toLowerCase()));
1295
1340
  const seen = new Set();
1296
1341
  const unique = [];
1297
1342
  for (const id of all) {
@@ -1302,9 +1347,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1302
1347
  unique.push(id);
1303
1348
  }
1304
1349
  return unique;
1305
- }, [backendAssignedSkillIds, isNewAgent, preloadedSkillIds]);
1350
+ }, [backendAssignedSkillIds, isLocalOnlyDraftAgent, preloadedSkillIds]);
1306
1351
  const selectedSkillIds = useMemo(() => {
1307
- const all = [...autoAssignedSkillIds, ...metadataSelectedSkillIds];
1352
+ const all = [
1353
+ ...autoAssignedSkillIds,
1354
+ ...backendAssignedSkillIds,
1355
+ ...metadataSelectedSkillIds,
1356
+ ];
1308
1357
  const seen = new Set();
1309
1358
  const unique = [];
1310
1359
  for (const id of all) {
@@ -1315,7 +1364,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1315
1364
  unique.push(id);
1316
1365
  }
1317
1366
  return unique;
1318
- }, [autoAssignedSkillIds, metadataSelectedSkillIds]);
1367
+ }, [autoAssignedSkillIds, backendAssignedSkillIds, metadataSelectedSkillIds]);
1319
1368
  useEffect(() => {
1320
1369
  let active = true;
1321
1370
  if (!showAgentSettings) {
@@ -1323,7 +1372,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1323
1372
  active = false;
1324
1373
  };
1325
1374
  }
1326
- if (!agent?.id || agent.status === "new") {
1375
+ if (!agent?.id || isLocalOnlyDraftAgent) {
1327
1376
  setTriggerSubscriptions([]);
1328
1377
  setTriggerSubscriptionsLoading(false);
1329
1378
  setTriggerSubscriptionsError(null);
@@ -1407,6 +1456,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1407
1456
  showAgentSettings,
1408
1457
  agent?.id,
1409
1458
  agent?.status,
1459
+ agent?.userId,
1410
1460
  agent?.profileId,
1411
1461
  mode,
1412
1462
  selectedSkillIds,
@@ -1444,7 +1494,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1444
1494
  return availableSkills.filter((skill) => listedProfileSkillIdSet.has(skill.id.toLowerCase()));
1445
1495
  }, [availableSkills, listedProfileSkillIdSet]);
1446
1496
  const profileFilteredSkillIdSet = useMemo(() => new Set(profileFilteredSkills.map((s) => s.id.toLowerCase())), [profileFilteredSkills]);
1447
- const availableSkillIdSet = useMemo(() => new Set(availableSkills.map((skill) => skill.id.toLowerCase())), [availableSkills]);
1497
+ const manuallyAssignableSkillIdSet = useMemo(() => new Set((activeProfile?.allowedSkills ?? [])
1498
+ .map((skill) => String(skill?.id || "").toLowerCase())
1499
+ .filter((id) => id.length > 0)), [activeProfile?.allowedSkills]);
1448
1500
  const selectableTemplateIdSet = useMemo(() => new Set(selectableTemplateIds.map((id) => id.toLowerCase())), [selectableTemplateIds]);
1449
1501
  const selectedSkills = useMemo(() => selectedSkillIds
1450
1502
  .map((id) => availableSkills.find((s) => s.id.toLowerCase() === id.toLowerCase()))
@@ -1453,8 +1505,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1453
1505
  const autoAssignedSkillSet = useMemo(() => new Set(autoAssignedSkillIds.map((id) => id.toLowerCase())), [autoAssignedSkillIds]);
1454
1506
  const previewAvailableTools = useMemo(() => {
1455
1507
  const baseToolNames = mode === "read-only"
1456
- ? activeProfile?.readOnlyToolNames ?? []
1457
- : activeProfile?.allowedToolNames ?? [];
1508
+ ? (activeProfile?.readOnlyToolNames ?? [])
1509
+ : (activeProfile?.allowedToolNames ?? []);
1458
1510
  const toolNames = new Set();
1459
1511
  for (const toolName of baseToolNames) {
1460
1512
  const normalized = String(toolName || "").trim();
@@ -1477,13 +1529,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1477
1529
  mode,
1478
1530
  selectedSkills,
1479
1531
  ]);
1480
- const displayedAvailableTools = isNewAgent
1532
+ const displayedAvailableTools = isLocalOnlyDraftAgent
1481
1533
  ? previewAvailableTools
1482
1534
  : availableTools;
1483
- const displayedAvailableToolsLoading = isNewAgent
1535
+ const displayedAvailableToolsLoading = isLocalOnlyDraftAgent
1484
1536
  ? false
1485
1537
  : availableToolsLoading;
1486
- const displayedAvailableToolsError = isNewAgent ? null : availableToolsError;
1538
+ const displayedAvailableToolsError = isLocalOnlyDraftAgent
1539
+ ? null
1540
+ : availableToolsError;
1487
1541
  // Remove deprecated cost limit fields from metadata to avoid confusion with agent/profile settings
1488
1542
  const sanitizeAgentMetadata = useCallback((meta) => {
1489
1543
  try {
@@ -1508,20 +1562,24 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1508
1562
  return meta;
1509
1563
  }
1510
1564
  }, []);
1511
- const updateSelectedSkillIds = useCallback(async (skillIds) => {
1512
- if (!agent?.id)
1513
- return;
1514
- const current = agentMetadata || {};
1515
- const currentAdditionalData = current.additionalData || {};
1516
- const nextAdditionalData = {
1517
- ...currentAdditionalData,
1518
- };
1519
- if (skillIds.length > 0) {
1520
- nextAdditionalData.skillIds = skillIds;
1565
+ const getSkillActionErrorMessage = useCallback((error) => {
1566
+ const message = error instanceof Error && error.message.trim()
1567
+ ? error.message.trim()
1568
+ : "Failed to update skill";
1569
+ if (message.includes("Skill is not available for this agent profile")) {
1570
+ return "This skill cannot be added for the current agent profile.";
1521
1571
  }
1522
- else {
1523
- delete nextAdditionalData.skillIds;
1572
+ return message;
1573
+ }, []);
1574
+ const clearLegacySelectedSkillsFromMetadata = useCallback(async () => {
1575
+ const legacySkillIds = metadataSelectedSkillIds;
1576
+ if (!agent?.id || legacySkillIds.length === 0) {
1577
+ return;
1524
1578
  }
1579
+ const current = agentMetadata || {};
1580
+ const currentAdditionalData = current.additionalData ||
1581
+ {};
1582
+ const nextAdditionalData = Object.fromEntries(Object.entries(currentAdditionalData).filter(([key]) => key.toLowerCase() !== "skillids"));
1525
1583
  const next = {
1526
1584
  ...current,
1527
1585
  };
@@ -1532,26 +1590,168 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1532
1590
  delete next.additionalData;
1533
1591
  }
1534
1592
  try {
1535
- if (agent.status === "new") {
1536
- setAgentMetadata(next);
1537
- return;
1538
- }
1539
1593
  await updateAgentContext(agent.id, next);
1540
1594
  setAgentMetadata(next);
1541
1595
  setAgent((prev) => prev ? { ...prev, agentContext: JSON.stringify(next) } : prev);
1542
1596
  }
1543
1597
  catch (e) {
1544
- console.error("Failed to update selected skills", e);
1598
+ console.error("Failed to clear legacy selected skills", e);
1599
+ }
1600
+ }, [
1601
+ agent?.id,
1602
+ agentMetadata,
1603
+ metadataSelectedSkillIds,
1604
+ setAgent,
1605
+ setAgentMetadata,
1606
+ ]);
1607
+ const parsePersistedAgentContext = (contextJson) => {
1608
+ try {
1609
+ if (!contextJson)
1610
+ return null;
1611
+ const parsedContext = JSON.parse(contextJson);
1612
+ if (!parsedContext || typeof parsedContext !== "object") {
1613
+ return null;
1614
+ }
1615
+ return sanitizeAgentMetadata(parsedContext);
1616
+ }
1617
+ catch {
1618
+ return null;
1619
+ }
1620
+ };
1621
+ const buildDraftPersistenceContext = () => {
1622
+ let effectiveContext = agentMetadata;
1623
+ try {
1624
+ const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
1625
+ const profile = activeProfile ||
1626
+ profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
1627
+ const isLiveMode = profile?.editorContextMode === "live";
1628
+ if (isLiveMode && typeof buildCurrentContext === "function") {
1629
+ const freshContext = buildCurrentContext();
1630
+ if (freshContext) {
1631
+ effectiveContext = {
1632
+ ...(agentMetadata || {}),
1633
+ items: freshContext.items,
1634
+ currentItemId: freshContext.currentItemId,
1635
+ components: freshContext.components,
1636
+ field: freshContext.field,
1637
+ activeWorkspace: freshContext.activeWorkspace,
1638
+ hasPageLoaded: freshContext.hasPageLoaded,
1639
+ openSidebars: freshContext.openSidebars,
1640
+ };
1641
+ }
1642
+ }
1643
+ }
1644
+ catch (e) {
1645
+ console.warn("[AgentTerminal] Failed to compute draft context:", e);
1545
1646
  }
1546
- }, [agent?.id, agent?.status, agentMetadata, setAgent, setAgentMetadata]);
1647
+ return sanitizeAgentMetadata(effectiveContext || null);
1648
+ };
1649
+ const ensureDraftAgentPersisted = async () => {
1650
+ if (!agent?.id) {
1651
+ throw new Error("Agent not ready. Please try again.");
1652
+ }
1653
+ if (!isLocalOnlyDraftAgent) {
1654
+ return agent;
1655
+ }
1656
+ const requestSettings = getPendingRequestSettings();
1657
+ if (!requestSettings.profileId) {
1658
+ throw new Error("Select an agent profile before adding a skill.");
1659
+ }
1660
+ const effectiveContext = buildDraftPersistenceContext();
1661
+ const persistedAgent = await persistDraftAgent({
1662
+ agentId: agent.id,
1663
+ sessionId: editContext?.sessionId || undefined,
1664
+ profileId: requestSettings.profileId,
1665
+ mode: requestSettings.mode,
1666
+ model: requestSettings.modelId || undefined,
1667
+ name: agent.name,
1668
+ context: effectiveContext,
1669
+ });
1670
+ pendingSettingsRef.current = null;
1671
+ const persistedMetadata = parsePersistedAgentContext(persistedAgent.agentContext) ??
1672
+ effectiveContext;
1673
+ setAgentMetadata(persistedMetadata ? { ...persistedMetadata } : null);
1674
+ setAgent((prev) => {
1675
+ const next = {
1676
+ ...(prev || {}),
1677
+ ...persistedAgent,
1678
+ };
1679
+ if (prev?.messages?.length && !persistedAgent.messages?.length) {
1680
+ next.messages = prev.messages;
1681
+ }
1682
+ return next;
1683
+ });
1684
+ onAgentUpdate?.(persistedAgent);
1685
+ return persistedAgent;
1686
+ };
1547
1687
  const handleAddSkill = useCallback(async (skillId) => {
1548
1688
  if (selectedSkillSet.has(skillId.toLowerCase()))
1549
1689
  return;
1550
- await updateSelectedSkillIds([...metadataSelectedSkillIds, skillId]);
1551
- }, [metadataSelectedSkillIds, selectedSkillSet, updateSelectedSkillIds]);
1690
+ if (!agent?.id)
1691
+ return;
1692
+ try {
1693
+ setSkillActionError(null);
1694
+ const persistedAgent = await ensureDraftAgentPersisted();
1695
+ await assignAgentSkill({ agentId: persistedAgent.id, skillId });
1696
+ setAgent((prev) => {
1697
+ if (!prev)
1698
+ return prev;
1699
+ const currentAssigned = Array.isArray(prev.assignedSkillIds)
1700
+ ? prev.assignedSkillIds
1701
+ : [];
1702
+ if (currentAssigned.some((existingId) => String(existingId).toLowerCase() === skillId.toLowerCase())) {
1703
+ return prev;
1704
+ }
1705
+ return {
1706
+ ...prev,
1707
+ assignedSkillIds: [...currentAssigned, skillId],
1708
+ };
1709
+ });
1710
+ await clearLegacySelectedSkillsFromMetadata();
1711
+ return true;
1712
+ }
1713
+ catch (e) {
1714
+ setSkillActionError(getSkillActionErrorMessage(e));
1715
+ return false;
1716
+ }
1717
+ }, [
1718
+ agent?.id,
1719
+ assignAgentSkill,
1720
+ clearLegacySelectedSkillsFromMetadata,
1721
+ ensureDraftAgentPersisted,
1722
+ getSkillActionErrorMessage,
1723
+ setAgent,
1724
+ selectedSkillSet,
1725
+ ]);
1552
1726
  const handleRemoveSkill = useCallback(async (skillId) => {
1553
- await updateSelectedSkillIds(metadataSelectedSkillIds.filter((id) => id.toLowerCase() !== skillId.toLowerCase()));
1554
- }, [metadataSelectedSkillIds, updateSelectedSkillIds]);
1727
+ if (!agent?.id)
1728
+ return;
1729
+ try {
1730
+ setSkillActionError(null);
1731
+ await revokeAgentSkill({ agentId: agent.id, skillId });
1732
+ setAgent((prev) => {
1733
+ if (!prev)
1734
+ return prev;
1735
+ const currentAssigned = Array.isArray(prev.assignedSkillIds)
1736
+ ? prev.assignedSkillIds
1737
+ : [];
1738
+ return {
1739
+ ...prev,
1740
+ assignedSkillIds: currentAssigned.filter((existingId) => String(existingId).toLowerCase() !== skillId.toLowerCase()),
1741
+ };
1742
+ });
1743
+ await clearLegacySelectedSkillsFromMetadata();
1744
+ }
1745
+ catch (e) {
1746
+ setSkillActionError(getSkillActionErrorMessage(e));
1747
+ }
1748
+ }, [
1749
+ agent?.id,
1750
+ clearLegacySelectedSkillsFromMetadata,
1751
+ getSkillActionErrorMessage,
1752
+ revokeAgentSkill,
1753
+ setAgent,
1754
+ ]);
1555
1755
  const handleOpenProfileSettings = useCallback(async () => {
1556
1756
  if (!editContext || !activeProfile?.id)
1557
1757
  return;
@@ -2232,7 +2432,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2232
2432
  if (toolCallMessageId && message.data && toolCallId) {
2233
2433
  const toolCallError = message.data.functionError || message.data.error || "";
2234
2434
  const isPruned = !!message.data?.isPruned || /^PRUNED$/i.test(String(toolCallError));
2235
- const toolCallCreatedDate = message.data.createdDate || message.timestamp || new Date().toISOString();
2435
+ const toolCallCreatedDate = message.data.createdDate ||
2436
+ message.timestamp ||
2437
+ new Date().toISOString();
2236
2438
  const toolCall = {
2237
2439
  id: toolCallId,
2238
2440
  messageId: toolCallMessageId,
@@ -2241,6 +2443,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2241
2443
  functionName: extractedToolCall.functionName,
2242
2444
  functionArguments: extractedToolCall.functionArguments,
2243
2445
  functionResult: message.data.functionResult || message.data.result || "",
2446
+ functionResultRichContent: message.data.richContent || undefined,
2244
2447
  functionError: toolCallError,
2245
2448
  isPruned,
2246
2449
  isCompleted: false,
@@ -2290,12 +2493,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2290
2493
  newArgsText !== existingArgsText) ||
2291
2494
  (existingArgsText === "{}" && newArgsText !== "{}");
2292
2495
  const hasNewResult = toolCall.functionResult && !existingToolCall.functionResult;
2496
+ const hasNewRichContent = toolCall.functionResultRichContent &&
2497
+ !existingToolCall.functionResultRichContent;
2293
2498
  const hasNewError = toolCall.functionError && !existingToolCall.functionError;
2294
2499
  const hasNewApprovalInfo = toolCall.requiresApproval && !existingToolCall.requiresApproval;
2295
2500
  const hasNewDbMessageId = toolCall.dbMessageId && !existingToolCall.dbMessageId;
2296
2501
  // Only update if there's meaningful new data
2297
2502
  if (hasMoreCompleteArgs ||
2298
2503
  hasNewResult ||
2504
+ hasNewRichContent ||
2299
2505
  hasNewError ||
2300
2506
  hasNewApprovalInfo ||
2301
2507
  hasNewDbMessageId) {
@@ -2315,6 +2521,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2315
2521
  ? newArgsText
2316
2522
  : existingArgsText || existing.functionArguments,
2317
2523
  functionResult: toolCall.functionResult || existing.functionResult,
2524
+ functionResultRichContent: toolCall.functionResultRichContent ||
2525
+ existing.functionResultRichContent,
2318
2526
  functionError: toolCall.functionError || existing.functionError,
2319
2527
  requiresApproval: toolCall.requiresApproval || existing.requiresApproval,
2320
2528
  };
@@ -2475,6 +2683,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2475
2683
  ? nextArgsText
2476
2684
  : existingToolCall.functionArguments,
2477
2685
  functionResult: message.data.functionResult || message.data.result || "",
2686
+ functionResultRichContent: message.data.richContent ||
2687
+ existingToolCall.functionResultRichContent,
2478
2688
  functionError: message.data.functionError || message.data.error || "",
2479
2689
  isCompleted: true,
2480
2690
  responseTimeMs: message.data.responseTimeMs,
@@ -2491,7 +2701,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2491
2701
  }
2492
2702
  else if (message.data && resultToolCallId && resultMessageId) {
2493
2703
  // Create new tool call if it doesn't exist
2494
- const toolCallCreatedDate = message.data.createdDate || message.timestamp || new Date().toISOString();
2704
+ const toolCallCreatedDate = message.data.createdDate ||
2705
+ message.timestamp ||
2706
+ new Date().toISOString();
2495
2707
  const toolCall = {
2496
2708
  id: resultToolCallId,
2497
2709
  messageId: resultMessageId,
@@ -2499,6 +2711,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2499
2711
  functionName: extractedToolCall.functionName,
2500
2712
  functionArguments: extractedToolCall.functionArguments,
2501
2713
  functionResult: message.data.functionResult || message.data.result || "",
2714
+ functionResultRichContent: message.data.richContent || undefined,
2502
2715
  functionError: message.data.functionError || message.data.error || "",
2503
2716
  isCompleted: true,
2504
2717
  responseTimeMs: message.data.responseTimeMs,
@@ -2593,7 +2806,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2593
2806
  // The agent might have been persisted after sending a prompt
2594
2807
  // Only treat as "new" if backend returns 404
2595
2808
  const hasExistingMessages = messagesRef.current.length > 0;
2596
- if (agentStub.status === "new" && !hasExistingMessages) {
2809
+ if (agentStub.status === "new" &&
2810
+ !agentStub.userId &&
2811
+ !hasExistingMessages) {
2597
2812
  // Only initialize as new if we have no messages yet (initial mount)
2598
2813
  // Derive initial profile from provided metadata if present
2599
2814
  const initialProfileIdFromMeta = (() => {
@@ -3939,14 +4154,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3939
4154
  return prev;
3940
4155
  return candidate;
3941
4156
  });
3942
- }, [profiles, agent?.id, agent?.profileId, agentStub.id, agentStub.profileId]);
4157
+ }, [
4158
+ profiles,
4159
+ agent?.id,
4160
+ agent?.profileId,
4161
+ agentStub.id,
4162
+ agentStub.profileId,
4163
+ ]);
3943
4164
  // Clear queued prompts when agent changes or is new;
3944
4165
  // initial fetch is handled by loadAgent() for better performance and reliability
3945
4166
  useEffect(() => {
3946
- if (!agent?.id || agent.status === "new") {
4167
+ if (!agent?.id || isLocalOnlyDraftAgent) {
3947
4168
  setQueuedPrompts([]);
3948
4169
  }
3949
- }, [agent?.id, agent?.status]);
4170
+ }, [agent?.id, isLocalOnlyDraftAgent]);
3950
4171
  // Update selected model when the active profile or agent model changes
3951
4172
  useEffect(() => {
3952
4173
  if (!activeProfile)
@@ -5224,17 +5445,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5224
5445
  const renderErrorBanner = () => {
5225
5446
  const currentAgent = agent || agentStub;
5226
5447
  const isErrorStatus = currentAgent?.status === "error";
5227
- const errorMessage = (isErrorStatus ? currentAgent?.statusMessage : null) || error;
5228
- if (!errorMessage)
5448
+ const rawErrorMessage = (isErrorStatus ? currentAgent?.statusMessage : null) || error;
5449
+ if (!rawErrorMessage)
5229
5450
  return null;
5451
+ // Clean the error message (statusMessage from DB may contain raw JSON)
5452
+ const errorMessage = toUserFacingAgentErrorMessage(rawErrorMessage) || rawErrorMessage;
5230
5453
  return (_jsx("div", { className: "m-3 rounded border border-red-300 bg-red-50 p-3 text-[11px] text-red-900", "data-testid": "agent-error-banner", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "mt-0.5 h-4 w-4 shrink-0 text-red-500", strokeWidth: 1 }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "mb-1 font-semibold", children: "Agent Error" }), _jsx("div", { className: "text-red-800", children: errorMessage })] })] }) }));
5231
5454
  };
5232
5455
  const renderBrowserClaimBanner = (variant = "inline") => {
5233
5456
  if (!agent?.id || !editContext?.sessionId)
5234
5457
  return null;
5235
- if (!isClaimedByCurrentSession &&
5236
- !isClaimedByAnotherBrowser &&
5237
- !isPendingBrowserCaptureWait) {
5458
+ if (!isClaimedByCurrentSession && !isClaimedByAnotherBrowser) {
5459
+ return null;
5460
+ }
5461
+ if (isPendingBrowserCaptureWait) {
5238
5462
  return null;
5239
5463
  }
5240
5464
  const label = isClaimedByCurrentSession
@@ -5258,6 +5482,28 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5258
5482
  };
5259
5483
  const fixedBrowserClaimBanner = renderBrowserClaimBanner("fixed");
5260
5484
  const inlineBrowserClaimBanner = null;
5485
+ const browserCaptureInlinePrompt = isPendingBrowserCaptureWait && !isClaimedByCurrentSession
5486
+ ? {
5487
+ toolNames: [
5488
+ "capture-page-screenshot",
5489
+ "capture-parhelia-ui-screenshot",
5490
+ "capture-page-dom",
5491
+ ],
5492
+ label: isClaimedByAnotherBrowser
5493
+ ? "Attached in another browser"
5494
+ : "No browser attached",
5495
+ description: isClaimedByAnotherBrowser
5496
+ ? "This capture request is waiting in another browser. Take over browser control here to continue."
5497
+ : "This capture request is waiting for a browser attachment. Attach this browser to continue.",
5498
+ actionLabel: isClaimedByAnotherBrowser
5499
+ ? "Take over browser control"
5500
+ : "Attach to this browser",
5501
+ isPending: isBrowserClaimMutationPending,
5502
+ onAction: () => {
5503
+ void handleClaimBrowser(isClaimedByAnotherBrowser);
5504
+ },
5505
+ }
5506
+ : null;
5261
5507
  useEffect(() => {
5262
5508
  if (agent?.status !== "waitingForInput") {
5263
5509
  setPendingBrowserCaptureDialogType(null);
@@ -5327,7 +5573,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5327
5573
  __html: sanitizeSvg(activeProfile.svgIcon),
5328
5574
  } })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), inlineBrowserClaimBanner, renderErrorBanner(), inlineDialog ? (inlineDialog) : latestSummaryAssistantGroup ? (_jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: _jsx(AiResponseMessage, { messages: summaryMessages, finished: !latestSummaryAssistantGroup.isLastGroup || !isExecuting, editOperations: summaryOperations, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
5329
5575
  activeProfile?.displayTitle ||
5330
- activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
5576
+ activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
5331
5577
  const text = (action.prompt ||
5332
5578
  action.value ||
5333
5579
  action.label ||
@@ -5480,7 +5726,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5480
5726
  const operationsForGroup = getOperationsForMessageGroup(convertedMessages, agentOperations);
5481
5727
  return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
5482
5728
  activeProfile?.displayTitle ||
5483
- activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
5729
+ activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
5484
5730
  const text = (action.prompt ||
5485
5731
  action.value ||
5486
5732
  action.label ||
@@ -5709,7 +5955,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5709
5955
  mode: nextMode,
5710
5956
  };
5711
5957
  try {
5712
- if (!agent?.id || agent.status === "new") {
5958
+ if (!agent?.id || isLocalOnlyDraftAgent) {
5713
5959
  setMode(nextMode);
5714
5960
  setAgentMetadata(nextMeta);
5715
5961
  pendingSettingsRef.current = {
@@ -5721,7 +5967,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5721
5967
  const result = await updateAgentSettings(agent.id, {
5722
5968
  mode: nextMode,
5723
5969
  });
5724
- if (result.success === false || result.updates?.mode === false) {
5970
+ if (result.success === false ||
5971
+ result.updates?.mode === false) {
5725
5972
  throw new Error("Mode change was not applied");
5726
5973
  }
5727
5974
  setMode(nextMode);
@@ -5748,7 +5995,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5748
5995
  return;
5749
5996
  setActiveProfile(nextProfile);
5750
5997
  try {
5751
- if (agent?.id && agent.status !== "new") {
5998
+ if (agent?.id && !isLocalOnlyDraftAgent) {
5752
5999
  await updateAgentSettings(agent.id, {
5753
6000
  profileId: nextProfile.id,
5754
6001
  profileName: nextProfile.name,
@@ -5806,7 +6053,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5806
6053
  const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name || "";
5807
6054
  setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
5808
6055
  try {
5809
- if (agent?.id && agent.status !== "new") {
6056
+ if (agent?.id && !isLocalOnlyDraftAgent) {
5810
6057
  await updateAgentSettings(agent.id, {
5811
6058
  model: modelName,
5812
6059
  });
@@ -5821,36 +6068,47 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5821
6068
  catch (err) {
5822
6069
  console.error("Failed to persist agent model", err);
5823
6070
  }
5824
- } })] })) : null, _jsxs("div", { children: [_jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-1 text-[11px] font-medium text-gray-700", children: [_jsx(Target, { className: "h-3 w-3", strokeWidth: 1 }), "Skills"] }), _jsxs(Popover, { open: showSkillPicker, onOpenChange: setShowSkillPicker, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { size: "xs", variant: "outline", className: "h-5 rounded border px-1.5 text-[10px] text-gray-600", "data-testid": "agent-skill-picker-trigger", children: [_jsx(Plus, { className: "mr-1 h-3 w-3", strokeWidth: 1.5 }), "Add"] }) }), _jsx(PopoverContent, { className: "w-88 p-2", align: "end", side: "bottom", children: _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-[11px] font-medium text-gray-700", children: "Select a skill" }), _jsxs("div", { className: "relative h-56 rounded border border-gray-200 bg-gray-50", children: [_jsx(ScrollingContentTree, { rootItemIds: skillRootIds, selectedItemId: selectedSkillIds[selectedSkillIds.length - 1] || undefined, expandedItemId: selectedSkillIds[selectedSkillIds.length - 1] || skillRootIds[0], scrollToSelected: true, hideRootNodes: false, onSelectionChange: (selection) => {
6071
+ } })] })) : null, _jsxs("div", { children: [_jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-1 text-[11px] font-medium text-gray-700", children: [_jsx(Target, { className: "h-3 w-3", strokeWidth: 1 }), "Skills"] }), _jsxs(Popover, { open: showSkillPicker, onOpenChange: (open) => {
6072
+ setShowSkillPicker(open);
6073
+ if (open) {
6074
+ setSkillActionError(null);
6075
+ }
6076
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { size: "xs", variant: "outline", className: "h-5 rounded border px-1.5 text-[10px] text-gray-600", "data-testid": "agent-skill-picker-trigger", children: [_jsx(Plus, { className: "mr-1 h-3 w-3", strokeWidth: 1.5 }), "Add"] }) }), _jsx(PopoverContent, { className: "w-88 p-2", align: "end", side: "bottom", children: _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-[11px] font-medium text-gray-700", children: "Select a skill" }), _jsxs("div", { className: "relative h-56 rounded border border-gray-200 bg-gray-50", children: [_jsx(ScrollingContentTree, { rootItemIds: skillRootIds, selectedItemId: selectedSkillIds[selectedSkillIds.length - 1] || undefined, expandedItemId: selectedSkillIds[selectedSkillIds.length - 1] || skillRootIds[0], scrollToSelected: true, hideRootNodes: false, onSelectionChange: (selection) => {
5825
6077
  const selected = selection[0];
5826
6078
  if (!selected?.id)
5827
6079
  return;
6080
+ setSkillActionError(null);
5828
6081
  if (selectableTemplateIdSet.size > 0 &&
5829
6082
  (!selected.templateId ||
5830
6083
  !selectableTemplateIdSet.has(selected.templateId.toLowerCase()))) {
5831
6084
  return;
5832
6085
  }
5833
- if (!availableSkillIdSet.has(selected.id.toLowerCase())) {
6086
+ if (!manuallyAssignableSkillIdSet.has(selected.id.toLowerCase())) {
6087
+ setSkillActionError("This skill cannot be added for the current agent profile.");
5834
6088
  return;
5835
6089
  }
5836
- void handleAddSkill(selected.id);
5837
- setShowSkillPicker(false);
6090
+ void (async () => {
6091
+ const added = await handleAddSkill(selected.id);
6092
+ if (added) {
6093
+ setShowSkillPicker(false);
6094
+ }
6095
+ })();
5838
6096
  } }), skillsLoading && (_jsx("div", { className: "bg-background/70 absolute inset-0 flex items-center justify-center text-[10px] text-gray-500", children: "Loading skills..." }))] }), !skillsLoading &&
5839
6097
  !skillsError &&
5840
- profileFilteredSkills.length > 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "Click a skill item in the tree to add it." })), skillsError && (_jsx("div", { className: "text-[10px] text-red-600", children: skillsError })), !skillsLoading &&
6098
+ profileFilteredSkills.length > 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "Click a skill item in the tree to add it." })), skillsError && (_jsx("div", { className: "text-[10px] text-red-600", children: skillsError })), !skillsError && skillActionError && (_jsx("div", { className: "text-[10px] text-red-600", children: skillActionError })), !skillsLoading &&
5841
6099
  !skillsError &&
5842
6100
  skillRootIds.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "No skill roots available." })), !skillsLoading &&
5843
6101
  !skillsError &&
5844
6102
  profileFilteredSkills.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: selectedSkillIds.length > 0
5845
- ? "All available/allowed skills are selected"
5846
- : "No available or allowed skills for this profile" }))] }) })] })] }), selectedSkillIds.length > 0 && (_jsx("div", { className: "mb-2 flex flex-wrap gap-1", children: selectedSkillIds.map((skillId) => {
6103
+ ? "All addable skills are selected"
6104
+ : "No skills can be added for this profile" }))] }) })] })] }), selectedSkillIds.length > 0 && (_jsx("div", { className: "mb-2 flex flex-wrap gap-1", children: selectedSkillIds.map((skillId) => {
5847
6105
  const skill = selectedSkills.find((s) => s.id === skillId);
5848
6106
  return (_jsxs("div", { className: "inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-100 px-1.5 py-0.5 text-[10px] text-gray-700", children: [_jsx("span", { children: skill?.name || skillId }), _jsx("button", { type: "button", className: "rounded p-0.5 text-gray-500 hover:bg-gray-200 hover:text-gray-700", title: "Open skill item", "aria-label": `Open ${skill?.name || skillId}`, onClick: () => {
5849
6107
  void handleOpenSkillItem(skillId);
5850
6108
  }, children: _jsx(ExternalLink, { className: "h-2.5 w-2.5", strokeWidth: 1.5 }) }), autoAssignedSkillSet.has(skillId.toLowerCase()) ? (_jsx("span", { className: "text-[9px] text-gray-500", children: "auto" })) : (_jsx("button", { type: "button", className: "rounded p-0.5 text-gray-500 hover:bg-gray-200 hover:text-gray-700", onClick: () => {
5851
6109
  void handleRemoveSkill(skillId);
5852
6110
  }, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
5853
- }) }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setToolsSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": toolsSectionExpanded, "data-testid": "agent-tools-section-toggle", children: [_jsxs("span", { children: ["Available tools (", displayedAvailableTools.length, ")"] }), toolsSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), toolsSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-tools-section", children: displayedAvailableToolsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading available tools..." })) : displayedAvailableTools.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [isNewAgent && (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Preview based on the current profile, mode, and selected skills." })), displayedAvailableTools.map((toolName) => (_jsx("div", { className: "rounded px-1 py-0.5 transition-colors hover:bg-white/60", "data-testid": "agent-tool-row", title: toolName, children: _jsx("div", { className: "truncate text-[10px] text-gray-700", children: toolName }) }, toolName)))] })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isNewAgent
6111
+ }) }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setToolsSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": toolsSectionExpanded, "data-testid": "agent-tools-section-toggle", children: [_jsxs("span", { children: ["Available tools (", displayedAvailableTools.length, ")"] }), toolsSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), toolsSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-tools-section", children: displayedAvailableToolsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading available tools..." })) : displayedAvailableTools.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [isLocalOnlyDraftAgent && (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Preview based on the current profile, mode, and selected skills." })), displayedAvailableTools.map((toolName) => (_jsx("div", { className: "rounded px-1 py-0.5 transition-colors hover:bg-white/60", "data-testid": "agent-tool-row", title: toolName, children: _jsx("div", { className: "truncate text-[10px] text-gray-700", children: toolName }) }, toolName)))] })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
5854
6112
  ? "No available tools for this profile and mode"
5855
6113
  : "No available tools" })) })), displayedAvailableToolsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: displayedAvailableToolsError }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setAllowancesSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": allowancesSectionExpanded, "data-testid": "agent-allowances-section-toggle", children: [_jsxs("span", { children: ["Allowances (", allowancesTotalCount, ")"] }), allowancesSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), allowancesSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-allowances-section", children: operationAllowancesLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading allowances..." })) : hasAnyAllowances ? (_jsx("div", { className: "space-y-1", children: allowanceGroups.map((group) => group.rows.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [_jsx("div", { className: "px-1 text-[9px] font-medium tracking-wide text-gray-400 uppercase", children: group.label }), group.rows.map((allowance, index) => {
5856
6114
  const sourceLabel = formatAllowanceSource(allowance.source);
@@ -5865,12 +6123,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5865
6123
  ]
5866
6124
  .filter(Boolean)
5867
6125
  .join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
5868
- })] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isNewAgent
6126
+ })] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
5869
6127
  ? "Allowances are shown after the agent is created"
5870
6128
  : "No active allowances" })) })), operationAllowancesError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: operationAllowancesError }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setSubscribedTriggersSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": subscribedTriggersSectionExpanded, "data-testid": "agent-subscribed-triggers-section-toggle", children: [_jsx("span", { children: `Subscribed triggers (${activeTriggerSubscriptions.length})` }), subscribedTriggersSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), subscribedTriggersSectionExpanded && (_jsx("div", { className: "max-h-28 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-subscribed-triggers-section", children: triggerSubscriptionsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading subscribed triggers..." })) : activeTriggerSubscriptions.length > 0 ? (_jsx("div", { className: "space-y-0.5", children: activeTriggerSubscriptions.map((sub) => {
5871
6129
  const filterText = (sub.filter || "").trim();
5872
6130
  return (_jsxs("div", { className: "flex items-baseline gap-1.5 rounded px-1 py-0.5 transition-colors hover:bg-white/60", children: [_jsx("div", { className: "shrink-0 text-[10px] font-medium text-gray-700", children: sub.triggerName }), filterText.length > 0 && (_jsx("div", { className: "truncate text-[9px] text-gray-400", title: filterText, children: filterText }))] }, sub.id));
5873
- }) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isNewAgent
6131
+ }) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
5874
6132
  ? "Subscribed triggers are shown after the agent is created"
5875
6133
  : "No active trigger subscriptions" })) })), triggerSubscriptionsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: triggerSubscriptionsError }))] })] }) })] }), 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", type: "button", 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-[10px] text-gray-700 hover:bg-gray-100", onClick: () => {
5876
6134
  setPrompt(p.prompt);