@alpaca-editor/core 1.0.4085 → 1.0.4088

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 (146) hide show
  1. package/dist/components/ui/card.d.ts +1 -1
  2. package/dist/components/ui/paste-button.d.ts +14 -0
  3. package/dist/components/ui/paste-button.js +114 -0
  4. package/dist/components/ui/paste-button.js.map +1 -0
  5. package/dist/config/config.js +60 -3
  6. package/dist/config/config.js.map +1 -1
  7. package/dist/config/types.d.ts +25 -0
  8. package/dist/editor/ContentTree.js +43 -21
  9. package/dist/editor/ContentTree.js.map +1 -1
  10. package/dist/editor/FieldListField.js +62 -2
  11. package/dist/editor/FieldListField.js.map +1 -1
  12. package/dist/editor/ai/AgentTerminal.d.ts +3 -1
  13. package/dist/editor/ai/AgentTerminal.js +96 -74
  14. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  15. package/dist/editor/ai/Agents.js +46 -2
  16. package/dist/editor/ai/Agents.js.map +1 -1
  17. package/dist/editor/ai/AiResponseMessage.js +171 -75
  18. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  19. package/dist/editor/ai/AiTerminal.js +27 -14
  20. package/dist/editor/ai/AiTerminal.js.map +1 -1
  21. package/dist/editor/client/EditorShell.js +110 -17
  22. package/dist/editor/client/EditorShell.js.map +1 -1
  23. package/dist/editor/client/editContext.d.ts +4 -0
  24. package/dist/editor/client/editContext.js.map +1 -1
  25. package/dist/editor/client/hooks/useSocketMessageHandler.d.ts +1 -0
  26. package/dist/editor/client/hooks/useSocketMessageHandler.js +54 -20
  27. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  28. package/dist/editor/client/hooks/useWorkbox.d.ts +1 -1
  29. package/dist/editor/client/hooks/useWorkbox.js +4 -4
  30. package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
  31. package/dist/editor/client/itemsRepository.d.ts +13 -1
  32. package/dist/editor/client/itemsRepository.js +34 -21
  33. package/dist/editor/client/itemsRepository.js.map +1 -1
  34. package/dist/editor/client/pageModelBuilder.js +1 -1
  35. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  36. package/dist/editor/control-center/Setup.d.ts +1 -0
  37. package/dist/editor/control-center/Setup.js +18 -0
  38. package/dist/editor/control-center/Setup.js.map +1 -0
  39. package/dist/editor/control-center/setup-steps/AiSetupStep.d.ts +2 -0
  40. package/dist/editor/control-center/setup-steps/AiSetupStep.js +287 -0
  41. package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +1 -0
  42. package/dist/editor/control-center/setup-steps/DbSetupStep.d.ts +2 -0
  43. package/dist/editor/control-center/setup-steps/DbSetupStep.js +46 -0
  44. package/dist/editor/control-center/setup-steps/DbSetupStep.js.map +1 -0
  45. package/dist/editor/control-center/setup-steps/IndexSetupStep.d.ts +2 -0
  46. package/dist/editor/control-center/setup-steps/IndexSetupStep.js +34 -0
  47. package/dist/editor/control-center/setup-steps/IndexSetupStep.js.map +1 -0
  48. package/dist/editor/control-center/setup-steps/SettingsSetupStep.d.ts +2 -0
  49. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js +104 -0
  50. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js.map +1 -0
  51. package/dist/editor/field-types/InternalLinkFieldEditor.js +3 -1
  52. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  53. package/dist/editor/field-types/RichTextEditorComponent.js +1 -1
  54. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  55. package/dist/editor/field-types/richtext/components/ReactSlate.js +2 -2
  56. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  57. package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +1 -1
  58. package/dist/editor/field-types/richtext/utils/profileServiceCache.js +16 -14
  59. package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -1
  60. package/dist/editor/menubar/toolbar-sections/CompareControls.js +1 -1
  61. package/dist/editor/menubar/toolbar-sections/CompareControls.js.map +1 -1
  62. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
  63. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  64. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
  65. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  66. package/dist/editor/page-editor-chrome/InlineEditor.js +25 -6
  67. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  68. package/dist/editor/page-viewer/EditorForm.js +9 -2
  69. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  70. package/dist/editor/page-viewer/PageViewerFrame.js +5 -0
  71. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  72. package/dist/editor/pageModel.d.ts +1 -0
  73. package/dist/editor/reviews/Comment.js +1 -1
  74. package/dist/editor/reviews/Comment.js.map +1 -1
  75. package/dist/editor/reviews/CommentDisplayPopover.js +3 -24
  76. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  77. package/dist/editor/reviews/CommentPopover.js +3 -23
  78. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  79. package/dist/editor/reviews/CommentView.js +2 -1
  80. package/dist/editor/reviews/CommentView.js.map +1 -1
  81. package/dist/editor/reviews/Comments.js +88 -37
  82. package/dist/editor/reviews/Comments.js.map +1 -1
  83. package/dist/editor/reviews/commentAi.js +3 -0
  84. package/dist/editor/reviews/commentAi.js.map +1 -1
  85. package/dist/editor/sidebar/Debug.js +1 -5
  86. package/dist/editor/sidebar/Debug.js.map +1 -1
  87. package/dist/editor/sidebar/ViewSelector.js +72 -6
  88. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  89. package/dist/editor/ui/Icons.d.ts +5 -0
  90. package/dist/editor/ui/Icons.js +14 -0
  91. package/dist/editor/ui/Icons.js.map +1 -1
  92. package/dist/editor/utils.d.ts +5 -0
  93. package/dist/editor/utils.js +29 -0
  94. package/dist/editor/utils.js.map +1 -1
  95. package/dist/revision.d.ts +2 -2
  96. package/dist/revision.js +2 -2
  97. package/dist/splash-screen/SplashScreen.js +2 -2
  98. package/dist/splash-screen/SplashScreen.js.map +1 -1
  99. package/dist/styles.css +3 -0
  100. package/dist/types.d.ts +2 -0
  101. package/package.json +1 -1
  102. package/src/components/ui/card.tsx +1 -1
  103. package/src/components/ui/paste-button.tsx +163 -0
  104. package/src/config/config.tsx +68 -2
  105. package/src/config/types.ts +26 -0
  106. package/src/editor/ContentTree.tsx +48 -23
  107. package/src/editor/FieldListField.tsx +75 -2
  108. package/src/editor/ai/AgentTerminal.tsx +118 -71
  109. package/src/editor/ai/Agents.tsx +52 -1
  110. package/src/editor/ai/AiResponseMessage.tsx +234 -78
  111. package/src/editor/ai/AiTerminal.tsx +30 -14
  112. package/src/editor/client/EditorShell.tsx +128 -25
  113. package/src/editor/client/editContext.ts +1 -0
  114. package/src/editor/client/hooks/useSocketMessageHandler.ts +70 -21
  115. package/src/editor/client/hooks/useWorkbox.ts +4 -4
  116. package/src/editor/client/itemsRepository.ts +56 -25
  117. package/src/editor/client/pageModelBuilder.ts +1 -1
  118. package/src/editor/control-center/Setup.tsx +26 -0
  119. package/src/editor/control-center/setup-steps/AiSetupStep.tsx +462 -0
  120. package/src/editor/control-center/setup-steps/DbSetupStep.tsx +84 -0
  121. package/src/editor/control-center/setup-steps/IndexSetupStep.tsx +56 -0
  122. package/src/editor/control-center/setup-steps/SettingsSetupStep.tsx +176 -0
  123. package/src/editor/field-types/InternalLinkFieldEditor.tsx +3 -1
  124. package/src/editor/field-types/RichTextEditorComponent.tsx +0 -1
  125. package/src/editor/field-types/richtext/components/ReactSlate.tsx +14 -6
  126. package/src/editor/field-types/richtext/utils/profileServiceCache.ts +42 -32
  127. package/src/editor/menubar/toolbar-sections/CompareControls.tsx +1 -0
  128. package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
  129. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +1 -0
  130. package/src/editor/page-editor-chrome/InlineEditor.tsx +29 -6
  131. package/src/editor/page-viewer/EditorForm.tsx +13 -2
  132. package/src/editor/page-viewer/PageViewerFrame.tsx +4 -0
  133. package/src/editor/pageModel.ts +1 -0
  134. package/src/editor/reviews/Comment.tsx +1 -1
  135. package/src/editor/reviews/CommentDisplayPopover.tsx +2 -22
  136. package/src/editor/reviews/CommentPopover.tsx +3 -24
  137. package/src/editor/reviews/CommentView.tsx +3 -2
  138. package/src/editor/reviews/Comments.tsx +162 -35
  139. package/src/editor/reviews/commentAi.ts +5 -0
  140. package/src/editor/sidebar/Debug.tsx +1 -5
  141. package/src/editor/sidebar/ViewSelector.tsx +144 -28
  142. package/src/editor/ui/Icons.tsx +55 -0
  143. package/src/editor/utils.ts +33 -0
  144. package/src/revision.ts +2 -2
  145. package/src/splash-screen/SplashScreen.tsx +5 -6
  146. package/src/types.ts +3 -0
