@promptbook/cli 0.113.0-5 → 0.113.0-8

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 (123) hide show
  1. package/apps/agents-server/src/app/admin/update/AdvancedOriginRepositoryPanel.tsx +76 -0
  2. package/apps/agents-server/src/app/admin/update/CurrentDeploymentCard.tsx +160 -0
  3. package/apps/agents-server/src/app/admin/update/PendingCommitsCard.tsx +86 -0
  4. package/apps/agents-server/src/app/admin/update/TargetEnvironmentCard.tsx +331 -0
  5. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +20 -873
  6. package/apps/agents-server/src/app/admin/update/UpdateJobCard.tsx +329 -0
  7. package/apps/agents-server/src/app/admin/update/UpdateOverview.ts +74 -0
  8. package/apps/agents-server/src/app/admin/update/buildDeploymentTimeBehindLabel.ts +29 -0
  9. package/apps/agents-server/src/app/admin/update/formatHumanReadableTimestamp.ts +18 -0
  10. package/apps/agents-server/src/app/admin/update/getUpdateJobFailureMessage.ts +13 -0
  11. package/apps/agents-server/src/app/admin/update/getUpdateJobSuccessMessage.ts +13 -0
  12. package/apps/agents-server/src/app/admin/update/useUpdateClientState.ts +441 -0
  13. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +13 -1
  14. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageClient.tsx +14 -559
  15. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageHeader.tsx +84 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageSection.tsx +52 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedCodePreview.tsx +138 -0
  18. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedHarnessSection.tsx +98 -0
  19. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/SourceBookSection.tsx +66 -0
  20. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeErrorBanner.tsx +30 -0
  21. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeExportWarningBanner.tsx +62 -0
  22. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/Transpiler.ts +9 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/useAgentCodeExportState.ts +478 -0
  24. package/apps/agents-server/src/app/system/utilities/mocked-chats/cloneMockedChatPreset.ts +17 -0
  25. package/apps/agents-server/src/app/system/utilities/mocked-chats/confirmDeleteMockedChat.ts +16 -0
  26. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedMessage.ts +81 -0
  27. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedParticipant.ts +146 -0
  28. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedSettings.ts +19 -0
  29. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDuplicatedDraft.ts +66 -0
  30. package/apps/agents-server/src/app/system/utilities/mocked-chats/findMockedChatById.ts +51 -0
  31. package/apps/agents-server/src/app/system/utilities/mocked-chats/persistMockedChats.ts +51 -0
  32. package/apps/agents-server/src/app/system/utilities/mocked-chats/useMockedChatsEditorState.ts +16 -426
  33. package/apps/agents-server/src/components/Homepage/buildFreeGraphBoxLayout.ts +446 -0
  34. package/apps/agents-server/src/components/Homepage/buildGraphLayoutNodes.ts +415 -157
  35. package/apps/agents-server/src/components/Homepage/useAgentsGraphCanvasState.ts +2 -1
  36. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +3 -1541
  37. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteQueryBuilder.ts +623 -0
  38. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteSupabaseClient.ts +64 -0
  39. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/ensureTable.ts +93 -0
  40. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteFilters.ts +202 -0
  41. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteMutationPayload.ts +131 -0
  42. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteRowOperations.ts +102 -0
  43. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteSql.ts +59 -0
  44. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTableSchema.ts +329 -0
  45. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTypes.ts +84 -0
  46. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteValueCodec.ts +63 -0
  47. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/normalizeSqliteError.ts +22 -0
  48. package/apps/agents-server/src/search/createDefaultServerSearchProviders/loadLocalOrganizationSearchDataset.ts +12 -1
  49. package/apps/agents-server/src/tools/send_email.ts +1 -2
  50. package/apps/agents-server/src/utils/agentOrganization/loadAgentOrganizationState.ts +13 -1
  51. package/apps/agents-server/src/utils/backup/createServerBackupZipStream.ts +7 -1382
  52. package/apps/agents-server/src/utils/backup/serverBackup/appendAgentBackupEntriesToZip.ts +27 -0
  53. package/apps/agents-server/src/utils/backup/serverBackup/appendConversationBackupEntriesToZip.ts +140 -0
  54. package/apps/agents-server/src/utils/backup/serverBackup/appendFileBackupEntriesToZip.ts +147 -0
  55. package/apps/agents-server/src/utils/backup/serverBackup/appendMessageBackupEntriesToZip.ts +65 -0
  56. package/apps/agents-server/src/utils/backup/serverBackup/appendMetadataBackupEntriesToZip.ts +39 -0
  57. package/apps/agents-server/src/utils/backup/serverBackup/appendSectionEntriesToZip.ts +92 -0
  58. package/apps/agents-server/src/utils/backup/serverBackup/appendUserBackupEntriesToZip.ts +80 -0
  59. package/apps/agents-server/src/utils/backup/serverBackup/createRedactedWalletBackupRecord.ts +34 -0
  60. package/apps/agents-server/src/utils/backup/serverBackup/createServerBackupManifest.ts +82 -0
  61. package/apps/agents-server/src/utils/backup/serverBackup/downloadBackupBinaryContent.ts +39 -0
  62. package/apps/agents-server/src/utils/backup/serverBackup/resolveFeedbackThreadMessages.ts +53 -0
  63. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupAttachments.ts +151 -0
  64. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupContext.ts +167 -0
  65. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupFilenames.ts +161 -0
  66. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupPreviews.ts +97 -0
  67. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupRowUtilities.ts +158 -0
  68. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupTypes.ts +59 -0
  69. package/apps/agents-server/src/utils/createLocalAgentSourceImporter.ts +159 -0
  70. package/apps/agents-server/src/utils/createMissingImportedAgentFallback.ts +60 -0
  71. package/apps/agents-server/src/utils/customDomainRouting.ts +157 -12
  72. package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +1 -1
  73. package/apps/agents-server/src/utils/importAgentWithFallback.ts +1 -58
  74. package/apps/agents-server/src/utils/localAgentRouteReferences.ts +167 -0
  75. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +1 -1
  76. package/apps/agents-server/src/utils/managementApi/managementApiAgents.ts +17 -3
  77. package/apps/agents-server/src/utils/messages/sendMessage.ts +5 -3
  78. package/apps/agents-server/src/utils/resolveAgentStateFromSource.ts +7 -1
  79. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +54 -5
  80. package/apps/agents-server/src/utils/resolveServerAgentContext.ts +12 -1
  81. package/apps/agents-server/src/utils/resolveStoredAgentState.ts +2 -1
  82. package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition/SpeechToTextFailoverRecognitionProviderRuntime.ts +0 -1
  83. package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition.ts +34 -2
  84. package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +39 -9
  85. package/apps/agents-server/tests/e2e/support/ChatHistoryNavigationSupport.ts +5 -0
  86. package/esm/index.es.js +96 -57
  87. package/esm/index.es.js.map +1 -1
  88. package/esm/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
  89. package/esm/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
  90. package/esm/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
  91. package/esm/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
  92. package/esm/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
  93. package/esm/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
  94. package/esm/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
  95. package/esm/src/version.d.ts +1 -1
  96. package/package.json +1 -1
  97. package/src/avatars/avatarAnimationScheduler.ts +2 -1
  98. package/src/avatars/visuals/octopus3d3AvatarVisual.ts +6 -2
  99. package/src/avatars/visuals/octopus3d4AvatarVisual.ts +14 -18
  100. package/src/book-components/Chat/Chat/insertDictationChunk.ts +3 -3
  101. package/src/book-components/Chat/Chat/learnDictationDictionary.ts +1 -1
  102. package/src/book-components/Chat/Chat/useChatInputAreaDictation.ts +133 -26
  103. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +3 -1
  104. package/src/cli/cli-commands/coder/boilerplateTemplates.ts +14 -21
  105. package/src/cli/cli-commands/coder/find-refactor-candidates.ts +9 -2
  106. package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +1 -4
  107. package/src/cli/cli-commands/coder/run.ts +3 -27
  108. package/src/cli/cli-commands/common/createPositiveIntegerOptionParser.ts +31 -0
  109. package/src/commitments/TEAM/TEAM.ts +5 -5
  110. package/src/other/templates/getTemplatesPipelineCollection.ts +727 -781
  111. package/src/utils/ascii-art/convertImageDataToAsciiArt.ts +1 -4
  112. package/src/version.ts +2 -2
  113. package/src/versions.txt +1 -0
  114. package/umd/index.umd.js +96 -57
  115. package/umd/index.umd.js.map +1 -1
  116. package/umd/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
  117. package/umd/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
  118. package/umd/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
  119. package/umd/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
  120. package/umd/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
  121. package/umd/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
  122. package/umd/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
  123. package/umd/src/version.d.ts +1 -1
