@promptbook/cli 0.112.0-97 → 0.112.0-99

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 (60) hide show
  1. package/apps/agents-server/README.md +3 -3
  2. package/apps/agents-server/src/app/admin/cli-access/CliAccessClient.tsx +99 -0
  3. package/apps/agents-server/src/app/admin/cli-access/page.tsx +14 -0
  4. package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +76 -325
  5. package/apps/agents-server/src/app/admin/database/page.tsx +1 -2
  6. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +24 -0
  7. package/apps/agents-server/src/app/api/admin/cli-access/route.ts +137 -0
  8. package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +7 -64
  9. package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +3 -3
  10. package/apps/agents-server/src/app/api/admin/servers/route.ts +4 -4
  11. package/apps/agents-server/src/app/api/chat/export/pdf/route.ts +63 -0
  12. package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +279 -0
  13. package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +336 -0
  14. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +4 -0
  15. package/apps/agents-server/src/database/$provideClientSql.ts +17 -4
  16. package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +24 -3
  17. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -11
  18. package/apps/agents-server/src/database/agentsServerDatabaseMode.ts +1 -20
  19. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -0
  20. package/apps/agents-server/src/languages/translations/czech.yaml +1 -0
  21. package/apps/agents-server/src/languages/translations/english.yaml +1 -0
  22. package/apps/agents-server/src/tools/$provideServer.ts +2 -2
  23. package/apps/agents-server/src/tools/BrowserConnectionProvider.ts +1 -1
  24. package/apps/agents-server/src/utils/chatExport/downloadChatPdfFromServer.ts +59 -0
  25. package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +37 -0
  26. package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +77 -237
  27. package/apps/agents-server/src/utils/createInteractiveTerminalEventStream.ts +84 -0
  28. package/apps/agents-server/src/utils/interactiveTerminalSession.ts +442 -0
  29. package/apps/agents-server/src/utils/serverCliAccess.ts +221 -0
  30. package/apps/agents-server/src/utils/serverRegistry.ts +4 -4
  31. package/apps/agents-server/src/utils/vpsConfiguration.ts +2 -0
  32. package/esm/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
  33. package/esm/index.es.js +2 -2
  34. package/esm/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  35. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  36. package/esm/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  37. package/esm/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  38. package/esm/src/version.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/src/book-components/Chat/Chat/Chat.tsx +2 -0
  41. package/src/book-components/Chat/Chat/ChatActionsBar.tsx +17 -9
  42. package/src/book-components/Chat/Chat/ChatProps.tsx +7 -0
  43. package/src/book-components/Chat/save/_common/ChatSaveFormatHandler.ts +40 -0
  44. package/src/book-components/Chat/save/_common/createChatExportFilename.ts +20 -0
  45. package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +1 -1
  46. package/src/other/templates/getTemplatesPipelineCollection.ts +706 -736
  47. package/src/version.ts +2 -2
  48. package/src/versions.txt +2 -0
  49. package/umd/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
  50. package/umd/index.umd.js +2 -2
  51. package/umd/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  52. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  53. package/umd/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  54. package/umd/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  55. package/umd/src/version.d.ts +1 -1
  56. package/apps/agents-server/src/database/$providePostgresPool.ts +0 -27
  57. package/apps/agents-server/src/database/postgres/$provideLocalPostgresSupabase.ts +0 -1261
  58. package/src/conversion/validation/_importPipeline.ts +0 -88
  59. /package/esm/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
  60. /package/umd/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
