@promptbook/cli 0.112.0-134 → 0.112.0-136
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/apps/agents-server/README.md +0 -1
- package/apps/agents-server/src/app/admin/chat-feedback/page.tsx +2 -4
- package/apps/agents-server/src/app/admin/chat-history/page.tsx +2 -4
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +246 -15
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +237 -4
- package/apps/agents-server/src/app/api/admin/update/log/route.ts +42 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +28 -9
- package/apps/agents-server/src/app/api/agents/export/route.ts +41 -0
- package/apps/agents-server/src/app/api/agents/import/route.ts +168 -0
- package/apps/agents-server/src/app/api/chat/route.ts +29 -7
- package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-feedback/route.ts +3 -3
- package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-history/export/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-history/route.ts +3 -3
- package/apps/agents-server/src/app/api/chat-streaming/route.ts +29 -0
- package/apps/agents-server/src/app/api/elevenlabs/tts/route.ts +60 -2
- package/apps/agents-server/src/app/api/federated-agents/route.ts +1 -1
- package/apps/agents-server/src/app/api/images/[filename]/route.ts +102 -0
- package/apps/agents-server/src/app/api/metadata/export/route.ts +34 -0
- package/apps/agents-server/src/app/api/metadata/import/route.ts +46 -0
- package/apps/agents-server/src/app/api/openai/v1/audio/transcriptions/route.ts +111 -2
- package/apps/agents-server/src/app/api/page-preview/check/route.ts +18 -0
- package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +15 -8
- package/apps/agents-server/src/app/api/team-agent-profile/route.ts +20 -0
- package/apps/agents-server/src/app/layout.tsx +1 -1
- package/apps/agents-server/src/components/AgentContextMenu/useAgentContextMenuItems.ts +27 -19
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +31 -1
- package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +60 -1
- package/apps/agents-server/src/components/Homepage/useAgentsListImportExportState.ts +523 -0
- package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +16 -0
- package/apps/agents-server/src/constants/defaultAgentAvatarVisual.ts +1 -1
- package/apps/agents-server/src/database/metadataDefaults.ts +0 -21
- package/apps/agents-server/src/database/migrations/2026-06-2700-default-agent-avatar-visual-octopus3d4.sql +16 -0
- package/apps/agents-server/src/database/seedCoreAgents.ts +235 -0
- package/apps/agents-server/src/database/seedDefaultAgents.ts +32 -1
- package/apps/agents-server/src/search/createDefaultServerSearchProviders/createFederatedAgentsSearchProvider.ts +1 -1
- package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +2 -9
- package/apps/agents-server/src/utils/agentOwnership.ts +54 -5
- package/apps/agents-server/src/utils/agentsTransfer/createAgentsExportZipStream.ts +68 -0
- package/apps/agents-server/src/utils/agentsTransfer/importAgentsFromFiles.ts +852 -0
- package/apps/agents-server/src/utils/backup/createBooksBackupZipStream.ts +15 -2
- package/apps/agents-server/src/utils/defaultAgents/loadDefaultAgentBooks.ts +46 -4
- package/apps/agents-server/src/utils/findAgentForCallerWriteAccess.ts +36 -0
- package/apps/agents-server/src/utils/getFederatedServers.ts +3 -74
- package/apps/agents-server/src/utils/getWellKnownAgentUrl.ts +10 -4
- package/apps/agents-server/src/utils/metadataConfigurationTransfer.ts +426 -0
- package/apps/agents-server/src/utils/paidApiRequestGuard.ts +241 -0
- package/apps/agents-server/src/utils/serverManagement/createManagedServer/bootstrapManagedServer.ts +2 -5
- package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +162 -0
- package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +36 -8
- package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +11 -2
- package/apps/agents-server/src/utils/userChat/resolveUserChatProgressToolHighlights.ts +100 -0
- package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +4 -3
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +272 -27
- package/apps/agents-server/src/utils/validateApiKey.ts +7 -3
- package/apps/agents-server/src/utils/vpsSelfUpdate.ts +110 -0
- package/esm/index.es.js +795 -39
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/core.index.d.ts +0 -2
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/servers.ts +1 -16
- package/src/_packages/core.index.ts +0 -2
- package/src/avatars/types/AvatarVisualDefinition.ts +1 -0
- package/src/avatars/visuals/avatarVisualRegistry.ts +2 -0
- package/src/avatars/visuals/octopus3d4AvatarVisual.ts +1295 -0
- package/src/other/templates/getTemplatesPipelineCollection.ts +762 -883
- package/src/utils/agents/resolveAgentAvatarImageUrl.ts +1 -1
- package/src/utils/validators/url/isValidAgentUrl.ts +5 -7
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -1
- package/umd/index.umd.js +795 -39
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/core.index.d.ts +0 -2
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/umd/src/version.d.ts +1 -1
- package/apps/agents-server/src/utils/defaultFederatedAgents/DefaultFederatedAgentsSyncOptions.ts +0 -9
- package/apps/agents-server/src/utils/defaultFederatedAgents/ensureDefaultFederatedAgentExists.ts +0 -277
- package/apps/agents-server/src/utils/defaultFederatedAgents/fetchCoreOrganizationPayload.ts +0 -39
- package/apps/agents-server/src/utils/defaultFederatedAgents/fetchFederatedAgentBook.ts +0 -43
- package/apps/agents-server/src/utils/defaultFederatedAgents/fetchWithDefaultFederatedAgentTimeout.ts +0 -28
- package/apps/agents-server/src/utils/defaultFederatedAgents/getDefaultFederatedAgentSyncPool.ts +0 -38
- package/apps/agents-server/src/utils/defaultFederatedAgents/loadActiveLocalAgentIdsByNormalizedName.ts +0 -41
- package/apps/agents-server/src/utils/defaultFederatedAgents/loadDefaultFederatedAgentSyncMetadata.ts +0 -76
- package/apps/agents-server/src/utils/defaultFederatedAgents/quoteIdentifier.ts +0 -11
- package/apps/agents-server/src/utils/defaultFederatedAgents/scheduleDefaultFederatedAgentsSync.ts +0 -88
- package/apps/agents-server/src/utils/defaultFederatedAgents/selectDefaultFederatedAgentsFromOrganizationPayload.ts +0 -181
- package/apps/agents-server/src/utils/defaultFederatedAgents/synchronizeDefaultFederatedAgents.ts +0 -77
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { spaceTrim } from 'spacetrim';
|
|
3
|
+
import { AgentCollectionInSupabase } from '../../../../src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase';
|
|
4
|
+
import type { AgentsDatabaseSchema } from '../../../../src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema';
|
|
5
|
+
import { parseAgentSource } from '../../../../src/book-2.0/agent-source/parseAgentSource';
|
|
6
|
+
import type { string_book } from '../../../../src/book-2.0/agent-source/string_book';
|
|
7
|
+
import { DatabaseError } from '../../../../src/errors/DatabaseError';
|
|
8
|
+
import { CORE_AGENT_DIRECTORY_NAME, loadCoreAgentBooks } from '../utils/defaultAgents/loadDefaultAgentBooks';
|
|
9
|
+
import { DEFAULT_AGENT_VISIBILITY } from '../utils/agentVisibility';
|
|
10
|
+
import { $getTableName } from './$getTableName';
|
|
11
|
+
import { $provideSupabaseForServer } from './$provideSupabaseForServer';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Logger surface used by the core-agent seeder.
|
|
15
|
+
*
|
|
16
|
+
* @private utility of `.core` folder seeding
|
|
17
|
+
*/
|
|
18
|
+
type SeedCoreAgentsLogger = Pick<Console, 'error' | 'info' | 'warn'>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for ensuring the bundled `.core` folder and core agents exist.
|
|
22
|
+
*
|
|
23
|
+
* @private utility of `.core` folder seeding
|
|
24
|
+
*/
|
|
25
|
+
export type SeedCoreAgentsOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* Optional explicit directory containing default `*.book` files.
|
|
28
|
+
*/
|
|
29
|
+
readonly defaultAgentDirectory?: string | null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Optional table prefix for the server namespace being seeded.
|
|
33
|
+
*/
|
|
34
|
+
readonly tablePrefix?: string | null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Optional logger for installer output.
|
|
38
|
+
*/
|
|
39
|
+
readonly logger?: SeedCoreAgentsLogger;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Result of one `.core` agents seed attempt.
|
|
44
|
+
*
|
|
45
|
+
* @private utility of `.core` folder seeding
|
|
46
|
+
*/
|
|
47
|
+
export type SeedCoreAgentsResult = {
|
|
48
|
+
/**
|
|
49
|
+
* Identifier of the `.core` folder, or `null` when no books were available and the folder was not created.
|
|
50
|
+
*/
|
|
51
|
+
readonly coreFolderId: number | null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Names of agents that were created during this run.
|
|
55
|
+
*/
|
|
56
|
+
readonly createdAgentNames: ReadonlyArray<string>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Ensures the `.core` folder and well-known core agents are present in the current server database.
|
|
61
|
+
*
|
|
62
|
+
* - Creates the `.core` folder when it is missing.
|
|
63
|
+
* - Creates each bundled `.core/*.book` agent when it is missing from the database.
|
|
64
|
+
* - Reuses the same persistence mechanism used by the default agent seeder.
|
|
65
|
+
*
|
|
66
|
+
* @param options - Optional seed controls.
|
|
67
|
+
* @returns Seed summary including the resolved `.core` folder id.
|
|
68
|
+
*
|
|
69
|
+
* @private utility of `.core` folder seeding
|
|
70
|
+
*/
|
|
71
|
+
export async function seedCoreAgents(options: SeedCoreAgentsOptions = {}): Promise<SeedCoreAgentsResult> {
|
|
72
|
+
const logger = options.logger ?? console;
|
|
73
|
+
const tablePrefix = options.tablePrefix ?? process.env.SUPABASE_TABLE_PREFIX ?? '';
|
|
74
|
+
const collection = new AgentCollectionInSupabase(resolveAgentsDatabaseSupabaseClient(), { tablePrefix });
|
|
75
|
+
|
|
76
|
+
const coreAgentBooks = await loadCoreAgentBooks({
|
|
77
|
+
defaultAgentDirectory: options.defaultAgentDirectory,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (coreAgentBooks.length === 0) {
|
|
81
|
+
logger.warn(
|
|
82
|
+
spaceTrim(`
|
|
83
|
+
Skipping \`.core\` agents because no bundled \`.core/*.book\` files were found.
|
|
84
|
+
`),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return { coreFolderId: null, createdAgentNames: [] };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const coreFolderId = await ensureCoreFolderExists();
|
|
91
|
+
const existingAgentNames = await loadExistingActiveAgentNames();
|
|
92
|
+
const createdAgentNames: Array<string> = [];
|
|
93
|
+
|
|
94
|
+
for (const [index, coreAgentBook] of coreAgentBooks.entries()) {
|
|
95
|
+
const agentName = parseCoreAgentName(coreAgentBook);
|
|
96
|
+
|
|
97
|
+
if (existingAgentNames.has(agentName)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const createdAgent = await collection.createAgent(coreAgentBook, {
|
|
102
|
+
folderId: coreFolderId,
|
|
103
|
+
sortOrder: index,
|
|
104
|
+
visibility: DEFAULT_AGENT_VISIBILITY,
|
|
105
|
+
});
|
|
106
|
+
createdAgentNames.push(createdAgent.agentName);
|
|
107
|
+
logger.info(`Created \`.core\` agent: ${createdAgent.agentName}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { coreFolderId, createdAgentNames };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reads the agent name from one bundled `.core` book source.
|
|
115
|
+
*
|
|
116
|
+
* @param coreAgentBook - Raw `.core` agent book source.
|
|
117
|
+
* @returns Persisted agent name parsed from the book.
|
|
118
|
+
*
|
|
119
|
+
* @private utility of `.core` folder seeding
|
|
120
|
+
*/
|
|
121
|
+
function parseCoreAgentName(coreAgentBook: string_book): string {
|
|
122
|
+
return parseAgentSource(coreAgentBook).agentName;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reads the set of active agent names already persisted on the current server.
|
|
127
|
+
*
|
|
128
|
+
* @returns Set of active agent names indexed for quick lookups.
|
|
129
|
+
*
|
|
130
|
+
* @private utility of `.core` folder seeding
|
|
131
|
+
*/
|
|
132
|
+
async function loadExistingActiveAgentNames(): Promise<Set<string>> {
|
|
133
|
+
const supabase = $provideSupabaseForServer();
|
|
134
|
+
const agentTableName = await $getTableName('Agent');
|
|
135
|
+
const selectResult = await supabase
|
|
136
|
+
.from(agentTableName)
|
|
137
|
+
.select('agentName')
|
|
138
|
+
.is('deletedAt', null);
|
|
139
|
+
|
|
140
|
+
if (selectResult.error) {
|
|
141
|
+
throw new DatabaseError(
|
|
142
|
+
spaceTrim(
|
|
143
|
+
(block) => `
|
|
144
|
+
Failed to read active agents before seeding \`.core\` agents:
|
|
145
|
+
|
|
146
|
+
${block(selectResult.error.message)}
|
|
147
|
+
`,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const existingAgentNames = new Set<string>();
|
|
153
|
+
for (const row of (selectResult.data || []) as Array<{ agentName: string }>) {
|
|
154
|
+
existingAgentNames.add(row.agentName);
|
|
155
|
+
}
|
|
156
|
+
return existingAgentNames;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Ensures the `.core` folder exists at the root and returns its identifier.
|
|
161
|
+
*
|
|
162
|
+
* @returns Persisted `.core` folder identifier.
|
|
163
|
+
*
|
|
164
|
+
* @private utility of `.core` folder seeding
|
|
165
|
+
*/
|
|
166
|
+
async function ensureCoreFolderExists(): Promise<number> {
|
|
167
|
+
const supabase = $provideSupabaseForServer();
|
|
168
|
+
const folderTableName = await $getTableName('AgentFolder');
|
|
169
|
+
|
|
170
|
+
const existingFolderResult = await supabase
|
|
171
|
+
.from(folderTableName)
|
|
172
|
+
.select('id')
|
|
173
|
+
.eq('name', CORE_AGENT_DIRECTORY_NAME)
|
|
174
|
+
.is('parentId', null)
|
|
175
|
+
.is('deletedAt', null)
|
|
176
|
+
.maybeSingle();
|
|
177
|
+
|
|
178
|
+
if (existingFolderResult.error) {
|
|
179
|
+
throw new DatabaseError(
|
|
180
|
+
spaceTrim(
|
|
181
|
+
(block) => `
|
|
182
|
+
Failed to read the \`${CORE_AGENT_DIRECTORY_NAME}\` folder before seeding core agents:
|
|
183
|
+
|
|
184
|
+
${block(existingFolderResult.error.message)}
|
|
185
|
+
`,
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (existingFolderResult.data) {
|
|
191
|
+
return (existingFolderResult.data as { id: number }).id;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const insertFolderResult = await supabase
|
|
195
|
+
.from(folderTableName)
|
|
196
|
+
.insert({
|
|
197
|
+
name: CORE_AGENT_DIRECTORY_NAME,
|
|
198
|
+
parentId: null,
|
|
199
|
+
sortOrder: 0,
|
|
200
|
+
icon: null,
|
|
201
|
+
color: null,
|
|
202
|
+
createdAt: new Date().toISOString(),
|
|
203
|
+
updatedAt: null,
|
|
204
|
+
} as never)
|
|
205
|
+
.select('id')
|
|
206
|
+
.maybeSingle();
|
|
207
|
+
|
|
208
|
+
if (insertFolderResult.error || !insertFolderResult.data) {
|
|
209
|
+
throw new DatabaseError(
|
|
210
|
+
spaceTrim(
|
|
211
|
+
(block) => `
|
|
212
|
+
Failed to create the \`${CORE_AGENT_DIRECTORY_NAME}\` folder while seeding core agents:
|
|
213
|
+
|
|
214
|
+
${block(
|
|
215
|
+
insertFolderResult.error?.message ||
|
|
216
|
+
`The database did not return the created \`${CORE_AGENT_DIRECTORY_NAME}\` folder.`,
|
|
217
|
+
)}
|
|
218
|
+
`,
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return (insertFolderResult.data as { id: number }).id;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Resolves the Supabase-shaped client for the Agent collection subset.
|
|
228
|
+
*
|
|
229
|
+
* @returns Supabase client typed for agent collection persistence.
|
|
230
|
+
*
|
|
231
|
+
* @private utility of `.core` folder seeding
|
|
232
|
+
*/
|
|
233
|
+
function resolveAgentsDatabaseSupabaseClient(): SupabaseClient<AgentsDatabaseSchema> {
|
|
234
|
+
return $provideSupabaseForServer() as unknown as SupabaseClient<AgentsDatabaseSchema>;
|
|
235
|
+
}
|
|
@@ -5,6 +5,7 @@ import { $provideSupabaseForServer } from './$provideSupabaseForServer';
|
|
|
5
5
|
import { DEFAULT_AGENT_VISIBILITY } from '../utils/agentVisibility';
|
|
6
6
|
import { loadDefaultAgentBooks } from '../utils/defaultAgents/loadDefaultAgentBooks';
|
|
7
7
|
import { loadAgentsServerEnvFile } from './loadAgentsServerEnvFile';
|
|
8
|
+
import { seedCoreAgents } from './seedCoreAgents';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Environment variable with an explicit default-agent source directory.
|
|
@@ -93,6 +94,36 @@ export async function seedDefaultAgents(options: SeedDefaultAgentsOptions = {}):
|
|
|
93
94
|
const logger = options.logger ?? console;
|
|
94
95
|
const tablePrefix = options.tablePrefix ?? process.env[SUPABASE_TABLE_PREFIX_ENV_NAME] ?? '';
|
|
95
96
|
const collection = new AgentCollectionInSupabase(resolveAgentsDatabaseSupabaseClient(), { tablePrefix });
|
|
97
|
+
const defaultAgentDirectory = options.defaultAgentDirectory ?? process.env[DEFAULT_AGENTS_DIRECTORY_ENV_NAME];
|
|
98
|
+
|
|
99
|
+
const result = await seedDefaultAgentBooks(collection, { defaultAgentDirectory, logger });
|
|
100
|
+
|
|
101
|
+
await seedCoreAgents({
|
|
102
|
+
defaultAgentDirectory,
|
|
103
|
+
tablePrefix,
|
|
104
|
+
logger,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Inserts the bundled top-level default agent books when the server is empty.
|
|
112
|
+
*
|
|
113
|
+
* @param collection - Agent collection bound to the current table prefix.
|
|
114
|
+
* @param options - Seeding context shared with `seedDefaultAgents`.
|
|
115
|
+
* @returns Seed summary for default agents only (core agents are tracked separately).
|
|
116
|
+
*
|
|
117
|
+
* @private utility of standalone default-agent seeding
|
|
118
|
+
*/
|
|
119
|
+
async function seedDefaultAgentBooks(
|
|
120
|
+
collection: AgentCollectionInSupabase,
|
|
121
|
+
options: {
|
|
122
|
+
readonly defaultAgentDirectory: string | undefined;
|
|
123
|
+
readonly logger: SeedDefaultAgentsLogger;
|
|
124
|
+
},
|
|
125
|
+
): Promise<SeedDefaultAgentsResult> {
|
|
126
|
+
const { defaultAgentDirectory, logger } = options;
|
|
96
127
|
const existingAgentCount = await countExistingAgents(collection);
|
|
97
128
|
|
|
98
129
|
if (existingAgentCount > 0) {
|
|
@@ -111,7 +142,7 @@ export async function seedDefaultAgents(options: SeedDefaultAgentsOptions = {}):
|
|
|
111
142
|
}
|
|
112
143
|
|
|
113
144
|
const defaultAgentBooks = await loadDefaultAgentBooks({
|
|
114
|
-
defaultAgentDirectory
|
|
145
|
+
defaultAgentDirectory,
|
|
115
146
|
});
|
|
116
147
|
|
|
117
148
|
if (defaultAgentBooks.length === 0) {
|
|
@@ -30,7 +30,7 @@ export function createFederatedAgentsSearchProvider(): ServerSearchProvider {
|
|
|
30
30
|
|
|
31
31
|
let federatedServers: string[] = [];
|
|
32
32
|
try {
|
|
33
|
-
federatedServers = await getFederatedServers(
|
|
33
|
+
federatedServers = await getFederatedServers();
|
|
34
34
|
} catch (error) {
|
|
35
35
|
console.error('[search] Failed to load federated servers:', error);
|
|
36
36
|
return [];
|
|
@@ -36,7 +36,7 @@ export async function $provideAgentCollectionForServer(): Promise<AgentCollectio
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
const supabase = $provideSupabaseForServer();
|
|
39
|
-
const {
|
|
39
|
+
const { tablePrefix } = await $provideServer();
|
|
40
40
|
const cachedAgentCollection = agentCollectionsByTablePrefix.get(tablePrefix);
|
|
41
41
|
|
|
42
42
|
if (cachedAgentCollection) {
|
|
@@ -50,16 +50,9 @@ export async function $provideAgentCollectionForServer(): Promise<AgentCollectio
|
|
|
50
50
|
|
|
51
51
|
agentCollectionsByTablePrefix.set(tablePrefix, providedCollection);
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
import('../utils/attachAgentPreparationScheduling'),
|
|
55
|
-
import('../utils/defaultFederatedAgents/scheduleDefaultFederatedAgentsSync'),
|
|
56
|
-
]);
|
|
53
|
+
const { attachAgentPreparationScheduling } = await import('../utils/attachAgentPreparationScheduling');
|
|
57
54
|
|
|
58
55
|
attachAgentPreparationScheduling(providedCollection, { tablePrefix });
|
|
59
|
-
scheduleDefaultFederatedAgentsSync({
|
|
60
|
-
tablePrefix,
|
|
61
|
-
localServerUrl: publicUrl.href,
|
|
62
|
-
});
|
|
63
56
|
|
|
64
57
|
return providedCollection;
|
|
65
58
|
}
|
|
@@ -69,9 +69,7 @@ export async function findOwnedAgentByIdentifier(userId: number, identifier: str
|
|
|
69
69
|
const tableName = await $getTableName('Agent');
|
|
70
70
|
const { data, error } = await supabase
|
|
71
71
|
.from(tableName)
|
|
72
|
-
.select(
|
|
73
|
-
'id, agentName, createdAt, updatedAt, permanentId, agentHash, agentSource, agentProfile, promptbookEngineVersion, folderId, sortOrder, deletedAt, visibility, userId',
|
|
74
|
-
)
|
|
72
|
+
.select(OWNED_AGENT_ROW_COLUMNS)
|
|
75
73
|
.eq('userId', userId as never)
|
|
76
74
|
.or(buildAgentNameOrIdFilter(identifier))
|
|
77
75
|
.limit(10);
|
|
@@ -80,7 +78,58 @@ export async function findOwnedAgentByIdentifier(userId: number, identifier: str
|
|
|
80
78
|
throw new Error(`Failed to load agent "${identifier}": ${error.message}`);
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
return pickAgentRowByIdentifier((data || []) as unknown as OwnedAgentRow[], identifier, 'for this owner');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Loads one agent by permanent id or agent name without an owner restriction.
|
|
86
|
+
*
|
|
87
|
+
* Owner-aware callers should use {@link findOwnedAgentByIdentifier}; this helper is reserved for administrative
|
|
88
|
+
* overrides where the caller has been independently authorized to read or mutate any agent.
|
|
89
|
+
*
|
|
90
|
+
* @param identifier - Permanent id or agent name.
|
|
91
|
+
* @returns Matching agent row or `null` when nothing matches.
|
|
92
|
+
*/
|
|
93
|
+
export async function findAgentByIdentifier(identifier: string): Promise<OwnedAgentRow | null> {
|
|
94
|
+
const supabase = $provideSupabaseForServer();
|
|
95
|
+
const tableName = await $getTableName('Agent');
|
|
96
|
+
const { data, error } = await supabase
|
|
97
|
+
.from(tableName)
|
|
98
|
+
.select(OWNED_AGENT_ROW_COLUMNS)
|
|
99
|
+
.or(buildAgentNameOrIdFilter(identifier))
|
|
100
|
+
.limit(10);
|
|
101
|
+
|
|
102
|
+
if (error) {
|
|
103
|
+
throw new Error(`Failed to load agent "${identifier}": ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return pickAgentRowByIdentifier((data || []) as unknown as OwnedAgentRow[], identifier, 'across agents');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Database columns selected for every agent row exposed by ownership-aware helpers.
|
|
111
|
+
*/
|
|
112
|
+
const OWNED_AGENT_ROW_COLUMNS =
|
|
113
|
+
'id, agentName, createdAt, updatedAt, permanentId, agentHash, agentSource, agentProfile, promptbookEngineVersion, folderId, sortOrder, deletedAt, visibility, userId';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Disambiguates a list of agent rows that all matched the same identifier.
|
|
117
|
+
*
|
|
118
|
+
* Resolution order:
|
|
119
|
+
* 1. Exact `permanentId` match — always preferred because permanent ids are unique.
|
|
120
|
+
* 2. Single `agentName` match — preserves the legacy lookup by human-readable name.
|
|
121
|
+
* 3. Single remaining row — accepts the only candidate found.
|
|
122
|
+
*
|
|
123
|
+
* @param rows - Candidate agent rows returned by Supabase.
|
|
124
|
+
* @param identifier - Identifier that was searched.
|
|
125
|
+
* @param ambiguityScope - Human-readable scope used in the ambiguity error message.
|
|
126
|
+
* @returns Best-matching row or `null` when no candidate matches.
|
|
127
|
+
*/
|
|
128
|
+
function pickAgentRowByIdentifier(
|
|
129
|
+
rows: OwnedAgentRow[],
|
|
130
|
+
identifier: string,
|
|
131
|
+
ambiguityScope: string,
|
|
132
|
+
): OwnedAgentRow | null {
|
|
84
133
|
if (rows.length === 0) {
|
|
85
134
|
return null;
|
|
86
135
|
}
|
|
@@ -100,7 +149,7 @@ export async function findOwnedAgentByIdentifier(userId: number, identifier: str
|
|
|
100
149
|
}
|
|
101
150
|
|
|
102
151
|
throw new Error(
|
|
103
|
-
`Agent identifier "${identifier}" is ambiguous
|
|
152
|
+
`Agent identifier "${identifier}" is ambiguous ${ambiguityScope}. Use the stable \`permanentId\` returned by the API.`,
|
|
104
153
|
);
|
|
105
154
|
}
|
|
106
155
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import JSZip from 'jszip';
|
|
2
|
+
import { getMetadata } from '../../database/getMetadata';
|
|
3
|
+
import { appendBooksBackupEntriesToZip, type BooksBackupZipStream } from '../backup/createBooksBackupZipStream';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Metadata key used for the human-readable server name.
|
|
7
|
+
*/
|
|
8
|
+
const SERVER_NAME_METADATA_KEY = 'SERVER_NAME';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Filename suffix used for agents-only exports.
|
|
12
|
+
*/
|
|
13
|
+
const AGENTS_EXPORT_FILENAME_SUFFIX = '.agents.zip';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fallback server filename stem when metadata does not provide a usable name.
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_AGENTS_EXPORT_FILENAME_STEM = 'promptbook-agents-server';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pattern matching non-alphanumeric filename slug separators.
|
|
22
|
+
*/
|
|
23
|
+
const FILENAME_SLUG_SEPARATOR_PATTERN = /[^a-z0-9]+/g;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Pattern matching leading or trailing filename slug separators.
|
|
27
|
+
*/
|
|
28
|
+
const FILENAME_SLUG_EDGE_SEPARATOR_PATTERN = /^-+|-+$/g;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Builds a ZIP stream containing only agent books organized by folder structure.
|
|
32
|
+
*
|
|
33
|
+
* @returns Download filename and stream payload.
|
|
34
|
+
*/
|
|
35
|
+
export async function createAgentsExportZipStream(): Promise<BooksBackupZipStream> {
|
|
36
|
+
const serverName = await getMetadata(SERVER_NAME_METADATA_KEY);
|
|
37
|
+
const zip = new JSZip();
|
|
38
|
+
|
|
39
|
+
await appendBooksBackupEntriesToZip(zip, '');
|
|
40
|
+
|
|
41
|
+
const stream = zip.generateNodeStream({
|
|
42
|
+
streamFiles: true,
|
|
43
|
+
compression: 'DEFLATE',
|
|
44
|
+
compressionOptions: { level: 6 },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
filename: `${createAgentsExportFilenameStem(serverName)}${AGENTS_EXPORT_FILENAME_SUFFIX}`,
|
|
49
|
+
stream,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a safe lowercase filename stem from server metadata.
|
|
55
|
+
*
|
|
56
|
+
* @param serverName - Raw server name from metadata.
|
|
57
|
+
* @returns Filename stem for the agents export.
|
|
58
|
+
*/
|
|
59
|
+
export function createAgentsExportFilenameStem(serverName: string | null | undefined): string {
|
|
60
|
+
const normalizedServerName = typeof serverName === 'string' ? serverName.trim().toLowerCase() : '';
|
|
61
|
+
const filenameStem = normalizedServerName
|
|
62
|
+
.normalize('NFKD')
|
|
63
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
64
|
+
.replace(FILENAME_SLUG_SEPARATOR_PATTERN, '-')
|
|
65
|
+
.replace(FILENAME_SLUG_EDGE_SEPARATOR_PATTERN, '');
|
|
66
|
+
|
|
67
|
+
return filenameStem || DEFAULT_AGENTS_EXPORT_FILENAME_STEM;
|
|
68
|
+
}
|