@@ -5,8 +5,13 @@ import { darken } from '../../../../../src/utils/color/operators/darken';
5
5
  import { textColor } from '../../../../../src/utils/color/operators/furthest';
6
6
  import { lighten } from '../../../../../src/utils/color/operators/lighten';
7
7
  import type { Node } from 'reactflow';
8
- import type { AgentWithVisibility, GraphNode, ServerGroup } from './buildGraphData';
9
- import { normalizeServerUrl } from './buildGraphData';
8
+ import type { AgentWithVisibility, GraphLink, GraphNode, ServerGroup } from './buildGraphDataTypes';
9
+ import {
10
+ buildFreeGraphBoxLayout,
11
+ type FreeGraphBoxLayoutLink,
12
+ type FreeGraphBoxLayoutPosition,
13
+ } from './buildFreeGraphBoxLayout';
14
+ import { normalizeServerUrl } from './normalizeServerUrl';
10
15
 
11
16
  /**
12
17
  * Width of each rendered agent node.
@@ -44,67 +49,116 @@ const FOLDER_PADDING_X = 24;
44
49
  const FOLDER_PADDING_Y = 20;
45
50
 
46
51
  /**
47
- * Horizontal gap between folders.
52
+ * Horizontal spacing between agent nodes.
48
53
  *
49
54
  * @private function of AgentsGraph
50
55
  */
51
- const FOLDER_GAP_X = 40;
56
+ const AGENT_HORIZONTAL_GAP = 16;
52
57
 
53
58
  /**
54
- * Vertical gap between folders.
59
+ * Vertical spacing between agent nodes.
55
60
  *
56
61
  * @private function of AgentsGraph
57
62
  */
