@promptbook/cli 0.112.0-111 → 0.112.0-112

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 (31) hide show
  1. package/apps/agents-server/src/app/admin/servers/CreateServerDialog.tsx +16 -0
  2. package/apps/agents-server/src/app/admin/servers/useCreateServerWizard.ts +31 -5
  3. package/apps/agents-server/src/app/api/admin/servers/route.ts +5 -0
  4. package/apps/agents-server/src/app/api/metadata/route.ts +4 -0
  5. package/apps/agents-server/src/database/customJavascript.ts +62 -1
  6. package/apps/agents-server/src/database/customStylesheet.ts +60 -1
  7. package/apps/agents-server/src/database/getMetadata.ts +84 -3
  8. package/apps/agents-server/src/instrumentation.ts +3 -0
  9. package/apps/agents-server/src/utils/errorReporting/registerServerErrorSentryLogging.ts +331 -0
  10. package/apps/agents-server/src/utils/errorReporting/sendApplicationErrorReportToSentry.ts +8 -153
  11. package/apps/agents-server/src/utils/errorReporting/sentryStore.ts +177 -0
  12. package/apps/agents-server/src/utils/serverManagement/createManagedServer/bootstrapManagedServer.ts +3 -1
  13. package/apps/agents-server/src/utils/serverManagement/createManagedServer/normalizeCreateServerInput.ts +6 -0
  14. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerDefaultAgents.ts +7 -3
  15. package/apps/agents-server/src/utils/serverManagement/createManagedServer.ts +5 -0
  16. package/apps/agents-server/src/utils/userChat/listUserChats.ts +109 -0
  17. package/esm/index.es.js +23 -4
  18. package/esm/index.es.js.map +1 -1
  19. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +2 -1
  20. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.test.d.ts +1 -0
  21. package/esm/src/version.d.ts +1 -1
  22. package/package.json +1 -1
  23. package/src/cli/cli-commands/agents-server/startAgentsServer.ts +17 -1
  24. package/src/other/templates/getTemplatesPipelineCollection.ts +724 -878
  25. package/src/version.ts +2 -2
  26. package/src/versions.txt +1 -0
  27. package/umd/index.umd.js +23 -4
  28. package/umd/index.umd.js.map +1 -1
  29. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +2 -1
  30. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.test.d.ts +1 -0
  31. package/umd/src/version.d.ts +1 -1
@@ -203,6 +203,22 @@ function CreateServerForm(props: {
203
203
  ) : null}
204
204
  </div>
205
205
 
206
+ <div className="rounded-lg border border-gray-200 bg-gray-50 p-4 text-sm text-gray-600">
207
+ <label htmlFor="create-server-default-agents" className="flex items-start gap-3">
208
+ <input
209
+ id="create-server-default-agents"
210
+ type="checkbox"
211
+ checked={wizardState.isDefaultAgentsInstalled}
212
+ onChange={(event) => updateWizardField('isDefaultAgentsInstalled', event.target.checked)}
213
+ className="mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
214
+ />
215
+ <span>
216
+ <span className="block font-semibold text-gray-900">Install default agents</span>
217
+ <span className="mt-1 block">Create bundled starter agents from agents/default.</span>
218
+ </span>
219
+ </label>
220
+ </div>
221
+
206
222
  <div className="rounded-lg border border-gray-200 bg-gray-50 p-4 text-sm text-gray-600">
207
223
  <p className="font-semibold text-gray-900">Admin user exists</p>
208
224
  <p className="mt-1">
