@alpaca-editor/core 1.0.4063 → 1.0.4064

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/config/config.js +3 -21
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/ConfirmationDialog.js +1 -1
  4. package/dist/editor/ConfirmationDialog.js.map +1 -1
  5. package/dist/editor/ContextMenu.js +1 -0
  6. package/dist/editor/ContextMenu.js.map +1 -1
  7. package/dist/editor/FieldListField.js +2 -2
  8. package/dist/editor/FieldListField.js.map +1 -1
  9. package/dist/editor/FieldListFieldWithFallbacks.js +1 -1
  10. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  11. package/dist/editor/MainLayout.d.ts +2 -0
  12. package/dist/editor/MainLayout.js +8 -0
  13. package/dist/editor/MainLayout.js.map +1 -1
  14. package/dist/editor/ScrollingContentTree.js +9 -4
  15. package/dist/editor/ScrollingContentTree.js.map +1 -1
  16. package/dist/editor/ai/AgentTerminal.js +382 -3
  17. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  18. package/dist/editor/ai/Agents.js +44 -3
  19. package/dist/editor/ai/Agents.js.map +1 -1
  20. package/dist/editor/client/EditorClient.js +54 -16
  21. package/dist/editor/client/EditorClient.js.map +1 -1
  22. package/dist/editor/client/editContext.d.ts +4 -2
  23. package/dist/editor/client/editContext.js.map +1 -1
  24. package/dist/editor/media-selector/TreeSelector.js +1 -1
  25. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  26. package/dist/editor/menubar/VersionSelector.js +1 -1
  27. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  28. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +2 -1
  29. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  30. package/dist/editor/page-viewer/EditorForm.js +7 -0
  31. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  32. package/dist/editor/page-viewer/PageViewerFrame.js +2 -1
  33. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  34. package/dist/editor/reviews/Comment.js +57 -9
  35. package/dist/editor/reviews/Comment.js.map +1 -1
  36. package/dist/editor/reviews/CommentEditor.js +2 -2
  37. package/dist/editor/reviews/CommentEditor.js.map +1 -1
  38. package/dist/editor/reviews/CommentPopover.js +1 -1
  39. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  40. package/dist/editor/reviews/CommentView.js +5 -5
  41. package/dist/editor/reviews/CommentView.js.map +1 -1
  42. package/dist/editor/services/agentService.d.ts +41 -0
  43. package/dist/editor/services/agentService.js +10 -0
  44. package/dist/editor/services/agentService.js.map +1 -1
  45. package/dist/editor/sidebar/ComponentTree.js +3 -2
  46. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  47. package/dist/editor/sidebar/SidebarView.d.ts +2 -1
  48. package/dist/editor/sidebar/SidebarView.js +2 -2
  49. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  50. package/dist/editor/ui/PerfectTree.js +1 -1
  51. package/dist/editor/ui/PerfectTree.js.map +1 -1
  52. package/dist/editor/ui/SimpleTabs.js +1 -1
  53. package/dist/index.d.ts +0 -1
  54. package/dist/index.js +0 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/revision.d.ts +2 -2
  57. package/dist/revision.js +2 -2
  58. package/dist/splash-screen/NewPage.js +2 -2
  59. package/dist/splash-screen/NewPage.js.map +1 -1
  60. package/dist/styles.css +6 -0
  61. package/dist/types.d.ts +2 -1
  62. package/package.json +1 -1
  63. package/src/config/config.tsx +5 -20
  64. package/src/editor/ConfirmationDialog.tsx +4 -1
  65. package/src/editor/ContextMenu.tsx +1 -0
  66. package/src/editor/FieldListField.tsx +13 -11
  67. package/src/editor/FieldListFieldWithFallbacks.tsx +1 -0
  68. package/src/editor/MainLayout.tsx +11 -0
  69. package/src/editor/ScrollingContentTree.tsx +10 -5
  70. package/src/editor/ai/AgentTerminal.tsx +555 -1
  71. package/src/editor/ai/Agents.tsx +64 -8
  72. package/src/editor/client/EditorClient.tsx +98 -29
  73. package/src/editor/client/editContext.ts +5 -2
  74. package/src/editor/media-selector/TreeSelector.tsx +1 -0
  75. package/src/editor/menubar/VersionSelector.tsx +4 -1
  76. package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +15 -2
  77. package/src/editor/page-viewer/EditorForm.tsx +13 -0
  78. package/src/editor/page-viewer/PageViewerFrame.tsx +2 -1
  79. package/src/editor/reviews/Comment.tsx +65 -9
  80. package/src/editor/reviews/CommentEditor.tsx +8 -1
  81. package/src/editor/reviews/CommentPopover.tsx +1 -0
  82. package/src/editor/reviews/CommentView.tsx +24 -3
  83. package/src/editor/services/agentService.ts +58 -0
  84. package/src/editor/sidebar/ComponentTree.tsx +6 -2
  85. package/src/editor/sidebar/SidebarView.tsx +8 -7
  86. package/src/editor/ui/PerfectTree.tsx +2 -1
  87. package/src/editor/ui/SimpleTabs.tsx +1 -1
  88. package/src/index.ts +0 -2
  89. package/src/revision.ts +2 -2
  90. package/src/splash-screen/NewPage.tsx +2 -2
  91. package/src/types.ts +2 -1
  92. package/styles.css +0 -2
  93. package/dist/fonts/index.d.ts +0 -4
  94. package/dist/fonts/index.js +0 -9
  95. package/dist/fonts/index.js.map +0 -1
  96. package/src/fonts/Geist-Black.woff2 +0 -0
  97. package/src/fonts/Geist-Bold.woff2 +0 -0
  98. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  99. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  100. package/src/fonts/Geist-Light.woff2 +0 -0
  101. package/src/fonts/Geist-Medium.woff2 +0 -0
  102. package/src/fonts/Geist-Regular.woff2 +0 -0
  103. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  104. package/src/fonts/Geist-Thin.woff2 +0 -0
  105. package/src/fonts/Geist[wght].woff2 +0 -0
  106. package/src/fonts/index.ts +0 -10