58
- const FOLDER_GAP_Y = 32;
63
+ const AGENT_VERTICAL_GAP = 16;
59
64
 
60
65
  /**
61
- * Maximum columns used in an agent folder grid.
66
+ * Server title strip height.
62
67
  *
63
68
  * @private function of AgentsGraph
64
69
  */
65
- const AGENT_MAX_COLUMNS = 4;
70
+ const SERVER_HEADER_HEIGHT = 28;
66
71
 
67
72
  /**
68
- * Horizontal spacing between agent nodes.
73
+ * Horizontal server padding.
69
74
  *
70
75
  * @private function of AgentsGraph
71
76
  */
72
- const AGENT_HORIZONTAL_GAP = 16;
77
+ const SERVER_PADDING_X = 32;
73
78
 
74
79
  /**
75
- * Vertical spacing between agent nodes.
80
+ * Vertical server padding.
76
81
  *
77
82
  * @private function of AgentsGraph
78
83
  */
79
- const AGENT_VERTICAL_GAP = 16;
84
+ const SERVER_PADDING_Y = 24;
80
85
 
81
86
  /**
82
- * Server title strip height.
87
+ * Horizontal gap between server groups.
83
88
  *
84
89
  * @private function of AgentsGraph
85
90
  */
86
- const SERVER_HEADER_HEIGHT = 28;
91
+ const SERVER_GAP_X = 96;
87
92
 
88
93
  /**
89
- * Horizontal server padding.
94
+ * Vertical gap between server groups.
90
95
  *
91
96
  * @private function of AgentsGraph
92
97
  */
93
- const SERVER_PADDING_X = 32;
98
+ const SERVER_GAP_Y = 80;
94
99
 
95
100
  /**
96
- * Vertical server padding.
101
+ * Preferred distance between linked agent nodes during layout relaxation.
97
102
  *
98
103
  * @private function of AgentsGraph
99
104
  */
100
- const SERVER_PADDING_Y = 24;
105
+ const AGENT_RELATIONSHIP_DISTANCE = 96;
101
106
 
102
107
  /**
103
- * Gap between server groups.
108
+ * Strength used to pull linked agent nodes together.
104
109
  *
105
110
  * @private function of AgentsGraph
106
111
  */
107
- const SERVER_GAP = 64;
112
+ const AGENT_RELATIONSHIP_STRENGTH = 0.045;
113
+
114
+ /**
115
+ * Strength used to keep unrelated agent nodes near their seeded placement.
116
+ *
117
+ * @private function of AgentsGraph
118
+ */
119
+ const AGENT_CENTER_PULL_STRENGTH = 0.014;
120
+
121
+ /**
122
+ * Preferred distance between linked folder groups during layout relaxation.
123
+ *
124
+ * @private function of AgentsGraph
125
+ */
126
+ const FOLDER_RELATIONSHIP_DISTANCE = 180;
127
+
128
+ /**
129
+ * Strength used to pull linked folder groups together.
130
+ *
131
+ * @private function of AgentsGraph
132
+ */
133
+ const FOLDER_RELATIONSHIP_STRENGTH = 0.035;
134
+
135
+ /**
136
+ * Strength used to keep unrelated folder groups near their seeded placement.
137
+ *
138
+ * @private function of AgentsGraph
139
+ */
140
+ const FOLDER_CENTER_PULL_STRENGTH = 0.012;
141
+
142
+ /**
143
+ * Preferred distance between linked server groups during layout relaxation.
144
+ *
145
+ * @private function of AgentsGraph
146
+ */
147
+ const SERVER_RELATIONSHIP_DISTANCE = 260;
148
+
149
+ /**
150
+ * Strength used to pull linked server groups together.
151
+ *
152
+ * @private function of AgentsGraph
153
+ */
154
+ const SERVER_RELATIONSHIP_STRENGTH = 0.028;
155
+
156
+ /**
157
+ * Strength used to keep remote server groups around the current server.
158
+ *
159
+ * @private function of AgentsGraph
160
+ */
161
+ const SERVER_CENTER_PULL_STRENGTH = 0.01;
108
162
 
109
163
  /**
110
164
  * Storage key prefix for persisted node positions.
@@ -132,15 +186,11 @@ type NodeVisualStyle = {
132
186
  */
133
187
  type FolderLayout = {
134
188
  folder: ServerGroup['folders'][number];
189
+ nodeId: string;
135
190
  width: number;
136
191
  height: number;
137
- agentColumns: number;
138
- contentWidth: number;
139
- contentHeight: number;
140
- column?: number;
141
- row?: number;
142
- x?: number;
143
- y?: number;
192
+ agentPositionsById: Map<string, FreeGraphBoxLayoutPosition>;
193
+ orderIndex: number;
144
194
  };
145
195
 
146
196
  /**
@@ -150,9 +200,33 @@ type FolderLayout = {
150
200
  */
