@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.
Files changed (94) hide show
  1. package/apps/agents-server/README.md +0 -1
  2. package/apps/agents-server/src/app/admin/chat-feedback/page.tsx +2 -4
  3. package/apps/agents-server/src/app/admin/chat-history/page.tsx +2 -4
  4. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +246 -15
  5. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +237 -4
  6. package/apps/agents-server/src/app/api/admin/update/log/route.ts +42 -0
  7. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +28 -9
  8. package/apps/agents-server/src/app/api/agents/export/route.ts +41 -0
  9. package/apps/agents-server/src/app/api/agents/import/route.ts +168 -0
  10. package/apps/agents-server/src/app/api/chat/route.ts +29 -7
  11. package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +2 -2
  12. package/apps/agents-server/src/app/api/chat-feedback/route.ts +3 -3
  13. package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +2 -2
  14. package/apps/agents-server/src/app/api/chat-history/export/route.ts +2 -2
  15. package/apps/agents-server/src/app/api/chat-history/route.ts +3 -3
  16. package/apps/agents-server/src/app/api/chat-streaming/route.ts +29 -0
  17. package/apps/agents-server/src/app/api/elevenlabs/tts/route.ts +60 -2
  18. package/apps/agents-server/src/app/api/federated-agents/route.ts +1 -1
  19. package/apps/agents-server/src/app/api/images/[filename]/route.ts +102 -0
  20. package/apps/agents-server/src/app/api/metadata/export/route.ts +34 -0
  21. package/apps/agents-server/src/app/api/metadata/import/route.ts +46 -0
  22. package/apps/agents-server/src/app/api/openai/v1/audio/transcriptions/route.ts +111 -2
  23. package/apps/agents-server/src/app/api/page-preview/check/route.ts +18 -0
  24. package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +15 -8
  25. package/apps/agents-server/src/app/api/team-agent-profile/route.ts +20 -0
  26. package/apps/agents-server/src/app/layout.tsx +1 -1
  27. package/apps/agents-server/src/components/AgentContextMenu/useAgentContextMenuItems.ts +27 -19
  28. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +31 -1
  29. package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +60 -1
  30. package/apps/agents-server/src/components/Homepage/useAgentsListImportExportState.ts +523 -0
  31. package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +16 -0
  32. package/apps/agents-server/src/constants/defaultAgentAvatarVisual.ts +1 -1
  33. package/apps/agents-server/src/database/metadataDefaults.ts +0 -21
  34. package/apps/agents-server/src/database/migrations/2026-06-2700-default-agent-avatar-visual-octopus3d4.sql +16 -0
  35. package/apps/agents-server/src/database/seedCoreAgents.ts +235 -0
  36. package/apps/agents-server/src/database/seedDefaultAgents.ts +32 -1
  37. package/apps/agents-server/src/search/createDefaultServerSearchProviders/createFederatedAgentsSearchProvider.ts +1 -1
  38. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +2 -9
  39. package/apps/agents-server/src/utils/agentOwnership.ts +54 -5
  40. package/apps/agents-server/src/utils/agentsTransfer/createAgentsExportZipStream.ts +68 -0
  41. package/apps/agents-server/src/utils/agentsTransfer/importAgentsFromFiles.ts +852 -0
  42. package/apps/agents-server/src/utils/backup/createBooksBackupZipStream.ts +15 -2
  43. package/apps/agents-server/src/utils/defaultAgents/loadDefaultAgentBooks.ts +46 -4
  44. package/apps/agents-server/src/utils/findAgentForCallerWriteAccess.ts +36 -0
  45. package/apps/agents-server/src/utils/getFederatedServers.ts +3 -74
  46. package/apps/agents-server/src/utils/getWellKnownAgentUrl.ts +10 -4
  47. package/apps/agents-server/src/utils/metadataConfigurationTransfer.ts +426 -0
  48. package/apps/agents-server/src/utils/paidApiRequestGuard.ts +241 -0
  49. package/apps/agents-server/src/utils/serverManagement/createManagedServer/bootstrapManagedServer.ts +2 -5
  50. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +162 -0
  51. package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +36 -8
  52. package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +11 -2
  53. package/apps/agents-server/src/utils/userChat/resolveUserChatProgressToolHighlights.ts +100 -0
  54. package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +4 -3
  55. package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +272 -27
  56. package/apps/agents-server/src/utils/validateApiKey.ts +7 -3
  57. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +110 -0
  58. package/esm/index.es.js +795 -39
  59. package/esm/index.es.js.map +1 -1
  60. package/esm/src/_packages/core.index.d.ts +0 -2
  61. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  62. package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  63. package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  64. package/esm/src/version.d.ts +1 -1
  65. package/package.json +1 -1
  66. package/servers.ts +1 -16
  67. package/src/_packages/core.index.ts +0 -2
  68. package/src/avatars/types/AvatarVisualDefinition.ts +1 -0
  69. package/src/avatars/visuals/avatarVisualRegistry.ts +2 -0
  70. package/src/avatars/visuals/octopus3d4AvatarVisual.ts +1295 -0
  71. package/src/other/templates/getTemplatesPipelineCollection.ts +762 -883
  72. package/src/utils/agents/resolveAgentAvatarImageUrl.ts +1 -1
  73. package/src/utils/validators/url/isValidAgentUrl.ts +5 -7
  74. package/src/version.ts +2 -2
  75. package/src/versions.txt +2 -1
  76. package/umd/index.umd.js +795 -39
  77. package/umd/index.umd.js.map +1 -1
  78. package/umd/src/_packages/core.index.d.ts +0 -2
  79. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  80. package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  81. package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  82. package/umd/src/version.d.ts +1 -1
  83. package/apps/agents-server/src/utils/defaultFederatedAgents/DefaultFederatedAgentsSyncOptions.ts +0 -9
  84. package/apps/agents-server/src/utils/defaultFederatedAgents/ensureDefaultFederatedAgentExists.ts +0 -277
  85. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchCoreOrganizationPayload.ts +0 -39
  86. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchFederatedAgentBook.ts +0 -43
  87. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchWithDefaultFederatedAgentTimeout.ts +0 -28
  88. package/apps/agents-server/src/utils/defaultFederatedAgents/getDefaultFederatedAgentSyncPool.ts +0 -38
  89. package/apps/agents-server/src/utils/defaultFederatedAgents/loadActiveLocalAgentIdsByNormalizedName.ts +0 -41
  90. package/apps/agents-server/src/utils/defaultFederatedAgents/loadDefaultFederatedAgentSyncMetadata.ts +0 -76
  91. package/apps/agents-server/src/utils/defaultFederatedAgents/quoteIdentifier.ts +0 -11
  92. package/apps/agents-server/src/utils/defaultFederatedAgents/scheduleDefaultFederatedAgentsSync.ts +0 -88
  93. package/apps/agents-server/src/utils/defaultFederatedAgents/selectDefaultFederatedAgentsFromOrganizationPayload.ts +0 -181
  94. package/apps/agents-server/src/utils/defaultFederatedAgents/synchronizeDefaultFederatedAgents.ts +0 -77
