@promptbook/cli 0.112.0-96 → 0.112.0-98

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 (79) hide show
  1. package/apps/agents-server/playwright.config.ts +2 -1
  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 +124 -34
  5. package/apps/agents-server/src/app/admin/servers/CreateServerDialog.tsx +46 -505
  6. package/apps/agents-server/src/app/admin/servers/ServersClient.tsx +23 -11
  7. package/apps/agents-server/src/app/admin/servers/ServersRegistryApi.ts +5 -0
  8. package/apps/agents-server/src/app/admin/servers/ServersRegistryDnsTypes.ts +87 -0
  9. package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +258 -128
  10. package/apps/agents-server/src/app/admin/servers/useCreateServerWizard.ts +46 -334
  11. package/apps/agents-server/src/app/admin/servers/useServersRegistryState.ts +26 -2
  12. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +435 -0
  13. package/apps/agents-server/src/app/admin/update/page.tsx +14 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +24 -0
  15. package/apps/agents-server/src/app/api/admin/cli-access/route.ts +137 -0
  16. package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +140 -0
  17. package/apps/agents-server/src/app/api/admin/code-runners/route.ts +4 -35
  18. package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +7 -2
  19. package/apps/agents-server/src/app/api/admin/servers/route.ts +95 -4
  20. package/apps/agents-server/src/app/api/admin/update/route.ts +52 -0
  21. package/apps/agents-server/src/app/api/auth/login/route.ts +8 -0
  22. package/apps/agents-server/src/app/api/auth/logout/route.ts +10 -2
  23. package/apps/agents-server/src/app/api/chat/export/pdf/route.ts +63 -0
  24. package/apps/agents-server/src/app/page.tsx +10 -0
  25. package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +279 -0
  26. package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +336 -0
  27. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +10 -0
  28. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +2 -0
  29. package/apps/agents-server/src/languages/translations/czech.yaml +2 -0
  30. package/apps/agents-server/src/languages/translations/english.yaml +2 -0
  31. package/apps/agents-server/src/middleware.ts +32 -0
  32. package/apps/agents-server/src/tools/BrowserConnectionProvider.ts +1 -1
  33. package/apps/agents-server/src/utils/chatExport/downloadChatPdfFromServer.ts +59 -0
  34. package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +37 -0
  35. package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +234 -0
  36. package/apps/agents-server/src/utils/codeRunnerConfiguration.ts +67 -0
  37. package/apps/agents-server/src/utils/createInteractiveTerminalEventStream.ts +84 -0
  38. package/apps/agents-server/src/utils/interactiveTerminalSession.ts +442 -0
  39. package/apps/agents-server/src/utils/serverCliAccess.ts +221 -0
  40. package/apps/agents-server/src/utils/serverManagement/standaloneVpsServerMetadata.ts +145 -0
  41. package/apps/agents-server/src/utils/serverRegistry.ts +3 -2
  42. package/apps/agents-server/src/utils/session.ts +37 -9
  43. package/apps/agents-server/src/utils/shibboleth/createShibbolethAuthenticationLogPayload.ts +173 -0
  44. package/apps/agents-server/src/utils/shibboleth/writeShibbolethAuthenticationLog.ts +27 -0
  45. package/apps/agents-server/src/utils/standaloneVpsDnsDiagnostics.ts +258 -0
  46. package/apps/agents-server/src/utils/standaloneVpsRawIpBootstrap.ts +87 -0
  47. package/apps/agents-server/src/utils/vpsConfiguration.ts +87 -13
  48. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +664 -0
  49. package/esm/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
  50. package/esm/index.es.js +7 -5
  51. package/esm/index.es.js.map +1 -1
  52. package/esm/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  53. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  54. package/esm/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  55. package/esm/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  56. package/esm/src/version.d.ts +1 -1
  57. package/package.json +1 -1
  58. package/src/book-components/Chat/Chat/Chat.tsx +2 -0
  59. package/src/book-components/Chat/Chat/ChatActionsBar.tsx +17 -9
  60. package/src/book-components/Chat/Chat/ChatProps.tsx +7 -0
  61. package/src/book-components/Chat/save/_common/ChatSaveFormatHandler.ts +40 -0
  62. package/src/book-components/Chat/save/_common/createChatExportFilename.ts +20 -0
  63. package/src/book-components/Chat/utils/renderMarkdown.ts +1 -3
  64. package/src/other/templates/getTemplatesPipelineCollection.ts +718 -790
  65. package/src/scrapers/document/DocumentScraper.ts +1 -1
  66. package/src/scrapers/document-legacy/LegacyDocumentScraper.ts +1 -1
  67. package/src/version.ts +2 -2
  68. package/src/versions.txt +2 -0
  69. package/umd/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
  70. package/umd/index.umd.js +7 -5
  71. package/umd/index.umd.js.map +1 -1
  72. package/umd/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  73. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  74. package/umd/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  75. package/umd/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  76. package/umd/src/version.d.ts +1 -1
  77. package/src/conversion/validation/_importPipeline.ts +0 -88
  78. /package/esm/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
  79. /package/umd/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,234 @@
