@promptbook/cli 0.112.0-96 → 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.
- package/apps/agents-server/README.md +3 -3
- package/apps/agents-server/playwright.config.ts +2 -1
- package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +358 -19
- package/apps/agents-server/src/app/admin/database/page.tsx +2 -1
- package/apps/agents-server/src/app/admin/servers/CreateServerDialog.tsx +46 -505
- package/apps/agents-server/src/app/admin/servers/ServersClient.tsx +23 -11
- package/apps/agents-server/src/app/admin/servers/ServersRegistryApi.ts +5 -0
- package/apps/agents-server/src/app/admin/servers/ServersRegistryDnsTypes.ts +87 -0
- package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +258 -128
- package/apps/agents-server/src/app/admin/servers/useCreateServerWizard.ts +46 -334
- package/apps/agents-server/src/app/admin/servers/useServersRegistryState.ts +26 -2
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +435 -0
- package/apps/agents-server/src/app/admin/update/page.tsx +14 -0
- package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +197 -0
- package/apps/agents-server/src/app/api/admin/code-runners/route.ts +4 -35
- package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +10 -5
- package/apps/agents-server/src/app/api/admin/servers/route.ts +97 -6
- package/apps/agents-server/src/app/api/admin/update/route.ts +52 -0
- package/apps/agents-server/src/app/api/auth/login/route.ts +8 -0
- package/apps/agents-server/src/app/api/auth/logout/route.ts +10 -2
- package/apps/agents-server/src/app/page.tsx +10 -0
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +6 -0
- package/apps/agents-server/src/database/$provideClientSql.ts +4 -17
- package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +3 -24
- package/apps/agents-server/src/database/$providePostgresPool.ts +27 -0
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +11 -1
- package/apps/agents-server/src/database/agentsServerDatabaseMode.ts +20 -1
- package/apps/agents-server/src/database/postgres/$provideLocalPostgresSupabase.ts +1261 -0
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -0
- package/apps/agents-server/src/languages/translations/czech.yaml +1 -0
- package/apps/agents-server/src/languages/translations/english.yaml +1 -0
- package/apps/agents-server/src/middleware.ts +32 -0
- package/apps/agents-server/src/tools/$provideServer.ts +2 -2
- package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +394 -0
- package/apps/agents-server/src/utils/codeRunnerConfiguration.ts +67 -0
- package/apps/agents-server/src/utils/serverManagement/standaloneVpsServerMetadata.ts +145 -0
- package/apps/agents-server/src/utils/serverRegistry.ts +7 -6
- package/apps/agents-server/src/utils/session.ts +37 -9
- package/apps/agents-server/src/utils/shibboleth/createShibbolethAuthenticationLogPayload.ts +173 -0
- package/apps/agents-server/src/utils/shibboleth/writeShibbolethAuthenticationLog.ts +27 -0
- package/apps/agents-server/src/utils/standaloneVpsDnsDiagnostics.ts +258 -0
- package/apps/agents-server/src/utils/standaloneVpsRawIpBootstrap.ts +87 -0
- package/apps/agents-server/src/utils/vpsConfiguration.ts +87 -15
- package/apps/agents-server/src/utils/vpsSelfUpdate.ts +664 -0
- package/esm/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +9 -1
- package/esm/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
- package/esm/index.es.js +8 -6
- package/esm/index.es.js.map +1 -1
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/book-components/Chat/utils/renderMarkdown.ts +1 -3
- package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +1 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +698 -755
- package/src/scrapers/document/DocumentScraper.ts +1 -1
- package/src/scrapers/document-legacy/LegacyDocumentScraper.ts +1 -1
- package/src/version.ts +2 -2
- package/src/versions.txt +1 -0
- package/umd/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +9 -1
- package/umd/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
- package/umd/index.umd.js +8 -6
- package/umd/index.umd.js.map +1 -1
- package/umd/src/version.d.ts +1 -1
|
@@ -136,6 +136,7 @@ header.viewAllUsers: Zobrazit všechny uživatele
|
|
|
136
136
|
header.createNewUser: Vytvořit nového uživatele
|
|
137
137
|
header.servers: Servery
|
|
138
138
|
header.environmentVariables: Proměnné prostředí
|
|
139
|
+
header.update: Aktualizace
|
|
139
140
|
header.database: Databáze
|
|
140
141
|
header.logs: Logy
|
|
141
142
|
header.codeRunners: Code runnery
|
|
@@ -138,6 +138,7 @@ header.viewAllUsers: View all users
|
|
|
138
138
|
header.createNewUser: Create new user
|
|
139
139
|
header.servers: Servers
|
|
140
140
|
header.environmentVariables: Environment variables
|
|
141
|
+
header.update: Update
|
|
141
142
|
header.database: Database
|
|
142
143
|
header.logs: Logs
|
|
143
144
|
header.codeRunners: Code runners
|
|
@@ -3,6 +3,7 @@ import { applyVisibilityHeaders } from './middleware/applyVisibilityHeaders';
|
|
|
3
3
|
import { createMiddlewareRequestContext } from './middleware/createMiddlewareRequestContext';
|
|
4
4
|
import { resolveAccessControlResponse } from './middleware/resolveAccessControlResponse';
|
|
5
5
|
import { resolveMiddlewareResponse } from './middleware/resolveMiddlewareResponse';
|
|
6
|
+
import { writeShibbolethAuthenticationLog } from './utils/shibboleth/writeShibbolethAuthenticationLog';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Main Next.js middleware coordinating request context loading, access control,
|
|
@@ -12,6 +13,15 @@ import { resolveMiddlewareResponse } from './middleware/resolveMiddlewareRespons
|
|
|
12
13
|
* @returns Middleware response.
|
|
13
14
|
*/
|
|
14
15
|
export async function middleware(request: NextRequest): Promise<NextResponse> {
|
|
16
|
+
if (shouldLogShibbolethRequestFromMiddleware(request)) {
|
|
17
|
+
writeShibbolethAuthenticationLog(request.headers, {
|
|
18
|
+
event: 'middleware-request',
|
|
19
|
+
pathname: request.nextUrl.pathname,
|
|
20
|
+
method: request.method,
|
|
21
|
+
hasSessionCookie: request.cookies.has('sessionToken'),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
const middlewareRequestContext = await createMiddlewareRequestContext(request);
|
|
16
26
|
const applyResponseHeaders = async (response: NextResponse): Promise<void> => {
|
|
17
27
|
await applyVisibilityHeaders({
|
|
@@ -58,3 +68,25 @@ export const config = {
|
|
|
58
68
|
'/((?!_next/static|_next/image|favicon.ico|logo-|fonts/|robots.txt|api/internal).*)',
|
|
59
69
|
],
|
|
60
70
|
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Decides whether the current middleware request should emit Shibboleth diagnostics.
|
|
74
|
+
*
|
|
75
|
+
* We log the first anonymous request plus explicit auth/SAML routes so standalone VPS
|
|
76
|
+
* pm2 logs show the incoming Shibboleth attributes without flooding every authenticated
|
|
77
|
+
* page load once the browser already carries a session cookie.
|
|
78
|
+
*
|
|
79
|
+
* @param request - Incoming middleware request.
|
|
80
|
+
* @returns `true` when the request is useful for Shibboleth diagnostics.
|
|
81
|
+
*/
|
|
82
|
+
function shouldLogShibbolethRequestFromMiddleware(request: NextRequest): boolean {
|
|
83
|
+
const pathname = request.nextUrl.pathname.toLowerCase();
|
|
84
|
+
const isAuthenticationPath = pathname.startsWith('/api/auth');
|
|
85
|
+
const isShibbolethPath = pathname.includes('shibboleth') || pathname.includes('saml');
|
|
86
|
+
|
|
87
|
+
if (isAuthenticationPath || isShibbolethPath) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return !request.cookies.has('sessionToken');
|
|
92
|
+
}
|
|
@@ -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 {
|
|
4
|
+
import { isAgentsServerStandaloneMode } 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 (
|
|
40
|
+
if (isAgentsServerStandaloneMode()) {
|
|
41
41
|
if (isLocalDevelopmentHost(requestHost)) {
|
|
42
42
|
return {
|
|
43
43
|
id: null,
|
|
@@ -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
|
+
}
|