@@ -1,5 +1,17 @@
1
1
  import React, { useEffect, useState, useRef, useCallback } from "react";
2
- import { Send, Bot, AlertCircle, Loader2, User } from "lucide-react";
2
+ import {
3
+ Send,
4
+ Bot,
5
+ AlertCircle,
6
+ Loader2,
7
+ User,
8
+ X,
9
+ FileText,
10
+ Puzzle,
11
+ Type,
12
+ Plus,
13
+ MessageSquare,
14
+ } from "lucide-react";
3
15
  import { DancingDots } from "./DancingDots";
4
16
  import {
5
17
  AgentChatMessage,
@@ -10,6 +22,8 @@ import {
10
22
  StartAgentRequest,
11
23
  Agent,
12
24
  AgentDetails,
25
+ updateAgentMetadata,
26
+ AgentMetadata,
13
27
  } from "../services/agentService";
14
28
  import { useEditContext } from "../client/editContext";
15
29
  import { Textarea } from "../../components/ui/textarea";
@@ -17,6 +31,8 @@ import { Button } from "../../components/ui/button";
17
31
  import { AiResponseMessage } from "./AiResponseMessage";
18
32
  import { AgentCostDisplay } from "./AgentCostDisplay";
19
33
  import { Message } from "./AiTerminal";
34
+ import { getComponentById } from "../componentTreeHelper";
35
+ import { Comment } from "../../types";
20
36
 
21
37
  // Simple user message component
22
38
  const UserMessage = ({ message }: { message: AgentChatMessage }) => {
@@ -160,7 +176,19 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
160
176
  const [isLoading, setIsLoading] = useState(false);
161
177
  const [isConnecting, setIsConnecting] = useState(false);
162
178
  const [isSubmitting, setIsSubmitting] = useState(false);
179
+ const [agentMetadata, setAgentMetadata] = useState<AgentMetadata | null>(
180
+ null,
181
+ );
163
182
  const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
183
+ const [resolvedPageName, setResolvedPageName] = useState<string | undefined>(
184
+ undefined,
185
+ );
186
+ const [resolvedComponentName, setResolvedComponentName] = useState<
187
+ string | undefined
188
+ >(undefined);
189
+ const [resolvedFieldName, setResolvedFieldName] = useState<
190
+ string | undefined
191
+ >(undefined);
164
192
  const [promptHistory, setPromptHistory] = useState<string[]>(() => {
165
193
  if (typeof window !== "undefined") {
166
194
  return JSON.parse(
@@ -732,6 +760,37 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
732
760
  setMessages([]);
733
761
  setError(null);
734
762
  setIsLoading(false);
763
+
764
+ // Initialize local context for a brand-new agent (not yet persisted)
765
+ const item = editContext?.currentItemDescriptor;
766
+ const initialLocalContext: AgentMetadata | null = item
767
+ ? {
768
+ additionalData: {
769
+ context: {
770
+ pages: [
771
+ {
772
+ id: item.id,
773
+ language: item.language,
774
+ version: item.version,
775
+ name: editContext?.contentEditorItem?.name,
776
+ },
777
+ ],
778
+ componentIds: editContext?.selection?.length
779
+ ? editContext.selection
780
+ : undefined,
781
+ field:
782
+ editContext?.focusedField &&
783
+ editContext.currentItemDescriptor
784
+ ? {
785
+ fieldId: editContext.focusedField.fieldId,
786
+ itemId: editContext.focusedField.item.id,
787
+ }
788
+ : undefined,
789
+ },
790
+ },
791
+ }
792
+ : null;
793
+ if (initialLocalContext) setAgentMetadata(initialLocalContext);
735
794
  return;
736
795
  }
737
796
 
@@ -743,6 +802,20 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
743
802
  setAgent(agentData);
744
803
  setMessages(agentData.messages || []);
745
804
 
805
+ // Parse metadata from DB if present (do not seed for existing agents)
806
+ const parsedMeta: AgentMetadata | null = (() => {
807
+ try {
808
+ return agentData.metadata
809
+ ? (JSON.parse(agentData.metadata) as AgentMetadata)
810
+ : null;
811
+ } catch {
812
+ return null;
813
+ }
814
+ })();
815
+
816
+ // For existing agents, use database metadata or none
817
+ setAgentMetadata(parsedMeta);
818
+
746
819
  // Connect to stream if agent is running (handle both string and numeric status)
747
820
  const isRunning =
748
821
  agentData.status === "running" || (agentData.status as any) === 1;
@@ -846,14 +919,32 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
846
919
 
847
920
  setMessages((prev) => [...prev, userMessage]);
848
921
 
922
+ const metaCtx = (agentMetadata as any)?.additionalData?.context as
923
+ | { componentIds?: string[] }
924
+ | undefined;
925
+ const selectionFromCtx = metaCtx?.componentIds?.length
926
+ ? metaCtx.componentIds
927
+ : undefined;
928
+ const effectiveSelection = selectionFromCtx?.length
929
+ ? selectionFromCtx
930
+ : editContext.selection && editContext.selection.length > 0
931
+ ? editContext.selection
932
+ : undefined;
933
+
849
934
  const request: StartAgentRequest = {
850
935
  agentId: agent.id,
851
936
  message: prompt.trim(),
852
937
  sessionId: editContext.sessionId,
853
938
  profileId: "default", // TODO: Get from context or settings
939
+ profile: "default",
854
940
  itemid: editContext.currentItemDescriptor?.id || "",
855
941
  language: editContext.currentItemDescriptor?.language || "en",
856
942
  version: editContext.currentItemDescriptor?.version || 1,
943
+ selection: effectiveSelection,
944
+ selectedText: editContext.selectedRange?.text || undefined,
945
+ addSelectedComponents: !!effectiveSelection?.length,
946
+ addContextContent: false,
947
+ addAllContent: false,
857
948
  };
858
949
 
859
950
  console.log("Starting agent:", request);
@@ -940,6 +1031,311 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
940
1031
  }
941
1032
  };
942
1033
 
1034
+ // Context info bar helpers - must be declared before any early returns to keep hooks order stable
1035
+ const removeContextKey = useCallback(
1036
+ async (
1037
+ key: "pages" | "componentIds" | "field" | "comment",
1038
+ index?: number,
1039
+ ) => {
1040
+ if (!agent?.id) return;
1041
+ const current = agentMetadata || {};
1042
+ const next: AgentMetadata = {
1043
+ ...current,
1044
+ additionalData: {
1045
+ ...(current.additionalData || {}),
1046
+ context: {
1047
+ ...((current.additionalData &&
1048
+ (current.additionalData as any).context) ||
1049
+ {}),
1050
+ },
1051
+ },
1052
+ } as AgentMetadata;
1053
+
1054
+ if (next.additionalData && (next.additionalData as any).context) {
1055
+ const ctx = (next.additionalData as any).context;
1056
+ if (key === "pages" && typeof index === "number" && ctx.pages) {
1057
+ ctx.pages.splice(index, 1);
1058
+ if (ctx.pages.length === 0) delete ctx.pages;
1059
+ } else if (
1060
+ key === "componentIds" &&
1061
+ typeof index === "number" &&
1062
+ ctx.componentIds
1063
+ ) {
1064
+ ctx.componentIds.splice(index, 1);
1065
+ if (ctx.componentIds.length === 0) delete ctx.componentIds;
1066
+ } else if (key === "field") {
1067
+ delete ctx.field;
1068
+ } else if (key === "comment") {
1069
+ delete ctx.comment;
1070
+ }
1071
+ }
1072
+
1073
+ try {
1074
+ // For new (not yet persisted) agents, only update local state
1075
+ if (agent.status === "new") {
1076
+ setAgentMetadata(next);
1077
+ return;
1078
+ }
1079
+
1080
+ // Persisted agents: update server and local cache
1081
+ await updateAgentMetadata(agent.id, next);
1082
+ setAgentMetadata(next);
1083
+ setAgent((prev) =>
1084
+ prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
1085
+ );
1086
+ } catch (e) {
1087
+ console.error("Failed to update agent metadata", e);
1088
+ }
1089
+ },
1090
+ [agent?.id, agentMetadata],
1091
+ );
1092
+
1093
+ const addPagesToContext = async () => {
1094
+ if (!agent?.id || !editContext?.currentItemDescriptor) return;
1095
+
1096
+ // Add the current page (currentItemDescriptor represents the current page)
1097
+ // Note: Currently only supports adding the current page. To support multiple pages,
1098
+ // we'd need a page selection mechanism in the UI (e.g., a page picker dialog)
1099
+ const item = editContext.currentItemDescriptor;
1100
+ const pageToAdd = {
1101
+ id: item.id,
1102
+ language: item.language,
1103
+ version: item.version,
1104
+ name: editContext.contentEditorItem?.name,
1105
+ path: agent?.itemPath,
1106
+ };
1107
+
1108
+ const current = agentMetadata || {};
1109
+ const currentPages =
1110
+ ((current.additionalData as any)?.context?.pages as Array<any>) || [];
1111
+
1112
+ // Check if this page is already in context
1113
+ const existingPageIds = new Set(
1114
+ currentPages.map((p: any) => `${p.id}-${p.language}-${p.version}`),
1115
+ );
1116
+
1117
+ if (
1118
+ existingPageIds.has(
1119
+ `${pageToAdd.id}-${pageToAdd.language}-${pageToAdd.version}`,
1120
+ )
1121
+ ) {
1122
+ return; // Page already exists
1123
+ }
1124
+
1125
+ const next: AgentMetadata = {
1126
+ ...current,
1127
+ additionalData: {
1128
+ ...(current.additionalData || {}),
1129
+ context: {
1130
+ ...((current.additionalData &&
1131
+ (current.additionalData as any).context) ||
1132
+ {}),
1133
+ pages: [...currentPages, pageToAdd],
1134
+ },
1135
+ },
1136
+ } as AgentMetadata;
1137
+
1138
+ try {
1139
+ if (agent.status === "new") {
1140
+ setAgentMetadata(next);
1141
+ return;
1142
+ }
1143
+ await updateAgentMetadata(agent.id, next);
1144
+ setAgentMetadata(next);
1145
+ setAgent((prev) =>
1146
+ prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
1147
+ );
1148
+ } catch (e) {
1149
+ console.error("Failed to update agent metadata (add pages)", e);
1150
+ }
1151
+ };
1152
+
1153
+ const addSelectedComponentsToContext = async () => {
1154
+ if (!agent?.id || !editContext?.selection?.length) return;
1155
+
1156
+ const current = agentMetadata || {};
1157
+ const currentComponentIds =
1158
+ ((current.additionalData as any)?.context?.componentIds as string[]) ||
1159
+ [];
1160
+
1161
+ // Merge with existing components, avoiding duplicates
1162
+ const existingIds = new Set(currentComponentIds);
1163
+ const newComponentIds = editContext.selection.filter(
1164
+ (id) => !existingIds.has(id),
1165
+ );
1166
+
1167
+ if (newComponentIds.length === 0) return; // No new components to add
1168
+
1169
+ const next: AgentMetadata = {
1170
+ ...current,
1171
+ additionalData: {
1172
+ ...(current.additionalData || {}),
1173
+ context: {
1174
+ ...((current.additionalData &&
1175
+ (current.additionalData as any).context) ||
1176
+ {}),
1177
+ componentIds: [...currentComponentIds, ...newComponentIds],
1178
+ },
1179
+ },
1180
+ } as AgentMetadata;
1181
+
1182
+ try {
1183
+ if (agent.status === "new") {
1184
+ setAgentMetadata(next);
1185
+ return;
1186
+ }
1187
+ await updateAgentMetadata(agent.id, next);
1188
+ setAgentMetadata(next);
1189
+ setAgent((prev) =>
1190
+ prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
1191
+ );
1192
+ } catch (e) {
1193
+ console.error("Failed to update agent metadata (add components)", e);
1194
+ }
1195
+ };
1196
+
1197
+ const addCommentToContext = async (comment: Comment) => {
1198
+ if (!agent?.id) return;
1199
+
1200
+ const selectedText = (() => {
1201
+ if (
1202
+ typeof comment.rangeStart === "number" &&
1203
+ typeof comment.rangeEnd === "number" &&
1204
+ comment.fieldValue
1205
+ ) {
1206
+ try {
1207
+ return comment.fieldValue.substring(
1208
+ Math.max(0, comment.rangeStart),
1209
+ Math.max(comment.rangeStart, comment.rangeEnd),
1210
+ );
1211
+ } catch {
1212
+ return undefined;
1213
+ }
1214
+ }
1215
+ return undefined;
1216
+ })();
1217
+
1218
+ const current = agentMetadata || {};
1219
+ const next: AgentMetadata = {
1220
+ ...current,
1221
+ additionalData: {
1222
+ ...(current.additionalData || {}),
1223
+ context: {
1224
+ ...((current.additionalData &&
1225
+ (current.additionalData as any).context) ||
1226
+ {}),
1227
+ comment: {
1228
+ id: comment.id,
1229
+ text: comment.text,
1230
+ fieldName: comment.fieldName,
1231
+ itemName: comment.itemName,
1232
+ author: comment.author,
1233
+ selectedText,
1234
+ rangeStart: comment.rangeStart,
1235
+ rangeEnd: comment.rangeEnd,
1236
+ },
1237
+ },
1238
+ },
1239
+ } as AgentMetadata;
1240
+
1241
+ try {
1242
+ if (agent.status === "new") {
1243
+ setAgentMetadata(next);
1244
+ return;
1245
+ }
1246
+ await updateAgentMetadata(agent.id, next);
1247
+ setAgentMetadata(next);
1248
+ setAgent((prev) =>
1249
+ prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
1250
+ );
1251
+ } catch (e) {
1252
+ console.error("Failed to update agent metadata (add comment)", e);
1253
+ }
1254
+ };
1255
+
1256
+ // Resolve display names when metadata or editor state changes
1257
+ useEffect(() => {
1258
+ const metaCtx = (agentMetadata as any)?.additionalData?.context as
1259
+ | {
1260
+ pages?: Array<{
1261
+ id: string;
1262
+ language: string;
1263
+ version: number;
1264
+ name?: string;
1265
+ }>;
1266
+ componentIds?: string[];
1267
+ field?: { fieldId: string; itemId: string };
1268
+ }
1269
+ | undefined;
1270
+ if (!metaCtx) {
1271
+ setResolvedPageName(undefined);
1272
+ setResolvedComponentName(undefined);
1273
+ setResolvedFieldName(undefined);
1274
+ return;
1275
+ }
1276
+
1277
+ // Page names (for now, just resolve the first page)
1278
+ if (metaCtx.pages?.length) {
1279
+ const firstPage = metaCtx.pages[0];
1280
+ if (firstPage) {
1281
+ let name: string | undefined = firstPage.name;
1282
+ if (
1283
+ !name &&
1284
+ editContext?.contentEditorItem?.descriptor.id === firstPage.id &&
1285
+ editContext?.contentEditorItem?.name
1286
+ ) {
1287
+ name = editContext.contentEditorItem.name;
1288
+ } else if (!name && agent?.itemPath) {
1289
+ name = agent.itemPath.split("/").pop() || undefined;
1290
+ }
1291
+ setResolvedPageName(name);
1292
+ } else {
1293
+ setResolvedPageName(undefined);
1294
+ }
1295
+ } else setResolvedPageName(undefined);
1296
+
1297
+ // Component names (for now, just resolve the first component)
1298
+ if (metaCtx.componentIds?.length && editContext?.page) {
1299
+ const firstComponentId = metaCtx.componentIds[0];
1300
+ if (firstComponentId) {
1301
+ try {
1302
+ const comp = getComponentById(firstComponentId, editContext.page);
1303
+ setResolvedComponentName(comp?.name);
1304
+ } catch {
1305
+ setResolvedComponentName(undefined);
1306
+ }
1307
+ } else {
1308
+ setResolvedComponentName(undefined);
1309
+ }
1310
+ } else setResolvedComponentName(undefined);
1311
+
1312
+ // Field name (async)
1313
+ (async () => {
1314
+ if (metaCtx.field && editContext?.itemsRepository) {
1315
+ try {
1316
+ const field = await editContext.itemsRepository.getField({
1317
+ item: {
1318
+ id: metaCtx.field.itemId,
1319
+ language: editContext.currentItemDescriptor?.language || "en",
1320
+ version: editContext.currentItemDescriptor?.version || 0,
1321
+ },
1322
+ fieldId: metaCtx.field.fieldId,
1323
+ });
1324
+ setResolvedFieldName(field?.name);
1325
+ } catch {
1326
+ setResolvedFieldName(undefined);
1327
+ }
1328
+ } else setResolvedFieldName(undefined);
1329
+ })();
1330
+ }, [
1331
+ agentMetadata,
1332
+ editContext?.page,
1333
+ editContext?.contentEditorItem,
1334
+ agent?.itemPath,
1335
+ editContext?.currentItemDescriptor,
1336
+ editContext?.itemsRepository,
1337
+ ]);
1338
+
943
1339
  if (isLoading) {
944
1340
  return (
945
1341
  <div className="flex h-full items-center justify-center">
@@ -954,6 +1350,161 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
954
1350
  // Calculate total token usage for cost display
955
1351
  const totalTokens = calculateTotalTokens(messages);
956
1352
 
1353
+ const renderContextInfoBar = () => {
1354
+ const ctx = (agentMetadata as any)?.additionalData?.context as
1355
+ | {
1356
+ pages?: Array<{
1357
+ id: string;
1358
+ language: string;
1359
+ version: number;
1360
+ path?: string;
1361
+ name?: string;
1362
+ }>;
1363
+ componentIds?: string[];
1364
+ field?: { fieldId: string; itemId: string; name?: string };
1365
+ }
1366
+ | undefined;
1367
+
1368
+ // Check if we have context chips or add buttons to show
1369
+ const hasContext =
1370
+ ctx && (ctx.pages?.length || ctx.componentIds?.length || ctx.field);
1371
+
1372
+ // Check if current page is already in context
1373
+ const currentPageAlreadyAdded =
1374
+ editContext?.currentItemDescriptor &&
1375
+ ctx?.pages?.some(
1376
+ (page) =>
1377
+ page.id === editContext.currentItemDescriptor?.id &&
1378
+ page.language === editContext.currentItemDescriptor?.language &&
1379
+ page.version === editContext.currentItemDescriptor?.version,
1380
+ );
1381
+
1382
+ // Check if current selection components are already in context
1383
+ const selectedComponentsAlreadyAdded =
1384
+ editContext?.selection?.length &&
1385
+ editContext.selection.every((selectedId) =>
1386
+ ctx?.componentIds?.includes(selectedId),
1387
+ );
1388
+
1389
+ const canAddPage =
1390
+ !!editContext?.currentItemDescriptor && !currentPageAlreadyAdded;
1391
+ const canAddSelection =
1392
+ !!editContext?.selection?.length && !selectedComponentsAlreadyAdded;
1393
+
1394
+ if (!hasContext && !canAddPage && !canAddSelection) return null;
1395
+
1396
+ const Chip = ({
1397
+ icon,
1398
+ label,
1399
+ onRemove,
1400
+ }: {
1401
+ icon: React.ReactNode;
1402
+ label: string;
1403
+ onRemove: () => void;
1404
+ }) => (
1405
+ <span className="inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-xs text-gray-700">
1406
+ {icon}
1407
+ <span className="max-w-[240px] truncate" title={label}>
1408
+ {label}
1409
+ </span>
1410
+ <button
1411
+ onClick={onRemove}
1412
+ className="ml-0.5 rounded p-0.5 hover:bg-gray-200"
1413
+ aria-label="Remove context"
1414
+ >
1415
+ <X className="h-3 w-3" strokeWidth={1} />
1416
+ </button>
1417
+ </span>
1418
+ );
1419
+
1420
+ const chips: React.ReactNode[] = [];
1421
+
1422
+ // Render page chips
1423
+ if (ctx?.pages?.length) {
1424
+ ctx.pages.forEach((page, index) => {
1425
+ const pageLabelName =
1426
+ page.name ||
1427
+ resolvedPageName ||
1428
+ (agent?.itemPath ? agent.itemPath.split("/").pop() : undefined);
1429
+ const pageLabel = `${pageLabelName || page.path || page.id} (${page.language}/${page.version})`;
1430
+ chips.push(
1431
+ <Chip
1432
+ key={`page-${index}`}
1433
+ icon={
1434
+ <FileText className="h-3 w-3 text-gray-500" strokeWidth={1} />
1435
+ }
1436
+ label={`Page: ${pageLabel}`}
1437
+ onRemove={() => removeContextKey("pages", index)}
1438
+ />,
1439
+ );
1440
+ });
1441
+ }
1442
+
1443
+ // Render component chips
1444
+ if (ctx?.componentIds?.length) {
1445
+ ctx.componentIds.forEach((componentId, index) => {
1446
+ let compLabel = componentId;
1447
+ if (index === 0 && resolvedComponentName) {
1448
+ compLabel = resolvedComponentName;
1449
+ } else if (editContext?.page) {
1450
+ try {
1451
+ const comp = getComponentById(componentId, editContext.page);
1452
+ compLabel = comp?.name || componentId;
1453
+ } catch {
1454
+ // Keep componentId as fallback
1455
+ }
1456
+ }
1457
+ chips.push(
1458
+ <Chip
1459
+ key={`component-${index}`}
1460
+ icon={<Puzzle className="h-3 w-3 text-gray-500" strokeWidth={1} />}
1461
+ label={`Component: ${compLabel}`}
1462
+ onRemove={() => removeContextKey("componentIds", index)}
1463
+ />,
1464
+ );
1465
+ });
1466
+ }
1467
+
1468
+ // Render field chip
1469
+ if (ctx?.field) {
1470
+ const fieldLabel = `${ctx.field.name || resolvedFieldName || ctx.field.fieldId}`;
1471
+ chips.push(
1472
+ <Chip
1473
+ key="field"
1474
+ icon={<Type className="h-3 w-3 text-gray-500" strokeWidth={1} />}
1475
+ label={`Field: ${fieldLabel}`}
1476
+ onRemove={() => removeContextKey("field")}
1477
+ />,
1478
+ );
1479
+ }
1480
+
1481
+ return (
1482
+ <div className="border-t border-gray-100 px-4 py-2">
1483
+ <div className="flex items-center justify-between text-xs text-gray-600">
1484
+ <div className="flex flex-wrap items-center gap-2">{chips}</div>
1485
+ <div className="ml-2 flex flex-wrap items-center gap-2">
1486
+ {canAddPage && (
1487
+ <Button size="sm" variant="outline" onClick={addPagesToContext}>
1488
+ <Plus className="mr-1 h-3 w-3" strokeWidth={1} /> Add current
1489
+ item
1490
+ </Button>
1491
+ )}
1492
+ {canAddSelection && (
1493
+ <Button
1494
+ size="sm"
1495
+ variant="outline"
1496
+ onClick={addSelectedComponentsToContext}
1497
+ >
1498
+ <Plus className="mr-1 h-3 w-3" strokeWidth={1} /> Add selected
1499
+ components
1500
+ </Button>
1501
+ )}
1502
+ </div>
1503
+ </div>
1504
+ </div>
1505
+ );
1506
+ };
1507
+
957
1508
  return (
958
1509
  <div className="flex h-full flex-col">
959
1510
  {/* Messages */}
@@ -1052,6 +1603,9 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
1052
1603
  </div>
1053
1604
  )}
1054
1605
 
1606
+ {/* Context Info Bar */}
1607
+ {renderContextInfoBar()}
1608
+
1055
1609
  {/* Input */}
1056
1610
  <div className="border-t border-gray-200 p-4">
1057
1611
  <div className="flex items-stretch gap-2">