@@ -0,0 +1,241 @@
1
+ import { createHash } from 'crypto';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { spaceTrim } from 'spacetrim';
4
+ import { getRequestIp } from '../middleware/createMiddlewareRequestContext/getRequestIp';
5
+ import { getSession } from './session';
6
+ import { validateApiKey } from './validateApiKey';
7
+
8
+ /**
9
+ * Categorizes the kind of paid third-party AI call a route performs.
10
+ *
11
+ * Each tier maps to its own per-identity sliding-window budget because the
12
+ * marginal cost (and therefore acceptable rate) differs significantly between
13
+ * speech-to-text, text-to-speech, and image generation.
14
+ *
15
+ * @private internal helper of paid AI proxy routes
16
+ */
17
+ export type PaidApiRequestTier = 'AUDIO_TRANSCRIPTION' | 'TEXT_TO_SPEECH' | 'IMAGE_GENERATION';
18
+
19
+ /**
20
+ * Per-tier rate limit definition.
21
+ *
22
+ * @private internal helper of paid AI proxy routes
23
+ */
24
+ type PaidApiRateLimitDefinition = {
25
+ /**
26
+ * Maximum number of paid calls permitted per identity in a single window.
27
+ */
28
+ readonly maxRequestsPerWindow: number;
29
+ /**
30
+ * Length of the sliding window in milliseconds.
31
+ */
32
+ readonly windowMs: number;
33
+ };
34
+
35
+ /**
36
+ * Per-identity, per-tier sliding-window budgets for paid third-party calls.
37
+ *
38
+ * Numbers are intentionally generous for normal interactive UI use while still
39
+ * blocking sustained abuse — e.g. an attacker who has obtained a valid API key
40
+ * or signed-in account cannot loop millions of image generations.
41
+ *
42
+ * @private internal constant of paid AI proxy routes
43
+ */
44
+ const PAID_API_RATE_LIMITS: Readonly<Record<PaidApiRequestTier, PaidApiRateLimitDefinition>> = {
45
+ AUDIO_TRANSCRIPTION: { maxRequestsPerWindow: 60, windowMs: 60_000 },
46
+ TEXT_TO_SPEECH: { maxRequestsPerWindow: 60, windowMs: 60_000 },
47
+ IMAGE_GENERATION: { maxRequestsPerWindow: 20, windowMs: 60_000 },
48
+ };
49
+
50
+ /**
51
+ * In-memory map of request timestamps keyed by `tier|identity`.
52
+ *
53
+ * In-memory state is intentional: the sliding window is a defense in depth on
54
+ * top of authentication, and we do not want to add a Redis dependency just for
55
+ * this guard. Operators who run multiple replicas should still configure a
56
+ * front-door rate limiter; this layer prevents single-process abuse.
57
+ *
58
+ * @private internal constant of paid AI proxy routes
59
+ */
60
+ const PAID_API_REQUEST_TIMESTAMPS: Map<string, Array<number>> = new Map();
61
+
62
+ /**
63
+ * Successful guard outcome describing the authenticated identity.
64
+ *
65
+ * @private internal helper of paid AI proxy routes
66
+ */
67
+ export type PaidApiRequestGuardSuccess = {
68
+ readonly ok: true;
69
+ /**
70
+ * Stable identity key used for rate limiting and audit logs.
71
+ *
72
+ * Derived from (in order): the signed session username, the bearer API key
73
+ * hash, and finally the request IP for anonymous fallbacks (which should
74
+ * not happen because `authenticatedOnly` rejects them).
75
+ */
76
+ readonly identityKey: string;
77
+ };
78
+
79
+ /**
80
+ * Failure outcome containing the response that should be returned verbatim.
81
+ *
82
+ * @private internal helper of paid AI proxy routes
83
+ */
84
+ export type PaidApiRequestGuardFailure = {
85
+ readonly ok: false;
86
+ readonly response: NextResponse;
87
+ };
88
+
89
+ /**
90
+ * Guard outcome returned by `guardPaidApiRequest`.
91
+ *
92
+ * @private internal helper of paid AI proxy routes
93
+ */
94
+ export type PaidApiRequestGuardResult = PaidApiRequestGuardSuccess | PaidApiRequestGuardFailure;
95
+
96
+ /**
97
+ * Combined authentication and per-identity rate-limit check for routes that
98
+ * proxy directly to paid third-party AI providers (OpenAI, ElevenLabs, ...).
99
+ *
100
+ * The guard enforces two invariants in a single place so that every paid
101
+ * endpoint stays consistent (DRY) and no new endpoint accidentally forgets
102
+ * authentication or rate limiting:
103
+ *
104
+ * 1. The caller must be authenticated via a signed session cookie or a valid
105
+ * `Authorization: Bearer ptbk_...` API key.
106
+ * 2. The caller must stay within the per-tier sliding-window budget defined
107
+ * in `PAID_API_RATE_LIMITS`.
108
+ *
109
+ * @param request - Incoming HTTP request.
110
+ * @param tier - Paid call category determining the applicable budget.
111
+ * @returns Either `{ ok: true, identityKey }` or `{ ok: false, response }`.
112
+ *
113
+ * @private internal helper of paid AI proxy routes
114
+ */
115
+ export async function guardPaidApiRequest(
116
+ request: NextRequest,
117
+ tier: PaidApiRequestTier,
118
+ ): Promise<PaidApiRequestGuardResult> {
119
+ const apiKeyValidation = await validateApiKey(request);
120
+ if (!apiKeyValidation.isValid) {
121
+ return {
122
+ ok: false,
123
+ response: NextResponse.json(
124
+ {
125
+ error: {
126
+ message: apiKeyValidation.error || 'Authentication required',
127
+ type: 'authentication_error',
128
+ },
129
+ },
130
+ { status: 401 },
131
+ ),
132
+ };
133
+ }
134
+
135
+ const identityKey = await resolvePaidApiIdentityKey(request, apiKeyValidation.token);
136
+
137
+ const rateLimitCheck = checkPaidApiRateLimit(tier, identityKey);
138
+ if (!rateLimitCheck.isAllowed) {
139
+ return {
140
+ ok: false,
141
+ response: NextResponse.json(
142
+ {
143
+ error: {
144
+ message: spaceTrim(`
145
+ Rate limit exceeded for paid AI proxy.
146
+
147
+ **Tier:** \`${tier}\`
148
+ **Limit:** ${rateLimitCheck.maxRequestsPerWindow} requests per ${Math.round(
149
+ rateLimitCheck.windowMs / 1000,
150
+ )} seconds.
151
+
152
+ Try again in **${rateLimitCheck.retryAfterSeconds}** seconds.
153
+ `),
154
+ type: 'rate_limit_exceeded',
155
+ },
156
+ },
157
+ {
158
+ status: 429,
159
+ headers: {
160
+ 'Retry-After': String(rateLimitCheck.retryAfterSeconds),
161
+ },
162
+ },
163
+ ),
164
+ };
165
+ }
166
+
167
+ return { ok: true, identityKey };
168
+ }
169
+
170
+ /**
171
+ * Resolves a stable per-identity key used to bucket rate-limit counts.
172
+ *
173
+ * @param request - Incoming HTTP request.
174
+ * @param apiKeyToken - Optional bearer token recovered by `validateApiKey`.
175
+ * @returns Identity key prefixed with the source kind (`user:`, `apikey:`, or `ip:`).
176
+ *
177
+ * @private internal helper of `guardPaidApiRequest`
178
+ */
179
+ async function resolvePaidApiIdentityKey(request: NextRequest, apiKeyToken: string | undefined): Promise<string> {
180
+ const session = await getSession();
181
+ if (session?.username) {
182
+ return `user:${session.username}`;
183
+ }
184
+
185
+ if (apiKeyToken) {
186
+ return `apikey:${createHash('sha256').update(apiKeyToken).digest('hex')}`;
187
+ }
188
+
189
+ return `ip:${getRequestIp(request)}`;
190
+ }
191
+
192
+ /**
193
+ * Sliding-window rate-limit check shared by all paid AI proxy tiers.
194
+ *
195
+ * @param tier - Paid call category determining the applicable budget.
196
+ * @param identityKey - Stable per-identity key from `resolvePaidApiIdentityKey`.
197
+ * @returns Decision describing whether the request is allowed and, if not, how long to wait.
198
+ *
199
+ * @private internal helper of `guardPaidApiRequest`
200
+ */
201
+ function checkPaidApiRateLimit(
202
+ tier: PaidApiRequestTier,
203
+ identityKey: string,
204
+ ): {
205
+ readonly isAllowed: boolean;
206
+ readonly maxRequestsPerWindow: number;
207
+ readonly windowMs: number;
208
+ readonly retryAfterSeconds: number;
209
+ } {
210
+ const { maxRequestsPerWindow, windowMs } = PAID_API_RATE_LIMITS[tier];
211
+ const bucketKey = `${tier}|${identityKey}`;
212
+ const now = Date.now();
213
+ const windowStart = now - windowMs;
214
+
215
+ const existingTimestamps = PAID_API_REQUEST_TIMESTAMPS.get(bucketKey) || [];
216
+ const recentTimestamps = existingTimestamps.filter((timestamp) => timestamp > windowStart);
217
+
218
+ if (recentTimestamps.length >= maxRequestsPerWindow) {
219
+ const oldestRelevantTimestamp = recentTimestamps[0]!;
220
+ const retryAfterMs = Math.max(1_000, oldestRelevantTimestamp + windowMs - now);
221
+ PAID_API_REQUEST_TIMESTAMPS.set(bucketKey, recentTimestamps);
222
+ return {
223
+ isAllowed: false,
224
+ maxRequestsPerWindow,
225
+ windowMs,
226
+ retryAfterSeconds: Math.ceil(retryAfterMs / 1_000),
227
+ };
228
+ }
229
+
230
+ recentTimestamps.push(now);
231
+ PAID_API_REQUEST_TIMESTAMPS.set(bucketKey, recentTimestamps);
232
+
233
+ return {
234
+ isAllowed: true,
235
+ maxRequestsPerWindow,
236
+ windowMs,
237
+ retryAfterSeconds: 0,
238
+ };
239
+ }
240
+
241
+ // Note: [🟢] Code for the Agents Server paid AI request guard should never be published into packages that could be imported into browser environment
@@ -5,7 +5,6 @@ import {
5
5
  acquireMigrationExecutionLock,
6
6
  releaseMigrationExecutionLock,
7
7
  } from '../../../database/acquireMigrationExecutionLock';
