@parhelia/core 0.1.12744 → 0.1.12752
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.
- package/dist/agents-view/AgentsView.js +1 -1
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/agents-view/AgentsWorkspaceView.js +41 -21
- package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
- package/dist/components/ui/PlaceholderInput.js +3 -3
- package/dist/components/ui/PlaceholderInput.js.map +1 -1
- package/dist/components/ui/PlaceholderInputTypes.js +2 -2
- package/dist/components/ui/PlaceholderInputTypes.js.map +1 -1
- package/dist/components/ui/PlaceholderItemSelector.js +2 -2
- package/dist/components/ui/PlaceholderItemSelector.js.map +1 -1
- package/dist/editor/LinkEditorDialog.js +2 -2
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/ai/AgentBanners.d.ts +11 -0
- package/dist/editor/ai/AgentBanners.js +16 -0
- package/dist/editor/ai/AgentBanners.js.map +1 -0
- package/dist/editor/ai/AgentCostDisplay.d.ts +12 -0
- package/dist/editor/ai/AgentCostDisplay.js +24 -1
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentDocumentList.js +5 -4
- package/dist/editor/ai/AgentDocumentList.js.map +1 -1
- package/dist/editor/ai/AgentInlineDialogContent.d.ts +17 -0
- package/dist/editor/ai/AgentInlineDialogContent.js +32 -0
- package/dist/editor/ai/AgentInlineDialogContent.js.map +1 -0
- package/dist/editor/ai/AgentSharingSection.d.ts +6 -0
- package/dist/editor/ai/AgentSharingSection.js +149 -0
- package/dist/editor/ai/AgentSharingSection.js.map +1 -0
- package/dist/editor/ai/AgentTerminal.d.ts +2 -1
- package/dist/editor/ai/AgentTerminal.js +298 -969
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AgentTerminalStatusBar.d.ts +18 -0
- package/dist/editor/ai/AgentTerminalStatusBar.js +431 -126
- package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
- package/dist/editor/ai/Agents.js +1 -1
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/HeartbeatMessage.d.ts +4 -0
- package/dist/editor/ai/HeartbeatMessage.js +7 -0
- package/dist/editor/ai/HeartbeatMessage.js.map +1 -0
- package/dist/editor/ai/InitialThinkingSplash.d.ts +3 -0
- package/dist/editor/ai/InitialThinkingSplash.js +9 -0
- package/dist/editor/ai/InitialThinkingSplash.js.map +1 -0
- package/dist/editor/ai/InlineAiDialog.js +50 -2
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/QueuedPromptsPanel.d.ts +4 -0
- package/dist/editor/ai/QueuedPromptsPanel.js +36 -0
- package/dist/editor/ai/QueuedPromptsPanel.js.map +1 -0
- package/dist/editor/ai/TodoListPanel.d.ts +5 -0
- package/dist/editor/ai/TodoListPanel.js +113 -0
- package/dist/editor/ai/TodoListPanel.js.map +1 -0
- package/dist/editor/ai/ToolCallDisplay.js +136 -136
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/UserMessage.d.ts +4 -0
- package/dist/editor/ai/UserMessage.js +42 -0
- package/dist/editor/ai/UserMessage.js.map +1 -0
- package/dist/editor/ai/agentMessageConversion.d.ts +11 -0
- package/dist/editor/ai/agentMessageConversion.js +134 -0
- package/dist/editor/ai/agentMessageConversion.js.map +1 -0
- package/dist/editor/ai/agentMessageGrouping.d.ts +22 -0
- package/dist/editor/ai/agentMessageGrouping.js +103 -0
- package/dist/editor/ai/agentMessageGrouping.js.map +1 -0
- package/dist/editor/ai/agentMessageHelpers.d.ts +23 -0
- package/dist/editor/ai/agentMessageHelpers.js +124 -0
- package/dist/editor/ai/agentMessageHelpers.js.map +1 -0
- package/dist/editor/ai/agentMessageMarkdown.d.ts +2 -0
- package/dist/editor/ai/agentMessageMarkdown.js +14 -0
- package/dist/editor/ai/agentMessageMarkdown.js.map +1 -0
- package/dist/editor/ai/agentTodoExtraction.d.ts +12 -0
- package/dist/editor/ai/agentTodoExtraction.js +205 -0
- package/dist/editor/ai/agentTodoExtraction.js.map +1 -0
- package/dist/editor/client/EditorShell.js +38 -51
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +1 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/operations.d.ts +3 -1
- package/dist/editor/client/operations.js +20 -2
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/menubar/VersionPreviewCard.js +34 -3
- package/dist/editor/menubar/VersionPreviewCard.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +2 -2
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +65 -48
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/SuggestionDisplayPopover.js +5 -1
- package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +24 -0
- package/dist/editor/services/agentService.js +40 -0
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +10 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/settings/panels/AgentProfileConfigPanel.js +1 -1
- package/dist/editor/settings/panels/AgentProfileConfigPanel.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/task-board/TaskBoardWorkspace.js +57 -26
- package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
- package/dist/task-board/components/ProjectDashboard.js +2 -3
- package/dist/task-board/components/ProjectDashboard.js.map +1 -1
- package/dist/task-board/components/TaskBoardTitlebar.js +2 -1
- package/dist/task-board/components/TaskBoardTitlebar.js.map +1 -1
- package/dist/task-board/components/TaskDetailPanel.d.ts +8 -0
- package/dist/task-board/components/TaskDetailPanel.js +5 -3
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/package.json +1 -1
- package/styles.css +11 -10
|
@@ -1,8 +1,8 @@
|
|
|
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 { flushSync } from "react-dom";
|
|
4
|
-
import { Send, AlertCircle, Loader2,
|
|
5
|
-
import { getAgent, startAgent, claimAgentBrowser, cancelAgentDialog, assignAgentSkill, persistDraftAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, getAgentDiagnostics, releaseAgentBrowser, revokeAgentSkill, } from "../services/agentService";
|
|
4
|
+
import { Send, AlertCircle, Loader2, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ArrowLeft, DollarSign, ExternalLink, Settings2, Target, X, Plus, } from "lucide-react";
|
|
5
|
+
import { getAgent, startAgent, claimAgentBrowser, cancelAgentDialog, assignAgentSkill, persistDraftAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, getAgentDiagnostics, releaseAgentBrowser, rejectToolCall, revokeAgentSkill, } from "../services/agentService";
|
|
6
6
|
import { parseAgentStatus } from "../services/agentStatus";
|
|
7
7
|
import { useEditContext, useFieldsEditContext } from "../client/editContext";
|
|
8
8
|
import { localStorageService } from "../services/localStorageService";
|
|
@@ -11,19 +11,31 @@ import { Button } from "../../components/ui/button";
|
|
|
11
11
|
import { PlaceholderInput, } from "../../components/ui/PlaceholderInput";
|
|
12
12
|
import { AiResponseMessage } from "./AiResponseMessage";
|
|
13
13
|
import { ContextInfoBar } from "./ContextInfoBar";
|
|
14
|
+
import { UserMessage } from "./UserMessage";
|
|
15
|
+
import { HeartbeatMessage } from "./HeartbeatMessage";
|
|
16
|
+
import { calculateTotalTokens, getOperationsForMessageGroup, groupConsecutiveMessages, mergeMessagesById, } from "./agentMessageGrouping";
|
|
17
|
+
import { convertAgentMessagesToAiFormat, extractToolCallFields, stringifyToolField, } from "./agentMessageConversion";
|
|
18
|
+
import { TodoListPanel } from "./TodoListPanel";
|
|
19
|
+
import { extractTodosFromMessages } from "./agentTodoExtraction";
|
|
20
|
+
import { QueuedPromptsPanel } from "./QueuedPromptsPanel";
|
|
21
|
+
import { AgentCapacityBanner, AgentCostLimitBanner, AgentErrorBanner, } from "./AgentBanners";
|
|
22
|
+
import { InitialThinkingSplash } from "./InitialThinkingSplash";
|
|
23
|
+
import { AgentInlineDialogContent } from "./AgentInlineDialogContent";
|
|
24
|
+
import { AGENT_HISTORY_LIMIT, MACHINE_CAPACITY_REASON, buildPlaceholderAgentDetails, formatAllowanceLabel, formatAllowanceSource, getAgentRunMessageAgentId, getAgentRunMessageDetail, getAgentRunMessageSeq, getVisibleDialogRegistry, isAgentErrorStatusValue, isFinishedServerExecutionStatus, isHeartbeatRunEventMessage, mergeAgentOperationHistory, normalizeDialogAgentId, normalizeProfileAllowanceOperations, normalizeServerExecutionStatus, } from "./agentMessageHelpers";
|
|
14
25
|
import { AgentDocumentList, } from "./AgentDocumentList";
|
|
15
26
|
import { AgentEditOperationsPanel } from "./EditOperationsPanel";
|
|
16
27
|
import { SpawnedAgentsPanel } from "./SpawnedAgentsPanel";
|
|
28
|
+
import { AgentSharingSection } from "./AgentSharingSection";
|
|
17
29
|
import { getComponentById } from "../componentTreeHelper";
|
|
18
30
|
import { toUserFacingAgentErrorMessage } from "../services/agentErrorMessage";
|
|
19
31
|
import { AgentGreeting } from "./AgentGreeting";
|
|
20
32
|
import { getAgentHistory } from "../services/editService";
|
|
21
|
-
import { QuestionnaireInline } from "./dialogs/QuestionnaireInline";
|
|
22
33
|
import { getBrowserCaptureClaim, setBrowserCaptureClaim, } from "./dialogs/browserBoundCapture";
|
|
34
|
+
import { invalidateAiProfilesCache } from "../services/aiService";
|
|
23
35
|
import { DIALOG_TYPES, } from "./dialogs/agentDialogTypes";
|
|
24
36
|
import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
|
|
25
37
|
import { SecretAgentIcon } from "../ui/Icons";
|
|
26
|
-
import { formatTime
|
|
38
|
+
import { formatTime } from "../utils";
|
|
27
39
|
import { cn } from "../../lib/utils";
|
|
28
40
|
import { sanitizeSvg } from "../../lib/sanitize";
|
|
29
41
|
import { Select } from "../../components/ui/select";
|
|
@@ -31,802 +43,22 @@ import { AgentTerminalStatusBar } from "./AgentTerminalStatusBar";
|
|
|
31
43
|
import { SimpleTabs } from "../ui/SimpleTabs";
|
|
32
44
|
import { Splitter } from "../ui/Splitter";
|
|
33
45
|
import { ScrollingContentTree } from "../ScrollingContentTree";
|
|
34
|
-
import { MarkdownDisplay, } from "../../components/MarkdownDisplay";
|
|
35
|
-
const AGENT_HISTORY_LIMIT = 1000;
|
|
36
46
|
const RECENT_RUN_EVENTS_LIMIT = 50;
|
|
37
|
-
function mergeAgentOperationHistory(existing, incoming, limit = AGENT_HISTORY_LIMIT) {
|
|
38
|
-
const merged = new Map(existing.map((operation) => [operation.id, operation]));
|
|
39
|
-
for (const operation of incoming) {
|
|
40
|
-
merged.set(operation.id, operation);
|
|
41
|
-
}
|
|
42
|
-
return Array.from(merged.values())
|
|
43
|
-
.sort((left, right) => new Date(right.date).getTime() - new Date(left.date).getTime())
|
|
44
|
-
.slice(0, limit);
|
|
45
|
-
}
|
|
46
|
-
const userMessageMarkdownComponents = {
|
|
47
|
-
h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm leading-5 font-semibold text-gray-900" })),
|
|
48
|
-
h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] leading-5 font-semibold text-gray-900" })),
|
|
49
|
-
h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] leading-5 font-semibold text-gray-900" })),
|
|
50
|
-
h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] leading-5 font-medium text-gray-800" })),
|
|
51
|
-
p: (props) => (_jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" })),
|
|
52
|
-
ul: (props) => (_jsx("ul", { ...props, className: "my-2 ml-5 list-disc space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
53
|
-
ol: (props) => (_jsx("ol", { ...props, className: "my-2 ml-5 list-decimal space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
54
|
-
li: (props) => (_jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" })),
|
|
55
|
-
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" })),
|
|
56
|
-
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 })),
|
|
57
|
-
};
|
|
58
|
-
function buildPlaceholderAgentDetails(agentStub) {
|
|
59
|
-
const now = new Date().toISOString();
|
|
60
|
-
const updated = agentStub.updatedDate || now;
|
|
61
|
-
// AgentDetails has required fields, but some workspaces only pass an Agent stub initially.
|
|
62
|
-
// This placeholder keeps streaming/tool-call UI working until `getAgent()` returns full details.
|
|
63
|
-
return {
|
|
64
|
-
...agentStub,
|
|
65
|
-
name: agentStub.name || "Agent",
|
|
66
|
-
userId: agentStub.userId || "",
|
|
67
|
-
updatedDate: updated,
|
|
68
|
-
profileName: agentStub.profileName || "",
|
|
69
|
-
model: agentStub.model || "",
|
|
70
|
-
createdDate: agentStub.createdDate || updated,
|
|
71
|
-
totalTokensUsed: 0,
|
|
72
|
-
totalInputTokens: 0,
|
|
73
|
-
totalOutputTokens: 0,
|
|
74
|
-
totalCachedInputTokens: 0,
|
|
75
|
-
totalInputTokenCost: 0,
|
|
76
|
-
totalOutputTokenCost: 0,
|
|
77
|
-
totalCachedInputTokenCost: 0,
|
|
78
|
-
totalImageCost: 0,
|
|
79
|
-
totalCost: 0,
|
|
80
|
-
currency: agentStub.currency || "USD",
|
|
81
|
-
messageCount: agentStub.messageCount || 0,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function normalizeDialogAgentId(value) {
|
|
85
|
-
return value?.trim().toLowerCase() || "";
|
|
86
|
-
}
|
|
87
|
-
function normalizeServerExecutionStatus(value) {
|
|
88
|
-
return (value || "").replace(/\s+/g, "").toLowerCase();
|
|
89
|
-
}
|
|
90
|
-
function isFinishedServerExecutionStatus(value) {
|
|
91
|
-
const normalized = normalizeServerExecutionStatus(value);
|
|
92
|
-
return normalized === "completed" || normalized === "cancelled";
|
|
93
|
-
}
|
|
94
|
-
const MACHINE_CAPACITY_REASON = "machineCapacity";
|
|
95
|
-
const MACHINE_CAPACITY_DETAIL = "waitingForCapacity";
|
|
96
|
-
function formatAllowanceSource(source) {
|
|
97
|
-
const normalized = source?.trim();
|
|
98
|
-
if (!normalized)
|
|
99
|
-
return null;
|
|
100
|
-
if (normalized === "user")
|
|
101
|
-
return "User granted";
|
|
102
|
-
if (normalized.startsWith("preconfigured:profile:"))
|
|
103
|
-
return "Profile";
|
|
104
|
-
if (normalized.startsWith("preconfigured:skill:"))
|
|
105
|
-
return "Skill";
|
|
106
|
-
if (normalized.startsWith("system:")) {
|
|
107
|
-
return normalized.slice("system:".length).replace(/[-_]+/g, " ").trim();
|
|
108
|
-
}
|
|
109
|
-
return normalized;
|
|
110
|
-
}
|
|
111
|
-
function formatAllowanceLabel(allowance) {
|
|
112
|
-
return `${allowance.operationType || "*"}${"itemPath" in allowance
|
|
113
|
-
? ` ${allowance.itemPath}`
|
|
114
|
-
: ` ${allowance.normalizedPath}`}`;
|
|
115
|
-
}
|
|
116
|
-
function getAgentRunMessageAgentId(payload) {
|
|
117
|
-
const agentId = payload?.agentId;
|
|
118
|
-
return typeof agentId === "string" ? normalizeDialogAgentId(agentId) : null;
|
|
119
|
-
}
|
|
120
|
-
function getAgentRunMessageSeq(payload) {
|
|
121
|
-
const seq = payload?.seq;
|
|
122
|
-
return typeof seq === "number" ? seq : null;
|
|
123
|
-
}
|
|
124
|
-
function getAgentRunMessageDetail(type, payload) {
|
|
125
|
-
if (type === "agent:run:delta") {
|
|
126
|
-
return payload?.type || null;
|
|
127
|
-
}
|
|
128
|
-
if (type === "agent:run:status") {
|
|
129
|
-
if (payload?.data?.reason === MACHINE_CAPACITY_REASON) {
|
|
130
|
-
return MACHINE_CAPACITY_DETAIL;
|
|
131
|
-
}
|
|
132
|
-
return payload?.data?.state || payload?.data?.status || null;
|
|
133
|
-
}
|
|
134
|
-
if (type === "agent:run:error") {
|
|
135
|
-
return payload?.error || null;
|
|
136
|
-
}
|
|
137
|
-
if (type === "agent:run:complete") {
|
|
138
|
-
return payload?.finalStatus || null;
|
|
139
|
-
}
|
|
140
|
-
if (type === "agent:run:start") {
|
|
141
|
-
return payload?.agentName || null;
|
|
142
|
-
}
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
function isHeartbeatRunEventMessage(message) {
|
|
146
|
-
if (!message)
|
|
147
|
-
return false;
|
|
148
|
-
if (message.type === "agent:run:delta") {
|
|
149
|
-
const deltaType = message.payload?.type;
|
|
150
|
-
return String(deltaType || "").toLowerCase() === "heartbeat";
|
|
151
|
-
}
|
|
152
|
-
if (message.type === "agent:run:status") {
|
|
153
|
-
const state = message.payload?.data?.state || message.payload?.data?.status;
|
|
154
|
-
return String(state || "").toLowerCase() === "heartbeat";
|
|
155
|
-
}
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
function getVisibleDialogRegistry() {
|
|
159
|
-
const registry = globalThis.__agentDialogVisibleCallbacks;
|
|
160
|
-
return registry && typeof registry === "object" ? registry : {};
|
|
161
|
-
}
|
|
162
|
-
function isAgentErrorStatusValue(status) {
|
|
163
|
-
return status === "error";
|
|
164
|
-
}
|
|
165
|
-
// Simple user message component
|
|
166
|
-
const UserMessage = ({ message }) => {
|
|
167
|
-
const content = message.content || "";
|
|
168
|
-
const [isTriggerExpanded, setIsTriggerExpanded] = useState(false);
|
|
169
|
-
// Trigger-sourced prompts are prefixed by backend as "[Trigger: {name}]: {content}"
|
|
170
|
-
const triggerPattern = /^\[Trigger: ([^\]]+)\]:\s*(.*)$/s;
|
|
171
|
-
const triggerMatch = content.match(triggerPattern);
|
|
172
|
-
const triggerName = triggerMatch?.[1]?.trim() || "";
|
|
173
|
-
const triggerContent = triggerMatch?.[2] || "";
|
|
174
|
-
const isTriggerMessage = triggerName.length > 0;
|
|
175
|
-
if (isTriggerMessage) {
|
|
176
|
-
return (_jsx("div", { className: "px-4 py-2", children: _jsxs("div", { className: "text-[11px]", children: [_jsxs("button", { type: "button", onClick: () => setIsTriggerExpanded((expanded) => !expanded), className: "text-theme-secondary hover:bg-theme-hover flex w-full items-center gap-2 rounded-md border-l-2 border-cyan-200 px-2 py-1 text-left transition-colors", "data-testid": "trigger-message-toggle", "data-expanded": isTriggerExpanded ? "true" : "false", children: [_jsx(Target, { className: "h-3.5 w-3.5 shrink-0", strokeWidth: 1.5 }), _jsxs("span", { className: "truncate font-medium", children: ["Trigger: ", triggerName] }), message.createdDate && (_jsx("span", { className: "ml-1 shrink-0 text-[10px] text-gray-400", children: formatTime(new Date(message.createdDate)) })), _jsx("span", { className: "ml-auto shrink-0", children: isTriggerExpanded ? (_jsx(ChevronUp, { className: "h-3.5 w-3.5" })) : (_jsx(ChevronDown, { className: "h-3.5 w-3.5" })) })] }), isTriggerExpanded && (_jsx("div", { className: "mt-1 border-l-2 border-cyan-100 pl-[1.35rem] text-[11px] text-gray-600", children: _jsx(MarkdownDisplay, { source: triggerContent, components: userMessageMarkdownComponents }) }))] }) }));
|
|
177
|
-
}
|
|
178
|
-
// Parse source agent name from content if it starts with "[From ...]:"
|
|
179
|
-
// Backend formats messages from other agents as "[From {sourceAgentName}]: {content}"
|
|
180
|
-
const fromPattern = /^\[From ([^\]]+)\]:\s*(.*)$/s;
|
|
181
|
-
const match = content.match(fromPattern);
|
|
182
|
-
let sourceAgentName;
|
|
183
|
-
let displayContent = content;
|
|
184
|
-
if (match && match[1]) {
|
|
185
|
-
const extractedName = match[1];
|
|
186
|
-
// If it says "From User", treat it as a regular user message
|
|
187
|
-
if (extractedName.toLowerCase() === "user") {
|
|
188
|
-
sourceAgentName = undefined;
|
|
189
|
-
displayContent = content; // Keep original content
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
sourceAgentName = extractedName;
|
|
193
|
-
displayContent = match[2] || ""; // Remove the "[From ...]:" prefix from content
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
// Fallback: check for source agent name in message metadata or properties
|
|
198
|
-
sourceAgentName =
|
|
199
|
-
message.sourceAgentName ||
|
|
200
|
-
message.metadata?.sourceAgentName ||
|
|
201
|
-
message.sourceAgent?.name;
|
|
202
|
-
}
|
|
203
|
-
const displayName = sourceAgentName ? `[From ${sourceAgentName}]` : "You";
|
|
204
|
-
return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "shrink-0", children: _jsx(User, { className: "text-theme-secondary h-5 w-5", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-[12px] font-medium text-gray-900", children: displayName }), message.createdDate && (_jsx("span", { className: "text-[12px] text-gray-400", "data-testid": "user-message-timestamp", "data-timestamp": message.createdDate, children: formatTime(new Date(message.createdDate)) }))] }), _jsx("div", { className: "prose prose max-w-none text-[12px] text-gray-700 select-text", children: _jsx(MarkdownDisplay, { source: displayContent, components: userMessageMarkdownComponents }) })] })] }));
|
|
205
|
-
};
|
|
206
|
-
const HeartbeatMessage = ({ message }) => {
|
|
207
|
-
return (_jsx("div", { className: "px-4 py-2", "data-testid": "agent-heartbeat-message", children: _jsxs("div", { className: "flex items-center gap-2 rounded-md border border-sky-100 bg-sky-50/80 px-3 py-2 text-[11px] text-sky-700", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", strokeWidth: 1.5 }), _jsx("span", { className: "min-w-0 flex-1", children: message.content }), message.createdDate && (_jsx("span", { className: "shrink-0 text-[10px] text-sky-500", children: formatTime(new Date(message.createdDate)) }))] }) }));
|
|
208
|
-
};
|
|
209
|
-
// Helper to extract todos from potentially incomplete JSON during streaming
|
|
210
|
-
const extractPartialTodos = (jsonText) => {
|
|
211
|
-
// First try to parse complete JSON
|
|
212
|
-
try {
|
|
213
|
-
const parsed = JSON.parse(jsonText);
|
|
214
|
-
const result = Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
215
|
-
return result;
|
|
216
|
-
}
|
|
217
|
-
catch (e) {
|
|
218
|
-
// If JSON is incomplete, try to extract whatever todo items we can find
|
|
219
|
-
const items = [];
|
|
220
|
-
// Look for individual todo objects in the partial JSON
|
|
221
|
-
// Match patterns like: { "text": "...", "done": false, "note": "..." }
|
|
222
|
-
// Handle various field orderings (text can be anywhere in the object)
|
|
223
|
-
const textPattern = /"text"\s*:\s*"([^"]+)"/g;
|
|
224
|
-
const textMatches = [];
|
|
225
|
-
let textMatch;
|
|
226
|
-
while ((textMatch = textPattern.exec(jsonText)) !== null) {
|
|
227
|
-
if (textMatch[1]) {
|
|
228
|
-
textMatches.push({
|
|
229
|
-
text: textMatch[1],
|
|
230
|
-
startIdx: textMatch.index,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// For each text field found, try to find the enclosing object
|
|
235
|
-
for (const { text, startIdx } of textMatches) {
|
|
236
|
-
// Find the opening brace before this text field
|
|
237
|
-
let openBrace = -1;
|
|
238
|
-
for (let i = startIdx - 1; i >= 0; i--) {
|
|
239
|
-
if (jsonText[i] === "{") {
|
|
240
|
-
openBrace = i;
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
if (jsonText[i] === "}")
|
|
244
|
-
break; // Hit another object's end
|
|
245
|
-
}
|
|
246
|
-
if (openBrace === -1)
|
|
247
|
-
continue;
|
|
248
|
-
// Find the closing brace after this text field
|
|
249
|
-
let closeBrace = -1;
|
|
250
|
-
let depth = 0;
|
|
251
|
-
for (let i = openBrace; i < jsonText.length; i++) {
|
|
252
|
-
if (jsonText[i] === "{")
|
|
253
|
-
depth++;
|
|
254
|
-
if (jsonText[i] === "}") {
|
|
255
|
-
depth--;
|
|
256
|
-
if (depth === 0) {
|
|
257
|
-
closeBrace = i;
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
// Extract the object and try to parse it
|
|
263
|
-
const objStr = closeBrace !== -1
|
|
264
|
-
? jsonText.substring(openBrace, closeBrace + 1)
|
|
265
|
-
: jsonText.substring(openBrace) + "}"; // Try to close incomplete object
|
|
266
|
-
try {
|
|
267
|
-
const obj = JSON.parse(objStr);
|
|
268
|
-
if (obj.title || obj.text) {
|
|
269
|
-
items.push({
|
|
270
|
-
title: obj.title || obj.text,
|
|
271
|
-
status: obj.status || "pending",
|
|
272
|
-
instructions: obj.instructions || obj.note || undefined,
|
|
273
|
-
agentProfileId: obj.agentProfileId || undefined,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch (e) {
|
|
278
|
-
// Skip malformed objects
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
// Also try to extract from partial objects at the end
|
|
282
|
-
// Look for the last opening brace and try to parse up to where we have valid content
|
|
283
|
-
const lines = jsonText.split("\n");
|
|
284
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
285
|
-
const partialJson = lines.slice(0, i + 1).join("\n");
|
|
286
|
-
// Try to close any open braces/brackets
|
|
287
|
-
let testJson = partialJson;
|
|
288
|
-
const openBraces = (testJson.match(/\{/g) || []).length;
|
|
289
|
-
const closeBraces = (testJson.match(/\}/g) || []).length;
|
|
290
|
-
const openBrackets = (testJson.match(/\[/g) || []).length;
|
|
291
|
-
const closeBrackets = (testJson.match(/\]/g) || []).length;
|
|
292
|
-
// Add missing closing characters
|
|
293
|
-
testJson += "]".repeat(openBrackets - closeBrackets);
|
|
294
|
-
testJson += "}".repeat(openBraces - closeBraces);
|
|
295
|
-
try {
|
|
296
|
-
const parsed = JSON.parse(testJson);
|
|
297
|
-
const partialItems = Array.isArray(parsed)
|
|
298
|
-
? parsed
|
|
299
|
-
: parsed?.items || [];
|
|
300
|
-
if (partialItems.length > items.length) {
|
|
301
|
-
return partialItems;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
catch (e) {
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return items;
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
const extractTodosFromMessages = (messages) => {
|
|
312
|
-
const todos = [];
|
|
313
|
-
const fencedTodoToken = "```todo_list";
|
|
314
|
-
const plainTodoToken = "todo_list";
|
|
315
|
-
for (const message of messages) {
|
|
316
|
-
if (message.role !== "assistant" || !message.content)
|
|
317
|
-
continue;
|
|
318
|
-
const content = message.content;
|
|
319
|
-
let cursor = 0;
|
|
320
|
-
while (cursor < content.length) {
|
|
321
|
-
const nextFenced = content.indexOf(fencedTodoToken, cursor);
|
|
322
|
-
const nextPlain = content.indexOf(plainTodoToken, cursor);
|
|
323
|
-
let todoStart = -1;
|
|
324
|
-
let isFenced = false;
|
|
325
|
-
if (nextFenced !== -1 && (nextPlain === -1 || nextFenced < nextPlain)) {
|
|
326
|
-
todoStart = nextFenced;
|
|
327
|
-
isFenced = true;
|
|
328
|
-
}
|
|
329
|
-
else if (nextPlain !== -1) {
|
|
330
|
-
// Check if it's at line start
|
|
331
|
-
const before = nextPlain > 0 ? content[nextPlain - 1] : "\n";
|
|
332
|
-
if (before === "\n" || before === "\r" || nextPlain === 0) {
|
|
333
|
-
todoStart = nextPlain;
|
|
334
|
-
isFenced = false;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (todoStart === -1)
|
|
338
|
-
break;
|
|
339
|
-
try {
|
|
340
|
-
let jsonText = "";
|
|
341
|
-
let isComplete = true;
|
|
342
|
-
if (isFenced) {
|
|
343
|
-
const afterToken = todoStart + fencedTodoToken.length;
|
|
344
|
-
const closePos = content.indexOf("```", afterToken);
|
|
345
|
-
if (closePos === -1) {
|
|
346
|
-
// Incomplete fenced block - extract what we have so far
|
|
347
|
-
jsonText = content.slice(afterToken).trim();
|
|
348
|
-
isComplete = false;
|
|
349
|
-
cursor = content.length; // Process till end
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
jsonText = content.slice(afterToken, closePos).trim();
|
|
353
|
-
cursor = closePos + 3;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
const afterToken = todoStart + plainTodoToken.length;
|
|
358
|
-
const braceStart = content.indexOf("{", afterToken);
|
|
359
|
-
if (braceStart === -1)
|
|
360
|
-
break;
|
|
361
|
-
let depth = 0;
|
|
362
|
-
let braceEnd = -1;
|
|
363
|
-
for (let i = braceStart; i < content.length; i++) {
|
|
364
|
-
if (content[i] === "{")
|
|
365
|
-
depth++;
|
|
366
|
-
if (content[i] === "}") {
|
|
367
|
-
depth--;
|
|
368
|
-
if (depth === 0) {
|
|
369
|
-
braceEnd = i;
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (braceEnd === -1) {
|
|
375
|
-
// Incomplete JSON - extract what we have
|
|
376
|
-
jsonText = content.slice(braceStart).trim();
|
|
377
|
-
isComplete = false;
|
|
378
|
-
cursor = content.length;
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
jsonText = content.slice(braceStart, braceEnd + 1).trim();
|
|
382
|
-
cursor = braceEnd + 1;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
// Use the partial extraction helper for incomplete JSON
|
|
386
|
-
const todoItems = isComplete
|
|
387
|
-
? (() => {
|
|
388
|
-
try {
|
|
389
|
-
const parsed = JSON.parse(jsonText);
|
|
390
|
-
return Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
391
|
-
}
|
|
392
|
-
catch (e) {
|
|
393
|
-
return [];
|
|
394
|
-
}
|
|
395
|
-
})()
|
|
396
|
-
: extractPartialTodos(jsonText);
|
|
397
|
-
const title = (() => {
|
|
398
|
-
try {
|
|
399
|
-
const parsed = JSON.parse(jsonText);
|
|
400
|
-
return Array.isArray(parsed) ? undefined : parsed?.title;
|
|
401
|
-
}
|
|
402
|
-
catch (e) {
|
|
403
|
-
return undefined;
|
|
404
|
-
}
|
|
405
|
-
})();
|
|
406
|
-
todoItems.forEach((item) => {
|
|
407
|
-
if (!item)
|
|
408
|
-
return;
|
|
409
|
-
const title = item.title ||
|
|
410
|
-
item.text ||
|
|
411
|
-
item.content ||
|
|
412
|
-
item.label ||
|
|
413
|
-
String(item.task || "");
|
|
414
|
-
if (!title)
|
|
415
|
-
return;
|
|
416
|
-
todos.push({
|
|
417
|
-
id: item.id,
|
|
418
|
-
title,
|
|
419
|
-
status: item.status || "pending",
|
|
420
|
-
instructions: item.instructions || item.note || item.description,
|
|
421
|
-
agentProfileId: item.agentProfileId,
|
|
422
|
-
messageId: message.id,
|
|
423
|
-
sourceTitle: item.sourceTitle,
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
catch (e) {
|
|
428
|
-
cursor++;
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
// Deduplicate todos by ID (or title if no ID), keeping the latest version
|
|
434
|
-
// Later entries (from more recent messages) overwrite earlier ones
|
|
435
|
-
const todoMap = new Map();
|
|
436
|
-
for (const todo of todos) {
|
|
437
|
-
const key = todo.id || todo.title;
|
|
438
|
-
todoMap.set(key, todo);
|
|
439
|
-
}
|
|
440
|
-
const result = Array.from(todoMap.values());
|
|
441
|
-
return result;
|
|
442
|
-
};
|
|
443
|
-
// TodoListPanel component
|
|
444
|
-
const TodoListPanel = ({ messages, agentMetadata, }) => {
|
|
445
|
-
const [isExpanded, setIsExpanded] = useState(true);
|
|
446
|
-
// First try to get todos from agent metadata (real-time updates)
|
|
447
|
-
// Server sends additionalData.todoList directly via contextChanged status
|
|
448
|
-
const metadataTodos = (() => {
|
|
449
|
-
try {
|
|
450
|
-
const todoList = agentMetadata?.additionalData?.todoList;
|
|
451
|
-
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
452
|
-
const rawItems = todoList.items
|
|
453
|
-
.map((item, idx) => ({
|
|
454
|
-
id: item.id || `metadata-${idx}`,
|
|
455
|
-
title: item.title || item.text || item.label || String(item.task || ""),
|
|
456
|
-
status: item.status || "pending",
|
|
457
|
-
instructions: item.instructions || item.note || item.description,
|
|
458
|
-
agentProfileId: item.agentProfileId,
|
|
459
|
-
messageId: undefined,
|
|
460
|
-
sourceTitle: todoList.title,
|
|
461
|
-
}))
|
|
462
|
-
.filter((item) => item.title);
|
|
463
|
-
// Apply deduplication to metadata todos as well
|
|
464
|
-
const todoMap = new Map();
|
|
465
|
-
for (const todo of rawItems) {
|
|
466
|
-
const key = todo.id || todo.title;
|
|
467
|
-
todoMap.set(key, todo);
|
|
468
|
-
}
|
|
469
|
-
return Array.from(todoMap.values());
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
catch (e) {
|
|
473
|
-
console.error("📋 Error extracting todos from metadata:", e);
|
|
474
|
-
}
|
|
475
|
-
return null;
|
|
476
|
-
})();
|
|
477
|
-
// If we have metadata todos, use them; otherwise extract from messages
|
|
478
|
-
const usingMetadata = metadataTodos && metadataTodos.length > 0;
|
|
479
|
-
const todos = usingMetadata
|
|
480
|
-
? metadataTodos
|
|
481
|
-
: extractTodosFromMessages(messages);
|
|
482
|
-
// Check if there's an active streaming message with incomplete todo content
|
|
483
|
-
const isUpdating = messages.some((msg) => {
|
|
484
|
-
if (msg.role !== "assistant" || msg.isCompleted)
|
|
485
|
-
return false;
|
|
486
|
-
const content = msg.content || "";
|
|
487
|
-
// Check for incomplete fenced todo blocks
|
|
488
|
-
const fencedStart = content.indexOf("```todo_list");
|
|
489
|
-
if (fencedStart !== -1) {
|
|
490
|
-
const afterStart = fencedStart + "```todo_list".length;
|
|
491
|
-
const closePos = content.indexOf("```", afterStart);
|
|
492
|
-
if (closePos === -1) {
|
|
493
|
-
// Incomplete fenced block
|
|
494
|
-
return true;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
// Check for incomplete plain todo blocks
|
|
498
|
-
const plainStart = content.indexOf("todo_list");
|
|
499
|
-
if (plainStart !== -1 && plainStart !== fencedStart) {
|
|
500
|
-
const before = plainStart > 0 ? content[plainStart - 1] : "\n";
|
|
501
|
-
if (before === "\n" || before === "\r" || plainStart === 0) {
|
|
502
|
-
const braceStart = content.indexOf("{", plainStart);
|
|
503
|
-
if (braceStart !== -1) {
|
|
504
|
-
let depth = 0;
|
|
505
|
-
let braceEnd = -1;
|
|
506
|
-
for (let i = braceStart; i < content.length; i++) {
|
|
507
|
-
if (content[i] === "{")
|
|
508
|
-
depth++;
|
|
509
|
-
if (content[i] === "}") {
|
|
510
|
-
depth--;
|
|
511
|
-
if (depth === 0) {
|
|
512
|
-
braceEnd = i;
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
if (braceEnd === -1) {
|
|
518
|
-
// Incomplete plain block
|
|
519
|
-
return true;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
return false;
|
|
525
|
-
});
|
|
526
|
-
if (todos.length === 0 && !isUpdating)
|
|
527
|
-
return null;
|
|
528
|
-
const completedCount = todos.filter((t) => t.status === "completed").length;
|
|
529
|
-
const totalCount = todos.length;
|
|
530
|
-
const getStatusIcon = (status) => {
|
|
531
|
-
switch (status) {
|
|
532
|
-
case "completed":
|
|
533
|
-
return (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-green-500 bg-green-500", children: _jsx("svg", { className: "h-3 w-3 text-white", fill: "none", strokeWidth: 2, stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) }));
|
|
534
|
-
case "in_progress":
|
|
535
|
-
return (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-blue-500 bg-blue-500", children: _jsx(Loader2, { className: "h-3 w-3 animate-spin text-white", strokeWidth: 2 }) }));
|
|
536
|
-
case "cancelled":
|
|
537
|
-
return (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-gray-400 bg-gray-400", children: _jsx("svg", { className: "h-3 w-3 text-white", fill: "none", strokeWidth: 2, stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) }));
|
|
538
|
-
default: // pending
|
|
539
|
-
return _jsx("div", { className: "h-4 w-4 rounded border-2 border-gray-300" });
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
const getTextClass = (status) => {
|
|
543
|
-
switch (status) {
|
|
544
|
-
case "completed":
|
|
545
|
-
return "text-gray-500 line-through";
|
|
546
|
-
case "cancelled":
|
|
547
|
-
return "text-gray-400 line-through";
|
|
548
|
-
case "in_progress":
|
|
549
|
-
return "text-blue-700 font-medium";
|
|
550
|
-
default:
|
|
551
|
-
return "text-gray-900";
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
return (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ListTodo, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }), _jsx("span", { className: "text-[11px] font-medium text-gray-700", children: "Todo List" }), isUpdating ? (_jsxs("span", { className: "text-theme-secondary flex items-center gap-1 text-[11px]", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), "Updating..."] })) : (_jsxs("span", { className: "text-[11px] text-gray-500", children: [completedCount, "/", totalCount, " completed"] }))] }), isExpanded ? (_jsx(ChevronUp, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDown, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isExpanded && (_jsxs("div", { className: "max-h-64 overflow-y-auto px-4 pb-3", children: [todos.length > 0 && (_jsx("div", { className: "space-y-1.5", children: todos.map((todo, idx) => (_jsxs("div", { className: "flex items-start gap-2 rounded bg-white p-2 text-[11px]", children: [_jsx("div", { className: "shrink-0 pt-0.5", children: getStatusIcon(todo.status) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: getTextClass(todo.status), children: todo.title }), todo.instructions && (_jsx("div", { className: "mt-0.5 text-[11px] text-gray-500", children: todo.instructions }))] })] }, todo.id || `${todo.messageId}-${idx}`))) })), isUpdating && (_jsxs("div", { className: `flex items-center gap-2 rounded px-3 py-2 text-[11px] ${todos.length > 0
|
|
555
|
-
? "bg-theme-secondary-light text-theme-secondary mt-2"
|
|
556
|
-
: "justify-center bg-white text-gray-500"}`, children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), _jsx("span", { children: todos.length > 0
|
|
557
|
-
? "Updating todo list..."
|
|
558
|
-
: "Loading todo list..." })] }))] }))] }));
|
|
559
|
-
};
|
|
560
|
-
const groupConsecutiveMessages = (agentMessages) => {
|
|
561
|
-
// Work directly with the messages array - streaming messages are identified by their properties
|
|
562
|
-
const allMessages = agentMessages;
|
|
563
|
-
const groups = [];
|
|
564
|
-
let currentAssistantGroup = [];
|
|
565
|
-
for (const message of allMessages) {
|
|
566
|
-
if (message.role === "user") {
|
|
567
|
-
// Finish any current assistant group
|
|
568
|
-
if (currentAssistantGroup.length > 0) {
|
|
569
|
-
groups.push({
|
|
570
|
-
type: "assistant-group",
|
|
571
|
-
messages: currentAssistantGroup,
|
|
572
|
-
});
|
|
573
|
-
currentAssistantGroup = [];
|
|
574
|
-
}
|
|
575
|
-
// Add user message
|
|
576
|
-
groups.push({ type: "user", messages: [message] });
|
|
577
|
-
}
|
|
578
|
-
else if (message.messageType === "heartbeat" ||
|
|
579
|
-
message.role === "system") {
|
|
580
|
-
if (currentAssistantGroup.length > 0) {
|
|
581
|
-
groups.push({
|
|
582
|
-
type: "assistant-group",
|
|
583
|
-
messages: currentAssistantGroup,
|
|
584
|
-
});
|
|
585
|
-
currentAssistantGroup = [];
|
|
586
|
-
}
|
|
587
|
-
groups.push({ type: "heartbeat", messages: [message] });
|
|
588
|
-
}
|
|
589
|
-
else if (message.role === "assistant") {
|
|
590
|
-
// Add to current assistant group
|
|
591
|
-
currentAssistantGroup.push(message);
|
|
592
|
-
}
|
|
593
|
-
// Skip tool messages as they're handled within assistant messages
|
|
594
|
-
}
|
|
595
|
-
// Add any remaining assistant group
|
|
596
|
-
if (currentAssistantGroup.length > 0) {
|
|
597
|
-
groups.push({ type: "assistant-group", messages: currentAssistantGroup });
|
|
598
|
-
}
|
|
599
|
-
return groups;
|
|
600
|
-
};
|
|
601
|
-
// Merge messages from DB and local state with ID-based deduplication
|
|
602
|
-
const mergeMessagesById = (dbMessages, localMessages) => {
|
|
603
|
-
const messageMap = new Map();
|
|
604
|
-
// Normalize ID key (lowercase) to avoid duplicates caused by casing differences
|
|
605
|
-
const keyOf = (id) => (id ? id.toLowerCase() : "");
|
|
606
|
-
// First, add all DB messages (source of truth for completed messages)
|
|
607
|
-
dbMessages.forEach((msg) => {
|
|
608
|
-
if (msg.id)
|
|
609
|
-
messageMap.set(keyOf(msg.id), msg);
|
|
610
|
-
});
|
|
611
|
-
// Then merge local messages (preserve streaming state, prefer longer content)
|
|
612
|
-
localMessages.forEach((localMsg) => {
|
|
613
|
-
if (!localMsg.id)
|
|
614
|
-
return;
|
|
615
|
-
const key = keyOf(localMsg.id);
|
|
616
|
-
const existingMsg = messageMap.get(key);
|
|
617
|
-
if (!existingMsg) {
|
|
618
|
-
// New message only in local state (e.g., streaming)
|
|
619
|
-
messageMap.set(key, localMsg);
|
|
620
|
-
}
|
|
621
|
-
else if (!localMsg.isCompleted && localMsg.messageType === "streaming") {
|
|
622
|
-
// Keep streaming version if more recent/longer (considering both content and reasoning)
|
|
623
|
-
const localLen = (localMsg.content?.length || 0) + (localMsg.reasoning?.length || 0);
|
|
624
|
-
const existingLen = (existingMsg.content?.length || 0) +
|
|
625
|
-
(existingMsg.reasoning?.length || 0);
|
|
626
|
-
if (localLen > existingLen) {
|
|
627
|
-
messageMap.set(key, localMsg);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
// Otherwise, keep the DB version (completed messages from DB are authoritative)
|
|
631
|
-
});
|
|
632
|
-
// Sort by messageIndex or createdDate to maintain order
|
|
633
|
-
// Ignore messageIndex if it's -1 (unassigned/streaming)
|
|
634
|
-
return Array.from(messageMap.values()).sort((a, b) => {
|
|
635
|
-
const aIndex = a.messageIndex === -1 ? Infinity : a.messageIndex;
|
|
636
|
-
const bIndex = b.messageIndex === -1 ? Infinity : b.messageIndex;
|
|
637
|
-
if (aIndex !== bIndex) {
|
|
638
|
-
return aIndex - bIndex;
|
|
639
|
-
}
|
|
640
|
-
return (new Date(a.createdDate || 0).getTime() -
|
|
641
|
-
new Date(b.createdDate || 0).getTime());
|
|
642
|
-
});
|
|
643
|
-
};
|
|
644
|
-
// Calculate total token usage and cost data from agent messages
|
|
645
|
-
const calculateTotalTokens = (messages) => {
|
|
646
|
-
const totals = messages.reduce((acc, message) => {
|
|
647
|
-
return {
|
|
648
|
-
input: acc.input + (message.inputTokens || 0),
|
|
649
|
-
output: acc.output + (message.outputTokens || 0),
|
|
650
|
-
cached: acc.cached + (message.cachedInputTokens || 0),
|
|
651
|
-
cacheWrite: acc.cacheWrite,
|
|
652
|
-
inputCost: acc.inputCost + (message.inputTokenCost || 0),
|
|
653
|
-
outputCost: acc.outputCost + (message.outputTokenCost || 0),
|
|
654
|
-
cachedCost: acc.cachedCost + (message.cachedInputTokenCost || 0),
|
|
655
|
-
cacheWriteCost: acc.cacheWriteCost,
|
|
656
|
-
imageCost: acc.imageCost,
|
|
657
|
-
totalCost: acc.totalCost + (message.totalCost || 0),
|
|
658
|
-
};
|
|
659
|
-
}, {
|
|
660
|
-
input: 0,
|
|
661
|
-
output: 0,
|
|
662
|
-
cached: 0,
|
|
663
|
-
cacheWrite: 0,
|
|
664
|
-
inputCost: 0,
|
|
665
|
-
outputCost: 0,
|
|
666
|
-
cachedCost: 0,
|
|
667
|
-
cacheWriteCost: 0,
|
|
668
|
-
imageCost: 0,
|
|
669
|
-
totalCost: 0,
|
|
670
|
-
});
|
|
671
|
-
return totals;
|
|
672
|
-
};
|
|
673
|
-
// Get edit operations for a message group by matching toolCallIds
|
|
674
|
-
const getOperationsForMessageGroup = (messages, agentOperations) => {
|
|
675
|
-
const toolCallIds = new Set(messages.flatMap((m) => m.tool_calls?.map((tc) => tc.id) ?? []));
|
|
676
|
-
const matched = agentOperations.filter((op) => op.toolCallId && toolCallIds.has(op.toolCallId));
|
|
677
|
-
return matched;
|
|
678
|
-
};
|
|
679
|
-
const stringifyToolField = (value) => {
|
|
680
|
-
if (value === undefined || value === null)
|
|
681
|
-
return undefined;
|
|
682
|
-
if (typeof value === "string") {
|
|
683
|
-
return value.trim().length > 0 ? value : undefined;
|
|
684
|
-
}
|
|
685
|
-
try {
|
|
686
|
-
return JSON.stringify(value);
|
|
687
|
-
}
|
|
688
|
-
catch {
|
|
689
|
-
return String(value);
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
const parseToolResultValue = (value) => {
|
|
693
|
-
if (value === undefined || value === null) {
|
|
694
|
-
return undefined;
|
|
695
|
-
}
|
|
696
|
-
if (typeof value === "object") {
|
|
697
|
-
return value;
|
|
698
|
-
}
|
|
699
|
-
if (typeof value !== "string") {
|
|
700
|
-
return String(value);
|
|
701
|
-
}
|
|
702
|
-
const trimmed = value.trim();
|
|
703
|
-
if (!trimmed) {
|
|
704
|
-
return undefined;
|
|
705
|
-
}
|
|
706
|
-
try {
|
|
707
|
-
let parsed = JSON.parse(trimmed);
|
|
708
|
-
if (typeof parsed === "string" &&
|
|
709
|
-
(parsed.startsWith("{") || parsed.startsWith("["))) {
|
|
710
|
-
parsed = JSON.parse(parsed);
|
|
711
|
-
}
|
|
712
|
-
if (parsed && typeof parsed === "object") {
|
|
713
|
-
return parsed;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
catch {
|
|
717
|
-
// Fall back to the original string when the payload is plain text.
|
|
718
|
-
}
|
|
719
|
-
return value;
|
|
720
|
-
};
|
|
721
|
-
const getFirstToolCallEnvelope = (data) => {
|
|
722
|
-
if (!data || typeof data !== "object")
|
|
723
|
-
return undefined;
|
|
724
|
-
const direct = data.toolCall || data.tool_call;
|
|
725
|
-
if (direct)
|
|
726
|
-
return direct;
|
|
727
|
-
const arrayCandidates = [data.tool_calls, data.toolCalls];
|
|
728
|
-
for (const candidate of arrayCandidates) {
|
|
729
|
-
if (Array.isArray(candidate) && candidate.length > 0) {
|
|
730
|
-
return candidate[0];
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return undefined;
|
|
734
|
-
};
|
|
735
|
-
const extractToolCallFields = (data) => {
|
|
736
|
-
const envelope = getFirstToolCallEnvelope(data);
|
|
737
|
-
const functionPayload = data?.function || envelope?.function;
|
|
738
|
-
const functionName = data?.functionName ||
|
|
739
|
-
data?.name ||
|
|
740
|
-
functionPayload?.name ||
|
|
741
|
-
envelope?.functionName ||
|
|
742
|
-
envelope?.name ||
|
|
743
|
-
"unknown";
|
|
744
|
-
const toolCallId = data?.toolCallId || data?.id || envelope?.id;
|
|
745
|
-
const functionArguments = stringifyToolField(data?.functionArguments) ||
|
|
746
|
-
stringifyToolField(data?.arguments) ||
|
|
747
|
-
stringifyToolField(functionPayload?.arguments) ||
|
|
748
|
-
stringifyToolField(envelope?.functionArguments) ||
|
|
749
|
-
stringifyToolField(envelope?.arguments) ||
|
|
750
|
-
"{}";
|
|
751
|
-
return {
|
|
752
|
-
toolCallId,
|
|
753
|
-
functionName,
|
|
754
|
-
functionArguments,
|
|
755
|
-
};
|
|
756
|
-
};
|
|
757
|
-
// Convert agent messages to AI terminal format for a response group
|
|
758
|
-
const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
759
|
-
return agentMessages.map((agentMessage) => {
|
|
760
|
-
const message = {
|
|
761
|
-
id: agentMessage.id,
|
|
762
|
-
content: agentMessage.content,
|
|
763
|
-
reasoning: agentMessage.reasoning,
|
|
764
|
-
formattedContent: agentMessage.content
|
|
765
|
-
?.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>")
|
|
766
|
-
?.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>")
|
|
767
|
-
?.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>")
|
|
768
|
-
?.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>")
|
|
769
|
-
?.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>")
|
|
770
|
-
?.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>")
|
|
771
|
-
?.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
|
772
|
-
?.replace(/\n/g, "<br/>"),
|
|
773
|
-
name: agentMessage.name,
|
|
774
|
-
role: agentMessage.role,
|
|
775
|
-
createdDate: agentMessage.createdDate,
|
|
776
|
-
tool_calls: agentMessage.toolCalls
|
|
777
|
-
? agentMessage.toolCalls.map((toolCall) => {
|
|
778
|
-
const isPruned = !!toolCall.isPruned ||
|
|
779
|
-
/^PRUNED$/i.test(toolCall.functionError || "");
|
|
780
|
-
const displayResult = parseToolResultValue(toolCall.functionResultRichContent) ?? toolCall.functionResult;
|
|
781
|
-
return {
|
|
782
|
-
id: toolCall.toolCallId,
|
|
783
|
-
displayName: toolCall.functionName,
|
|
784
|
-
function: {
|
|
785
|
-
name: toolCall.functionName,
|
|
786
|
-
arguments: toolCall.functionArguments,
|
|
787
|
-
result: displayResult,
|
|
788
|
-
error: toolCall.functionError,
|
|
789
|
-
},
|
|
790
|
-
// Pass through approval info if present on the tool call
|
|
791
|
-
requiresApproval: toolCall.requiresApproval,
|
|
792
|
-
// Pass through prune metadata so the terminal can render a neutral state
|
|
793
|
-
isPruned,
|
|
794
|
-
prunedAt: toolCall.prunedAt,
|
|
795
|
-
// Pass through isCompleted so ToolCallDisplay knows when to hide spinner
|
|
796
|
-
isCompleted: toolCall.isCompleted,
|
|
797
|
-
// Tool call is streaming if message is not completed and tool call has no result yet
|
|
798
|
-
isStreaming: !agentMessage.isCompleted &&
|
|
799
|
-
!toolCall.isCompleted &&
|
|
800
|
-
!displayResult &&
|
|
801
|
-
!toolCall.functionError &&
|
|
802
|
-
!isPruned,
|
|
803
|
-
// Pass through message IDs for approval/rejection events
|
|
804
|
-
messageId: toolCall.messageId,
|
|
805
|
-
dbMessageId: toolCall.dbMessageId,
|
|
806
|
-
responseTimeMs: toolCall.responseTimeMs,
|
|
807
|
-
createdDate: toolCall.createdDate,
|
|
808
|
-
};
|
|
809
|
-
})
|
|
810
|
-
: [],
|
|
811
|
-
};
|
|
812
|
-
if (agentMessage.toolCallId) {
|
|
813
|
-
message.tool_call_id = agentMessage.toolCallId;
|
|
814
|
-
}
|
|
815
|
-
return message;
|
|
816
|
-
});
|
|
817
|
-
};
|
|
818
47
|
// interface AgentTerminalProps {
|
|
819
48
|
// agentStub: Agent;
|
|
820
49
|
// }
|
|
821
|
-
export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, displayMode = "full", showSummaryInput = false, hideContext = false, hideBottomControls = false, hideGreeting = false, defaultCollapseJson = false, simpleMode = false, className, initialPrompt, onAgentUpdate, onMessage, onInteractionSubmitted, onQuestionnaireOpenChange, questionnaireFooterActions, hideSummaryMessages = false, summaryPlaceholderActions, summaryPlaceholderMessage, hideSummaryWaitingPlaceholder = false, }) {
|
|
50
|
+
export function AgentTerminal({ agentStub, initialMetadata, profiles, onReloadProfiles, isActive = true, compact = false, displayMode = "full", showSummaryInput = false, hideContext = false, hideBottomControls = false, hideGreeting = false, defaultCollapseJson = false, simpleMode = false, className, initialPrompt, onAgentUpdate, onMessage, onInteractionSubmitted, onQuestionnaireOpenChange, questionnaireFooterActions, hideSummaryMessages = false, summaryPlaceholderActions, summaryPlaceholderMessage, hideSummaryWaitingPlaceholder = false, }) {
|
|
822
51
|
const editContext = useEditContext();
|
|
823
52
|
const fieldsContext = useFieldsEditContext();
|
|
824
53
|
const [agent, setAgent] = useState(() => buildPlaceholderAgentDetails(agentStub));
|
|
825
54
|
const [messages, setMessages] = useState([]);
|
|
826
55
|
const [agentOperations, setAgentOperations] = useState([]);
|
|
827
|
-
const [prompt, setPrompt] = useState("");
|
|
56
|
+
const [prompt, setPrompt] = useState(() => localStorageService.getItem(`editor.agent.draftPrompt.${agentStub.id}`) || "");
|
|
828
57
|
const [inputPlaceholder, setInputPlaceholder] = useState("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
|
|
829
|
-
|
|
58
|
+
// Existing (persisted) agents start in loading state so the loading
|
|
59
|
+
// screen renders on first paint — avoids an empty-terminal flash between
|
|
60
|
+
// mount and the loadAgent() effect.
|
|
61
|
+
const [isLoading, setIsLoading] = useState(() => agentStub.status !== "new");
|
|
830
62
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
831
63
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
832
64
|
const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
|
|
@@ -1154,7 +386,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1154
386
|
};
|
|
1155
387
|
const [mode, setMode] = useState("supervised");
|
|
1156
388
|
const [queuedPrompts, setQueuedPrompts] = useState([]);
|
|
1157
|
-
const [expandedQueuedTriggerIds, setExpandedQueuedTriggerIds] = useState({});
|
|
1158
389
|
const [contextPanelsActiveTab, setContextPanelsActiveTab] = useState(0);
|
|
1159
390
|
const [hiddenContextPanelTabIds, setHiddenContextPanelTabIds] = useState(new Set());
|
|
1160
391
|
const [showCostAndAgent, setShowCostAndAgent] = useState(false);
|
|
@@ -1364,6 +595,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1364
595
|
active = false;
|
|
1365
596
|
};
|
|
1366
597
|
}
|
|
598
|
+
// Always refresh profiles (and therefore available models) when the
|
|
599
|
+
// Agent settings popover opens, so changes made in Sitecore are picked
|
|
600
|
+
// up even if the websocket invalidation was missed.
|
|
601
|
+
invalidateAiProfilesCache();
|
|
602
|
+
void onReloadProfiles?.();
|
|
1367
603
|
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
1368
604
|
setTriggerSubscriptions([]);
|
|
1369
605
|
setTriggerSubscriptionsLoading(false);
|
|
@@ -1477,24 +713,71 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1477
713
|
agent?.userId,
|
|
1478
714
|
agent?.profileId,
|
|
1479
715
|
mode,
|
|
716
|
+
onReloadProfiles,
|
|
1480
717
|
]);
|
|
1481
718
|
const activeTriggerSubscriptions = useMemo(() => triggerSubscriptions.filter((sub) => sub.isActive), [triggerSubscriptions]);
|
|
719
|
+
const profileOperationAllowances = useMemo(() => {
|
|
720
|
+
const profileSource = activeProfile?.id
|
|
721
|
+
? `preconfigured:profile:${activeProfile.id}`
|
|
722
|
+
: "preconfigured:profile";
|
|
723
|
+
return {
|
|
724
|
+
sitecore: (activeProfile?.sitecoreAllowances ?? []).flatMap((allowance) => {
|
|
725
|
+
const itemIdentifier = String(allowance?.itemIdentifier || "").trim();
|
|
726
|
+
if (!itemIdentifier)
|
|
727
|
+
return [];
|
|
728
|
+
return normalizeProfileAllowanceOperations(allowance.operationTypes).map((operationType) => ({
|
|
729
|
+
itemId: itemIdentifier,
|
|
730
|
+
itemPath: itemIdentifier,
|
|
731
|
+
operationType,
|
|
732
|
+
scopeType: "itemSubtree",
|
|
733
|
+
source: profileSource,
|
|
734
|
+
grantedBy: null,
|
|
735
|
+
grantedAt: null,
|
|
736
|
+
}));
|
|
737
|
+
}),
|
|
738
|
+
filesystem: (activeProfile?.filesystemAllowances ?? []).flatMap((allowance) => {
|
|
739
|
+
const normalizedPath = String(allowance?.path || "").trim();
|
|
740
|
+
if (!normalizedPath)
|
|
741
|
+
return [];
|
|
742
|
+
return normalizeProfileAllowanceOperations(allowance.operationTypes).map((operationType) => ({
|
|
743
|
+
normalizedPath,
|
|
744
|
+
operationType,
|
|
745
|
+
source: profileSource,
|
|
746
|
+
grantedBy: null,
|
|
747
|
+
grantedAt: null,
|
|
748
|
+
}));
|
|
749
|
+
}),
|
|
750
|
+
};
|
|
751
|
+
}, [
|
|
752
|
+
activeProfile?.filesystemAllowances,
|
|
753
|
+
activeProfile?.id,
|
|
754
|
+
activeProfile?.sitecoreAllowances,
|
|
755
|
+
]);
|
|
756
|
+
const displayedOperationAllowances = isLocalOnlyDraftAgent
|
|
757
|
+
? profileOperationAllowances
|
|
758
|
+
: operationAllowances;
|
|
759
|
+
const displayedOperationAllowancesLoading = isLocalOnlyDraftAgent
|
|
760
|
+
? false
|
|
761
|
+
: operationAllowancesLoading;
|
|
762
|
+
const displayedOperationAllowancesError = isLocalOnlyDraftAgent
|
|
763
|
+
? null
|
|
764
|
+
: operationAllowancesError;
|
|
1482
765
|
const allowanceGroups = useMemo(() => [
|
|
1483
766
|
{
|
|
1484
767
|
key: "sitecore",
|
|
1485
768
|
label: "Sitecore",
|
|
1486
|
-
rows:
|
|
769
|
+
rows: displayedOperationAllowances.sitecore,
|
|
1487
770
|
},
|
|
1488
771
|
{
|
|
1489
772
|
key: "filesystem",
|
|
1490
773
|
label: "Filesystem",
|
|
1491
|
-
rows:
|
|
774
|
+
rows: displayedOperationAllowances.filesystem,
|
|
1492
775
|
},
|
|
1493
|
-
], [
|
|
1494
|
-
const hasAnyAllowances = useMemo(() =>
|
|
1495
|
-
|
|
1496
|
-
const allowancesTotalCount = useMemo(() =>
|
|
1497
|
-
|
|
776
|
+
], [displayedOperationAllowances]);
|
|
777
|
+
const hasAnyAllowances = useMemo(() => displayedOperationAllowances.sitecore.length > 0 ||
|
|
778
|
+
displayedOperationAllowances.filesystem.length > 0, [displayedOperationAllowances]);
|
|
779
|
+
const allowancesTotalCount = useMemo(() => displayedOperationAllowances.sitecore.length +
|
|
780
|
+
displayedOperationAllowances.filesystem.length, [displayedOperationAllowances]);
|
|
1498
781
|
const listedProfileSkillIdSet = useMemo(() => {
|
|
1499
782
|
const ids = [
|
|
1500
783
|
...(activeProfile?.availableSkills ?? []),
|
|
@@ -1813,6 +1096,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1813
1096
|
useEffect(() => {
|
|
1814
1097
|
localStorageService.setItem("editor.agent.promptHistory", promptHistory);
|
|
1815
1098
|
}, [promptHistory]);
|
|
1099
|
+
// Rehydrate draft when switching to a different agent so each agent keeps its own draft.
|
|
1100
|
+
useEffect(() => {
|
|
1101
|
+
setPrompt(localStorageService.getItem(`editor.agent.draftPrompt.${agentStub.id}`) || "");
|
|
1102
|
+
}, [agentStub.id]);
|
|
1103
|
+
// Persist the in-progress prompt per agent so it survives workspace switches/unmounts.
|
|
1104
|
+
useEffect(() => {
|
|
1105
|
+
const key = `editor.agent.draftPrompt.${agentStub.id}`;
|
|
1106
|
+
if (prompt) {
|
|
1107
|
+
localStorageService.setItem(key, prompt);
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
localStorageService.removeItem(key);
|
|
1111
|
+
}
|
|
1112
|
+
}, [prompt, agentStub.id]);
|
|
1816
1113
|
useEffect(() => {
|
|
1817
1114
|
// Keep messagesRef synchronized with messages state
|
|
1818
1115
|
messagesRef.current = messages;
|
|
@@ -1858,6 +1155,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1858
1155
|
const documentListRef = useRef(null);
|
|
1859
1156
|
const placeholderInputRef = useRef(null);
|
|
1860
1157
|
const promptPlaceholderInputRef = useRef(null);
|
|
1158
|
+
const shouldSubmitFilledPlaceholderInputRef = useRef(false);
|
|
1861
1159
|
const messagesContainerRef = useRef(null);
|
|
1862
1160
|
const inlineDialogContainerRef = useRef(null);
|
|
1863
1161
|
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
|
|
@@ -1905,16 +1203,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1905
1203
|
setIsVoiceSupported(false);
|
|
1906
1204
|
}
|
|
1907
1205
|
}, []);
|
|
1908
|
-
// Auto-focus terminal input on mount
|
|
1206
|
+
// Auto-focus terminal input on mount, and place cursor at the end of any
|
|
1207
|
+
// restored draft prompt (e.g. when switching workspaces with the same agent).
|
|
1909
1208
|
useEffect(() => {
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
textareaRef.current.focus();
|
|
1916
|
-
}
|
|
1209
|
+
const textarea = textareaRef.current;
|
|
1210
|
+
if (!textarea)
|
|
1211
|
+
return;
|
|
1212
|
+
try {
|
|
1213
|
+
textarea.focus({ preventScroll: true });
|
|
1917
1214
|
}
|
|
1215
|
+
catch {
|
|
1216
|
+
textarea.focus();
|
|
1217
|
+
}
|
|
1218
|
+
const end = textarea.value.length;
|
|
1219
|
+
textarea.selectionStart = end;
|
|
1220
|
+
textarea.selectionEnd = end;
|
|
1918
1221
|
}, []);
|
|
1919
1222
|
// Start voice recognition
|
|
1920
1223
|
const startVoice = () => {
|
|
@@ -2311,12 +1614,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2311
1614
|
output: Number(cost.tokens?.output) || 0,
|
|
2312
1615
|
cached: Number(cost.tokens?.cached) || 0,
|
|
2313
1616
|
cacheWrite: Number(cost.tokens?.cacheWrite) || 0,
|
|
1617
|
+
inputAudio: Number(cost.tokens?.inputAudio) || 0,
|
|
1618
|
+
outputAudio: Number(cost.tokens?.outputAudio) || 0,
|
|
1619
|
+
outputImage: Number(cost.tokens?.outputImage) || 0,
|
|
1620
|
+
reasoning: Number(cost.tokens?.reasoning) || 0,
|
|
2314
1621
|
inputCost: Number(cost.input) || 0,
|
|
2315
1622
|
outputCost: Number(cost.output) || 0,
|
|
2316
1623
|
cachedCost: Number(cost.cached) || 0,
|
|
2317
1624
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
1625
|
+
inputAudioCost: Number(cost.inputAudio) || 0,
|
|
1626
|
+
outputAudioCost: Number(cost.outputAudio) || 0,
|
|
1627
|
+
outputImageCost: Number(cost.outputImage) || 0,
|
|
2318
1628
|
imageCost: Number(cost.imageCost ?? cost.totalImageCost) ||
|
|
2319
1629
|
0,
|
|
1630
|
+
markupCost: Number(cost.markup) || 0,
|
|
1631
|
+
reportedTotalCost: Number(cost.reportedTotal) || 0,
|
|
2320
1632
|
totalCost: Number(cost.total) || 0,
|
|
2321
1633
|
currency: "USD",
|
|
2322
1634
|
};
|
|
@@ -2650,11 +1962,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2650
1962
|
output: Number(cost.tokens?.output) || 0,
|
|
2651
1963
|
cached: Number(cost.tokens?.cached) || 0,
|
|
2652
1964
|
cacheWrite: Number(cost.tokens?.cacheWrite) || 0,
|
|
1965
|
+
inputAudio: Number(cost.tokens?.inputAudio) || 0,
|
|
1966
|
+
outputAudio: Number(cost.tokens?.outputAudio) || 0,
|
|
1967
|
+
outputImage: Number(cost.tokens?.outputImage) || 0,
|
|
1968
|
+
reasoning: Number(cost.tokens?.reasoning) || 0,
|
|
2653
1969
|
inputCost: Number(cost.input) || 0,
|
|
2654
1970
|
outputCost: Number(cost.output) || 0,
|
|
2655
1971
|
cachedCost: Number(cost.cached) || 0,
|
|
2656
1972
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
1973
|
+
inputAudioCost: Number(cost.inputAudio) || 0,
|
|
1974
|
+
outputAudioCost: Number(cost.outputAudio) || 0,
|
|
1975
|
+
outputImageCost: Number(cost.outputImage) || 0,
|
|
2657
1976
|
imageCost: Number(cost.imageCost) || 0,
|
|
1977
|
+
markupCost: Number(cost.markup) || 0,
|
|
1978
|
+
reportedTotalCost: Number(cost.reportedTotal) || 0,
|
|
2658
1979
|
totalCost: Number(cost.total) || 0,
|
|
2659
1980
|
currency: "USD",
|
|
2660
1981
|
};
|
|
@@ -2699,11 +2020,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2699
2020
|
output: Number(data.totalOutputTokens) || 0,
|
|
2700
2021
|
cached: Number(data.totalCachedTokens) || 0,
|
|
2701
2022
|
cacheWrite: Number(data.totalCacheWriteTokens) || 0,
|
|
2023
|
+
inputAudio: Number(data.totalInputAudioTokens) || 0,
|
|
2024
|
+
outputAudio: Number(data.totalOutputAudioTokens) || 0,
|
|
2025
|
+
outputImage: Number(data.totalOutputImageTokens) || 0,
|
|
2026
|
+
reasoning: Number(data.totalReasoningTokens) || 0,
|
|
2702
2027
|
inputCost: Number(data.totalInputTokenCost) || 0,
|
|
2703
2028
|
outputCost: Number(data.totalOutputTokenCost) || 0,
|
|
2704
2029
|
cachedCost: Number(data.totalCachedTokenCost) || 0,
|
|
2705
2030
|
cacheWriteCost: Number(data.totalCacheWriteTokenCost) || 0,
|
|
2031
|
+
inputAudioCost: Number(data.totalInputAudioCost) || 0,
|
|
2032
|
+
outputAudioCost: Number(data.totalOutputAudioCost) || 0,
|
|
2033
|
+
outputImageCost: Number(data.totalOutputImageCost) || 0,
|
|
2706
2034
|
imageCost: Number(data.totalImageCost) || 0,
|
|
2035
|
+
markupCost: Number(data.totalMarkupCost) || 0,
|
|
2036
|
+
reportedTotalCost: Number(data.totalReportedCost) || 0,
|
|
2707
2037
|
totalCost: Number(data.totalCost) || 0,
|
|
2708
2038
|
currency: data.currency || "USD",
|
|
2709
2039
|
};
|
|
@@ -3697,11 +3027,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3697
3027
|
output: Number(totals.totalOutputTokens) || 0,
|
|
3698
3028
|
cached: Number(totals.totalCachedInputTokens) || 0,
|
|
3699
3029
|
cacheWrite: Number(totals.totalCacheWriteTokens) || 0,
|
|
3030
|
+
inputAudio: Number(totals.totalInputAudioTokens) || 0,
|
|
3031
|
+
outputAudio: Number(totals.totalOutputAudioTokens) || 0,
|
|
3032
|
+
outputImage: Number(totals.totalOutputImageTokens) || 0,
|
|
3033
|
+
reasoning: Number(totals.totalReasoningTokens) || 0,
|
|
3700
3034
|
inputCost: Number(totals.totalInputTokenCost) || 0,
|
|
3701
3035
|
outputCost: Number(totals.totalOutputTokenCost) || 0,
|
|
3702
3036
|
cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
|
|
3703
3037
|
cacheWriteCost: Number(totals.totalCacheWriteTokenCost) || 0,
|
|
3038
|
+
inputAudioCost: Number(totals.totalInputAudioCost) || 0,
|
|
3039
|
+
outputAudioCost: Number(totals.totalOutputAudioCost) || 0,
|
|
3040
|
+
outputImageCost: Number(totals.totalOutputImageCost) || 0,
|
|
3704
3041
|
imageCost: Number(totals.totalImageCost) || 0,
|
|
3042
|
+
markupCost: Number(totals.totalMarkupCost) || 0,
|
|
3043
|
+
reportedTotalCost: Number(totals.totalReportedCost) || 0,
|
|
3705
3044
|
totalCost: totalCost,
|
|
3706
3045
|
currency: totals.currency,
|
|
3707
3046
|
};
|
|
@@ -4468,15 +3807,44 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4468
3807
|
}
|
|
4469
3808
|
}, []);
|
|
4470
3809
|
const handleSubmit = async (promptOverride) => {
|
|
3810
|
+
const hasPromptOverride = promptOverride !== undefined;
|
|
4471
3811
|
// Guard against double-submit and missing context
|
|
4472
3812
|
if (isSubmitting) {
|
|
4473
3813
|
console.warn("[AgentTerminal] handleSubmit blocked: already submitting");
|
|
4474
3814
|
return;
|
|
4475
3815
|
}
|
|
4476
|
-
//
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
3816
|
+
// If there are pending tool call approvals, treat this submission as a
|
|
3817
|
+
// supersede: reject all pending tool calls and continue with the new prompt.
|
|
3818
|
+
const pendingToReject = allPendingApprovals;
|
|
3819
|
+
if (pendingToReject.length > 0) {
|
|
3820
|
+
const agentIdForReject = agent?.id || agentStub.id;
|
|
3821
|
+
if (!agentIdForReject) {
|
|
3822
|
+
console.error("[AgentTerminal] handleSubmit blocked: cannot supersede pending approvals — no agentId");
|
|
3823
|
+
setError("Agent not ready. Please try again.");
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
try {
|
|
3827
|
+
await Promise.all(pendingToReject.map(async (pending) => {
|
|
3828
|
+
await rejectToolCall({
|
|
3829
|
+
agentId: agentIdForReject,
|
|
3830
|
+
messageId: pending.dbMessageId || pending.messageId,
|
|
3831
|
+
toolCallId: pending.toolCallId,
|
|
3832
|
+
note: "Superseded by new user instructions",
|
|
3833
|
+
});
|
|
3834
|
+
window.dispatchEvent(new CustomEvent("agent:toolApprovalResolved", {
|
|
3835
|
+
detail: {
|
|
3836
|
+
messageId: pending.dbMessageId || pending.messageId,
|
|
3837
|
+
toolCallId: pending.toolCallId,
|
|
3838
|
+
approved: false,
|
|
3839
|
+
},
|
|
3840
|
+
}));
|
|
3841
|
+
}));
|
|
3842
|
+
}
|
|
3843
|
+
catch (rejectErr) {
|
|
3844
|
+
console.error("[AgentTerminal] Failed to supersede pending tool calls:", rejectErr);
|
|
3845
|
+
setError(`Failed to cancel pending tool call: ${rejectErr?.message || String(rejectErr)}`);
|
|
3846
|
+
return;
|
|
3847
|
+
}
|
|
4480
3848
|
}
|
|
4481
3849
|
if (!editContext) {
|
|
4482
3850
|
console.error("[AgentTerminal] handleSubmit blocked: editContext is undefined");
|
|
@@ -4484,12 +3852,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4484
3852
|
return;
|
|
4485
3853
|
}
|
|
4486
3854
|
// If placeholder input is active, delegate to its submit method
|
|
4487
|
-
if (activePlaceholderInput && allPlaceholdersFilled) {
|
|
4488
|
-
placeholderInputRef.current
|
|
3855
|
+
if (!hasPromptOverride && activePlaceholderInput && allPlaceholdersFilled) {
|
|
3856
|
+
const placeholderInput = placeholderInputRef.current;
|
|
3857
|
+
if (!placeholderInput)
|
|
3858
|
+
return;
|
|
3859
|
+
shouldSubmitFilledPlaceholderInputRef.current = true;
|
|
3860
|
+
placeholderInput.submit();
|
|
4489
3861
|
return;
|
|
4490
3862
|
}
|
|
4491
3863
|
// If prompt contains placeholders, delegate to the prompt placeholder input's submit method
|
|
4492
|
-
if (
|
|
3864
|
+
if (!hasPromptOverride &&
|
|
3865
|
+
prompt &&
|
|
4493
3866
|
/\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) &&
|
|
4494
3867
|
allPlaceholdersFilled) {
|
|
4495
3868
|
promptPlaceholderInputRef.current?.submit();
|
|
@@ -5554,6 +4927,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5554
4927
|
: "completed");
|
|
5555
4928
|
return;
|
|
5556
4929
|
}
|
|
4930
|
+
// Backstop: if the backend reports an error but we never received the
|
|
4931
|
+
// agent:run:error lifecycle event (e.g. it was missed or the broadcast
|
|
4932
|
+
// path failed to fire), surface the error locally instead of waiting
|
|
4933
|
+
// for the user to reload.
|
|
4934
|
+
if (normalizeServerExecutionStatus(serverStatus) === "error") {
|
|
4935
|
+
const rawError = diagnostics.execution?.error ?? null;
|
|
4936
|
+
const errorMsg = toUserFacingAgentErrorMessage(rawError) ||
|
|
4937
|
+
rawError ||
|
|
4938
|
+
"AI could not complete this request.";
|
|
4939
|
+
clearHeartbeatMessages();
|
|
4940
|
+
setLastRunStatusReason(null);
|
|
4941
|
+
setError(errorMsg);
|
|
4942
|
+
setAgent((prev) => prev ? { ...prev, status: "error", statusMessage: errorMsg } : prev);
|
|
4943
|
+
setIsWaitingForResponse(false);
|
|
4944
|
+
isWaitingRef.current = false;
|
|
4945
|
+
setIsConnecting(false);
|
|
4946
|
+
setIsAgentThinking(false);
|
|
4947
|
+
return;
|
|
4948
|
+
}
|
|
5557
4949
|
const serverLastSeq = diagnostics.currentSession?.lastDelivery?.lastSeq ??
|
|
5558
4950
|
diagnostics.transport?.lastSeq ??
|
|
5559
4951
|
null;
|
|
@@ -5621,6 +5013,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5621
5013
|
isActive,
|
|
5622
5014
|
isExecuting,
|
|
5623
5015
|
appendToolUiEvent,
|
|
5016
|
+
clearHeartbeatMessages,
|
|
5624
5017
|
loadAgent,
|
|
5625
5018
|
settleCompletedRun,
|
|
5626
5019
|
]);
|
|
@@ -5712,56 +5105,47 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5712
5105
|
detail: { agentId: parentAgentId },
|
|
5713
5106
|
}));
|
|
5714
5107
|
}, [parentAgentId]);
|
|
5715
|
-
const
|
|
5108
|
+
const shouldShowLoadingContent = isLoading && !activeInlineDialog && !agent && messages.length === 0;
|
|
5109
|
+
const loadingContent = shouldShowLoadingContent ? (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-[11px] text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) })) : null;
|
|
5716
5110
|
const renderContextInfoBar = () => (_jsx(ContextInfoBar, { agent: agent, agentMetadata: agentMetadata, setAgentMetadata: setAgentMetadata, setAgent: setAgent, resolvedPageName: resolvedPageName, resolvedComponentName: resolvedComponentName, resolvedFieldName: resolvedFieldName, isLiveEditorContextMode: isLiveEditorContextMode, omitEditorContext: omitsEditorContext, onRefreshContext: handleRefreshContext }));
|
|
5111
|
+
const handleExtendCostLimit = useCallback(async () => {
|
|
5112
|
+
if (!agent?.id)
|
|
5113
|
+
return;
|
|
5114
|
+
try {
|
|
5115
|
+
const result = await updateAgentCostLimit(agent.id, "extend");
|
|
5116
|
+
if (result.success && result.costLimit !== undefined) {
|
|
5117
|
+
setAgent((prev) => prev
|
|
5118
|
+
? {
|
|
5119
|
+
...prev,
|
|
5120
|
+
costLimit: result.costLimit,
|
|
5121
|
+
status: prev.status === "costLimitReached"
|
|
5122
|
+
? "running"
|
|
5123
|
+
: prev.status,
|
|
5124
|
+
}
|
|
5125
|
+
: prev);
|
|
5126
|
+
}
|
|
5127
|
+
setCostLimitExceeded(null);
|
|
5128
|
+
setIsWaitingForResponse(true);
|
|
5129
|
+
isWaitingRef.current = true;
|
|
5130
|
+
setIsAgentThinking(true);
|
|
5131
|
+
setShouldAutoScroll(true);
|
|
5132
|
+
}
|
|
5133
|
+
catch (e) {
|
|
5134
|
+
console.error("Failed to extend cost limit:", e);
|
|
5135
|
+
setError(e instanceof Error ? e.message : "Failed to extend cost limit");
|
|
5136
|
+
}
|
|
5137
|
+
}, [agent?.id]);
|
|
5717
5138
|
const renderCostLimitBanner = () => {
|
|
5718
5139
|
if (!costLimitExceeded)
|
|
5719
5140
|
return null;
|
|
5720
|
-
const { totalCost, costLimit
|
|
5721
|
-
return (
|
|
5722
|
-
if (!agent?.id)
|
|
5723
|
-
return;
|
|
5724
|
-
try {
|
|
5725
|
-
// Extend cost limit - backend will automatically resume the agent
|
|
5726
|
-
const result = await updateAgentCostLimit(agent.id, "extend");
|
|
5727
|
-
// Update the agent's cost limit and clear the costLimitReached
|
|
5728
|
-
// status in local state so the useEffect watcher doesn't
|
|
5729
|
-
// immediately re-show the banner before the backend status
|
|
5730
|
-
// update arrives.
|
|
5731
|
-
if (result.success && result.costLimit !== undefined) {
|
|
5732
|
-
setAgent((prev) => prev
|
|
5733
|
-
? {
|
|
5734
|
-
...prev,
|
|
5735
|
-
costLimit: result.costLimit,
|
|
5736
|
-
status: prev.status === "costLimitReached"
|
|
5737
|
-
? "running"
|
|
5738
|
-
: prev.status,
|
|
5739
|
-
}
|
|
5740
|
-
: prev);
|
|
5741
|
-
}
|
|
5742
|
-
// Clear the banner and set waiting state
|
|
5743
|
-
// Agent will resume automatically via backend's ResumeAgentAsync
|
|
5744
|
-
setCostLimitExceeded(null);
|
|
5745
|
-
setIsWaitingForResponse(true);
|
|
5746
|
-
isWaitingRef.current = true;
|
|
5747
|
-
setIsAgentThinking(true);
|
|
5748
|
-
setShouldAutoScroll(true);
|
|
5749
|
-
}
|
|
5750
|
-
catch (e) {
|
|
5751
|
-
console.error("Failed to extend cost limit:", e);
|
|
5752
|
-
setError(e instanceof Error
|
|
5753
|
-
? e.message
|
|
5754
|
-
: "Failed to extend cost limit");
|
|
5755
|
-
}
|
|
5756
|
-
}, children: "Extend limit and continue" }) })] }));
|
|
5141
|
+
const { totalCost, costLimit } = costLimitExceeded;
|
|
5142
|
+
return (_jsx(AgentCostLimitBanner, { totalCost: totalCost, costLimit: costLimit, onExtend: handleExtendCostLimit }));
|
|
5757
5143
|
};
|
|
5758
5144
|
const renderErrorBanner = () => {
|
|
5759
5145
|
const currentAgent = agent || agentStub;
|
|
5760
5146
|
const isErrorStatus = currentAgent?.status === "error";
|
|
5761
5147
|
const isWaitingForInputStatus = currentAgent?.status === "waitingForInput";
|
|
5762
5148
|
const isWaitingForApprovalStatus = currentAgent?.status === "waitingForApproval";
|
|
5763
|
-
// Show error banner for error status, or for any terminal status that still
|
|
5764
|
-
// carries a statusMessage (e.g. agent closed after an error).
|
|
5765
5149
|
const isTerminalWithError = !isErrorStatus &&
|
|
5766
5150
|
!!currentAgent?.statusMessage &&
|
|
5767
5151
|
currentAgent?.status !== "running" &&
|
|
@@ -5773,9 +5157,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5773
5157
|
: null) || error;
|
|
5774
5158
|
if (!rawErrorMessage)
|
|
5775
5159
|
return null;
|
|
5776
|
-
// Clean the error message (statusMessage from DB may contain raw JSON)
|
|
5777
5160
|
const errorMessage = toUserFacingAgentErrorMessage(rawErrorMessage) || rawErrorMessage;
|
|
5778
|
-
return
|
|
5161
|
+
return _jsx(AgentErrorBanner, { message: errorMessage });
|
|
5779
5162
|
};
|
|
5780
5163
|
const renderCapacityBanner = () => {
|
|
5781
5164
|
if (lastRunStatusReason !== MACHINE_CAPACITY_REASON) {
|
|
@@ -5784,7 +5167,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5784
5167
|
const currentAgent = agent || agentStub;
|
|
5785
5168
|
const message = currentAgent?.statusMessage ||
|
|
5786
5169
|
"Waiting for capacity. The agent will start automatically when a slot becomes available.";
|
|
5787
|
-
return
|
|
5170
|
+
return _jsx(AgentCapacityBanner, { message: message });
|
|
5788
5171
|
};
|
|
5789
5172
|
const renderBrowserClaimBanner = (variant = "inline") => {
|
|
5790
5173
|
if (!agent?.id || !editContext?.sessionId)
|
|
@@ -5875,31 +5258,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5875
5258
|
const renderInlineDialogContent = () => {
|
|
5876
5259
|
if (!activeInlineDialog)
|
|
5877
5260
|
return null;
|
|
5878
|
-
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5879
|
-
return (_jsx("div", { ref: inlineDialogContainerRef, className: cn("agent-inline-dialog min-h-0 overflow-hidden", displayMode === "full" && "h-full"), children: _jsx(QuestionnaireInline, { requestId: activeInlineDialog.request.callbackId, agentId: activeInlineDialog.request.agentId, title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, footerActions: questionnaireFooterActions, onClose: (result) => {
|
|
5880
|
-
activeInlineDialog.onComplete(result);
|
|
5881
|
-
setActiveInlineDialog(null);
|
|
5882
|
-
void onInteractionSubmitted?.();
|
|
5883
|
-
}, onCancel: () => {
|
|
5884
|
-
activeInlineDialog.onCancel();
|
|
5885
|
-
setActiveInlineDialog(null);
|
|
5886
|
-
} }) }));
|
|
5887
|
-
}
|
|
5888
5261
|
const dialogRegistration = editContext?.configuration?.editor?.agentDialogs?.find((d) => d.dialogType === activeInlineDialog.request.dialogType);
|
|
5889
|
-
|
|
5890
|
-
const DialogComponent = dialogRegistration.component;
|
|
5891
|
-
return (_jsx("div", { className: "agent-inline-dialog", children: _jsx(DialogComponent, { title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, onClose: (result) => {
|
|
5892
|
-
activeInlineDialog.onComplete(result);
|
|
5893
|
-
setActiveInlineDialog(null);
|
|
5894
|
-
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5895
|
-
void onInteractionSubmitted?.();
|
|
5896
|
-
}
|
|
5897
|
-
}, onCancel: () => {
|
|
5898
|
-
activeInlineDialog.onCancel();
|
|
5899
|
-
setActiveInlineDialog(null);
|
|
5900
|
-
} }) }));
|
|
5901
|
-
}
|
|
5902
|
-
return (_jsx("div", { className: "agent-inline-dialog", children: _jsxs("div", { className: "p-4 text-sm text-red-500", children: ["Unknown dialog type: ", activeInlineDialog.request.dialogType] }) }));
|
|
5262
|
+
return (_jsx(AgentInlineDialogContent, { activeInlineDialog: activeInlineDialog, setActiveInlineDialog: setActiveInlineDialog, containerRef: inlineDialogContainerRef, displayMode: displayMode, questionnaireFooterActions: questionnaireFooterActions, dialogRegistration: dialogRegistration, onInteractionSubmitted: onInteractionSubmitted }));
|
|
5903
5263
|
};
|
|
5904
5264
|
const latestSummaryAssistantGroup = useMemo(() => {
|
|
5905
5265
|
if (hideSummaryMessages)
|
|
@@ -5931,10 +5291,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5931
5291
|
const summaryOperations = latestSummaryAssistantGroup
|
|
5932
5292
|
? getOperationsForMessageGroup(summaryMessages, agentOperations)
|
|
5933
5293
|
: [];
|
|
5934
|
-
return (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className || ""}`, children: [fixedBrowserClaimBanner,
|
|
5935
|
-
|
|
5936
|
-
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5937
|
-
} })) : (_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, renderCapacityBanner(), 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 ||
|
|
5294
|
+
return (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className || ""}`, children: [fixedBrowserClaimBanner, error &&
|
|
5295
|
+
!isAgentErrorStatusValue((agent || agentStub)?.status) && (_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-[11px] font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-[11px] text-red-700", children: error })] })] }) })), renderCapacityBanner(), renderErrorBanner(), _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [showInitialThinkingSplash && (_jsx(InitialThinkingSplash, { svgIcon: activeProfile?.svgIcon })), inlineBrowserClaimBanner, 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 ||
|
|
5938
5296
|
activeProfile?.displayTitle ||
|
|
5939
5297
|
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
|
|
5940
5298
|
const text = (action.prompt ||
|
|
@@ -5963,10 +5321,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5963
5321
|
activeProfile?.displayTitle ||
|
|
5964
5322
|
activeProfile?.name ||
|
|
5965
5323
|
"Agent" }), _jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date()) })] }), _jsxs("div", { className: "flex items-center gap-1 pt-2", children: [_jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400" })] })] })] })), _jsx("div", { ref: messagesEndRef })] }), showSummaryInput && !activeInlineDialog ? (_jsxs("div", { className: cn("border-t border-gray-200 p-4", simpleMode && "pb-10"), children: [activePlaceholderInput ? (_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
5324
|
+
const shouldSubmitFilledText = shouldSubmitFilledPlaceholderInputRef.current ||
|
|
5325
|
+
activePlaceholderInput.behavior !== "compose" ||
|
|
5326
|
+
hideBottomControls;
|
|
5327
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
5966
5328
|
setActivePlaceholderInput(null);
|
|
5967
5329
|
setAllPlaceholdersFilled(false);
|
|
5968
|
-
if (
|
|
5969
|
-
!hideBottomControls) {
|
|
5330
|
+
if (!shouldSubmitFilledText) {
|
|
5970
5331
|
setPrompt(filledText);
|
|
5971
5332
|
setInputPlaceholder("Review and edit, then press Enter to send");
|
|
5972
5333
|
if (textareaRef.current) {
|
|
@@ -5978,32 +5339,30 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
5978
5339
|
}
|
|
5979
5340
|
catch { }
|
|
5980
5341
|
}
|
|
5342
|
+
return;
|
|
5981
5343
|
}
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
handleStop();
|
|
5986
|
-
}
|
|
5987
|
-
catch { }
|
|
5344
|
+
if (isExecuting) {
|
|
5345
|
+
try {
|
|
5346
|
+
handleStop();
|
|
5988
5347
|
}
|
|
5989
|
-
|
|
5348
|
+
catch { }
|
|
5990
5349
|
}
|
|
5350
|
+
void handleSubmit(filledText);
|
|
5991
5351
|
}, onCancel: () => {
|
|
5352
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
5992
5353
|
setActivePlaceholderInput(null);
|
|
5993
5354
|
setAllPlaceholdersFilled(false);
|
|
5994
5355
|
} })) : prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) ? (_jsx(PlaceholderInput, { ref: promptPlaceholderInputRef, text: prompt, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
5995
|
-
|
|
5356
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
5996
5357
|
setAllPlaceholdersFilled(false);
|
|
5997
5358
|
if (filledText.trim()) {
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
catch { }
|
|
6003
|
-
}
|
|
6004
|
-
sendQuickMessage(filledText);
|
|
5359
|
+
void handleSubmit(filledText);
|
|
5360
|
+
}
|
|
5361
|
+
else {
|
|
5362
|
+
setPrompt("");
|
|
6005
5363
|
}
|
|
6006
5364
|
}, onCancel: () => {
|
|
5365
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
6007
5366
|
setPrompt("");
|
|
6008
5367
|
setAllPlaceholdersFilled(false);
|
|
6009
5368
|
setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
|
|
@@ -6045,7 +5404,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
6045
5404
|
})()
|
|
6046
5405
|
: null;
|
|
6047
5406
|
const fullModeInlineDialog = displayMode === "full" ? renderInlineDialogContent() : null;
|
|
6048
|
-
const fullModeUpperContent = (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [fixedBrowserClaimBanner, _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [messages.length === 0 && !error && !hideGreeting && (_jsx("div", { className: "flex h-full items-center justify-center", children: !activeProfile ? (_jsx(Loader2, { className: "mx-auto h-8 w-8 animate-spin text-gray-400" })) : (_jsx(AgentGreeting, { profile: activeProfile, onPromptClick: (p) => {
|
|
5407
|
+
const fullModeUpperContent = (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [fixedBrowserClaimBanner, renderCapacityBanner(), renderErrorBanner(), _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [messages.length === 0 && !error && !hideGreeting && !isLoading && (_jsx("div", { className: "flex h-full items-center justify-center", children: !activeProfile ? (_jsx(Loader2, { className: "mx-auto h-8 w-8 animate-spin text-gray-400" })) : (_jsx(AgentGreeting, { profile: activeProfile, onPromptClick: (p) => {
|
|
6049
5408
|
setPrompt(p);
|
|
6050
5409
|
// Use setTimeout to ensure state is updated before submission
|
|
6051
5410
|
setTimeout(() => {
|
|
@@ -6058,9 +5417,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
6058
5417
|
handleSubmit();
|
|
6059
5418
|
}
|
|
6060
5419
|
}, 0);
|
|
6061
|
-
} })) })), showInitialThinkingSplash && (_jsx(
|
|
6062
|
-
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
6063
|
-
} })) : (_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, renderCapacityBanner(), renderErrorBanner(), _jsxs("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: [(() => {
|
|
5420
|
+
} })) })), showInitialThinkingSplash && (_jsx(InitialThinkingSplash, { svgIcon: activeProfile?.svgIcon })), inlineBrowserClaimBanner, _jsxs("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: [(() => {
|
|
6064
5421
|
const groups = groupConsecutiveMessages(messages);
|
|
6065
5422
|
return groups.map((group, groupIndex) => {
|
|
6066
5423
|
const isLastGroup = groupIndex === groups.length - 1;
|
|
@@ -6187,33 +5544,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
6187
5544
|
hasTodoContent,
|
|
6188
5545
|
hasSpawnedAgents,
|
|
6189
5546
|
agent?.id && hasHistoryContent,
|
|
6190
|
-
].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && (_jsx(AgentEditOperationsPanel, { operations: agentOperations }))] }))),
|
|
6191
|
-
let triggerName = "";
|
|
6192
|
-
if (qp.data) {
|
|
6193
|
-
try {
|
|
6194
|
-
const parsed = JSON.parse(qp.data);
|
|
6195
|
-
triggerName = parsed?.triggerName?.trim() || "";
|
|
6196
|
-
}
|
|
6197
|
-
catch {
|
|
6198
|
-
// Ignore invalid JSON metadata and render as regular queued prompt.
|
|
6199
|
-
}
|
|
6200
|
-
}
|
|
6201
|
-
const isTriggerQueuedPrompt = !qp.sourceAgentName && triggerName.length > 0;
|
|
6202
|
-
const isTriggerExpanded = !!expandedQueuedTriggerIds[qp.id];
|
|
6203
|
-
if (isTriggerQueuedPrompt) {
|
|
6204
|
-
return (_jsxs("div", { className: "text-[11px]", "data-testid": "queued-prompt-item", children: [_jsxs("button", { type: "button", onClick: () => setExpandedQueuedTriggerIds((prev) => ({
|
|
6205
|
-
...prev,
|
|
6206
|
-
[qp.id]: !prev[qp.id],
|
|
6207
|
-
})), className: "flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-amber-800 transition-colors hover:bg-amber-100/60", "data-testid": "queued-trigger-toggle", "data-expanded": isTriggerExpanded ? "true" : "false", children: [_jsx(Target, { className: "h-3 w-3 shrink-0 text-amber-500", strokeWidth: 2 }), _jsx("span", { className: "truncate font-medium", children: triggerName }), qp.createdDate && (_jsx("span", { className: "ml-1 shrink-0 text-[10px] text-amber-500", children: formatTime(new Date(qp.createdDate)) })), _jsx("span", { className: "ml-auto shrink-0 text-amber-400", children: isTriggerExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3" })) : (_jsx(ChevronDown, { className: "h-3 w-3" })) })] }), isTriggerExpanded && (_jsx("div", { className: "mt-0.5 border-l-2 border-amber-200 pl-5 text-[11px] text-amber-700/80", children: qp.prompt }))] }, qp.id));
|
|
6208
|
-
}
|
|
6209
|
-
return (_jsx("div", { className: "rounded-md border border-amber-200 bg-white p-2.5 text-[11px]", "data-testid": "queued-prompt-item", children: _jsx("div", { className: "flex items-start justify-between gap-2", children: _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "mb-1 flex items-center gap-1.5", children: qp.sourceAgentName ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-medium text-gray-700", children: ["From ", qp.sourceAgentName] }), qp.priority > 5 && (_jsx("span", { className: "rounded bg-red-100 px-1.5 py-0.5 text-[11px] font-medium text-red-700", children: "High Priority" }))] })) : (_jsx("span", { className: "font-medium text-gray-700", children: "From User" })) }), _jsx("div", { className: "wrap-break-word whitespace-pre-wrap text-gray-600", "data-testid": "queued-prompt-text", children: qp.prompt }), _jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px] text-gray-400", children: [qp.createdDate && (_jsxs("span", { children: ["Queued ", formatTime(new Date(qp.createdDate))] })), qp.scheduledFor &&
|
|
6210
|
-
new Date(qp.scheduledFor).getTime() >
|
|
6211
|
-
new Date(qp.createdDate || 0).getTime() +
|
|
6212
|
-
1000 && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-300", children: "\u2022" }), _jsxs("span", { className: "font-medium text-amber-600", children: ["Scheduled for", " ", new Date(qp.scheduledFor).toDateString() ===
|
|
6213
|
-
new Date().toDateString()
|
|
6214
|
-
? formatTime(new Date(qp.scheduledFor))
|
|
6215
|
-
: formatDateTime(new Date(qp.scheduledFor))] })] }))] })] }) }) }, qp.id));
|
|
6216
|
-
}) })] }) }))] }));
|
|
5547
|
+
].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && (_jsx(AgentEditOperationsPanel, { operations: agentOperations }))] }))), !simpleMode && (_jsx(QueuedPromptsPanel, { queuedPrompts: queuedPrompts }))] }));
|
|
6217
5548
|
const showQuestionnaireSplitter = isQuestionnaireDialogOpen && !!fullModeInlineDialog;
|
|
6218
5549
|
const fullModeContent = showQuestionnaireSplitter ? (_jsx(Splitter, { panels: [
|
|
6219
5550
|
{
|
|
@@ -6233,12 +5564,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
6233
5564
|
// Placeholder Input (from quick actions)
|
|
6234
5565
|
// Show internal buttons only in splash mode (hideBottomControls) since external buttons won't be visible there
|
|
6235
5566
|
_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
5567
|
+
const shouldSubmitFilledText = shouldSubmitFilledPlaceholderInputRef.current ||
|
|
5568
|
+
activePlaceholderInput.behavior !== "compose" ||
|
|
5569
|
+
hideBottomControls;
|
|
5570
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
6236
5571
|
setActivePlaceholderInput(null);
|
|
6237
5572
|
setAllPlaceholdersFilled(false);
|
|
6238
|
-
|
|
6239
|
-
// because the regular textarea is hidden and user can't manually send
|
|
6240
|
-
if (activePlaceholderInput.behavior === "compose" &&
|
|
6241
|
-
!hideBottomControls) {
|
|
5573
|
+
if (!shouldSubmitFilledText) {
|
|
6242
5574
|
setPrompt(filledText);
|
|
6243
5575
|
setInputPlaceholder("Review and edit, then press Enter to send");
|
|
6244
5576
|
if (textareaRef.current) {
|
|
@@ -6250,37 +5582,34 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
6250
5582
|
}
|
|
6251
5583
|
catch { }
|
|
6252
5584
|
}
|
|
5585
|
+
return;
|
|
6253
5586
|
}
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
try {
|
|
6258
|
-
handleStop();
|
|
6259
|
-
}
|
|
6260
|
-
catch { }
|
|
5587
|
+
if (isExecuting) {
|
|
5588
|
+
try {
|
|
5589
|
+
handleStop();
|
|
6261
5590
|
}
|
|
6262
|
-
|
|
5591
|
+
catch { }
|
|
6263
5592
|
}
|
|
5593
|
+
void handleSubmit(filledText);
|
|
6264
5594
|
}, onCancel: () => {
|
|
5595
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
6265
5596
|
setActivePlaceholderInput(null);
|
|
6266
5597
|
setAllPlaceholdersFilled(false);
|
|
6267
5598
|
} })) : prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) ? (
|
|
6268
5599
|
// Template mode: show PlaceholderInput when prompt contains placeholders
|
|
6269
5600
|
// Show internal buttons only in splash mode (hideBottomControls) since external buttons won't be visible there
|
|
6270
5601
|
_jsx(PlaceholderInput, { ref: promptPlaceholderInputRef, text: prompt, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
6271
|
-
|
|
5602
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
6272
5603
|
setAllPlaceholdersFilled(false);
|
|
6273
5604
|
// Auto-submit after filling placeholders
|
|
6274
5605
|
if (filledText.trim()) {
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
catch { }
|
|
6280
|
-
}
|
|
6281
|
-
sendQuickMessage(filledText);
|
|
5606
|
+
void handleSubmit(filledText);
|
|
5607
|
+
}
|
|
5608
|
+
else {
|
|
5609
|
+
setPrompt("");
|
|
6282
5610
|
}
|
|
6283
5611
|
}, onCancel: () => {
|
|
5612
|
+
shouldSubmitFilledPlaceholderInputRef.current = false;
|
|
6284
5613
|
setPrompt("");
|
|
6285
5614
|
setAllPlaceholdersFilled(false);
|
|
6286
5615
|
setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
|
|
@@ -6488,27 +5817,27 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
6488
5817
|
}, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
|
|
6489
5818
|
}) }))] }), _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
|
|
6490
5819
|
? "No available tools for this profile and mode"
|
|
6491
|
-
: "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:
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
? "
|
|
6506
|
-
: "No active allowances" })) })),
|
|
5820
|
+
: "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: displayedOperationAllowancesLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading allowances..." })) : hasAnyAllowances ? (_jsxs("div", { className: "space-y-1", children: [isLocalOnlyDraftAgent && (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Preview based on the current profile." })), 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) => {
|
|
5821
|
+
const sourceLabel = formatAllowanceSource(allowance.source);
|
|
5822
|
+
const pathLabel = "itemPath" in allowance
|
|
5823
|
+
? allowance.itemPath
|
|
5824
|
+
: allowance.normalizedPath;
|
|
5825
|
+
return (_jsxs("div", { className: "rounded px-1 py-0.5 transition-colors hover:bg-white/60", "data-testid": `agent-allowance-row-${group.key}`, children: [_jsxs("div", { className: "flex items-baseline gap-1.5", children: [_jsx("div", { className: "shrink-0 text-[10px] font-medium text-gray-700", children: allowance.operationType ||
|
|
5826
|
+
"*" }), _jsx("div", { className: "truncate text-[9px] text-gray-500", title: formatAllowanceLabel(allowance), children: pathLabel })] }), (sourceLabel ||
|
|
5827
|
+
allowance.grantedBy) && (_jsx("div", { className: "truncate pl-6 text-[9px] text-gray-400", children: [
|
|
5828
|
+
sourceLabel,
|
|
5829
|
+
allowance.grantedBy,
|
|
5830
|
+
]
|
|
5831
|
+
.filter(Boolean)
|
|
5832
|
+
.join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
|
|
5833
|
+
})] }, group.key)) : null)] })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
5834
|
+
? "No configured allowances for this profile"
|
|
5835
|
+
: "No active allowances" })) })), displayedOperationAllowancesError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: displayedOperationAllowancesError }))] }), _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) => {
|
|
6507
5836
|
const filterText = (sub.filter || "").trim();
|
|
6508
5837
|
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));
|
|
6509
5838
|
}) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6510
5839
|
? "Subscribed triggers are shown after the agent is created"
|
|
6511
|
-
: "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: () => {
|
|
5840
|
+
: "No active trigger subscriptions" })) })), triggerSubscriptionsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: triggerSubscriptionsError }))] }), agent?.id && !isLocalOnlyDraftAgent ? (_jsx("div", { className: "border-t border-gray-200 pt-2", children: _jsx(AgentSharingSection, { agentId: agent.id, canManage: agent?.canManage ?? true }) })) : 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", 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: () => {
|
|
6512
5841
|
setPrompt(p.prompt);
|
|
6513
5842
|
setShowPredefined(false);
|
|
6514
5843
|
if (textareaRef.current)
|