@@ -0,0 +1,336 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useState } from 'react';
4
+
5
+ /**
6
+ * Minimal terminal-session shape used by the shared admin terminal hook.
7
+ */
8
+ export type AdminTerminalSession = {
9
+ /**
10
+ * Session identifier.
11
+ */
12
+ readonly id: string;
13
+
14
+ /**
15
+ * Whether the terminal process is still running.
16
+ */
17
+ readonly isRunning: boolean;
18
+
19
+ /**
20
+ * Buffered terminal output.
21
+ */
22
+ readonly output: string;
23
+
24
+ /**
25
+ * ISO timestamp when the session started.
26
+ */
27
+ readonly startedAt: string;
28
+
29
+ /**
30
+ * ISO timestamp when the session finished.
31
+ */
32
+ readonly finishedAt: string | null;
33
+
34
+ /**
35
+ * Exit code when available.
36
+ */
37
+ readonly exitCode: number | null;
38
+
39
+ /**
40
+ * Exit signal when available.
41
+ */
42
+ readonly signal: string | null;
43
+ };
44
+
45
+ /**
46
+ * Shared API response shape used by the admin terminal routes.
47
+ */
48
+ type AdminTerminalSessionResponse<TSession extends AdminTerminalSession> = {
49
+ /**
50
+ * Loaded or updated session snapshot.
51
+ */
52
+ readonly session: TSession | null;
53
+
54
+ /**
55
+ * API error, when the request failed.
56
+ */
57
+ readonly error?: string;
58
+ };
59
+
60
+ /**
61
+ * Options accepted by the shared admin terminal hook.
62
+ */
63
+ type UseAdminTerminalSessionOptions = {
64
+ /**
65
+ * Base API route used for GET/POST/PATCH/DELETE requests.
66
+ */
67
+ readonly basePath: string;
68
+
69
+ /**
70
+ * Fallback error message used when loading the latest session fails.
71
+ */
72
+ readonly loadErrorMessage: string;
73
+
74
+ /**
75
+ * Fallback error message used when starting a new session fails.
76
+ */
77
+ readonly startErrorMessage: string;
78
+
79
+ /**
80
+ * Fallback error message used when sending input fails.
81
+ */
82
+ readonly sendErrorMessage: string;
83
+
84
+ /**
85
+ * Fallback error message used when stopping a session fails.
86
+ */
87
+ readonly stopErrorMessage: string;
88
+
89
+ /**
90
+ * Success message shown after starting a new session.
91
+ */
92
+ readonly startSuccessMessage: string;
93
+
94
+ /**
95
+ * Success message shown when the process exits with code `0`.
96
+ */
97
+ readonly finishSuccessMessage: string;
98
+
99
+ /**
100
+ * Error message shown when the process exits with a non-zero code.
101
+ */
102
+ readonly finishErrorMessage: string;
103
+ };
104
+
105
+ /**
106
+ * Shared browser hook for admin pages that start, stream, write to, and stop one terminal session.
107
+ *
108
+ * @param options - API endpoints and user-facing messages for the terminal.
109
+ * @returns Session state, status messages, and imperative helpers.
110
+ */
111
+ export function useAdminTerminalSession<TSession extends AdminTerminalSession>(
112
+ options: UseAdminTerminalSessionOptions,
113
+ ) {
114
+ const [session, setSession] = useState<TSession | null>(null);
115
+ const [input, setInput] = useState('');
116
+ const [isLoadingSession, setIsLoadingSession] = useState(true);
117
+ const [isStarting, setIsStarting] = useState(false);
118
+ const [isSending, setIsSending] = useState(false);
119
+ const [isStopping, setIsStopping] = useState(false);
120
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
121
+ const [successMessage, setSuccessMessage] = useState<string | null>(null);
122
+
123
+ /**
124
+ * Loads the latest session snapshot for the current terminal.
125
+ */
126
+ const loadSession = useCallback(async (): Promise<void> => {
127
+ try {
128
+ setIsLoadingSession(true);
129
+ setErrorMessage(null);
130
+
131
+ const response = await fetch(options.basePath, { cache: 'no-store' });
132
+ const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
133
+
134
+ if (!response.ok) {
135
+ throw new Error(payload.error || options.loadErrorMessage);
136
+ }
137
+
138
+ setSession(payload.session);
139
+ } catch (error) {
140
+ setErrorMessage(error instanceof Error ? error.message : options.loadErrorMessage);
141
+ } finally {
142
+ setIsLoadingSession(false);
143
+ }
144
+ }, [options.basePath, options.loadErrorMessage]);
145
+
146
+ useEffect(() => {
147
+ void loadSession();
148
+ }, [loadSession]);
149
+
150
+ useEffect(() => {
151
+ const sessionId = session?.id;
152
+ if (!sessionId) {
153
+ return;
154
+ }
155
+
156
+ const eventSource = new EventSource(`${options.basePath}?sessionId=${encodeURIComponent(sessionId)}&stream=1`);
157
+
158
+ const handleSnapshot = (event: MessageEvent<string>) => {
159
+ const payload = JSON.parse(event.data) as TSession;
160
+ setSession(payload);
161
+ };
162
+ const handleOutput = (event: MessageEvent<string>) => {
163
+ const payload = JSON.parse(event.data) as { readonly chunk: string };
164
+ setSession((currentSession) => {
165
+ if (!currentSession || currentSession.id !== sessionId) {
166
+ return currentSession;
167
+ }
168
+
169
+ return {
170
+ ...currentSession,
171
+ output: currentSession.output + payload.chunk,
172
+ };
173
+ });
174
+ };
175
+ const handleExit = (event: MessageEvent<string>) => {
176
+ const payload = JSON.parse(event.data) as TSession;
177
+ setSession(payload);
178
+ setIsStarting(false);
179
+ setIsSending(false);
180
+ setIsStopping(false);
181
+
182
+ if (payload.exitCode === 0) {
183
+ setSuccessMessage(options.finishSuccessMessage);
184
+ setErrorMessage(null);
185
+ } else {
186
+ setSuccessMessage(null);
187
+ setErrorMessage(options.finishErrorMessage);
188
+ }
189
+
190
+ eventSource.close();
191
+ };
192
+
193
+ eventSource.addEventListener('snapshot', handleSnapshot as EventListener);
194
+ eventSource.addEventListener('output', handleOutput as EventListener);
195
+ eventSource.addEventListener('exit', handleExit as EventListener);
196
+ eventSource.onerror = () => {
197
+ eventSource.close();
198
+ };
199
+
200
+ return () => {
201
+ eventSource.close();
202
+ };
203
+ }, [options.basePath, options.finishErrorMessage, options.finishSuccessMessage, session?.id]);
204
+
205
+ /**
206
+ * Starts or reconnects to the managed terminal session.
207
+ */
208
+ const startSession = useCallback(async (): Promise<void> => {
209
+ try {
210
+ setIsStarting(true);
211
+ setErrorMessage(null);
212
+ setSuccessMessage(null);
213
+
214
+ const response = await fetch(options.basePath, { method: 'POST' });
215
+ const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
216
+
217
+ if (!response.ok || !payload.session) {
218
+ throw new Error(payload.error || options.startErrorMessage);
219
+ }
220
+
221
+ setSession(payload.session);
222
+ setSuccessMessage(options.startSuccessMessage);
223
+ } catch (error) {
224
+ setErrorMessage(error instanceof Error ? error.message : options.startErrorMessage);
225
+ } finally {
226
+ setIsStarting(false);
227
+ }
228
+ }, [options.basePath, options.startErrorMessage, options.startSuccessMessage]);
229
+
230
+ /**
231
+ * Sends one raw input chunk to the running terminal session.
232
+ *
233
+ * @param nextInput - Raw text or control characters to write.
234
+ */
235
+ const sendInput = useCallback(
236
+ async (nextInput: string): Promise<void> => {
237
+ if (!session) {
238
+ return;
239
+ }
240
+
241
+ try {
242
+ setIsSending(true);
243
+ setErrorMessage(null);
244
+
245
+ const response = await fetch(options.basePath, {
246
+ method: 'PATCH',
247
+ headers: {
248
+ 'Content-Type': 'application/json',
249
+ },
250
+ body: JSON.stringify({
251
+ sessionId: session.id,
252
+ input: nextInput,
253
+ }),
254
+ });
255
+ const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
256
+
257
+ if (!response.ok) {
258
+ throw new Error(payload.error || options.sendErrorMessage);
259
+ }
260
+
261
+ if (payload.session) {
262
+ setSession(payload.session);
263
+ }
264
+ } catch (error) {
265
+ setErrorMessage(error instanceof Error ? error.message : options.sendErrorMessage);
266
+ } finally {
267
+ setIsSending(false);
268
+ }
269
+ },
270
+ [options.basePath, options.sendErrorMessage, session],
271
+ );
272
+
273
+ /**
274
+ * Stops the active terminal session.
275
+ */
276
+ const stopSession = useCallback(async (): Promise<void> => {
277
+ if (!session) {
278
+ return;
279
+ }
280
+
281
+ try {
282
+ setIsStopping(true);
283
+ setErrorMessage(null);
284
+
285
+ const response = await fetch(options.basePath, {
286
+ method: 'DELETE',
287
+ headers: {
288
+ 'Content-Type': 'application/json',
289
+ },
290
+ body: JSON.stringify({
291
+ sessionId: session.id,
292
+ }),
293
+ });
294
+ const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
295
+
296
+ if (!response.ok) {
297
+ throw new Error(payload.error || options.stopErrorMessage);
298
+ }
299
+
300
+ if (payload.session) {
301
+ setSession(payload.session);
302
+ }
303
+ } catch (error) {
304
+ setErrorMessage(error instanceof Error ? error.message : options.stopErrorMessage);
305
+ } finally {
306
+ setIsStopping(false);
307
+ }
308
+ }, [options.basePath, options.stopErrorMessage, session]);
309
+
310
+ /**
311
+ * Clears any terminal status messages shown above the panel.
312
+ */
313
+ const clearMessages = useCallback((): void => {
314
+ setErrorMessage(null);
315
+ setSuccessMessage(null);
316
+ }, []);
317
+
318
+ return {
319
+ session,
320
+ input,
321
+ setInput,
322
+ isLoadingSession,
323
+ isStarting,
324
+ isSending,
325
+ isStopping,
326
+ errorMessage,
327
+ successMessage,
328
+ setErrorMessage,
329
+ setSuccessMessage,
330
+ clearMessages,
331
+ loadSession,
332
+ startSession,
333
+ sendInput,
334
+ stopSession,
335
+ };
336
+ }
@@ -220,6 +220,10 @@ export function buildHeaderSystemMenuItems({
220
220
  label: translate('header.codeRunners'),
221
221
  href: '/admin/code-runners',
222
222
  } as SubMenuItem,
223
+ {
224
+ label: translate('header.cliAccess'),
225
+ href: '/admin/cli-access',
226
+ } as SubMenuItem,
223
227
  ]