1
+ import { createVpsInstallerCommandEnvironment, resolveVpsInstallerScriptPath } from './vpsConfiguration';
2
+ import {
3
+ getInteractiveTerminalSession,
4
+ getLatestInteractiveTerminalSession,
5
+ startInteractiveTerminalSession,
6
+ stopInteractiveTerminalSession,
7
+ subscribeToInteractiveTerminalSession,
8
+ type InteractiveTerminalSessionSnapshot,
9
+ type InteractiveTerminalSessionSubscriber,
10
+ writeInteractiveTerminalSessionInput,
11
+ } from './interactiveTerminalSession';
12
+
13
+ /**
14
+ * Serializable snapshot of one interactive code-runner authentication session.
15
+ */
16
+ export type CodeRunnerAuthenticationSessionSnapshot = {
17
+ /**
18
+ * Session identifier used by the browser UI.
19
+ */
20
+ readonly id: string;
21
+
22
+ /**
23
+ * Runner being authenticated.
24
+ */
25
+ readonly agent: string;
26
+
27
+ /**
28
+ * Whether the interactive runner process is still active.
29
+ */
30
+ readonly isRunning: boolean;
31
+
32
+ /**
33
+ * Buffered session output shown in the admin terminal.
34
+ */
35
+ readonly output: string;
36
+
37
+ /**
38
+ * Session start timestamp in ISO format.
39
+ */
40
+ readonly startedAt: string;
41
+
42
+ /**
43
+ * Session finish timestamp in ISO format, when available.
44
+ */
45
+ readonly finishedAt: string | null;
46
+
47
+ /**
48
+ * Process exit code once the session has finished.
49
+ */
50
+ readonly exitCode: number | null;
51
+
52
+ /**
53
+ * Process signal once the session has finished.
54
+ */
55
+ readonly signal: NodeJS.Signals | null;
56
+ };
57
+
58
+ /**
59
+ * Browser stream callbacks used by one subscribed UI client.
60
+ */
61
+ export type CodeRunnerAuthenticationSessionSubscriber = {
62
+ /**
63
+ * Called whenever new terminal output arrives.
64
+ */
65
+ readonly onOutput: (event: Parameters<InteractiveTerminalSessionSubscriber['onOutput']>[0]) => void;
66
+
67
+ /**
68
+ * Called once the session exits.
69
+ */
70
+ readonly onExit: (event: { readonly type: 'exit'; readonly snapshot: CodeRunnerAuthenticationSessionSnapshot }) => void;
71
+ };
72
+
73
+ /**
74
+ * Starts a streamed authentication session for the currently configured standalone runner.
75
+ *
76
+ * @param agent - Runner identifier stored in `.env`.
77
+ * @returns Existing running session for the same runner or a new one.
78
+ */
79
+ export async function startCodeRunnerAuthenticationSession(
80
+ agent: string,
81
+ ): Promise<CodeRunnerAuthenticationSessionSnapshot> {
82
+ const scriptPath = await resolveVpsInstallerScriptPath();
83
+ if (!scriptPath) {
84
+ throw new Error('The VPS installer script could not be found on this server.');
85
+ }
86
+
87
+ return toRequiredCodeRunnerAuthenticationSessionSnapshot(
88
+ startInteractiveTerminalSession({
89
+ sessionKey: buildCodeRunnerAuthenticationSessionKey(agent),
90
+ title: `${agent} authentication`,
91
+ command: 'bash',
92
+ arguments: [scriptPath, 'authenticate-runner'],
93
+ env: createVpsInstallerCommandEnvironment({
94
+ isNonInteractiveModeEnabled: false,
95
+ isProcessRestartEnabled: false,
96
+ }),
97
+ metadata: {
98
+ agent,
99
+ },
100
+ unavailableErrorMessage: 'Interactive code-runner authentication is available only on the Linux VPS runtime.',
101
+ }),
102
+ agent,
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Returns the latest known authentication session for a runner.
108
+ *
109
+ * @param agent - Runner identifier.
110
+ * @returns Serializable session snapshot or `null`.
111
+ */
112
+ export function getLatestCodeRunnerAuthenticationSession(
113
+ agent: string,
114
+ ): CodeRunnerAuthenticationSessionSnapshot | null {
115
+ return toCodeRunnerAuthenticationSessionSnapshot(
116
+ getLatestInteractiveTerminalSession(buildCodeRunnerAuthenticationSessionKey(agent)),
117
+ agent,
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Looks up one authentication session by id.
123
+ *
124
+ * @param sessionId - Session identifier.
125
+ * @returns Serializable session snapshot or `null`.
126
+ */
127
+ export function getCodeRunnerAuthenticationSession(
128
+ sessionId: string,
129
+ ): CodeRunnerAuthenticationSessionSnapshot | null {
130
+ return toCodeRunnerAuthenticationSessionSnapshot(getInteractiveTerminalSession(sessionId));
131
+ }
132
+
133
+ /**
134
+ * Subscribes one browser connection to live terminal events.
135
+ *
136
+ * @param sessionId - Session identifier.
137
+ * @param subscriber - Stream callbacks.
138
+ * @returns Cleanup callback.
139
+ */
140
+ export function subscribeToCodeRunnerAuthenticationSession(
141
+ sessionId: string,
142
+ subscriber: CodeRunnerAuthenticationSessionSubscriber,
143
+ ): (() => void) | null {
144
+ return subscribeToInteractiveTerminalSession(sessionId, {
145
+ onOutput: subscriber.onOutput,
146
+ onExit: ({ snapshot }) =>
147
+ subscriber.onExit({
148
+ type: 'exit',
149
+ snapshot: toRequiredCodeRunnerAuthenticationSessionSnapshot(snapshot),
150
+ }),
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Sends interactive input to a running authentication terminal.
156
+ *
157
+ * @param sessionId - Session identifier.
158
+ * @param input - Raw text to write to stdin.
159
+ * @returns Updated session snapshot.
160
+ */
161
+ export function writeCodeRunnerAuthenticationSessionInput(
162
+ sessionId: string,
163
+ input: string,
164
+ ): CodeRunnerAuthenticationSessionSnapshot {
165
+ return toRequiredCodeRunnerAuthenticationSessionSnapshot(writeInteractiveTerminalSessionInput(sessionId, input));
166
+ }
167
+
168
+ /**
169
+ * Stops a running authentication terminal.
170
+ *
171
+ * @param sessionId - Session identifier.
172
+ * @returns Updated session snapshot.
173
+ */
174
+ export function stopCodeRunnerAuthenticationSession(
175
+ sessionId: string,
176
+ ): CodeRunnerAuthenticationSessionSnapshot {
177
+ return toRequiredCodeRunnerAuthenticationSessionSnapshot(stopInteractiveTerminalSession(sessionId));
178
+ }
179
+
180
+ /**
181
+ * Builds the stable logical session key for one runner authentication terminal.
182
+ *
183
+ * @param agent - Runner identifier.
184
+ * @returns Stable session key.
185
+ */
186
+ function buildCodeRunnerAuthenticationSessionKey(agent: string): string {
187
+ return `code-runner-authentication:${agent}`;
188
+ }
189
+
190
+ /**
191
+ * Converts one generic terminal snapshot into the runner-specific browser shape.
192
+ *
193
+ * @param session - Generic terminal snapshot.
194
+ * @param fallbackAgent - Agent name used when older sessions miss metadata.
195
+ * @returns Runner-specific snapshot or `null`.
196
+ */
197
+ function toCodeRunnerAuthenticationSessionSnapshot(
198
+ session: InteractiveTerminalSessionSnapshot | null,
199
+ fallbackAgent = '',
200
+ ): CodeRunnerAuthenticationSessionSnapshot | null {
201
+ if (!session) {
202
+ return null;
203
+ }
204
+
205
+ return {
206
+ id: session.id,
207
+ agent: session.metadata.agent || fallbackAgent,
208
+ isRunning: session.isRunning,
209
+ output: session.output,
210
+ startedAt: session.startedAt,
211
+ finishedAt: session.finishedAt,
212
+ exitCode: session.exitCode,
213
+ signal: session.signal,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Converts one generic terminal snapshot into a required runner-specific snapshot.
219
+ *
220
+ * @param session - Generic terminal snapshot.
221
+ * @returns Runner-specific snapshot.
222
+ */
223
+ function toRequiredCodeRunnerAuthenticationSessionSnapshot(
224
+ session: InteractiveTerminalSessionSnapshot | null,
225
+ fallbackAgent = '',
226
+ ): CodeRunnerAuthenticationSessionSnapshot {
227
+ const mappedSession = toCodeRunnerAuthenticationSessionSnapshot(session, fallbackAgent);
228
+
229
+ if (!mappedSession) {
230
+ throw new Error('Authentication session was not found.');
231
+ }
232
+
233
+ return mappedSession;
234
+ }
@@ -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,84 @@
1
+ import type { InteractiveTerminalSessionSubscriber } from './interactiveTerminalSession';
2
+
3
+ /**
4
+ * Minimal browser-safe terminal session shape needed by the shared SSE helper.
5
+ */
6
+ type InteractiveTerminalEventStreamSession = {
7
+ readonly isRunning: boolean;
8
+ };
9
+
10
+ /**
11
+ * Generic subscriber shape used by one streamed browser terminal route.
12
+ */
13
+ type InteractiveTerminalEventStreamSubscriber<TSession extends InteractiveTerminalEventStreamSession> = {
14
+ readonly onOutput: InteractiveTerminalSessionSubscriber['onOutput'];
15
+ readonly onExit: (event: { readonly type: 'exit'; readonly snapshot: TSession }) => void;
16
+ };
17
+
18
+ /**
19
+ * Creates one SSE response that replays buffered terminal output and then streams live events.
20
+ *
21
+ * @param request - Browser stream request.
22
+ * @param sessionId - Terminal session id.
23
+ * @param session - Existing session snapshot.
24
+ * @param subscribe - Subscription function for live terminal events.
25
+ * @returns Event stream response.
26
+ */
27
+ export function createInteractiveTerminalEventStream<TSession extends InteractiveTerminalEventStreamSession>(
28
+ request: Request,
29
+ sessionId: string,
30
+ session: TSession,
31
+ subscribe: (
32
+ sessionId: string,
33
+ subscriber: InteractiveTerminalEventStreamSubscriber<TSession>,
34
+ ) => (() => void) | null,
35
+ ): Response {
36
+ const encoder = new TextEncoder();
37
+
38
+ return new Response(
39
+ new ReadableStream({
40
+ start(controller) {
41
+ const emitEvent = (event: string, payload: unknown) => {
42
+ controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`));
43
+ };
44
+
45
+ emitEvent('snapshot', session);
46
+
47
+ if (!session.isRunning) {
48
+ controller.close();
49
+ return;
50
+ }
51
+
52
+ const unsubscribe = subscribe(sessionId, {
53
+ onOutput: ({ chunk }) => emitEvent('output', { chunk }),
54
+ onExit: ({ snapshot: nextSession }) => {
55
+ emitEvent('exit', nextSession);
56
+ unsubscribe?.();
57
+ controller.close();
58
+ },
59
+ });
60
+
61
+ if (!unsubscribe) {
62
+ controller.error(new Error('Terminal session was not found.'));
63
+ return;
64
+ }
65
+
66
+ request.signal.addEventListener(
67
+ 'abort',
68
+ () => {
69
+ unsubscribe();
70
+ controller.close();
71
+ },
72
+ { once: true },
73
+ );
74
+ },
75
+ }),
76
+ {
77
+ headers: {
78
+ 'Content-Type': 'text/event-stream',
79
+ 'Cache-Control': 'no-cache, no-transform',
80
+ Connection: 'keep-alive',
81
+ },
82
+ },
83
+ );
84
+ }