@promptbook/cli 0.112.0-95 → 0.112.0-97

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 (70) hide show
  1. package/apps/agents-server/README.md +3 -3
  2. package/apps/agents-server/next.config.ts +8 -1
  3. package/apps/agents-server/playwright.config.ts +4 -1
  4. package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +358 -19
  5. package/apps/agents-server/src/app/admin/database/DatabaseAdminClient.tsx +38 -0
  6. package/apps/agents-server/src/app/admin/database/DatabaseAdminStudioSurface.tsx +42 -0
  7. package/apps/agents-server/src/app/admin/database/page.tsx +34 -0
  8. package/apps/agents-server/src/app/admin/servers/CreateServerDialog.tsx +46 -505
  9. package/apps/agents-server/src/app/admin/servers/ServersClient.tsx +23 -11
  10. package/apps/agents-server/src/app/admin/servers/ServersRegistryApi.ts +5 -0
  11. package/apps/agents-server/src/app/admin/servers/ServersRegistryDnsTypes.ts +87 -0
  12. package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +258 -128
  13. package/apps/agents-server/src/app/admin/servers/useCreateServerWizard.ts +46 -334
  14. package/apps/agents-server/src/app/admin/servers/useServersRegistryState.ts +26 -2
  15. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +435 -0
  16. package/apps/agents-server/src/app/admin/update/page.tsx +14 -0
  17. package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +197 -0
  18. package/apps/agents-server/src/app/api/admin/code-runners/route.ts +4 -35
  19. package/apps/agents-server/src/app/api/admin/database/studio/route.ts +113 -0
  20. package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +10 -5
  21. package/apps/agents-server/src/app/api/admin/servers/route.ts +97 -6
  22. package/apps/agents-server/src/app/api/admin/update/route.ts +52 -0
  23. package/apps/agents-server/src/app/api/auth/login/route.ts +8 -0
  24. package/apps/agents-server/src/app/api/auth/logout/route.ts +10 -2
  25. package/apps/agents-server/src/app/layout.tsx +1 -0
  26. package/apps/agents-server/src/app/page.tsx +10 -0
  27. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +10 -0
  28. package/apps/agents-server/src/database/$provideClientSql.ts +4 -21
  29. package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +252 -0
  30. package/apps/agents-server/src/database/$providePostgresPool.ts +27 -0
  31. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +11 -1
  32. package/apps/agents-server/src/database/agentsServerDatabaseMode.ts +20 -1
  33. package/apps/agents-server/src/database/postgres/$provideLocalPostgresSupabase.ts +1261 -0
  34. package/apps/agents-server/src/database/resolvePostgresConnectionString.ts +26 -0
  35. package/apps/agents-server/src/database/sqlite/$provideAgentsServerSqliteDatabase.ts +83 -0
  36. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +20 -71
  37. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +2 -0
  38. package/apps/agents-server/src/languages/translations/czech.yaml +2 -0
  39. package/apps/agents-server/src/languages/translations/english.yaml +2 -0
  40. package/apps/agents-server/src/middleware.ts +32 -0
  41. package/apps/agents-server/src/tools/$provideServer.ts +2 -2
  42. package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +394 -0
  43. package/apps/agents-server/src/utils/codeRunnerConfiguration.ts +67 -0
  44. package/apps/agents-server/src/utils/serverManagement/standaloneVpsServerMetadata.ts +145 -0
  45. package/apps/agents-server/src/utils/serverRegistry.ts +7 -6
  46. package/apps/agents-server/src/utils/session.ts +37 -9
  47. package/apps/agents-server/src/utils/shibboleth/createShibbolethAuthenticationLogPayload.ts +173 -0
  48. package/apps/agents-server/src/utils/shibboleth/writeShibbolethAuthenticationLog.ts +27 -0
  49. package/apps/agents-server/src/utils/standaloneVpsDnsDiagnostics.ts +258 -0
  50. package/apps/agents-server/src/utils/standaloneVpsRawIpBootstrap.ts +87 -0
  51. package/apps/agents-server/src/utils/vpsConfiguration.ts +87 -15
  52. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +664 -0
  53. package/esm/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +9 -1
  54. package/esm/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
  55. package/esm/index.es.js +8 -6
  56. package/esm/index.es.js.map +1 -1
  57. package/esm/src/version.d.ts +1 -1
  58. package/package.json +2 -1
  59. package/src/book-components/Chat/utils/renderMarkdown.ts +1 -3
  60. package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +1 -1
  61. package/src/other/templates/getTemplatesPipelineCollection.ts +767 -745
  62. package/src/scrapers/document/DocumentScraper.ts +1 -1
  63. package/src/scrapers/document-legacy/LegacyDocumentScraper.ts +1 -1
  64. package/src/version.ts +2 -2
  65. package/src/versions.txt +2 -1
  66. package/umd/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +9 -1
  67. package/umd/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
  68. package/umd/index.umd.js +8 -6
  69. package/umd/index.umd.js.map +1 -1
  70. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,394 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from 'child_process';