224
228
  : []),
225
229
  ];
@@ -1,5 +1,6 @@
1
1
  import { $isRunningInNode } from '@promptbook-local/utils';
2
- import { $providePostgresPool } from './$providePostgresPool';
2
+ import { Pool } from 'pg';
3
+ import { resolvePostgresConnectionString } from './resolvePostgresConnectionString';
3
4
 
4
5
  /**
5
6
  * SQL tagged-template executor used by server routes and utilities.
@@ -30,6 +31,13 @@ export type ClientSqlExecutor = ClientSql & {
30
31
  readonly raw: ClientSqlRaw;
31
32
  };
32
33
 
34
+ /**
35
+ * Shared PostgreSQL pool reused across all requests in the server process.
36
+ *
37
+ * @private internal singleton of Agents Server database layer
38
+ */
39
+ let clientPool: Pool | undefined;
40
+
33
41
  /**
34
42
  * Provides SQL tagged-template client for server-side PostgreSQL access.
35
43
  *
@@ -40,7 +48,12 @@ export async function $provideClientSql(): Promise<ClientSqlExecutor> {
40
48
  throw new Error('Function `$provideClientSql` can only be used in Node.js runtime.');
41
49
  }
42
50
 
43
- const clientPool = $providePostgresPool();
51
+ if (!clientPool) {
52
+ clientPool = new Pool({
53
+ connectionString: resolvePostgresConnectionString(),
54
+ ssl: { rejectUnauthorized: false },
55
+ });
56
+ }
44
57
 
45
58
  const executeTemplate = async <TRow = Array<Record<string, unknown>>>(
46
59
  templateStrings: TemplateStringsArray,
@@ -55,7 +68,7 @@ export async function $provideClientSql(): Promise<ClientSqlExecutor> {
55
68
  }
56
69
 
57
70
  const text = textChunks.join('');
58
- const result = await clientPool.query(text, [...templateValues]);
71
+ const result = await clientPool!.query(text, [...templateValues]);
59
72
  return result.rows as TRow;
60
73
  };
61
74
 
@@ -63,7 +76,7 @@ export async function $provideClientSql(): Promise<ClientSqlExecutor> {
63
76
  text: string,
64
77
  values: ReadonlyArray<unknown> = [],
65
78
  ): Promise<TRow> => {
66
- const result = await clientPool.query(text, [...values]);
79
+ const result = await clientPool!.query(text, [...values]);
67
80
  return result.rows as TRow;
68
81
  };
69
82
 
@@ -1,7 +1,7 @@
1
1
  import type { StudioBFFSqlLintDetails, StudioBFFSqlLintResult } from '@prisma/studio-core/data/bff';
2
- import type { Pool, PoolClient } from 'pg';
3
- import { $providePostgresPool } from './$providePostgresPool';
2
+ import { Pool, type PoolClient } from 'pg';
4
3
  import { isAgentsServerSqliteMode } from './agentsServerDatabaseMode';
4
+ import { resolvePostgresConnectionString } from './resolvePostgresConnectionString';
5
5
  import {
6
6
  $provideAgentsServerSqliteDatabase,
7
7
  type AgentsServerSqliteDatabase,
@@ -31,6 +31,11 @@ export type DatabaseAdminExecutor = {
31
31
  readonly lintSql: (details: StudioBFFSqlLintDetails) => Promise<StudioBFFSqlLintResult>;
32
32
  };
33
33
 
34
+ /**
35
+ * Shared PostgreSQL pool for Embedded Prisma Studio requests.
36
+ */
37
+ let databaseAdminPostgresPool: Pool | null = null;
38
+
34
39
  /**
35
40
  * Provides a raw SQL executor for the configured Agents Server database backend.
36
41
  *
@@ -48,7 +53,7 @@ export function $provideDatabaseAdminExecutor(): DatabaseAdminExecutor {
48
53
  * @returns PostgreSQL-backed database admin executor.
49
54
  */