8
- import { scheduleDefaultFederatedAgentsSync } from '../../defaultFederatedAgents/scheduleDefaultFederatedAgentsSync';
9
8
  import { createServerPublicUrl, invalidateRegisteredServersCache } from '../../serverRegistry';
10
9
  import type { CreateServerResult } from '../createManagedServer';
11
10
  import { resolveManagedServerConnectionString } from '../resolveManagedServerConnectionString';
@@ -14,6 +13,7 @@ import { createFailedServerResult } from './createFailedServerResult';
14
13
  import { createSqlRecorder } from './createSqlRecorder';
15
14
  import { insertManagedServerRegistryRow } from './insertManagedServerRegistryRow';
16
15
  import type { NormalizedCreateServerInput } from './normalizeCreateServerInput';
16
+ import { seedServerCoreAgents } from './seedServerCoreAgents';
17
17
  import { seedServerDefaultAgents } from './seedServerDefaultAgents';
18
18
  import { seedServerMetadata } from './seedServerMetadata';
19
19
  import { seedServerUsers } from './seedServerUsers';
@@ -50,15 +50,12 @@ export async function bootstrapManagedServer(input: NormalizedCreateServerInput)
50
50
  if (input.isDefaultAgentsInstalled) {
51
51
  await seedServerDefaultAgents(client, input, sqlRecorder);
52
52
  }