2
+ import { randomUUID } from 'crypto';
3
+ import { EventEmitter } from 'events';
4
+ import { spaceTrim } from 'spacetrim';
5
+ import { createVpsInstallerCommandEnvironment, resolveVpsInstallerScriptPath } from './vpsConfiguration';
6
+
7
+ const MAX_SESSION_OUTPUT_LENGTH = 200_000;
8
+ const SESSION_RETENTION_MILLISECONDS = 30 * 60 * 1000;
9
+
10
+ /**
11
+ * Serializable snapshot of one interactive code-runner authentication session.
12
+ */
13
+ export type CodeRunnerAuthenticationSessionSnapshot = {
14
+ /**
15
+ * Session identifier used by the browser UI.
16
+ */
17
+ readonly id: string;
18
+
19
+ /**
20
+ * Runner being authenticated.
21
+ */
22
+ readonly agent: string;
23
+
24
+ /**
25
+ * Whether the interactive runner process is still active.
26
+ */
27
+ readonly isRunning: boolean;
28
+
29
+ /**
30
+ * Buffered session output shown in the admin terminal.
31
+ */
32
+ readonly output: string;
33
+
34
+ /**
35
+ * Session start timestamp in ISO format.
36
+ */
37
+ readonly startedAt: string;
38
+
39
+ /**
40
+ * Session finish timestamp in ISO format, when available.
41
+ */
42
+ readonly finishedAt: string | null;
43
+
44
+ /**
45
+ * Process exit code once the session has finished.
46
+ */
47
+ readonly exitCode: number | null;
48
+
49
+ /**
50
+ * Process signal once the session has finished.
51
+ */
52
+ readonly signal: NodeJS.Signals | null;
53
+ };
54
+
55
+ /**
56
+ * Output event emitted while the authentication terminal is running.
57
+ */
58
+ type CodeRunnerAuthenticationOutputEvent = {
59
+ readonly type: 'output';
60
+ readonly chunk: string;
61
+ };
62
+
63
+ /**
64
+ * Exit event emitted when the authentication terminal finishes.
65
+ */
66
+ type CodeRunnerAuthenticationExitEvent = {
67
+ readonly type: 'exit';
68
+ readonly snapshot: CodeRunnerAuthenticationSessionSnapshot;
69
+ };
70
+
71
+ /**
72
+ * Internal mutable session state stored in-process.
73
+ */
74
+ type CodeRunnerAuthenticationSession = {
75
+ readonly id: string;
76
+ readonly agent: string;
77
+ readonly process: ChildProcessWithoutNullStreams;
78
+ readonly events: EventEmitter<{
79
+ output: [CodeRunnerAuthenticationOutputEvent];
80
+ exit: [CodeRunnerAuthenticationExitEvent];
81
+ }>;
82
+ readonly startedAt: Date;
83
+ cleanupTimeout: NodeJS.Timeout | null;
84
+ output: string;
85
+ isRunning: boolean;
86
+ finishedAt: Date | null;
87
+ exitCode: number | null;
88
+ signal: NodeJS.Signals | null;
89
+ };
90
+
91
+ /**
92
+ * Shared in-memory state reused across requests in the standalone server process.
93
+ */
94
+ type CodeRunnerAuthenticationState = {
95
+ readonly sessionsById: Map<string, CodeRunnerAuthenticationSession>;
96
+ readonly latestSessionIdByAgent: Map<string, string>;
97
+ };
98
+
99
+ /**
100
+ * Browser stream callbacks used by one subscribed UI client.
101
+ */
102
+ export type CodeRunnerAuthenticationSessionSubscriber = {
103
+ /**
104
+ * Called whenever new terminal output arrives.
105
+ */
106
+ readonly onOutput: (event: CodeRunnerAuthenticationOutputEvent) => void;
107
+
108
+ /**
109
+ * Called once the session exits.
110
+ */
111
+ readonly onExit: (event: CodeRunnerAuthenticationExitEvent) => void;
112
+ };
113
+
114
+ /**
115
+ * Starts a streamed authentication session for the currently configured standalone runner.
116
+ *
117
+ * @param agent - Runner identifier stored in `.env`.
118
+ * @returns Existing running session for the same runner or a new one.
119
+ */
120
+ export async function startCodeRunnerAuthenticationSession(
121
+ agent: string,
122
+ ): Promise<CodeRunnerAuthenticationSessionSnapshot> {
123
+ const existingSession = getLatestCodeRunnerAuthenticationSession(agent);
124
+ if (existingSession?.isRunning) {
125
+ return existingSession;
126
+ }
127
+
128
+ if (process.platform !== 'linux') {
129
+ throw new Error('Interactive code-runner authentication is available only on the Linux VPS runtime.');
130
+ }
131
+
132
+ const scriptPath = await resolveVpsInstallerScriptPath();
133
+ if (!scriptPath) {
134
+ throw new Error('The VPS installer script could not be found on this server.');
135
+ }
136
+
137
+ const sessionId = randomUUID();
138
+ const childProcess = spawn('bash', [scriptPath, 'authenticate-runner'], {
139
+ env: createVpsInstallerCommandEnvironment({
140
+ isNonInteractiveModeEnabled: false,
141
+ isProcessRestartEnabled: false,
142
+ }),
143
+ stdio: ['pipe', 'pipe', 'pipe'],
144
+ });
145
+
146
+ const session: CodeRunnerAuthenticationSession = {
147
+ id: sessionId,
148
+ agent,
149
+ process: childProcess,
150
+ events: new EventEmitter(),
151
+ startedAt: new Date(),
152
+ cleanupTimeout: null,
153
+ output: '',
154
+ isRunning: true,
155
+ finishedAt: null,
156
+ exitCode: null,
157
+ signal: null,
158
+ };
159
+
160
+ const state = getCodeRunnerAuthenticationState();
161
+ state.sessionsById.set(sessionId, session);
162
+ state.latestSessionIdByAgent.set(agent, sessionId);
163
+
164
+ childProcess.stdout.setEncoding('utf-8');
165
+ childProcess.stderr.setEncoding('utf-8');
166
+ childProcess.stdout.on('data', (chunk: string) => appendCodeRunnerAuthenticationOutput(session, chunk));
167
+ childProcess.stderr.on('data', (chunk: string) => appendCodeRunnerAuthenticationOutput(session, chunk));
168
+ childProcess.on('close', (exitCode, signal) => finalizeCodeRunnerAuthenticationSession(session, exitCode, signal));
169
+ childProcess.on('error', (error) => {
170
+ appendCodeRunnerAuthenticationOutput(
171
+ session,
172
+ spaceTrim(`
173
+ Failed to start the code-runner authentication process.
174
+
175
+ ${error.message}
176
+ `) + '\n',
177
+ );
178
+ });
179
+
180
+ return createCodeRunnerAuthenticationSessionSnapshot(session);
181
+ }
182
+
183
+ /**
184
+ * Returns the latest known authentication session for a runner.
185
+ *
186
+ * @param agent - Runner identifier.
187
+ * @returns Serializable session snapshot or `null`.
188
+ */
189
+ export function getLatestCodeRunnerAuthenticationSession(
190
+ agent: string,
191
+ ): CodeRunnerAuthenticationSessionSnapshot | null {
192
+ const sessionId = getCodeRunnerAuthenticationState().latestSessionIdByAgent.get(agent);
193
+ if (!sessionId) {
194
+ return null;
195
+ }
196
+
197
+ return getCodeRunnerAuthenticationSession(sessionId);
198
+ }
199
+
200
+ /**
201
+ * Looks up one authentication session by id.
202
+ *
203
+ * @param sessionId - Session identifier.
204
+ * @returns Serializable session snapshot or `null`.
205
+ */
206
+ export function getCodeRunnerAuthenticationSession(
207
+ sessionId: string,
208
+ ): CodeRunnerAuthenticationSessionSnapshot | null {
209
+ const session = getCodeRunnerAuthenticationState().sessionsById.get(sessionId);
210
+ return session ? createCodeRunnerAuthenticationSessionSnapshot(session) : null;
211
+ }
212
+
213
+ /**
214
+ * Subscribes one browser connection to live terminal events.
215
+ *
216
+ * @param sessionId - Session identifier.
217
+ * @param subscriber - Stream callbacks.
218
+ * @returns Cleanup callback.
219
+ */
220
+ export function subscribeToCodeRunnerAuthenticationSession(
221
+ sessionId: string,
222
+ subscriber: CodeRunnerAuthenticationSessionSubscriber,
223
+ ): (() => void) | null {
224
+ const session = getCodeRunnerAuthenticationState().sessionsById.get(sessionId);
225
+ if (!session) {
226
+ return null;
227
+ }
228
+
229
+ session.events.on('output', subscriber.onOutput);
230
+ session.events.on('exit', subscriber.onExit);
231
+
232
+ return () => {
233
+ session.events.off('output', subscriber.onOutput);
234
+ session.events.off('exit', subscriber.onExit);
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Sends interactive input to a running authentication terminal.
240
+ *
241
+ * @param sessionId - Session identifier.
242
+ * @param input - Raw text to write to stdin.
243
+ * @returns Updated session snapshot.
244
+ */
245
+ export function writeCodeRunnerAuthenticationSessionInput(
246
+ sessionId: string,
247
+ input: string,
248
+ ): CodeRunnerAuthenticationSessionSnapshot {
249
+ const session = getRequiredCodeRunnerAuthenticationSession(sessionId);
250
+
251
+ if (!session.isRunning) {
252
+ throw new Error('The authentication session has already finished.');
253
+ }
254
+
255
+ session.process.stdin.write(input);
256
+ return createCodeRunnerAuthenticationSessionSnapshot(session);
257
+ }
258
+
259
+ /**
260
+ * Stops a running authentication terminal.
261
+ *
262
+ * @param sessionId - Session identifier.
263
+ * @returns Updated session snapshot.
264
+ */
265
+ export function stopCodeRunnerAuthenticationSession(
266
+ sessionId: string,
267
+ ): CodeRunnerAuthenticationSessionSnapshot {
268
+ const session = getRequiredCodeRunnerAuthenticationSession(sessionId);
269
+
270
+ if (session.isRunning) {
271
+ session.process.kill('SIGTERM');
272
+ }
273
+
274
+ return createCodeRunnerAuthenticationSessionSnapshot(session);
275
+ }
276
+
277
+ /**
278
+ * Returns the mutable singleton state for authentication sessions.
279
+ *
280
+ * @returns Shared in-memory session state.
281
+ */
282
+ function getCodeRunnerAuthenticationState(): CodeRunnerAuthenticationState {
283
+ const globalState = globalThis as typeof globalThis & {
284
+ __promptbookCodeRunnerAuthenticationState?: CodeRunnerAuthenticationState;
285
+ };
286
+
287
+ if (!globalState.__promptbookCodeRunnerAuthenticationState) {
288
+ globalState.__promptbookCodeRunnerAuthenticationState = {
289
+ sessionsById: new Map(),
290
+ latestSessionIdByAgent: new Map(),
291
+ };
292
+ }
293
+
294
+ return globalState.__promptbookCodeRunnerAuthenticationState;
295
+ }
296
+
297
+ /**
298
+ * Appends terminal output while keeping the buffer size bounded.
299
+ *
300
+ * @param session - Active authentication session.
301
+ * @param rawChunk - Terminal chunk from stdout or stderr.
302
+ */
303
+ function appendCodeRunnerAuthenticationOutput(
304
+ session: CodeRunnerAuthenticationSession,
305
+ rawChunk: string | Buffer,
306
+ ): void {
307
+ const chunk = normalizeCodeRunnerAuthenticationOutput(rawChunk.toString());
308
+ if (!chunk) {
309
+ return;
310
+ }
311
+
312
+ session.output = (session.output + chunk).slice(-MAX_SESSION_OUTPUT_LENGTH);
313
+ session.events.emit('output', {
314
+ type: 'output',
315
+ chunk,
316
+ });
317
+ }
318
+
319
+ /**
320
+ * Normalizes streamed terminal output for browser rendering.
321
+ *
322
+ * @param output - Raw process output chunk.
323
+ * @returns UI-friendly terminal text.
324
+ */
325
+ function normalizeCodeRunnerAuthenticationOutput(output: string): string {
326
+ return output.replace(/\r\n/gu, '\n').replace(/\r/gu, '\n');
327
+ }
328
+
329
+ /**
330
+ * Marks one authentication session as finished and schedules retention cleanup.
331
+ *
332
+ * @param session - Finished authentication session.
333
+ * @param exitCode - Process exit code.
334
+ * @param signal - Process signal.
335
+ */
336
+ function finalizeCodeRunnerAuthenticationSession(
337
+ session: CodeRunnerAuthenticationSession,
338
+ exitCode: number | null,
339
+ signal: NodeJS.Signals | null,
340
+ ): void {
341
+ session.isRunning = false;
342
+ session.finishedAt = new Date();
343
+ session.exitCode = exitCode;
344
+ session.signal = signal;
345
+
346
+ session.events.emit('exit', {
347
+ type: 'exit',
348
+ snapshot: createCodeRunnerAuthenticationSessionSnapshot(session),
349
+ });
350
+
351
+ if (session.cleanupTimeout) {
352
+ clearTimeout(session.cleanupTimeout);
353
+ }
354
+
355
+ session.cleanupTimeout = setTimeout(() => {
356
+ getCodeRunnerAuthenticationState().sessionsById.delete(session.id);
357
+ }, SESSION_RETENTION_MILLISECONDS);
358
+ }
359
+
360
+ /**
361
+ * Reads one required mutable session and throws when missing.
362
+ *
363
+ * @param sessionId - Session identifier.
364
+ * @returns Mutable session state.
365
+ */
366
+ function getRequiredCodeRunnerAuthenticationSession(sessionId: string): CodeRunnerAuthenticationSession {
367
+ const session = getCodeRunnerAuthenticationState().sessionsById.get(sessionId);
368
+ if (!session) {
369
+ throw new Error('Authentication session was not found.');
370
+ }
371
+
372
+ return session;
373
+ }
374
+
375
+ /**
376
+ * Converts mutable session state into a browser-safe snapshot.
377
+ *
378
+ * @param session - Mutable in-memory session.
379
+ * @returns Serializable session snapshot.
380
+ */
381
+ function createCodeRunnerAuthenticationSessionSnapshot(
382
+ session: CodeRunnerAuthenticationSession,
383
+ ): CodeRunnerAuthenticationSessionSnapshot {
384
+ return {
385
+ id: session.id,
386
+ agent: session.agent,
387
+ isRunning: session.isRunning,
388
+ output: session.output,
389
+ startedAt: session.startedAt.toISOString(),
390
+ finishedAt: session.finishedAt ? session.finishedAt.toISOString() : null,
391
+ exitCode: session.exitCode,
392
+ signal: session.signal,
393
+ };
394
+ }
@@ -0,0 +1,67 @@
1
+ import { execFile } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { listVpsEnvironmentVariables } from './vpsConfiguration';
4
+
5
+ const execFileAsync = promisify(execFile);
6
+
7
+ /**
8
+ * Saved standalone VPS code-runner configuration exposed to the admin UI.
9
+ */
10
+ export type ConfiguredCodeRunner = {
11
+ /**
12
+ * Runner identifier persisted in `.env`.
13
+ */
14
+ readonly agent: string;
15
+
16
+ /**
17
+ * Model identifier persisted in `.env`.
18
+ */
19
+ readonly model: string;
20
+
21
+ /**
22
+ * Thinking level persisted in `.env`.
23
+ */
24
+ readonly thinkingLevel: string;
25
+ };
26
+
27
+ /**
28
+ * Reads the currently configured standalone VPS code runner from managed environment variables.
29
+ *
30
+ * @returns Saved code-runner settings with fallback defaults.
31
+ */
32
+ export async function readConfiguredCodeRunner(): Promise<ConfiguredCodeRunner> {
33
+ const snapshot = await listVpsEnvironmentVariables();
34
+ const environmentByKey = Object.fromEntries(snapshot.variables.map((variable) => [variable.key, variable.value])) as Record<
35
+ string,
36
+ string
37
+ >;
38
+
39
+ return {
40
+ agent: environmentByKey.PTBK_AGENT || process.env.PTBK_AGENT || 'github-copilot',
41
+ model: environmentByKey.PTBK_MODEL || process.env.PTBK_MODEL || 'gpt-5.4',
42
+ thinkingLevel: environmentByKey.PTBK_THINKING_LEVEL || process.env.PTBK_THINKING_LEVEL || 'xhigh',
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Resolves a short runner-authentication status for the configured runner.
48
+ *
49
+ * @param agent - Runner id.
50
+ * @returns Human-readable status.
51
+ */
52
+ export async function resolveCodeRunnerStatus(agent: string): Promise<string> {
53
+ if (agent !== 'github-copilot') {
54
+ return 'Status check is currently available for GitHub Copilot CLI only.';
55
+ }
56
+
57
+ try {
58
+ const { stdout, stderr } = await execFileAsync('copilot', ['auth', 'status'], {
59
+ timeout: 10_000,
60
+ maxBuffer: 128 * 1024,
61
+ });
62
+
63
+ return [stdout, stderr].filter(Boolean).join('\n').trim() || 'GitHub Copilot CLI returned no status output.';
64
+ } catch (error) {
65
+ return error instanceof Error ? error.message : 'GitHub Copilot CLI status check failed.';
66
+ }
67
+ }
@@ -0,0 +1,145 @@
1
+ import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import { spaceTrim } from 'spacetrim';
3
+ import { DatabaseError } from '../../../../../src/errors/DatabaseError';
4
+ import { $provideSupabaseForServer } from '../../database/$provideSupabaseForServer';
5
+ import type { ServerRecord } from '../serverRegistry';
6
+
7
+ /**
8
+ * Metadata key storing the human-facing server name.
9
+ *
10
+ * @private constant of standalone VPS server management.
11
+ */
12
+ const SERVER_NAME_METADATA_KEY = 'SERVER_NAME';
13
+
14
+ /**
15
+ * Metadata keys that use the uploaded server icon.
16
+ *
17
+ * @private constant of standalone VPS server management.
18
+ */
19
+ const SERVER_ICON_METADATA_KEYS = ['SERVER_LOGO_URL', 'SERVER_FAVICON_URL'] as const;
20
+
21
+ /**
22
+ * Minimal metadata row used by standalone VPS server setup.
23
+ *
24
+ * @private type of standalone VPS server management.
25
+ */
26
+ type StandaloneVpsMetadataRow = {
27
+ /**
28
+ * Metadata key.
29
+ */
30
+ readonly key: string;
31
+
32
+ /**
33
+ * Metadata value.
34
+ */
35
+ readonly value: string;
36
+
37
+ /**
38
+ * Optional admin-facing note.
39
+ */
40
+ readonly note: string | null;
41
+
42
+ /**
43
+ * Last update timestamp.
44
+ */
45
+ readonly updatedAt: string;
46
+ };
47
+
48
+ /**
49
+ * Applies visible standalone VPS setup values into the prefixed metadata table.
50
+ *
51
+ * @param input - Server prefix and optional metadata values.
52
+ *
53
+ * @private internal standalone VPS server management helper.
54
+ */
55
+ export async function applyStandaloneVpsServerMetadata(input: {
56
+ readonly tablePrefix: string;
57
+ readonly name?: string | null;
58
+ readonly iconUrl?: string | null;
59
+ }): Promise<void> {
60
+ const metadataRows = createStandaloneVpsMetadataRows(input);
61
+ if (metadataRows.length === 0) {
62
+ return;
63
+ }
64
+
65
+ const metadataTableName = `${input.tablePrefix}Metadata`;
66
+ const supabase = $provideSupabaseForServer() as SupabaseClient;
67
+ const { error } = await supabase.from(metadataTableName).upsert([...metadataRows], {
68
+ onConflict: 'key',
69
+ });
70
+
71
+ if (error) {
72
+ throw new DatabaseError(
73
+ spaceTrim(`
74
+ Failed to update standalone VPS server metadata.
75
+
76
+ ${error.message}
77
+ `),
78
+ );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Resolves the configured display name for a standalone VPS virtual server.
84
+ *
85
+ * @param server - Virtual server row derived from the `SERVERS` environment variable.
86
+ * @returns Metadata server name or the virtual row name when metadata is missing.
87
+ *
88
+ * @private internal standalone VPS server management helper.
89
+ */
90
+ export async function resolveStandaloneVpsServerDisplayName(server: ServerRecord): Promise<string> {
91
+ const metadataTableName = `${server.tablePrefix}Metadata`;
92
+ const supabase = $provideSupabaseForServer() as SupabaseClient;
93
+ const { data, error } = await supabase
94
+ .from(metadataTableName)
95
+ .select('value')
96
+ .eq('key', SERVER_NAME_METADATA_KEY)
97
+ .maybeSingle<{ readonly value: string | null }>();
98
+
99
+ if (error) {
100
+ return server.name;
101
+ }
102
+
103
+ const metadataName = typeof data?.value === 'string' ? data.value.trim() : '';
104
+ return metadataName || server.name;
105
+ }
106
+
107
+ /**
108
+ * Creates metadata rows from visible setup fields.
109
+ *
110
+ * @param input - Raw setup values.
111
+ * @returns Metadata rows ready for upsert.
112
+ *
113
+ * @private function of standalone VPS server management.
114
+ */
115
+ function createStandaloneVpsMetadataRows(input: {
116
+ readonly name?: string | null;
117
+ readonly iconUrl?: string | null;
118
+ }): ReadonlyArray<StandaloneVpsMetadataRow> {
119
+ const updatedAt = new Date().toISOString();
120
+ const rows: Array<StandaloneVpsMetadataRow> = [];
121
+ const name = typeof input.name === 'string' ? input.name.trim() : '';
122
+ const iconUrl = typeof input.iconUrl === 'string' ? input.iconUrl.trim() : '';
123
+
124
+ if (name) {
125
+ rows.push({
126
+ key: SERVER_NAME_METADATA_KEY,
127
+ value: name,
128
+ note: null,
129
+ updatedAt,
130
+ });
131
+ }
132
+
133
+ if (iconUrl) {
134
+ for (const key of SERVER_ICON_METADATA_KEYS) {
135
+ rows.push({
136
+ key,
137
+ value: iconUrl,
138
+ note: null,
139
+ updatedAt,
140
+ });
141
+ }
142
+ }
143
+
144
+ return rows;
145
+ }
@@ -1,7 +1,7 @@
1
1
  import { createClient, type SupabaseClient } from '@supabase/supabase-js';
2
2
  import { spaceTrim } from 'spacetrim';
3
3
  import { DatabaseError } from '../../../../src/errors/DatabaseError';
4
- import { isAgentsServerSqliteMode } from '../database/agentsServerDatabaseMode';
4
+ import { isAgentsServerStandaloneMode } from '../database/agentsServerDatabaseMode';
5
5
 
6
6
  /**
7
7
  * Supported `_Server.environment` values.
@@ -139,7 +139,7 @@ export async function listRegisteredServersUsingServiceRole(options?: {
139
139
  }): Promise<Array<ServerRecord>> {
140
140
  const environmentServers = listEnvironmentRegisteredServers();
141
141
 
142
- if (isAgentsServerSqliteMode()) {
142
+ if (isAgentsServerStandaloneMode()) {
143
143
  return environmentServers;
144
144
  }
145
145
 
@@ -171,13 +171,14 @@ export async function listRegisteredServersUsingServiceRole(options?: {
171
171
  /**
172
172
  * Loads virtual server records from the comma-separated `SERVERS` environment variable.
173
173
  *
174
- * @returns Server records with deterministic table prefixes derived from normalized domains.
174
+ * @returns Server records with deterministic table prefixes from the configured server prefix or normalized domains.
175
175
  */
176
176
  export function listEnvironmentRegisteredServers(): Array<ServerRecord> {
177
177
  const rawServers = process.env[SERVERS_ENV_NAME];
178
178
  if (!rawServers) {
179
179
  return [];
180
180
  }
181
+ const configuredTablePrefix = process.env.SUPABASE_TABLE_PREFIX?.trim() || '';
181
182
 
182
183
  const normalizedDomains = uniqueStrings(
183
184
  rawServers
@@ -191,7 +192,7 @@ export function listEnvironmentRegisteredServers(): Array<ServerRecord> {
191
192
  name: domain,
192
193
  environment: SERVER_ENVIRONMENT.PRODUCTION,
193
194
  domain,
194
- tablePrefix: buildEnvironmentServerTablePrefix(domain),
195
+ tablePrefix: configuredTablePrefix || buildEnvironmentServerTablePrefix(domain),
195
196
  createdAt: ENVIRONMENT_SERVER_TIMESTAMP,
196
197
  updatedAt: ENVIRONMENT_SERVER_TIMESTAMP,
197
198
  }));
@@ -349,10 +350,10 @@ export function isServerEnvironment(value: string): value is ServerEnvironment {
349
350
  * @returns Shared untyped Supabase client.
350
351
  */
351
352
  export function getServerRegistryClient(): SupabaseClient {
352
- if (isAgentsServerSqliteMode()) {
353
+ if (isAgentsServerStandaloneMode()) {
353
354
  throw new DatabaseError(
354
355
  spaceTrim(`
355
- Cannot create a Supabase server-registry client while Agents Server is using SQLite.
356
+ Cannot create a Supabase server-registry client while Agents Server is using a standalone database backend.
356
357
  `),
357
358
  );
358
359
  }