50
55
  function $providePostgresDatabaseAdminExecutor(): DatabaseAdminExecutor {
51
- const pool = $providePostgresPool();
56
+ const pool = $provideDatabaseAdminPostgresPool();
52
57
 
53
58
  return {
54
59
  execute: (query) => executePostgresDatabaseAdminQuery(pool, query),
@@ -77,6 +82,22 @@ function $provideSqliteDatabaseAdminExecutor(): DatabaseAdminExecutor {
77
82
  };
78
83
  }
79
84
 
85
+ /**
86
+ * Provides the shared PostgreSQL connection pool for raw database admin access.
87
+ *
88
+ * @returns Shared PostgreSQL pool.
89
+ */
90
+ function $provideDatabaseAdminPostgresPool(): Pool {
91
+ if (!databaseAdminPostgresPool) {
92
+ databaseAdminPostgresPool = new Pool({
93
+ connectionString: resolvePostgresConnectionString(),
94
+ ssl: { rejectUnauthorized: false },
95
+ });
96
+ }
97
+
98
+ return databaseAdminPostgresPool;
99
+ }
100
+
80
101
  /**
81
102
  * Executes one PostgreSQL query for Embedded Prisma Studio.
82
103
  *
@@ -1,12 +1,9 @@
1
- import { createRequire } from 'module';
2
1
  import { $isRunningInNode } from '@promptbook-local/utils';
3
2
  import { createClient, SupabaseClient } from '@supabase/supabase-js';
4
- import { isAgentsServerPostgresMode, isAgentsServerSqliteMode } from './agentsServerDatabaseMode';
3
+ import { isAgentsServerSqliteMode } from './agentsServerDatabaseMode';
5
4
  import { $provideLocalSqliteSupabase } from './sqlite/$provideLocalSqliteSupabase';
6
5
  import { AgentsServerDatabase } from './schema';
7
6
 
8
- const requirePostgresSupabase = createRequire(__filename);
9
-
10
7
  /**
11
8
  * Internal cache for `$provideSupabaseForServer`
12
9
  *
@@ -35,13 +32,6 @@ export function $provideSupabaseForServer(): SupabaseClient<AgentsServerDatabase
35
32
  return $provideLocalSqliteSupabase() as SupabaseClient<AgentsServerDatabase>;
36
33
  }
37
34
 
38
- if (isAgentsServerPostgresMode()) {
39
- const { $provideLocalPostgresSupabase } = requirePostgresSupabase(
40
- './postgres/$provideLocalPostgresSupabase',
41
- ) as typeof import('./postgres/$provideLocalPostgresSupabase');
42
- return $provideLocalPostgresSupabase() as SupabaseClient<AgentsServerDatabase>;
43
- }
44
-
45
35
  if (!supabase) {
46
36
  // Create a single supabase client for interacting with your database
47
37
  supabase = createClient<AgentsServerDatabase>(
@@ -11,7 +11,7 @@ export const AGENTS_SERVER_SQLITE_PATH_ENV_NAME = 'PTBK_AGENTS_SERVER_SQLITE_PAT
11
11
  /**
12
12
  * Supported Agents Server database backends.
13
13
  */
14
- export type AgentsServerDatabaseMode = 'supabase' | 'sqlite' | 'postgres';
14
+ export type AgentsServerDatabaseMode = 'supabase' | 'sqlite';
15
15
 
16
16
  /**
17
17
  * Resolves the configured Agents Server database backend.
@@ -23,10 +23,6 @@ export function resolveAgentsServerDatabaseMode(): AgentsServerDatabaseMode {
23
23
  return 'sqlite';
24
24
  }
25
25
 
26
- if (rawMode === 'postgres' || rawMode === 'postgresql') {
27
- return 'postgres';
28
- }
29
-
30
26
  return 'supabase';
31
27
  }
32
28
 
@@ -36,18 +32,3 @@ export function resolveAgentsServerDatabaseMode(): AgentsServerDatabaseMode {
36
32
  export function isAgentsServerSqliteMode(): boolean {
37
33
  return resolveAgentsServerDatabaseMode() === 'sqlite';
38
34
  }
39
-
40
- /**
41
- * Returns whether the Agents Server is using the standalone PostgreSQL backend.
42
- */
43
- export function isAgentsServerPostgresMode(): boolean {
44
- return resolveAgentsServerDatabaseMode() === 'postgres';
45
- }
46
-
47
- /**
48
- * Returns whether the Agents Server is using a standalone local database backend.
49
- */
50
- export function isAgentsServerStandaloneMode(): boolean {
51
- const databaseMode = resolveAgentsServerDatabaseMode();
52
- return databaseMode === 'sqlite' || databaseMode === 'postgres';
53
- }
@@ -146,6 +146,7 @@ export const SERVER_TRANSLATION_KEYS = [
146
146
  'header.database',
147
147
  'header.logs',
148
148
  'header.codeRunners',
149
+ 'header.cliAccess',
149
150
  'header.models',
150
151
  'header.openApiDocumentation',
151
152
  'header.apiTokens',
@@ -140,6 +140,7 @@ header.update: Aktualizace
140
140
  header.database: Databáze
141
141
  header.logs: Logy
142
142
  header.codeRunners: Code runnery
143
+ header.cliAccess: CLI přístup
143
144
  header.models: Modely
144
145
  header.openApiDocumentation: Dokumentace OpenAPI
145
146
  header.apiTokens: API tokeny
@@ -142,6 +142,7 @@ header.update: Update
142
142
  header.database: Database
143
143
  header.logs: Logs
144
144
  header.codeRunners: Code runners
145
+ header.cliAccess: CLI Access
145
146
  header.models: Models
146
147
  header.openApiDocumentation: OpenAPI Documentation
147
148
  header.apiTokens: API Tokens
@@ -1,7 +1,7 @@
1
1
  import { NEXT_PUBLIC_SITE_URL, SUPABASE_TABLE_PREFIX } from '@/config';
2
2
  import { headers } from 'next/headers';
3
3
  import { cache } from 'react';
4
- import { isAgentsServerStandaloneMode } from '../database/agentsServerDatabaseMode';
4
+ import { isAgentsServerSqliteMode } from '../database/agentsServerDatabaseMode';
5
5
  import { resolveInternalServerOrigin } from '../utils/resolveInternalServerOrigin';
6
6
  import { createServerPublicUrl, listRegisteredServersUsingServiceRole } from '../utils/serverRegistry';
7
7
  import { resolveServerSelection } from '../utils/serverSelection';
@@ -37,7 +37,7 @@ const getCachedProvidedServer = cache(async (): Promise<ProvidedServer> => {
37
37
  const requestHost = headersList.get('host');
38
38
  const xPromptbookServer = headersList.get('x-promptbook-server');
39
39
 
40
- if (isAgentsServerStandaloneMode()) {
40
+ if (isAgentsServerSqliteMode()) {
41
41
  if (isLocalDevelopmentHost(requestHost)) {
42
42
  return {
43
43
  id: null,
@@ -410,7 +410,7 @@ export class BrowserConnectionProvider {
410
410
  await mkdir(userDataDir, { recursive: true });
411
411
 
412
412
  const launchOptions: NonNullable<Parameters<typeof chromium.launchPersistentContext>[1]> = {
413
- headless: false,
413
+ headless: true,
414
414
  args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
415
415
  };
416
416
 
@@ -0,0 +1,59 @@
1
+ import type { ChatMessage } from '../../../../../src/book-components/Chat/types/ChatMessage';
2
+ import type { ChatParticipant } from '../../../../../src/book-components/Chat/types/ChatParticipant';
3
+ import { downloadBlob, parseFilenameFromContentDisposition } from '../download/browserFileDownload';
4
+
5
+ /**
6
+ * Payload sent to the Agents Server PDF export endpoint.
7
+ *
8
+ * @private internal type for `downloadChatPdfFromServer`
9
+ */
10
+ type DownloadChatPdfFromServerOptions = {
11
+ readonly title: string;
12
+ readonly messages: ReadonlyArray<ChatMessage>;
13
+ readonly participants: ReadonlyArray<ChatParticipant>;
14
+ };
15
+
16
+ /**
17
+ * Requests a server-rendered chat PDF and triggers the browser download.
18
+ *
19
+ * @param options - Chat export payload.
20
+ */
21
+ export async function downloadChatPdfFromServer(options: DownloadChatPdfFromServerOptions): Promise<void> {
22
+ const response = await fetch('/api/chat/export/pdf', {
23
+ method: 'POST',
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ },
27
+ body: JSON.stringify({
28
+ title: options.title,
29
+ messages: options.messages,
30
+ participants: options.participants.map(serializeChatParticipantForExport),
31
+ }),
32
+ });
33
+
34
+ if (!response.ok) {
35
+ const payload = (await response.json().catch(() => null)) as { error?: string } | null;
36
+ throw new Error(payload?.error || 'Failed to export chat as PDF.');
37
+ }
38
+
39
+ const pdfBlob = await response.blob();
40
+ const filename =
41
+ parseFilenameFromContentDisposition(response.headers.get('Content-Disposition')) || 'chat-export.pdf';
42
+
43
+ downloadBlob(pdfBlob, filename);
44
+ }
45
+
46
+ /**
47
+ * Normalizes participant data so the payload stays JSON-safe when custom color helpers are used.
48
+ *
49
+ * @param participant - One chat participant.
50
+ * @returns Serializable participant payload.
51
+ *
52
+ * @private helper for `downloadChatPdfFromServer`
53
+ */
54
+ function serializeChatParticipantForExport(participant: ChatParticipant): ChatParticipant {
55
+ return {
56
+ ...participant,
57
+ color: participant.color ? String(participant.color) : undefined,
58
+ };
59
+ }
@@ -0,0 +1,37 @@
1
+ import { $provideBrowserForServer } from '@/src/tools/$provideBrowserForServer';
2
+
3
+ /**
4
+ * Browser viewport used while rendering standalone HTML exports to PDF.
5
+ */
6
+ const CHAT_EXPORT_PDF_VIEWPORT = {
7
+ width: 1280,
8
+ height: 1600,
9
+ } as const;
10
+
11
+ /**
12
+ * Prints standalone HTML into a PDF using the shared server-side Chromium instance.
13
+ *
14
+ * @param html - Fully rendered standalone HTML document.
15
+ * @returns PDF bytes ready for an HTTP response.
16
+ */
17
+ export async function renderHtmlToPdfOnServer(html: string): Promise<Buffer> {
18
+ const browserContext = await $provideBrowserForServer();
19
+ const page = await browserContext.newPage();
20
+
21
+ try {
22
+ await page.setViewportSize(CHAT_EXPORT_PDF_VIEWPORT);
23
+ await page.emulateMedia({ media: 'print' });
24
+ await page.setContent(html, { waitUntil: 'load' });
25
+ await page.evaluate(async () => {
26
+ await document.fonts?.ready;
27
+ });
28
+
29
+ return await page.pdf({
30
+ format: 'Letter',
31
+ printBackground: true,
32
+ preferCSSPageSize: true,
33
+ });
34
+ } finally {
35
+ await page.close();
36
+ }
37
+ }