@@ -121,6 +121,11 @@ export type CreateServerWizardState = {
121
121
  */
122
122
  additionalUsers: WizardUser[];
123
123
 
124
+ /**
125
+ * Whether bundled default agents should be installed during server creation.
126
+ */
127
+ isDefaultAgentsInstalled: boolean;
128
+
124
129
  /**
125
130
  * Initial metadata values.
126
131
  */
@@ -155,7 +160,10 @@ export type CreateServerWizardError = {
155
160
  * @private function of <ServersClient/>
156
161
  */
157
162
  export type UpdateCreateServerWizardField = <
158
- TFieldName extends keyof Pick<CreateServerWizardState, 'name' | 'domain' | 'iconUrl'>,
163
+ TFieldName extends keyof Pick<
164
+ CreateServerWizardState,
165
+ 'name' | 'domain' | 'iconUrl' | 'isDefaultAgentsInstalled'
166
+ >,
159
167
  >(
160
168
  fieldName: TFieldName,
161
169
  value: CreateServerWizardState[TFieldName],
@@ -262,6 +270,7 @@ function createInitialWizardState(): CreateServerWizardState {
262
270
  password: '',
263
271
  },
264
272
  additionalUsers: [],
273
+ isDefaultAgentsInstalled: true,
265
274
  initialSettings: {
266
275
  language: 'en',
267
276
  homepageMessage: '',
@@ -345,7 +354,8 @@ function hasCreateServerWizardChanges(wizardState: CreateServerWizardState): boo
345
354
  wizardState.name !== initialWizardState.name ||
346
355
  wizardState.identifier !== initialWizardState.identifier ||
347
356
  wizardState.domain !== initialWizardState.domain ||
348
- wizardState.iconUrl !== initialWizardState.iconUrl
357
+ wizardState.iconUrl !== initialWizardState.iconUrl ||
358
+ wizardState.isDefaultAgentsInstalled !== initialWizardState.isDefaultAgentsInstalled
349
359
  );
350
360
  }
351
361
 
@@ -402,16 +412,32 @@ export function useCreateServerWizard(options: UseCreateServerWizardOptions): Us
402
412
  const updateWizardField = useCallback<UpdateCreateServerWizardField>((fieldName, value) => {
403
413
  setWizardState((previous) => {
404
414
  if (fieldName === 'name') {
415
+ const name = value as CreateServerWizardState['name'];
416
+
417
+ return {
418
+ ...previous,
419
+ name,
420
+ identifier: createServerIdentifierFromName(name),
421
+ };
422
+ }
423
+
424
+ if (fieldName === 'domain') {
425
+ return {
426
+ ...previous,
427
+ domain: value as CreateServerWizardState['domain'],
428
+ };
429
+ }
430
+
431
+ if (fieldName === 'iconUrl') {
405
432
  return {
406
433
  ...previous,
407
- name: value,
408
- identifier: createServerIdentifierFromName(value),
434
+ iconUrl: value as CreateServerWizardState['iconUrl'],
409
435
  };
410
436
  }
411
437
 
412
438
  return {
413
439
  ...previous,
414
- [fieldName]: value,
440
+ isDefaultAgentsInstalled: value as CreateServerWizardState['isDefaultAgentsInstalled'],
415
441
  };
416
442
  });
417
443
  }, []);
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
2
2
  import { spaceTrim } from 'spacetrim';
3
3
  import { DatabaseError } from '../../../../../../../src/errors/DatabaseError';
4
4
  import { isAgentsServerSqliteMode } from '../../../../database/agentsServerDatabaseMode';
5
+ import { seedDefaultAgents } from '../../../../database/seedDefaultAgents';
5
6
  import { resolveCurrentServerRegistryContext } from '../../../../utils/currentServerRegistryContext';
6
7
  import { isUserAdmin } from '../../../../utils/isUserAdmin';
7
8
  import { isUserGlobalAdmin } from '../../../../utils/isUserGlobalAdmin';
@@ -97,6 +98,7 @@ export async function POST(request: Request) {
97
98
 
98
99
  const body = withEnvironmentAdminUser((await request.json()) as CreateServerInput);
99
100
  if (isAgentsServerSqliteMode()) {
101
+ const isDefaultAgentsInstalled = body.isDefaultAgentsInstalled !== false;
100
102
  const normalizedDomain = normalizeServerDomain(body.domain);
101
103
  if (!normalizedDomain) {
102
104
  return NextResponse.json({ error: 'A valid domain is required.' }, { status: 400 });
@@ -114,6 +116,9 @@ export async function POST(request: Request) {
114
116
  iconUrl: body.iconUrl,
115
117
  });
116
118
  }
119
+ if (isDefaultAgentsInstalled) {
120
+ await seedDefaultAgents({ tablePrefix });
121
+ }
117
122
 
118
123
  return NextResponse.json(
119
124
  {
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import { keepUnused } from '../../../../../../src/utils/organization/keepUnused';
3
3
  import { $getTableName } from '../../../database/$getTableName';
4
4
  import { $provideSupabase } from '../../../database/$provideSupabase';
5
+ import { invalidateMetadataCache } from '../../../database/getMetadata';
5
6
  import { validateMetadataValue } from '../../../database/metadataDefaults';
6
7
  import { isUserAdmin } from '../../../utils/isUserAdmin';
7
8
 
@@ -121,6 +122,7 @@ export async function POST(request: NextRequest) {
121
122
  return NextResponse.json({ error: error.message }, { status: 500 });
122
123
  }
123
124
 
125
+ invalidateMetadataCache();
124
126
  return NextResponse.json(data);
125
127
  }
126
128
 
@@ -152,6 +154,7 @@ export async function PUT(request: NextRequest) {
152
154
  return NextResponse.json({ error: error.message }, { status: 500 });
153
155
  }
154
156
 
157
+ invalidateMetadataCache();
155
158
  return NextResponse.json(data);
156
159
  }
157
160
 
@@ -179,5 +182,6 @@ export async function DELETE(request: NextRequest) {
179
182
  return NextResponse.json({ error: error.message }, { status: 500 });
180
183
  }
181
184
 
185
+ invalidateMetadataCache();
182
186
  return NextResponse.json({ success: true });
183
187
  }
@@ -14,6 +14,13 @@ export const MAX_CUSTOM_JAVASCRIPT_LENGTH = 100_000;
14
14
  */
15
15
  const CUSTOM_JAVASCRIPT_TABLE_BASENAME = 'CustomJavascript';
16
16
 
17
+ /**
18
+ * Process-level cache lifetime for custom JavaScript rows.
19
+ *
20
+ * @private
21
+ */
22
+ const CUSTOM_JAVASCRIPT_CACHE_TTL_MS = 30_000;
23
+
17
24
  /**
18
25
  * Stored `CustomJavascript` row shape.
19
26
  *
@@ -90,6 +97,28 @@ type DynamicSupabaseClient = {
90
97
  from: (tableName: string) => DynamicCustomJavascriptTableQuery;
91
98
  };
92
99
 
100
+ /**
101
+ * Cached custom JavaScript rows keyed by the resolved table name.
102
+ *
103
+ * @private
104
+ */
105
+ const cachedCustomJavascriptByTableName = new Map<
106
+ string,
107
+ {
108
+ readonly loadedAt: number;
109
+ readonly rowsPromise: Promise<CustomJavascriptRow[]>;
110
+ }
111
+ >();
112
+
113
+ /**
114
+ * Clears process-level custom JavaScript cache after admin writes.
115
+ *
116
+ * @private
117
+ */
118
+ export function invalidateCustomJavascriptCache(): void {
119
+ cachedCustomJavascriptByTableName.clear();
120
+ }
121
+
93
122
  /**
94
123
  * Resolves the prefixed table name for `CustomJavascript`.
95
124
  *
@@ -137,8 +166,37 @@ function getCustomJavascriptClient() {
137
166
  */
138
167
  export async function getCustomJavascriptFiles(): Promise<CustomJavascriptRow[]> {
139
168
  const table = await getCustomJavascriptTableName();
140
- const supabase = getCustomJavascriptClient();
169
+ const cachedJavascript = cachedCustomJavascriptByTableName.get(table);
170
+ if (cachedJavascript && Date.now() - cachedJavascript.loadedAt < CUSTOM_JAVASCRIPT_CACHE_TTL_MS) {
171
+ return cachedJavascript.rowsPromise;
172
+ }
173
+
174
+ const rowsPromise = loadCustomJavascriptFilesFromDatabase(table);
175
+ cachedCustomJavascriptByTableName.set(table, {
176
+ loadedAt: Date.now(),
177
+ rowsPromise,
178
+ });
179
+
180
+ try {
181
+ return await rowsPromise;
182
+ } catch (error) {
183
+ if (cachedCustomJavascriptByTableName.get(table)?.rowsPromise === rowsPromise) {
184
+ cachedCustomJavascriptByTableName.delete(table);
185
+ }
186
+ throw error;
187
+ }
188
+ }
141
189
 
190
+ /**
191
+ * Reads custom JavaScript rows from the database.
192
+ *
193
+ * @param table - Resolved CustomJavascript table name.
194
+ * @returns Stored JavaScript rows.
195
+ *
196
+ * @private
197
+ */
198
+ async function loadCustomJavascriptFilesFromDatabase(table: string): Promise<CustomJavascriptRow[]> {
199
+ const supabase = getCustomJavascriptClient();
142
200
  const { data, error } = await supabase.from(table).select('*').order('scope', { ascending: true });
143
201
 
144
202
  if (error) {
@@ -234,6 +292,7 @@ export async function saveCustomJavascriptFile({
234
292
  throw new Error(`Failed to save custom JavaScript: ${error.message || String(error)}`);
235
293
  }
236
294
 
295
+ invalidateCustomJavascriptCache();
237
296
  return data as CustomJavascriptRow;
238
297
  }
239
298
 
@@ -255,4 +314,6 @@ export async function deleteCustomJavascriptFile(id: number): Promise<void> {
255
314
 
256
315
  throw new Error(`Failed to delete custom JavaScript: ${error.message || String(error)}`);
257
316
  }
317
+
318
+ invalidateCustomJavascriptCache();
258
319
  }
@@ -7,6 +7,11 @@ import { $provideSupabase } from './$provideSupabase';
7
7
  */
8
8
  const CUSTOM_STYLESHEET_TABLE_BASENAME = 'CustomStylesheet';
9
9
 
10
+ /**
11
+ * Process-level cache lifetime for custom stylesheet rows.
12
+ */
13
+ const CUSTOM_STYLESHEET_CACHE_TTL_MS = 30_000;
14
+
10
15
  /**
11
16
  * Stored CustomStylesheet row shape.
12
17
  *
@@ -82,6 +87,28 @@ type DynamicSupabaseClient = {
82
87
  from: (tableName: string) => DynamicCustomStylesheetTableQuery;
83
88
  };
84
89
 
90
+ /**
91
+ * Cached custom stylesheet rows keyed by the resolved table name.
92
+ *
93
+ * @private
94
+ */
95
+ const cachedCustomStylesheetsByTableName = new Map<
96
+ string,
97
+ {
98
+ readonly loadedAt: number;
99
+ readonly rowsPromise: Promise<CustomStylesheetRow[]>;
100
+ }
101
+ >();
102
+
103
+ /**
104
+ * Clears process-level stylesheet cache after admin writes.
105
+ *
106
+ * @public
107
+ */
108
+ export function invalidateCustomStylesheetCache(): void {
109
+ cachedCustomStylesheetsByTableName.clear();
110
+ }
111
+
85
112
  /**
86
113
  * Resolves the prefixed table name for CustomStylesheet.
87
114
  *
@@ -127,8 +154,37 @@ function getCustomStylesheetClient(): DynamicSupabaseClient {
127
154
  */
128
155
  export async function listCustomStylesheets(): Promise<CustomStylesheetRow[]> {
129
156
  const table = await getCustomStylesheetTableName();
130
- const supabase = getCustomStylesheetClient();
157
+ const cachedStylesheets = cachedCustomStylesheetsByTableName.get(table);
158
+ if (cachedStylesheets && Date.now() - cachedStylesheets.loadedAt < CUSTOM_STYLESHEET_CACHE_TTL_MS) {
159
+ return cachedStylesheets.rowsPromise;
160
+ }
161
+
162
+ const rowsPromise = loadCustomStylesheetsFromDatabase(table);
163
+ cachedCustomStylesheetsByTableName.set(table, {
164
+ loadedAt: Date.now(),
165
+ rowsPromise,
166
+ });
167
+
168
+ try {
169
+ return await rowsPromise;
170
+ } catch (error) {
171
+ if (cachedCustomStylesheetsByTableName.get(table)?.rowsPromise === rowsPromise) {
172
+ cachedCustomStylesheetsByTableName.delete(table);
173
+ }
174
+ throw error;
175
+ }
176
+ }
131
177
 
178
+ /**
179
+ * Reads custom stylesheet rows from the database.
180
+ *
181
+ * @param table - Resolved CustomStylesheet table name.
182
+ * @returns Stored stylesheet rows.
183
+ *
184
+ * @private
185
+ */
186
+ async function loadCustomStylesheetsFromDatabase(table: string): Promise<CustomStylesheetRow[]> {
187
+ const supabase = getCustomStylesheetClient();
132
188
  const { data, error } = await supabase.from(table).select('*').order('createdAt', { ascending: true });
133
189
 
134
190
  if (error) {
@@ -206,6 +262,7 @@ export async function saveCustomStylesheetFile({
206
262
  throw new Error(`Failed to save custom stylesheet: ${error.message || String(error)}`);
207
263
  }
208
264
 
265
+ invalidateCustomStylesheetCache();
209
266
  return data as CustomStylesheetRow;
210
267
  }
211
268
 
@@ -229,4 +286,6 @@ export async function deleteCustomStylesheetFile(id: number): Promise<void> {
229
286
 
230
287
  throw new Error(`Failed to delete custom stylesheet: ${error.message || String(error)}`);
231
288
  }
289
+
290
+ invalidateCustomStylesheetCache();
232
291
  }
@@ -10,6 +10,45 @@ import { cache } from 'react';
10
10
  */
11
11
  const metadataDefaultsMap = new Map<string, string>(metadataDefaults.map((metadata) => [metadata.key, metadata.value]));
12
12
 
13
+ /**
14
+ * Process-level cache lifetime for metadata reads.
15
+ *
16
+ * @private Internal helper for metadata lookups in `apps/agents-server`.
17
+ */
18
+ const METADATA_CACHE_TTL_MS = 30_000;
19
+
20
+ /**
21
+ * Cached metadata table payload keyed by the resolved table name.
22
+ *
23
+ * @private Internal helper for metadata lookups in `apps/agents-server`.
24
+ */
25
+ const cachedMetadataValuesByTableName = new Map<
26
+ string,
27
+ {
28
+ readonly loadedAt: number;
29
+ readonly valuesPromise: Promise<Map<string, string | null>>;
30
+ }
31
+ >();
32
+
33
+ /**
34
+ * Metadata row shape loaded by the lightweight metadata select.
35
+ *
36
+ * @private Internal helper for metadata lookups in `apps/agents-server`.
37
+ */
38
+ type MetadataValueRow = {
39
+ readonly key: string;
40
+ readonly value: string | null;
41
+ };
42
+
43
+ /**
44
+ * Clears process-level metadata cache after admin metadata writes.
45
+ *
46
+ * @public exported from `apps/agents-server`
47
+ */
48
+ export function invalidateMetadataCache(): void {
49
+ cachedMetadataValuesByTableName.clear();
50
+ }
51
+
13
52
  /**
14
53
  * Loads the full metadata table once per request so callers can cheaply project subsets.
15
54
  *
@@ -18,17 +57,59 @@ const metadataDefaultsMap = new Map<string, string>(metadataDefaults.map((metada
18
57
  * @private Internal helper for batched metadata lookups in `apps/agents-server`.
19
58
  */
20
59
  const loadAllMetadataValues = cache(async (): Promise<Map<string, string | null>> => {
21
- const supabase = $provideSupabase();
22
60
  const table = await $getTableName('Metadata');
61
+ return loadCachedMetadataValues(table);
62
+ });
63
+
64
+ /**
65
+ * Loads metadata values using a short process-level cache shared by consecutive requests.
66
+ *
67
+ * @param table - Resolved metadata table name.
68
+ * @returns Metadata values keyed by metadata key.
69
+ *
70
+ * @private Internal helper for metadata lookups in `apps/agents-server`.
71
+ */
72
+ async function loadCachedMetadataValues(table: string): Promise<Map<string, string | null>> {
73
+ const cachedMetadataValues = cachedMetadataValuesByTableName.get(table);
74
+ if (cachedMetadataValues && Date.now() - cachedMetadataValues.loadedAt < METADATA_CACHE_TTL_MS) {
75
+ return cachedMetadataValues.valuesPromise;
76
+ }
77
+
78
+ const valuesPromise = loadMetadataValuesFromDatabase(table);
79
+ cachedMetadataValuesByTableName.set(table, {
80
+ loadedAt: Date.now(),
81
+ valuesPromise,
82
+ });
83
+
84
+ try {
85
+ return await valuesPromise;
86
+ } catch (error) {
87
+ if (cachedMetadataValuesByTableName.get(table)?.valuesPromise === valuesPromise) {
88
+ cachedMetadataValuesByTableName.delete(table);
89
+ }
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Reads all persisted metadata values from the database.
96
+ *
97
+ * @param table - Resolved metadata table name.
98
+ * @returns Metadata values keyed by metadata key.
99
+ *
100
+ * @private Internal helper for metadata lookups in `apps/agents-server`.
101
+ */
102
+ async function loadMetadataValuesFromDatabase(table: string): Promise<Map<string, string | null>> {
103
+ const supabase = $provideSupabase();
23
104
  const { data } = await supabase.from(table).select('key, value');
24
105
 
25
106
  const loadedMap = new Map<string, string | null>();
26
- for (const row of data ?? []) {
107
+ for (const row of (data ?? []) as Array<MetadataValueRow>) {
27
108
  loadedMap.set(row.key, row.value);
28
109
  }
29
110
 
30
111
  return loadedMap;
31
- });
112
+ }
32
113
 
33
114
  /**
34
115
  * Get metadata value by key
@@ -10,6 +10,9 @@ export async function register(): Promise<void> {
10
10
  }
11
11
 
12
12
  try {
13
+ const { registerServerErrorSentryLogging } = await import('./utils/errorReporting/registerServerErrorSentryLogging');
14
+ registerServerErrorSentryLogging();
15
+
13
16
  const { registerNodeRuntimeInstrumentation } = await import('./instrumentation-node');
14
17
  await registerNodeRuntimeInstrumentation();
15
18
  } catch (error) {