151
201
  type ServerLayout = {
152
202
  serverGroup: ServerGroup;
203
+ nodeId: string;
153
204
  folderLayouts: FolderLayout[];
205
+ folderPositionsById: Map<string, FreeGraphBoxLayoutPosition>;
154
206
  width: number;
155
207
  height: number;
208
+ orderIndex: number;
209
+ };
210
+
211
+ /**
212
+ * Hierarchical placement metadata for one agent node.
213
+ *
214
+ * @private function of AgentsGraph
215
+ */
216
+ type AgentLayoutLocation = {
217
+ serverNodeId: string;
218
+ folderNodeId: string;
219
+ };
220
+
221
+ /**
222
+ * Weighted layout link accumulator.
223
+ *
224
+ * @private function of AgentsGraph
225
+ */
226
+ type AggregatedLayoutLink = {
227
+ sourceId: string;
228
+ targetId: string;
229
+ weight: number;
156
230
  };
157
231
 
158
232
  /**
@@ -299,139 +373,326 @@ export const saveStoredPositions = (storageKey: string, positions: StoredPositio
299
373
  };
300
374
 
301
375
  /**
302
- * Build nodes for servers, folders, and agents using a hierarchical grid layout that keeps the current server centered.
303
- *
304
- * @private function of AgentsGraph
376
+ * Build the stable React Flow server group node id.
305
377
  */