@@ -35,7 +35,7 @@ import { AgentCostDisplay } from "./AgentCostDisplay";
35
35
  import { Message } from "./AiTerminal";
36
36
  import { getComponentById } from "../componentTreeHelper";
37
37
  import { Comment } from "../../types";
38
- import { AiProfile, loadAiProfiles } from "../services/aiService";
38
+ import { AiProfile } from "../services/aiService";
39
39
  import {
40
40
  Popover,
41
41
  PopoverContent,
@@ -179,9 +179,11 @@ const convertAgentMessagesToAiFormat = (
179
179
  export function AgentTerminal({
180
180
  agentStub,
181
181
  initialMetadata,
182
+ profiles,
182
183
  }: {
183
184
  agentStub: Agent;
184
185
  initialMetadata?: AgentMetadata;
186
+ profiles: AiProfile[];
185
187
  }) {
186
188
  const editContext = useEditContext();
187
189
  const fieldsContext = useFieldsEditContext();
@@ -245,7 +247,6 @@ export function AgentTerminal({
245
247
  });
246
248
  const [currentHistoryIndex, setCurrentHistoryIndex] = useState<number>(-1);
247
249
  const [showPredefined, setShowPredefined] = useState(false);
248
- const [profiles, setProfiles] = useState<AiProfile[]>([]);
249
250
  const [activeProfile, setActiveProfile] = useState<AiProfile | undefined>(
250
251
  undefined,
251
252
  );
@@ -909,48 +910,102 @@ export function AgentTerminal({
909
910
  setIsLoading(false);
910
911
 
911
912
  // Initialize local context for a brand-new agent (not yet persisted)
912
- // Prefer explicitly provided metadata; otherwise seed with page, selected componentIds, and focused field if available
913
+ // Seed with current page/selection/focused field. If initialMetadata is provided (e.g., profile only), merge context in.
914
+ const item = editContext?.currentItemDescriptor;
915
+ const localCtx: AgentMetadata | null = item
916
+ ? {
917
+ additionalData: {
918
+ context: {
919
+ pages: [
920
+ {
921
+ id: item.id,
922
+ language: item.language,
923
+ version: item.version,
924
+ name: editContext?.contentEditorItem?.name,
925
+ },
926
+ ],
927
+ componentIds: editContext?.selection?.length
928
+ ? editContext.selection
929
+ : undefined,
930
+ field:
931
+ fieldsContext?.focusedField?.fieldId &&
932
+ (fieldsContext.focusedField as any)?.item?.id
933
+ ? {
934
+ fieldId: fieldsContext.focusedField.fieldId,
935
+ itemId: (fieldsContext.focusedField as any).item.id,
936
+ name: (fieldsContext.focusedField as any).fieldName,
937
+ }
938
+ : undefined,
939
+ },
940
+ },
941
+ }
942
+ : null;
943
+
944
+ let nextMetadata: AgentMetadata | null = null;
913
945
  if (initialMetadata) {
914
- setAgentMetadata(initialMetadata);
946
+ // Merge initial metadata (e.g., profile) with local context
947
+ const base: AgentMetadata = {
948
+ ...(initialMetadata as any),
949
+ additionalData: {
950
+ ...((initialMetadata as any)?.additionalData || {}),
951
+ context: {
952
+ ...(((initialMetadata as any)?.additionalData
953
+ ?.context as any) || {}),
954
+ },
955
+ },
956
+ } as AgentMetadata;
957
+
958
+ if (localCtx?.additionalData?.context) {
959
+ const ctx = base.additionalData!.context as any;
960
+ const local = localCtx.additionalData!.context as any;
961
+ // Merge pages (avoid duplicates)
962
+ if (local.pages && local.pages.length) {
963
+ const existing = new Set(
964
+ (ctx.pages || []).map(
965
+ (p: any) => `${p.id}-${p.language}-${p.version}`,
966
+ ),
967
+ );
968
+ const mergedPages = [
969
+ ...(ctx.pages || []),
970
+ ...local.pages.filter(
971
+ (p: any) =>
972
+ !existing.has(`${p.id}-${p.language}-${p.version}`),
973
+ ),
974
+ ];
975
+ if (mergedPages.length) ctx.pages = mergedPages;
976
+ }
977
+ // Merge componentIds
978
+ if (local.componentIds && local.componentIds.length) {
979
+ const existingIds = new Set(ctx.componentIds || []);
980
+ const mergedIds = [
981
+ ...(ctx.componentIds || []),
982
+ ...local.componentIds.filter(
983
+ (id: string) => !existingIds.has(id),
984
+ ),
985
+ ];
986
+ if (mergedIds.length) ctx.componentIds = mergedIds;
987
+ }
988
+ // Set field if missing
989
+ if (!ctx.field && local.field) {
990
+ ctx.field = local.field;
991
+ }
992
+ }
993
+
994
+ nextMetadata = base;
995
+ } else {
996
+ nextMetadata = localCtx;
997
+ }
998
+
999
+ if (nextMetadata) {
1000
+ setAgentMetadata(nextMetadata);
915
1001
  // If an initial prompt is provided via metadata, seed the input once
916
1002
  try {
917
- const maybePrompt = (initialMetadata as any)?.additionalData
1003
+ const maybePrompt = (nextMetadata as any)?.additionalData
918
1004
  ?.initialPrompt;
919
1005
  if (typeof maybePrompt === "string" && maybePrompt.trim()) {
920
1006
  setPrompt(maybePrompt);
921
1007
  }
922
1008
  } catch {}
923
- } else {
924
- const item = editContext?.currentItemDescriptor;
925
- const initialLocalContext: AgentMetadata | null = item
926
- ? {
927
- additionalData: {
928
- context: {
929
- pages: [
930
- {
931
- id: item.id,
932
- language: item.language,
933
- version: item.version,
934
- name: editContext?.contentEditorItem?.name,
935
- },
936
- ],
937
- componentIds: editContext?.selection?.length
938
- ? editContext.selection
939
- : undefined,
940
- field:
941
- fieldsContext?.focusedField?.fieldId &&
942
- (fieldsContext.focusedField as any)?.item?.id
943
- ? {
944
- fieldId: fieldsContext.focusedField.fieldId,
945
- itemId: (fieldsContext.focusedField as any).item.id,
946
- name: (fieldsContext.focusedField as any).fieldName,
947
- }
948
- : undefined,
949
- },
950
- },
951
- }
952
- : null;
953
- if (initialLocalContext) setAgentMetadata(initialLocalContext);
954
1009
  }
955
1010
  return;
956
1011
  }
@@ -1052,23 +1107,7 @@ export function AgentTerminal({
1052
1107
  );
1053
1108
  }, []);
1054
1109
 
1055
- // Load AI profiles for predefined prompts
1056
- useEffect(() => {
1057
- let cancelled = false;
1058
- (async () => {
1059
- try {
1060
- if (!editContext?.currentItemDescriptor) return;
1061
- const fetched = await loadAiProfiles(editContext.currentItemDescriptor);
1062
- if (cancelled) return;
1063
- setProfiles(fetched || []);
1064
- } catch (e) {
1065
- console.error("Failed to load AI profiles", e);
1066
- }
1067
- })();
1068
- return () => {
1069
- cancelled = true;
1070
- };
1071
- }, [editContext?.currentItemDescriptor]);
1110
+ // Profiles are provided by parent component (Agents). No local loading here.
1072
1111
 
1073
1112
  // Select active profile based on agent.profileId or default to first
1074
1113
  useEffect(() => {
@@ -1121,6 +1160,17 @@ export function AgentTerminal({
1121
1160
 
1122
1161
  if (!agentId) return;
1123
1162
 
1163
+ const shouldAddTodoFormat = /\b(to-?do|check\s*list|task\s*list)\b/i.test(
1164
+ prompt,
1165
+ );
1166
+ const todoFormatInstructions =
1167
+ "If you produce a to-do list, output it as a 'todo_list' JSON block. Prefer one of these two forms only, with no extra commentary before/after it: \n" +
1168
+ '```todo_list\n{ "title": "<optional title>", "items": [ { "text": "<task>", "done": false, "note": "<optional>" } ] }\n```\n' +
1169
+ "or a plain header: todo_list: { ... }. Include only fields: title (optional), items (array of {text, done?, note?}).";
1170
+ const finalMessageForAgent = shouldAddTodoFormat
1171
+ ? `${prompt.trim()}\n\n${todoFormatInstructions}`
1172
+ : prompt.trim();
1173
+
1124
1174
  // Add user message to local state immediately for better UX
1125
1175
  const userMessage: AgentChatMessage = {
1126
1176
  id: `user-${Date.now()}`,
@@ -1147,20 +1197,17 @@ export function AgentTerminal({
1147
1197
  setMessages((prev) => [...prev, userMessage]);
1148
1198
 
1149
1199
  const metaCtx = (agentMetadata as any)?.additionalData?.context as
1150
- | { componentIds?: string[] }
1200
+ | { componentIds?: string[]; comment?: { selectedText?: string } }
1151
1201
  | undefined;
1152
1202
  const selectionFromCtx = metaCtx?.componentIds?.length
1153
1203
  ? metaCtx.componentIds
1154
1204
  : undefined;
1155
- const effectiveSelection = selectionFromCtx?.length
1156
- ? selectionFromCtx
1157
- : editContext.selection && editContext.selection.length > 0
1158
- ? editContext.selection
1159
- : undefined;
1205
+ const effectiveSelection = selectionFromCtx;
1206
+ const selectedTextFromCtx = metaCtx?.comment?.selectedText || undefined;
1160
1207
 
1161
1208
  const request: StartAgentRequest = {
1162
1209
  agentId: agent.id,
1163
- message: prompt.trim(),
1210
+ message: finalMessageForAgent,
1164
1211
  sessionId: editContext.sessionId,
1165
1212
  profileId: activeProfile?.id || "default",
1166
1213
  profile: activeProfile?.name || "default",
@@ -1169,7 +1216,7 @@ export function AgentTerminal({
1169
1216
  language: editContext.currentItemDescriptor?.language || "en",
1170
1217
  version: editContext.currentItemDescriptor?.version || 1,
1171
1218
  selection: effectiveSelection,
1172
- selectedText: editContext.selectedRange?.text || undefined,
1219
+ selectedText: selectedTextFromCtx,
1173
1220
  addSelectedComponents: !!effectiveSelection?.length,
1174
1221
  addContextContent: false,
1175
1222
  addAllContent: false,
@@ -1296,16 +1343,13 @@ export function AgentTerminal({
1296
1343
  setMessages((prev) => [...prev, userMessage]);
1297
1344
 
1298
1345
  const metaCtx = (agentMetadata as any)?.additionalData?.context as
1299
- | { componentIds?: string[] }
1346
+ | { componentIds?: string[]; comment?: { selectedText?: string } }
1300
1347
  | undefined;
1301
1348
  const selectionFromCtx = metaCtx?.componentIds?.length
1302
1349
  ? metaCtx.componentIds
1303
1350
  : undefined;
1304
- const effectiveSelection = selectionFromCtx?.length
1305
- ? selectionFromCtx
1306
- : editContext.selection && editContext.selection.length > 0
1307
- ? editContext.selection
1308
- : undefined;
1351
+ const effectiveSelection = selectionFromCtx;
1352
+ const selectedTextFromCtx = metaCtx?.comment?.selectedText || undefined;
1309
1353
 
1310
1354
  const request: StartAgentRequest = {
1311
1355
  agentId: agent.id,
@@ -1318,7 +1362,7 @@ export function AgentTerminal({
1318
1362
  language: editContext.currentItemDescriptor?.language || "en",
1319
1363
  version: editContext.currentItemDescriptor?.version || 1,
1320
1364
  selection: effectiveSelection,
1321
- selectedText: editContext.selectedRange?.text || undefined,
1365
+ selectedText: selectedTextFromCtx,
1322
1366
  addSelectedComponents: !!effectiveSelection?.length,
1323
1367
  addContextContent: false,
1324
1368
  addAllContent: false,
@@ -1957,12 +2001,12 @@ export function AgentTerminal({
1957
2001
  })}
1958
2002
  </div>
1959
2003
 
1960
- {/* Show dancing dots only after 1s of inactivity */}
1961
- {showDots && <DancingDots />}
1962
-
1963
2004
  <div ref={messagesEndRef} />
1964
2005
  </div>
1965
2006
 
2007
+ {/* Show dancing dots only after 1s of inactivity */}
2008
+ {showDots && <DancingDots />}
2009
+
1966
2010
  {/* Context Info Bar */}
1967
2011
  {renderContextInfoBar()}
1968
2012
 
@@ -1982,6 +2026,7 @@ export function AgentTerminal({
1982
2026
  onKeyDown={handleKeyPress}
1983
2027
  placeholder="Type your message... (Enter to send, Ctrl+Enter for new line)"
1984
2028
  className="h-[80px] flex-1 resize-none overflow-y-auto text-xs"
2029
+ data-testid="agent-terminal-prompt"
1985
2030
  disabled={isSubmitting}
1986
2031
  />
1987
2032
  </div>
@@ -1998,6 +2043,7 @@ export function AgentTerminal({
1998
2043
  }}
1999
2044
  title="Profile"
2000
2045
  aria-label="Profile"
2046
+ data-testid="agent-profile-select"
2001
2047
  >
2002
2048
  {profiles.map((p) => (
2003
2049
  <option key={p.id} value={p.id}>
@@ -2013,6 +2059,7 @@ export function AgentTerminal({
2013
2059
  onChange={(e) => setSelectedModelId(e.target.value)}
2014
2060
  title="Model"
2015
2061
  aria-label="Model"
2062
+ data-testid="agent-model-select"
2016
2063
  >
2017
2064
  {activeProfile.models.map((m) => (
2018
2065
  <option key={m.id} value={m.id}>
@@ -58,6 +58,7 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
58
58
  const [agents, setAgents] = useState<Agent[]>([]);
59
59
  const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
60
60
  const activeAgentIdRef = useRef<string | null>(null);
61
+ const singleAgentModeRef = useRef<boolean>(false);
61
62
  const [historyPopoverOpen, setHistoryPopoverOpen] = useState(false);
62
63
  const [menuPopoverOpen, setMenuPopoverOpen] = useState(false);
63
64
  const [addPopoverOpen, setAddPopoverOpen] = useState(false);
@@ -73,6 +74,16 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
73
74
 
74
75
  const editContext = useEditContext();
75
76
 
77
+ // Detect single-agent mode from query parameter once on mount
78
+ useEffect(() => {
79
+ try {
80
+ const params = new URLSearchParams(window.location.search);
81
+ const value = params.get("singleAgent");
82
+ singleAgentModeRef.current =
83
+ value === "true" || params.has("singleAgent");
84
+ } catch {}
85
+ }, []);
86
+
76
87
  // Helper function to filter agents by name
77
88
  const getFilteredInactiveAgents = (): Agent[] => {
78
89
  if (!historyFilter.trim()) {
@@ -139,6 +150,29 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
139
150
  };
140
151
  }, [addPopoverOpen, agents.length, editContext?.currentItemDescriptor]);
141
152
 
153
+ // Ensure profiles are loaded once for terminals (centralized load)
154
+ useEffect(() => {
155
+ let cancelled = false;
156
+ (async () => {
157
+ try {
158
+ if (!editContext?.currentItemDescriptor) return;
159
+ setLoadingProfiles(true);
160
+ const profiles = await loadAiProfiles(
161
+ editContext.currentItemDescriptor,
162
+ );
163
+ if (cancelled) return;
164
+ setAvailableProfiles(profiles || []);
165
+ } catch (e) {
166
+ console.error("Failed to load AI profiles", e);
167
+ } finally {
168
+ if (!cancelled) setLoadingProfiles(false);
169
+ }
170
+ })();
171
+ return () => {
172
+ cancelled = true;
173
+ };
174
+ }, [editContext?.currentItemDescriptor]);
175
+
142
176
  // Listen for external requests to add a new agent (e.g., AI command)
143
177
  useEffect(() => {
144
178
  const handleAddNewAgent = (ev: Event) => {
@@ -229,6 +263,15 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
229
263
 
230
264
  const loadAgentsFromBackend = async () => {
231
265
  try {
266
+ // In single-agent mode, skip loading any pre-existing/open agents or history
267
+ if (singleAgentModeRef.current) {
268
+ setAgents([]);
269
+ setInactiveAgents([]);
270
+ setActiveAgentIdWithStorage(null);
271
+ setLoadingAgents(false);
272
+ return;
273
+ }
274
+
232
275
  setLoadingAgents(true);
233
276
 
234
277
  // Load active agents
@@ -458,6 +501,7 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
458
501
  createAgentWithSelectedProfile(value);
459
502
  }}
460
503
  aria-label="Profile"
504
+ data-testid="agents-empty-profile-select"
461
505
  >
462
506
  <option value="">Select a profile…</option>
463
507
  {availableProfiles.map((p) => (
@@ -676,7 +720,11 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
676
720
  className="text-gray-600 hover:text-gray-800"
677
721
  />
678
722
  </PopoverTrigger>
679
- <PopoverContent className="w-72 p-3" align="end">
723
+ <PopoverContent
724
+ className="w-72 p-3"
725
+ align="end"
726
+ data-testid="agents-add-popover"
727
+ >
680
728
  <div className="mb-2 text-xs font-medium text-gray-600">
681
729
  Select profile
682
730
  </div>
@@ -689,6 +737,8 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
689
737
  key={p.id}
690
738
  onClick={() => createAgentWithSelectedProfile(p.id)}
691
739
  className="w-full cursor-pointer rounded px-3 py-2 text-left text-xs hover:bg-gray-50"
740
+ data-testid="agents-add-profile-option"
741
+ data-profile-id={p.id}
692
742
  >
693
743
  {p.name}
694
744
  </button>
@@ -722,6 +772,7 @@ export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
722
772
  <AgentTerminal
723
773
  agentStub={agent}
724
774
  initialMetadata={initialMetadataMap[agent.id]}
775
+ profiles={availableProfiles}
725
776
  />
726
777
  </div>
727
778
  ))}