53
+ await seedServerCoreAgents(client, input, sqlRecorder);
53
54
 
54
55
  await client.query('COMMIT');
55
56
  sqlRecorder.addStatement('COMMIT');
56
57
 
57
58
  invalidateRegisteredServersCache();
58
- scheduleDefaultFederatedAgentsSync({
59
- tablePrefix: server.tablePrefix,
60
- localServerUrl: createServerPublicUrl(server.domain).href,
61
- });
62
59
 
63
60
  return {
64
61
  ok: true,
@@ -0,0 +1,162 @@
1
+ import type { Client } from 'pg';
2
+ import { createAgentPersistenceRecords } from '../../../../../../src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords';
3
+ import { DEFAULT_AGENT_VISIBILITY } from '../../agentVisibility';
4
+ import { CORE_AGENT_DIRECTORY_NAME, loadCoreAgentBooks } from '../../defaultAgents/loadDefaultAgentBooks';
5
+ import { createInsertStatement, quoteIdentifier, type SqlRecorder } from './createSqlRecorder';
6
+ import type { NormalizedCreateServerInput } from './normalizeCreateServerInput';
7
+
8
+ /**
9
+ * Loads bundled `.core` agent books from the repository and persists them into the newly created server.
10
+ *
11
+ * Each `*.book` file in `agents/default/.core` becomes one persisted agent in the `.core` folder with its initial
12
+ * history snapshot.
13
+ *
14
+ * @param client - Connected PostgreSQL client inside the bootstrap transaction.
15
+ * @param input - Normalized create-server payload.
16
+ * @param sqlRecorder - Mutable SQL dump recorder.
17
+ *
18
+ * @private function of createManagedServer
19
+ */
20
+ export async function seedServerCoreAgents(
21
+ client: Client,
22
+ input: NormalizedCreateServerInput,
23
+ sqlRecorder: SqlRecorder,
24
+ ): Promise<void> {
25
+ const coreAgentBooks = await loadCoreAgentBooks();
26
+ if (coreAgentBooks.length === 0) {
27
+ return;
28
+ }
29
+
30
+ const coreFolderId = await insertCoreFolder(client, input, sqlRecorder);
31
+ const agentTableIdentifier = quoteIdentifier(`${input.tablePrefix}Agent`);
32
+ const agentHistoryTableIdentifier = quoteIdentifier(`${input.tablePrefix}AgentHistory`);
33
+
34
+ for (const [index, coreAgentBook] of coreAgentBooks.entries()) {
35
+ const createdAt = new Date().toISOString();
36
+ const { agentInsertRecord, agentHistoryInsertRecord } = createAgentPersistenceRecords(
37
+ coreAgentBook,
38
+ { folderId: coreFolderId, sortOrder: index, visibility: DEFAULT_AGENT_VISIBILITY },
39
+ createdAt,
40
+ );
41
+
42
+ await client.query(
43
+ `
44
+ INSERT INTO ${agentTableIdentifier} (
45
+ "agentName",
46
+ "createdAt",
47
+ "updatedAt",
48
+ "permanentId",
49
+ "agentHash",
50
+ "agentSource",
51
+ "agentProfile",
52
+ "promptbookEngineVersion",
53
+ "usage",
54
+ "folderId",
55
+ "sortOrder",
56
+ "visibility"
57
+ )
58
+ VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9::jsonb, $10, $11, $12)
59
+ `,
60
+ [
61
+ agentInsertRecord.agentName,
62
+ agentInsertRecord.createdAt,
63
+ agentInsertRecord.updatedAt,
64
+ agentInsertRecord.permanentId,
65
+ agentInsertRecord.agentHash,
66
+ agentInsertRecord.agentSource,
67
+ JSON.stringify(agentInsertRecord.agentProfile),
68
+ agentInsertRecord.promptbookEngineVersion,
69
+ JSON.stringify(agentInsertRecord.usage),
70
+ coreFolderId,
71
+ agentInsertRecord.sortOrder ?? index,
72
+ agentInsertRecord.visibility ?? DEFAULT_AGENT_VISIBILITY,
73
+ ],
74
+ );
75
+ sqlRecorder.addStatement(
76
+ createInsertStatement(`${input.tablePrefix}Agent`, {
77
+ ...agentInsertRecord,
78
+ agentProfile: JSON.stringify(agentInsertRecord.agentProfile),
79
+ usage: JSON.stringify(agentInsertRecord.usage),
80
+ folderId: coreFolderId,
81
+ sortOrder: agentInsertRecord.sortOrder ?? index,
82
+ visibility: agentInsertRecord.visibility ?? DEFAULT_AGENT_VISIBILITY,
83
+ }),
84
+ );
85
+
86
+ await client.query(
87
+ `
88
+ INSERT INTO ${agentHistoryTableIdentifier} (
89
+ "createdAt",
90
+ "agentName",
91
+ "permanentId",
92
+ "agentHash",
93
+ "previousAgentHash",
94
+ "agentSource",
95
+ "promptbookEngineVersion",
96
+ "versionName"
97
+ )
98
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
99
+ `,
100
+ [
101
+ agentHistoryInsertRecord.createdAt,
102
+ agentHistoryInsertRecord.agentName,
103
+ agentHistoryInsertRecord.permanentId,
104
+ agentHistoryInsertRecord.agentHash,
105
+ agentHistoryInsertRecord.previousAgentHash,
106
+ agentHistoryInsertRecord.agentSource,
107
+ agentHistoryInsertRecord.promptbookEngineVersion,
108
+ agentHistoryInsertRecord.versionName,
109
+ ],
110
+ );
111
+ sqlRecorder.addStatement(createInsertStatement(`${input.tablePrefix}AgentHistory`, agentHistoryInsertRecord));
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Inserts the `.core` folder row for the newly bootstrapped server.
117
+ *
118
+ * @param client - Connected PostgreSQL client inside the bootstrap transaction.
119
+ * @param input - Normalized create-server payload.
120
+ * @param sqlRecorder - Mutable SQL dump recorder.
121
+ * @returns Identifier of the inserted `.core` folder.
122
+ *
123
+ * @private function of createManagedServer
124
+ */
125
+ async function insertCoreFolder(
126
+ client: Client,
127
+ input: NormalizedCreateServerInput,
128
+ sqlRecorder: SqlRecorder,
129
+ ): Promise<number> {
130
+ const agentFolderTableIdentifier = quoteIdentifier(`${input.tablePrefix}AgentFolder`);
131
+ const createdAt = new Date().toISOString();
132
+ const insertResult = await client.query<{ id: number }>(
133
+ `
134
+ INSERT INTO ${agentFolderTableIdentifier} (
135
+ "name",
136
+ "parentId",
137
+ "sortOrder",
138
+ "icon",
139
+ "color",
140
+ "createdAt",
141
+ "updatedAt"
142
+ )
143
+ VALUES ($1, NULL, $2, NULL, NULL, $3, NULL)
144
+ RETURNING "id"
145
+ `,
146
+ [CORE_AGENT_DIRECTORY_NAME, 0, createdAt],
147
+ );
148
+
149
+ sqlRecorder.addStatement(
150
+ createInsertStatement(`${input.tablePrefix}AgentFolder`, {
151
+ name: CORE_AGENT_DIRECTORY_NAME,
152
+ parentId: null,
153
+ sortOrder: 0,
154
+ icon: null,
155
+ color: null,
156
+ createdAt,
157
+ updatedAt: null,
158
+ }),
159
+ );
160
+
161
+ return insertResult.rows[0].id;
162
+ }
@@ -27,7 +27,8 @@ import type { ChatMessage, ChatPrompt, LlmToolDefinition, ToolCall } from '@prom
27
27
  import { resolveTeamInternalAgentAccessToken } from '../../../../../src/commitments/_common/teamInternalAgentAccess';
28
28
  import type { ChatPromptResult } from '../../../../../src/execution/PromptResult';
29
29
  import { createUserChatMessagePrompt } from './createUserChatMessagePrompt';
30
- import type { UserChatProgressPhase } from './userChatProgressCard';
30
+ import { resolveUserChatProgressToolHighlights } from './resolveUserChatProgressToolHighlights';
31
+ import type { UserChatProgressContext, UserChatProgressPhase } from './userChatProgressCard';
31
32
  import type { UserChatJobRecord } from './UserChatJobRecord';
32
33
 
33
34
  /**
@@ -55,9 +56,13 @@ type RunUserChatJobPromptSnapshotFactory = (
55
56
  ) => NonNullable<ChatMessage['prompt']>;
56
57
 
57
58
  /**
58
- * Reports one user-facing durable chat progress phase.
59
+ * Reports one user-facing durable chat progress phase together with the real runtime
60
+ * details already known at that point in the worker lifecycle.
59
61
  */
60
- type RunUserChatJobProgressReporter = (phase: UserChatProgressPhase) => Promise<void>;
62
+ type RunUserChatJobProgressReporter = (
63
+ phase: UserChatProgressPhase,
64
+ context?: UserChatProgressContext,
65
+ ) => Promise<void>;
61
66
 
62
67
  /**
63
68
  * Prepares the full agent/runtime context required to execute one durable user chat job.
@@ -75,7 +80,12 @@ export async function createRunUserChatJobExecutionContext(options: {
75
80
  promptContent: ChatPrompt['content'];
76
81
  reportProgress?: RunUserChatJobProgressReporter;
77
82
  }) {
78
- await options.reportProgress?.('reading_context');
83
+ const initialProgressContext: UserChatProgressContext = {
84
+ agentPermanentId: options.job.agentPermanentId,
85
+ attachmentCount: options.userMessageAttachments?.length ?? 0,
86
+ };
87
+
88
+ await options.reportProgress?.('reading_context', initialProgressContext);
79
89
 
80
90
  const [localServerUrl, collection, baseAgentReferenceResolver] = await Promise.all([
81
91
  resolveCurrentOrInternalServerOrigin(),
@@ -83,7 +93,7 @@ export async function createRunUserChatJobExecutionContext(options: {
83
93
  $provideAgentReferenceResolver(),
84
94
  ]);
85
95
 
86
- await options.reportProgress?.('preparing_agent');
96
+ await options.reportProgress?.('preparing_agent', initialProgressContext);
87
97
 
88
98
  const resolvedAgentContext = await resolveCachedServerAgentContext({
89
99
  collection,
@@ -103,8 +113,22 @@ export async function createRunUserChatJobExecutionContext(options: {
103
113
  const projectRepositories = extractProjectRepositoriesFromAgentSource(agentSource);
104
114
  const calendarConnections = extractUseCalendarConnectionsFromAgentSource(agentSource);
105
115
  const useEmailConfiguration = extractUseEmailConfigurationFromAgentSource(agentSource);
116
+ const agentToolDefinitions = preparedAgentModelRequirements.modelRequirements.tools ?? [];
117
+ const agentKnowledgeSources = preparedAgentModelRequirements.modelRequirements.knowledgeSources ?? [];
106
118
 
107
- await options.reportProgress?.('checking_capabilities');
119
+ const checkingCapabilitiesProgressContext: UserChatProgressContext = {
120
+ ...initialProgressContext,
121
+ agentName: resolvedAgentName,
122
+ agentPermanentId,
123
+ knowledgeSourceCount: agentKnowledgeSources.length,
124
+ toolCount: agentToolDefinitions.length,
125
+ toolHighlights: resolveUserChatProgressToolHighlights(agentToolDefinitions),
126
+ hasCalendarAccess: calendarConnections.length > 0,
127
+ hasEmailAccess: useEmailConfiguration.isEnabled,
128
+ hasProjectAccess: projectRepositories.length > 0,
129
+ };
130
+
131
+ await options.reportProgress?.('checking_capabilities', checkingCapabilitiesProgressContext);
108
132
 
109
133
  const { projectGithubToken, calendarGoogleAccessToken, emailSmtpCredential } =
110
134
  await resolveRunUserChatJobCredentials({
@@ -179,8 +203,6 @@ export async function createRunUserChatJobExecutionContext(options: {
179
203
  });
180
204
  }
181
205
 
182
- await options.reportProgress?.('starting_response');
183
-
184
206
  const agentKitResult = await agentKitCacheManager.getOrCreateAgentKitAgent(
185
207
  agentSource,
186
208
  resolvedAgentName,
@@ -203,6 +225,12 @@ export async function createRunUserChatJobExecutionContext(options: {
203
225
  teacherAgent: await getTeacherRemoteAgent(),
204
226
  });
205
227
 
228
+ await options.reportProgress?.('starting_response', {
229
+ ...checkingCapabilitiesProgressContext,
230
+ provider: provider ?? undefined,
231
+ isAgentKitCached: agentKitResult.fromCache,
232
+ });
233
+
206
234
  return {
207
235
  collection,
208
236
  agent,
@@ -1,8 +1,16 @@
1
- import { createUserChatProgressCard, type UserChatProgressPhase } from './userChatProgressCard';
1
+ import {
2
+ createUserChatProgressCard,
3
+ type UserChatProgressContext,
4
+ type UserChatProgressPhase,
5
+ } from './userChatProgressCard';
2
6
  import { updateUserChatAssistantMessage } from './updateUserChatAssistantMessage';
3
7
 
4
8
  /**
5
9
  * Persists one durable chat progress phase onto the active assistant placeholder.
10
+ *
11
+ * The optional `context` carries real runtime details (agent name, provider, tool count,
12
+ * attachments, integrations) so the persisted progress card reflects what the worker is
13
+ * actually doing on this turn instead of a fixed scripted summary.
6
14
  */
7
15
  export async function persistUserChatJobProgressCard(options: {
8
16
  readonly userId: number;
@@ -10,6 +18,7 @@ export async function persistUserChatJobProgressCard(options: {
10
18
  readonly chatId: string;
11
19
  readonly assistantMessageId: string;
12
20
  readonly phase: UserChatProgressPhase;
21
+ readonly context?: UserChatProgressContext;
13
22
  }): Promise<void> {
14
23
  await updateUserChatAssistantMessage({
15
24
  userId: options.userId,
@@ -24,7 +33,7 @@ export async function persistUserChatJobProgressCard(options: {
24
33
  lifecycleState,
25
34
  lifecycleError: undefined,
26
35
  isComplete: false,
27
- progressCard: createUserChatProgressCard(options.phase),
36
+ progressCard: createUserChatProgressCard(options.phase, options.context),
28
37
  };
29
38
  },
30
39
  });
@@ -0,0 +1,100 @@
1
+ import type { LlmToolDefinition } from '@promptbook-local/types';
2
+
3
+ /**
4
+ * Maximum number of tool highlight labels surfaced in the progress card text.
5
+ *
6
+ * Keeps the rendered sentence readable when an agent exposes many tools.
7
+ */
8
+ const MAX_TOOL_HIGHLIGHTS = 4;
9
+
10
+ /**
11
+ * Maps technical tool identifiers to short user-facing labels shown in the
12
+ * durable chat progress card while capabilities are being checked.
13
+ */
14
+ const USER_CHAT_PROGRESS_TOOL_LABEL_BY_NAME: Record<string, string> = {
15
+ web_search: 'web search',
16
+ deep_search: 'deep research',
17
+ useSearchEngine: 'web search',
18
+ search: 'web search',
19
+ useBrowser: 'web browser',
20
+ browse: 'web browser',
21
+ fetch_url_content: 'web browser',
22
+ run_browser: 'web browser',
23
+ get_current_time: 'time awareness',
24
+ useTime: 'time awareness',
25
+ set_timeout: 'timers',
26
+ cancel_timeout: 'timers',
27
+ list_timeouts: 'timers',
28
+ update_timeout: 'timers',
29
+ get_user_location: 'user location',
30
+ send_email: 'email sending',
31
+ useEmail: 'email sending',
32
+ spawn_agent: 'teammate agents',
33
+ retrieve_user_memory: 'long-term memory',
34
+ store_user_memory: 'long-term memory',
35
+ project_list_files: 'project files',
36
+ project_read_file: 'project files',
37
+ project_upsert_file: 'project files',
38
+ project_delete_file: 'project files',
39
+ project_create_branch: 'project repository',
40
+ project_create_pull_request: 'project repository',
41
+ };
42
+
43
+ /**
44
+ * Tool identifiers that should never appear as user-facing capability highlights.
45
+ *
46
+ * `agent_progress` is an internal runtime tool used by the worker to update this
47
+ * very progress card, and `assistant_preparation` is the engine-level chip shown
48
+ * while the model is being prepared. Neither describes a user-relevant capability.
49
+ */
50
+ const HIDDEN_PROGRESS_TOOL_NAMES: ReadonlySet<string> = new Set(['agent_progress', 'assistant_preparation']);
51
+
52
+ /**
53
+ * Derives a short, deduplicated list of user-facing tool labels for one durable chat turn.
54
+ *
55
+ * @public exported for `createRunUserChatJobExecutionContext`
56
+ */
57
+ export function resolveUserChatProgressToolHighlights(
58
+ tools: ReadonlyArray<LlmToolDefinition>,
59
+ ): ReadonlyArray<string> {
60
+ const highlights: Array<string> = [];
61
+ const seenLabels = new Set<string>();
62
+
63
+ for (const tool of tools) {
64
+ if (!tool || typeof tool.name !== 'string') {
65
+ continue;
66
+ }
67
+
68
+ if (HIDDEN_PROGRESS_TOOL_NAMES.has(tool.name)) {
69
+ continue;
70
+ }
71
+
72
+ const label = USER_CHAT_PROGRESS_TOOL_LABEL_BY_NAME[tool.name] ?? humanizeToolName(tool.name);
73
+ if (!label || seenLabels.has(label)) {
74
+ continue;
75
+ }
76
+
77
+ seenLabels.add(label);
78
+ highlights.push(label);
79
+
80
+ if (highlights.length >= MAX_TOOL_HIGHLIGHTS) {
81
+ break;
82
+ }
83
+ }
84
+
85
+ return highlights;
86
+ }
87
+
88
+ /**
89
+ * Converts a technical tool identifier into a short readable label as a fallback.
90
+ *
91
+ * @private internal helper of `resolveUserChatProgressToolHighlights`
92
+ */
93
+ function humanizeToolName(name: string): string {
94
+ const normalized = name.replace(/[_-]+/g, ' ').trim().toLowerCase();
95
+ if (!normalized) {
96
+ return '';
97
+ }
98
+
99
+ return normalized;
100
+ }