306
- export const buildGraphLayoutNodes = (params: {
307
- serverGroups: ServerGroup[];
308
- orderIndexByNodeId: Map<string, number>;
309
- publicUrl: string;
310
- storedPositions: StoredPositions;
311
- onNodeOpen: (node: GraphNode) => void;
312
- }): Node[] => {
313
- const { serverGroups, orderIndexByNodeId, publicUrl, storedPositions, onNodeOpen } = params;
314
- const nodes: Node[] = [];
378
+ const buildServerNodeId = (serverUrl: string): string => `server:${serverUrl}`;
315
379
 
316
- if (serverGroups.length === 0) {
317
- return nodes;
380
+ /**
381
+ * Build the stable React Flow folder group node id.
382
+ */
383
+ const buildFolderNodeId = (serverUrl: string, folderId: number | null): string =>
384
+ `folder:${serverUrl}:${folderId ?? 'root'}`;
385
+
386
+ /**
387
+ * Add or increment an undirected layout link.
388
+ */
389
+ function addAggregatedLayoutLink(
390
+ linksByKey: Map<string, AggregatedLayoutLink>,
391
+ sourceId: string,
392
+ targetId: string,
393
+ ): void {
394
+ if (sourceId === targetId) {
395
+ return;
318
396
  }
319
397
 
320
- const serverLayouts = serverGroups.map((serverGroup) => {
321
- const folderLayouts: FolderLayout[] = serverGroup.folders.map((folder): FolderLayout => {
322
- const agentCount = folder.agents.length;
323
- const agentColumns = Math.max(1, Math.min(AGENT_MAX_COLUMNS, Math.ceil(Math.sqrt(agentCount || 1))));
324
- const rows = Math.max(1, Math.ceil(agentCount / agentColumns));
325
- const contentWidth = agentColumns * NODE_WIDTH + Math.max(0, agentColumns - 1) * AGENT_HORIZONTAL_GAP;
326
- const contentHeight = rows * NODE_HEIGHT + Math.max(0, rows - 1) * AGENT_VERTICAL_GAP;
327
- const width = Math.max(contentWidth + FOLDER_PADDING_X * 2, NODE_WIDTH + FOLDER_PADDING_X * 2);
328
- const height = Math.max(
329
- contentHeight + FOLDER_PADDING_Y * 2 + FOLDER_HEADER_HEIGHT,
330
- NODE_HEIGHT + FOLDER_PADDING_Y * 2 + FOLDER_HEADER_HEIGHT,
331
- );
332
-
333
- return {
334
- folder,
335
- width,
336
- height,
337
- agentColumns,
338
- contentWidth,
339
- contentHeight,
340
- };
341
- });
398
+ const isSourceFirst = sourceId.localeCompare(targetId) <= 0;
399
+ const normalizedSourceId = isSourceFirst ? sourceId : targetId;
400
+ const normalizedTargetId = isSourceFirst ? targetId : sourceId;
401
+ const key = `${normalizedSourceId}->${normalizedTargetId}`;
402
+ const existingLink = linksByKey.get(key);
342
403
 
343
- const folderColumnCount = Math.max(1, Math.ceil(Math.sqrt(folderLayouts.length)));
344
- const folderRowCount = Math.max(1, Math.ceil(folderLayouts.length / folderColumnCount));
345
- const columnWidths = Array(folderColumnCount).fill(0);
346
- const rowHeights = Array(folderRowCount).fill(0);
347
-
348
- folderLayouts.forEach((layout, index) => {
349
- const column = index % folderColumnCount;
350
- const row = Math.floor(index / folderColumnCount);
351
- layout.column = column;
352
- layout.row = row;
353
- columnWidths[column] = Math.max(columnWidths[column], layout.width);
354
- rowHeights[row] = Math.max(rowHeights[row], layout.height);
355
- });
404
+ if (existingLink) {
405
+ existingLink.weight += 1;
406
+ return;
407
+ }
356
408
 
357
- const columnOffsets: number[] = [];
358
- for (let columnIndex = 0; columnIndex < folderColumnCount; columnIndex += 1) {
359
- columnOffsets[columnIndex] =
360
- columnIndex === 0
361
- ? SERVER_PADDING_X
362
- : columnOffsets[columnIndex - 1] + columnWidths[columnIndex - 1] + FOLDER_GAP_X;
363
- }
409
+ linksByKey.set(key, {
410
+ sourceId: normalizedSourceId,
411
+ targetId: normalizedTargetId,
412
+ weight: 1,
413
+ });
414
+ }
364
415
 
365
- const rowOffsets: number[] = [];
366
- for (let rowIndex = 0; rowIndex < folderRowCount; rowIndex += 1) {
367
- rowOffsets[rowIndex] =
368
- rowIndex === 0
369
- ? SERVER_HEADER_HEIGHT + SERVER_PADDING_Y
370
- : rowOffsets[rowIndex - 1] + rowHeights[rowIndex - 1] + FOLDER_GAP_Y;
371
- }
416
+ /**
417
+ * Convert aggregated layout links to the free-layout helper shape.
418
+ */
419
+ function finalizeAggregatedLayoutLinks(linksByKey: ReadonlyMap<string, AggregatedLayoutLink>): FreeGraphBoxLayoutLink[] {
420
+ return Array.from(linksByKey.values()).map((link) => ({
421
+ sourceId: link.sourceId,
422
+ targetId: link.targetId,
423
+ weight: link.weight,
424
+ }));
425
+ }
372
426
 
373
- folderLayouts.forEach((layout) => {
374
- layout.x = columnOffsets[layout.column ?? 0];
375
- layout.y = rowOffsets[layout.row ?? 0];
376
- });
427
+ /**
428
+ * Build server and folder lookup metadata for every agent node.
429
+ */
430
+ function buildAgentLayoutLocationById(serverGroups: ReadonlyArray<ServerGroup>): Map<string, AgentLayoutLocation> {
431
+ const agentLayoutLocationById = new Map<string, AgentLayoutLocation>();
377
432
 
378
- const serverWidth =
379
- columnWidths.reduce((sum, width) => sum + width, 0) +
380
- Math.max(folderColumnCount - 1, 0) * FOLDER_GAP_X +
381
- SERVER_PADDING_X * 2;
382
- const serverHeight =
383
- SERVER_HEADER_HEIGHT +
384
- SERVER_PADDING_Y * 2 +
385
- rowHeights.reduce((sum, height) => sum + height, 0) +
386
- Math.max(folderRowCount - 1, 0) * FOLDER_GAP_Y;
387
-
388
- return { serverGroup, folderLayouts, width: serverWidth, height: serverHeight } satisfies ServerLayout;
433
+ serverGroups.forEach((serverGroup) => {
434
+ const serverNodeId = buildServerNodeId(serverGroup.serverUrl);
435
+
436
+ serverGroup.folders.forEach((folder) => {
437
+ const folderNodeId = buildFolderNodeId(serverGroup.serverUrl, folder.id);
438
+
439
+ folder.agents.forEach((agent) => {
440
+ agentLayoutLocationById.set(agent.id, {
441
+ serverNodeId,
442
+ folderNodeId,
443
+ });
444
+ });
445
+ });
389
446
  });
390
447
 
391
- const maxServerWidth = Math.max(NODE_WIDTH, ...serverLayouts.map((layout) => layout.width));
392
- const maxServerHeight = Math.max(NODE_HEIGHT, ...serverLayouts.map((layout) => layout.height));
393
- const serverColumnCount = Math.max(1, Math.ceil(Math.sqrt(serverLayouts.length)));
394
- const serverRowCount = Math.ceil(serverLayouts.length / serverColumnCount);
395
- const centerColumn = (serverColumnCount - 1) / 2;
396
- const centerRow = (serverRowCount - 1) / 2;
397
- const serverSpacingX = maxServerWidth + SERVER_GAP;
398
- const serverSpacingY = maxServerHeight + SERVER_GAP;
399
-
400
- const serverCoords: { row: number; column: number }[] = [];
401
- for (let row = 0; row < serverRowCount; row += 1) {
402
- for (let column = 0; column < serverColumnCount; column += 1) {
403
- serverCoords.push({ row, column });
448
+ return agentLayoutLocationById;
449
+ }
450
+
451
+ /**
452
+ * Build weighted layout links between agents inside one folder.
453
+ */
454
+ function buildAgentLayoutLinks(
455
+ folder: ServerGroup['folders'][number],
456
+ graphLinks: ReadonlyArray<GraphLink>,
457
+ ): FreeGraphBoxLayoutLink[] {
458
+ const agentIds = new Set(folder.agents.map((agent) => agent.id));
459
+ const linksByKey = new Map<string, AggregatedLayoutLink>();
460
+
461
+ graphLinks.forEach((link) => {
462
+ if (!agentIds.has(link.source) || !agentIds.has(link.target)) {
463
+ return;
404
464
  }
405
- }
406
465
 
407
- serverCoords.sort((left, right) => {
408
- const leftDistance = Math.abs(left.row - centerRow) + Math.abs(left.column - centerColumn);
409
- const rightDistance = Math.abs(right.row - centerRow) + Math.abs(right.column - centerColumn);
410
- if (leftDistance !== rightDistance) {
411
- return leftDistance - rightDistance;
466
+ addAggregatedLayoutLink(linksByKey, link.source, link.target);
467
+ });
468
+
469
+ return finalizeAggregatedLayoutLinks(linksByKey);
470
+ }
471
+
472
+ /**
473
+ * Build weighted layout links between folders inside one server.
474
+ */
475
+ function buildFolderLayoutLinks(
476
+ serverNodeId: string,
477
+ graphLinks: ReadonlyArray<GraphLink>,
478
+ agentLayoutLocationById: ReadonlyMap<string, AgentLayoutLocation>,
479
+ ): FreeGraphBoxLayoutLink[] {
480
+ const linksByKey = new Map<string, AggregatedLayoutLink>();
481
+
482
+ graphLinks.forEach((link) => {
483
+ const sourceLocation = agentLayoutLocationById.get(link.source);
484
+ const targetLocation = agentLayoutLocationById.get(link.target);
485
+
486
+ if (!sourceLocation || !targetLocation) {
487
+ return;
412
488
  }
413
- if (left.row !== right.row) {
414
- return left.row - right.row;
489
+
490
+ if (sourceLocation.serverNodeId !== serverNodeId || targetLocation.serverNodeId !== serverNodeId) {
491
+ return;
415
492
  }
416
- return left.column - right.column;
493
+
494
+ addAggregatedLayoutLink(linksByKey, sourceLocation.folderNodeId, targetLocation.folderNodeId);
417
495
  });
418
496
 
419
- const centerOffsetX = centerColumn * serverSpacingX;
420
- const centerOffsetY = centerRow * serverSpacingY;
497
+ return finalizeAggregatedLayoutLinks(linksByKey);
498
+ }
421
499
 
422
- serverLayouts.forEach((layout, index) => {
423
- const coord = serverCoords[index];
424
- if (!coord) {
500
+ /**
501
+ * Build weighted layout links between server groups.
502
+ */
503
+ function buildServerLayoutLinks(
504
+ graphLinks: ReadonlyArray<GraphLink>,
505
+ agentLayoutLocationById: ReadonlyMap<string, AgentLayoutLocation>,
506
+ ): FreeGraphBoxLayoutLink[] {
507
+ const linksByKey = new Map<string, AggregatedLayoutLink>();
508
+
509
+ graphLinks.forEach((link) => {
510
+ const sourceLocation = agentLayoutLocationById.get(link.source);
511
+ const targetLocation = agentLayoutLocationById.get(link.target);
512
+
513
+ if (!sourceLocation || !targetLocation) {
425
514
  return;
426
515
  }
427
- const serverX = coord.column * serverSpacingX - centerOffsetX;
428
- const serverY = coord.row * serverSpacingY - centerOffsetY;
429
- const serverNodeId = `server:${layout.serverGroup.serverUrl}`;
516
+
517
+ addAggregatedLayoutLink(linksByKey, sourceLocation.serverNodeId, targetLocation.serverNodeId);
518
+ });
519
+
520
+ return finalizeAggregatedLayoutLinks(linksByKey);
521
+ }
522
+
523
+ /**
524
+ * Build the free-layout dimensions and node positions for one folder.
525
+ */
526
+ function buildFolderLayout(
527
+ serverUrl: string,
528
+ folder: ServerGroup['folders'][number],
529
+ orderIndex: number,
530
+ graphLinks: ReadonlyArray<GraphLink>,
531
+ ): FolderLayout {
532
+ const nodeId = buildFolderNodeId(serverUrl, folder.id);
533
+ const agentLayout = buildFreeGraphBoxLayout(
534
+ folder.agents.map((agent, agentIndex) => ({
535
+ id: agent.id,
536
+ width: NODE_WIDTH,
537
+ height: NODE_HEIGHT,
538
+ orderIndex: agentIndex,
539
+ })),
540
+ buildAgentLayoutLinks(folder, graphLinks),
541
+ {
542
+ paddingX: FOLDER_PADDING_X,
543
+ paddingY: FOLDER_PADDING_Y,
544
+ gapX: AGENT_HORIZONTAL_GAP,
545
+ gapY: AGENT_VERTICAL_GAP,
546
+ relationshipDistance: AGENT_RELATIONSHIP_DISTANCE,
547
+ relationshipStrength: AGENT_RELATIONSHIP_STRENGTH,
548
+ centerPullStrength: AGENT_CENTER_PULL_STRENGTH,
549
+ },
550
+ );
551
+
552
+ return {
553
+ folder,
554
+ nodeId,
555
+ width: Math.max(agentLayout.width, NODE_WIDTH + FOLDER_PADDING_X * 2),
556
+ height: Math.max(agentLayout.height + FOLDER_HEADER_HEIGHT, NODE_HEIGHT + FOLDER_PADDING_Y * 2 + FOLDER_HEADER_HEIGHT),
557
+ agentPositionsById: agentLayout.positionsById,
558
+ orderIndex,
559
+ };
560
+ }
561
+
562
+ /**
563
+ * Build the free-layout dimensions and folder positions for one server.
564
+ */
565
+ function buildServerLayout(
566
+ serverGroup: ServerGroup,
567
+ orderIndex: number,
568
+ graphLinks: ReadonlyArray<GraphLink>,
569
+ agentLayoutLocationById: ReadonlyMap<string, AgentLayoutLocation>,
570
+ ): ServerLayout {
571
+ const nodeId = buildServerNodeId(serverGroup.serverUrl);
572
+ const folderLayouts = serverGroup.folders.map((folder, folderIndex) =>
573
+ buildFolderLayout(serverGroup.serverUrl, folder, folderIndex, graphLinks),
574
+ );
575
+ const rootFolder = folderLayouts.find((folderLayout) => folderLayout.folder.id === null);
576
+ const folderLayout = buildFreeGraphBoxLayout(
577
+ folderLayouts.map((layout) => ({
578
+ id: layout.nodeId,
579
+ width: layout.width,
580
+ height: layout.height,
581
+ orderIndex: layout.orderIndex,
582
+ })),
583
+ buildFolderLayoutLinks(nodeId, graphLinks, agentLayoutLocationById),
584
+ {
585
+ paddingX: SERVER_PADDING_X,
586
+ paddingY: SERVER_PADDING_Y,
587
+ gapX: FOLDER_PADDING_X * 2,
588
+ gapY: FOLDER_PADDING_Y * 2,
589
+ relationshipDistance: FOLDER_RELATIONSHIP_DISTANCE,
590
+ relationshipStrength: FOLDER_RELATIONSHIP_STRENGTH,
591
+ centerPullStrength: FOLDER_CENTER_PULL_STRENGTH,
592
+ centerItemId: rootFolder?.nodeId,
593
+ },
594
+ );
595
+
596
+ return {
597
+ serverGroup,
598
+ nodeId,
599
+ folderLayouts,
600
+ folderPositionsById: folderLayout.positionsById,
601
+ width: Math.max(folderLayout.width, NODE_WIDTH + SERVER_PADDING_X * 2),
602
+ height: Math.max(folderLayout.height + SERVER_HEADER_HEIGHT, NODE_HEIGHT + SERVER_PADDING_Y * 2 + SERVER_HEADER_HEIGHT),
603
+ orderIndex,
604
+ };
605
+ }
606
+
607
+ /**
608
+ * Build free-layout server group positions while keeping the current server centered.
609
+ */
610
+ function buildServerPositionsById(
611
+ serverLayouts: ReadonlyArray<ServerLayout>,
612
+ graphLinks: ReadonlyArray<GraphLink>,
613
+ agentLayoutLocationById: ReadonlyMap<string, AgentLayoutLocation>,
614
+ publicUrl: string,
615
+ ): Map<string, FreeGraphBoxLayoutPosition> {
616
+ const normalizedPublicUrl = normalizeServerUrl(publicUrl);
617
+ const currentServerNodeId = buildServerNodeId(normalizedPublicUrl);
618
+ const centerServerLayout =
619
+ serverLayouts.find((layout) => layout.nodeId === currentServerNodeId) || serverLayouts[0] || null;
620
+ const serverLayout = buildFreeGraphBoxLayout(
621
+ serverLayouts.map((layout) => ({
622
+ id: layout.nodeId,
623
+ width: layout.width,
624
+ height: layout.height,
625
+ orderIndex: layout.orderIndex,
626
+ })),
627
+ buildServerLayoutLinks(graphLinks, agentLayoutLocationById),
628
+ {
629
+ paddingX: 0,
630
+ paddingY: 0,
631
+ gapX: SERVER_GAP_X,
632
+ gapY: SERVER_GAP_Y,
633
+ relationshipDistance: SERVER_RELATIONSHIP_DISTANCE,
634
+ relationshipStrength: SERVER_RELATIONSHIP_STRENGTH,
635
+ centerPullStrength: SERVER_CENTER_PULL_STRENGTH,
636
+ centerItemId: centerServerLayout?.nodeId,
637
+ },
638
+ );
639
+
640
+ if (!centerServerLayout) {
641
+ return serverLayout.positionsById;
642
+ }
643
+
644
+ const centerServerPosition = serverLayout.positionsById.get(centerServerLayout.nodeId);
645
+ if (!centerServerPosition) {
646
+ return serverLayout.positionsById;
647
+ }
648
+
649
+ const centerX = centerServerPosition.x + centerServerLayout.width / 2;
650
+ const centerY = centerServerPosition.y + centerServerLayout.height / 2;
651
+ const centeredPositionsById = new Map<string, FreeGraphBoxLayoutPosition>();
652
+
653
+ serverLayout.positionsById.forEach((position, serverNodeId) => {
654
+ centeredPositionsById.set(serverNodeId, {
655
+ x: position.x - centerX,
656
+ y: position.y - centerY,
657
+ });
658
+ });
659
+
660
+ return centeredPositionsById;
661
+ }
662
+
663
+ /**
664
+ * Build nodes for servers, folders, and agents using a hierarchical free layout that keeps the current server centered.
665
+ *
666
+ * @private function of AgentsGraph
667
+ */
668
+ export const buildGraphLayoutNodes = (params: {
669
+ serverGroups: ServerGroup[];
670
+ links: GraphLink[];
671
+ orderIndexByNodeId: Map<string, number>;
672
+ publicUrl: string;
673
+ storedPositions: StoredPositions;
674
+ onNodeOpen: (node: GraphNode) => void;
675
+ }): Node[] => {
676
+ const { serverGroups, links, orderIndexByNodeId, publicUrl, storedPositions, onNodeOpen } = params;
677
+ const nodes: Node[] = [];
678
+
679
+ if (serverGroups.length === 0) {
680
+ return nodes;
681
+ }
682
+
683
+ const agentLayoutLocationById = buildAgentLayoutLocationById(serverGroups);
684
+ const serverLayouts = serverGroups.map((serverGroup, serverIndex) =>
685
+ buildServerLayout(serverGroup, serverIndex, links, agentLayoutLocationById),
686
+ );
687
+ const serverPositionsById = buildServerPositionsById(serverLayouts, links, agentLayoutLocationById, publicUrl);
688
+
689
+ serverLayouts.forEach((layout) => {
690
+ const serverPosition = serverPositionsById.get(layout.nodeId) ?? { x: 0, y: 0 };
430
691
 
431
692
  nodes.push({
432
- id: serverNodeId,
693
+ id: layout.nodeId,
433
694
  type: 'serverGroup',
434
- position: { x: serverX, y: serverY },
695
+ position: serverPosition,
435
696
  data: {
436
697
  label: layout.serverGroup.label,
437
698
  agentCount: layout.serverGroup.folders.reduce((sum, folder) => sum + folder.agents.length, 0),
@@ -447,15 +708,19 @@ export const buildGraphLayoutNodes = (params: {
447
708
  });
448
709
 
449
710
  layout.folderLayouts.forEach((folderLayout) => {
450
- const folderNodeId = `folder:${layout.serverGroup.serverUrl}:${folderLayout.folder.id ?? 'root'}`;
711
+ const folderPosition = layout.folderPositionsById.get(folderLayout.nodeId) ?? {
712
+ x: SERVER_PADDING_X,
713
+ y: SERVER_PADDING_Y,
714
+ };
715
+
451
716
  nodes.push({
452
- id: folderNodeId,
717
+ id: folderLayout.nodeId,
453
718
  type: 'folderGroup',
454
- parentId: serverNodeId,
719
+ parentId: layout.nodeId,
455
720
  extent: 'parent',
456
721
  position: {
457
- x: folderLayout.x ?? SERVER_PADDING_X,
458
- y: folderLayout.y ?? SERVER_HEADER_HEIGHT + SERVER_PADDING_Y,
722
+ x: folderPosition.x,
723
+ y: SERVER_HEADER_HEIGHT + folderPosition.y,
459
724
  },
460
725
  data: {
461
726
  label: folderLayout.folder.label,
@@ -470,7 +735,7 @@ export const buildGraphLayoutNodes = (params: {
470
735
  draggable: false,
471
736
  });
472
737
 
473
- folderLayout.folder.agents.forEach((agent, agentIndex) => {
738
+ folderLayout.folder.agents.forEach((agent) => {
474
739
  const { imageUrl, placeholderUrl } = getAgentImageUrls(agent.agent, publicUrl);
475
740
  const style = buildAgentChipStyle(agent.agent);
476
741
  const orderIndex = orderIndexByNodeId.get(agent.id) ?? null;
@@ -479,27 +744,20 @@ export const buildGraphLayoutNodes = (params: {
479
744
  tooltipParts.push(`Folder: ${folderLayout.folder.label}`);
480
745
  }
481
746
  const tooltip = tooltipParts.filter(Boolean).join('\n');
482
-
483
- const column = agentIndex % folderLayout.agentColumns;
484
- const row = Math.floor(agentIndex / folderLayout.agentColumns);
485
- const horizontalAvailable = folderLayout.width - FOLDER_PADDING_X * 2;
486
- const horizontalOffset = Math.max(0, (horizontalAvailable - folderLayout.contentWidth) / 2);
487
- const agentX = FOLDER_PADDING_X + horizontalOffset + column * (NODE_WIDTH + AGENT_HORIZONTAL_GAP);
488
- const verticalAvailable = folderLayout.height - FOLDER_PADDING_Y * 2 - FOLDER_HEADER_HEIGHT;
489
- const verticalOffset = Math.max(0, (verticalAvailable - folderLayout.contentHeight) / 2);
490
- const agentY =
491
- FOLDER_HEADER_HEIGHT + FOLDER_PADDING_Y + verticalOffset + row * (NODE_HEIGHT + AGENT_VERTICAL_GAP);
492
-
747
+ const agentPosition = folderLayout.agentPositionsById.get(agent.id) ?? {
748
+ x: FOLDER_PADDING_X,
749
+ y: FOLDER_PADDING_Y,
750
+ };
493
751
  const storedPosition = storedPositions[agent.id];
494
752
  const finalPosition =
495
- storedPosition && storedPosition.parentId === folderNodeId
753
+ storedPosition && storedPosition.parentId === folderLayout.nodeId
496
754
  ? { x: storedPosition.x, y: storedPosition.y }
497
- : { x: agentX, y: agentY };
755
+ : { x: agentPosition.x, y: FOLDER_HEADER_HEIGHT + agentPosition.y };
498
756
 
499
757
  nodes.push({
500
758
  id: agent.id,
501
759
  type: 'agent',
502
- parentId: folderNodeId,
760
+ parentId: folderLayout.nodeId,
503
761
  extent: 'parent',
504
762
  position: finalPosition,
505
763
  data: {