@promptbook/cli 0.113.0-10 → 0.113.0-11

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 (40) hide show
  1. package/apps/agents-server/src/app/admin/{code-runners/CodeRunnersClient.tsx → harness-auth/HarnessAuthClient.tsx} +98 -65
  2. package/apps/agents-server/src/app/admin/{code-runners → harness-auth}/page.tsx +4 -4
  3. package/apps/agents-server/src/app/api/admin/{code-runners → harness-auth}/authentication/route.ts +17 -17
  4. package/apps/agents-server/src/app/api/admin/{code-runners → harness-auth}/route.ts +13 -13
  5. package/apps/agents-server/src/app/layout.tsx +16 -0
  6. package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +32 -24
  7. package/apps/agents-server/src/components/Footer/Footer.tsx +38 -15
  8. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +5 -4
  9. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +4 -1
  10. package/apps/agents-server/src/constants/harnessAuthRoutes.ts +20 -0
  11. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -1
  12. package/apps/agents-server/src/languages/translations/czech.yaml +2 -2
  13. package/apps/agents-server/src/languages/translations/english.yaml +1 -1
  14. package/apps/agents-server/src/utils/{codeRunnerAuthentication.ts → harnessAuthentication.ts} +72 -56
  15. package/apps/agents-server/src/utils/{codeRunnerConfiguration.ts → harnessConfiguration.ts} +18 -12
  16. package/apps/agents-server/src/utils/taskTerminal/resolveAdminTaskTerminalSession.ts +28 -5
  17. package/apps/agents-server/src/utils/vpsConfiguration.ts +3 -3
  18. package/apps/agents-server/src/utils/vpsSelfUpdate/readAgentsServerFooterVersion.ts +335 -0
  19. package/apps/agents-server/src/utils/vpsSelfUpdate/vpsSelfUpdateJobHistory.ts +107 -4
  20. package/esm/index.es.js +697 -591
  21. package/esm/index.es.js.map +1 -1
  22. package/esm/scripts/run-codex-prompts/common/sleepWithCountdown.d.ts +1 -0
  23. package/esm/scripts/run-codex-prompts/common/waitUntilWorldTimeDeadline.d.ts +17 -0
  24. package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +3 -16
  25. package/esm/src/utils/agents/terminalAgentAvatarVisual.d.ts +94 -0
  26. package/esm/src/utils/agents/terminalAgentAvatarVisual.test.d.ts +1 -0
  27. package/esm/src/version.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/src/other/templates/getTemplatesPipelineCollection.ts +790 -807
  30. package/src/utils/agents/terminalAgentAvatarVisual.ts +261 -0
  31. package/src/version.ts +2 -2
  32. package/src/versions.txt +1 -0
  33. package/umd/index.umd.js +697 -591
  34. package/umd/index.umd.js.map +1 -1
  35. package/umd/scripts/run-codex-prompts/common/sleepWithCountdown.d.ts +1 -0
  36. package/umd/scripts/run-codex-prompts/common/waitUntilWorldTimeDeadline.d.ts +17 -0
  37. package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +3 -16
  38. package/umd/src/utils/agents/terminalAgentAvatarVisual.d.ts +94 -0
  39. package/umd/src/utils/agents/terminalAgentAvatarVisual.test.d.ts +1 -0
  40. package/umd/src/version.d.ts +1 -1
@@ -3,15 +3,15 @@
3
3
  import { Loader2, Save, ServerCog } from 'lucide-react';
4
4
  import { useCallback, useEffect, useState } from 'react';
5
5
  import { AdminTerminalCard } from '../../../components/AdminTerminal/AdminTerminalCard';
6
- import { AdminXtermTerminal } from '../../../components/AdminTerminal/AdminXtermTerminal';
7
6
  import { useAdminTerminalSession } from '../../../components/AdminTerminal/useAdminTerminalSession';
8
7
  import { Card } from '../../../components/Homepage/Card';
8
+ import { HARNESS_AUTH_API_PATH, HARNESS_AUTHENTICATION_API_PATH } from '../../../constants/harnessAuthRoutes';
9
9
 
10
10
  /**
11
- * Code-runner API response.
11
+ * Harness Auth API response.
12
12
  */
13
- type CodeRunnersResponse = {
14
- readonly agent?: string;
13
+ type HarnessAuthResponse = {
14
+ readonly harness?: string;
15
15
  readonly model?: string;
16
16
  readonly thinkingLevel?: string;
17
17
  readonly status?: string;
@@ -25,9 +25,9 @@ type CodeRunnersResponse = {
25
25
  /**
26
26
  * Browser-safe snapshot of one authentication terminal session.
27
27
  */
28
- type CodeRunnerAuthenticationSession = {
28
+ type HarnessAuthenticationSession = {
29
29
  readonly id: string;
30
- readonly agent: string;
30
+ readonly harness: string;
31
31
  readonly isRunning: boolean;
32
32
  readonly output: string;
33
33
  readonly startedAt: string;
@@ -37,9 +37,9 @@ type CodeRunnerAuthenticationSession = {
37
37
  };
38
38
 
39
39
  /**
40
- * Supported runner options shown by the standalone UI.
40
+ * Supported harness options shown by the standalone UI.
41
41
  */
42
- const RUNNER_OPTIONS = [
42
+ const HARNESS_OPTIONS = [
43
43
  { value: 'github-copilot', label: 'GitHub Copilot' },
44
44
  { value: 'openai-codex', label: 'OpenAI Codex' },
45
45
  { value: 'claude-code', label: 'Claude Code' },
@@ -48,7 +48,7 @@ const RUNNER_OPTIONS = [
48
48
  ] as const;
49
49
 
50
50
  /**
51
- * Contextual UI copy for the runner authentication terminal.
51
+ * Contextual UI copy for the harness authentication terminal.
52
52
  */
53
53
  const AUTHENTICATION_HINTS: Record<string, string> = {
54
54
  'github-copilot':
@@ -60,20 +60,58 @@ const AUTHENTICATION_HINTS: Record<string, string> = {
60
60
  opencode:
61
61
  'Start the terminal and complete the Opencode authentication flow directly there, including any browser/device confirmation, then exit the CLI.',
62
62
  gemini:
63
- 'Start the terminal and complete the Gemini CLI authentication prompts there, then exit the CLI after it confirms that the runner is ready.',
63
+ 'Start the terminal and complete the Gemini CLI authentication prompts there, then exit the CLI after it confirms that the harness is ready.',
64
64
  };
65
65
 
66
66
  /**
67
- * Shared input styling for code-runner controls.
67
+ * Shared input styling for harness controls.
68
68
  */
69
69
  const INPUT_CLASS_NAME =
70
70
  'w-full rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:bg-gray-50 disabled:text-gray-500';
71
71
 
72
72
  /**
73
- * Client UI for configuring the local coding runner used by durable chats.
73
+ * Shared plain-output text styling for non-interactive harness command results.
74
74
  */
75
- export function CodeRunnersClient() {
76
- const [agent, setAgent] = useState('github-copilot');
75
+ const HARNESS_PLAIN_OUTPUT_TEXT_CLASS_NAME =
76
+ 'whitespace-pre-wrap break-words font-mono text-xs leading-5 text-slate-600';
77
+
78
+ /**
79
+ * Props for the non-terminal harness output block.
80
+ */
81
+ type HarnessPlainOutputBlockProps = {
82
+ /**
83
+ * Visible output heading.
84
+ */
85
+ readonly title: string;
86
+
87
+ /**
88
+ * Plain output text to show.
89
+ */
90
+ readonly output: string;
91
+
92
+ /**
93
+ * Optional max-height and overflow classes for longer output.
94
+ */
95
+ readonly outputSizeClassName?: string;
96
+ };
97
+
98
+ /**
99
+ * Plain text command output block used when an interactive terminal would be misleading.
100
+ */
101
+ function HarnessPlainOutputBlock({ title, output, outputSizeClassName = '' }: HarnessPlainOutputBlockProps) {
102
+ return (
103
+ <div className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
104
+ <p className="text-sm font-semibold text-slate-700">{title}</p>
105
+ <pre className={`mt-2 ${outputSizeClassName} ${HARNESS_PLAIN_OUTPUT_TEXT_CLASS_NAME}`}>{output}</pre>
106
+ </div>
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Client UI for configuring the local harness used by durable chats.
112
+ */
113
+ export function HarnessAuthClient() {
114
+ const [harness, setHarness] = useState('github-copilot');
77
115
  const [model, setModel] = useState('gpt-5.4');
78
116
  const [thinkingLevel, setThinkingLevel] = useState('xhigh');
79
117
  const [status, setStatus] = useState('');
@@ -82,15 +120,15 @@ export function CodeRunnersClient() {
82
120
  const [configurationErrorMessage, setConfigurationErrorMessage] = useState<string | null>(null);
83
121
  const [configurationSuccessMessage, setConfigurationSuccessMessage] = useState<string | null>(null);
84
122
  const [applyOutput, setApplyOutput] = useState<string | null>(null);
85
- const authenticationTerminal = useAdminTerminalSession<CodeRunnerAuthenticationSession>({
86
- basePath: '/api/admin/code-runners/authentication',
123
+ const authenticationTerminal = useAdminTerminalSession<HarnessAuthenticationSession>({
124
+ basePath: HARNESS_AUTHENTICATION_API_PATH,
87
125
  loadErrorMessage: 'Failed to load the authentication session.',
88
126
  startErrorMessage: 'Failed to start the authentication session.',
89
127
  sendErrorMessage: 'Failed to send authentication input.',
90
128
  stopErrorMessage: 'Failed to stop the authentication session.',
91
- startSuccessMessage: 'Runner authentication terminal started.',
92
- finishSuccessMessage: 'Runner authentication finished.',
93
- finishErrorMessage: 'Runner authentication session ended with an error.',
129
+ startSuccessMessage: 'Harness authentication terminal started.',
130
+ finishSuccessMessage: 'Harness authentication finished.',
131
+ finishErrorMessage: 'Harness authentication session ended with an error.',
94
132
  });
95
133
  const {
96
134
  session: authenticationSession,
@@ -106,27 +144,27 @@ export function CodeRunnersClient() {
106
144
  stopSession: stopAuthenticationSession,
107
145
  } = authenticationTerminal;
108
146
  const authenticationHint =
109
- AUTHENTICATION_HINTS[agent] ||
110
- 'Start the terminal, complete the runner authentication flow there, and exit the CLI when the runner is ready.';
147
+ AUTHENTICATION_HINTS[harness] ||
148
+ 'Start the terminal, complete the harness authentication flow there, and exit the CLI when the harness is ready.';
111
149
  const errorMessage = configurationErrorMessage ?? authenticationErrorMessage;
112
150
  const successMessage = configurationSuccessMessage ?? authenticationSuccessMessage;
113
151
 
114
152
  /**
115
- * Loads current code-runner settings.
153
+ * Loads current harness settings.
116
154
  */
117
155
  const loadConfiguration = useCallback(async (): Promise<void> => {
118
156
  try {
119
157
  setIsLoading(true);
120
158
  setConfigurationErrorMessage(null);
121
159
 
122
- const response = await fetch('/api/admin/code-runners', { cache: 'no-store' });
123
- const payload = (await response.json()) as CodeRunnersResponse;
160
+ const response = await fetch(HARNESS_AUTH_API_PATH, { cache: 'no-store' });
161
+ const payload = (await response.json()) as HarnessAuthResponse;
124
162
 
125
163
  if (!response.ok) {
126
- throw new Error(payload.error || 'Failed to load code-runner configuration.');
164
+ throw new Error(payload.error || 'Failed to load harness configuration.');
127
165
  }
128
166
 
129
- setAgent(payload.agent || 'github-copilot');
167
+ setHarness(payload.harness || 'github-copilot');
130
168
  setModel(payload.model || 'gpt-5.4');
131
169
  setThinkingLevel(payload.thinkingLevel || 'xhigh');
132
170
  setStatus(payload.status || '');
@@ -134,7 +172,7 @@ export function CodeRunnersClient() {
134
172
  await loadAuthenticationSession();
135
173
  } catch (error) {
136
174
  setConfigurationErrorMessage(
137
- error instanceof Error ? error.message : 'Failed to load code-runner configuration.',
175
+ error instanceof Error ? error.message : 'Failed to load harness configuration.',
138
176
  );
139
177
  } finally {
140
178
  setIsLoading(false);
@@ -154,7 +192,7 @@ export function CodeRunnersClient() {
154
192
  }, [authenticationSession?.finishedAt, loadConfiguration]);
155
193
 
156
194
  /**
157
- * Saves code-runner settings into `.env`.
195
+ * Saves harness settings into `.env`.
158
196
  */
159
197
  async function saveConfiguration(applyRuntimeConfiguration: boolean): Promise<void> {
160
198
  try {
@@ -164,32 +202,32 @@ export function CodeRunnersClient() {
164
202
  clearAuthenticationMessages();
165
203
  setApplyOutput(null);
166
204
 
167
- const response = await fetch('/api/admin/code-runners', {
205
+ const response = await fetch(HARNESS_AUTH_API_PATH, {
168
206
  method: 'PATCH',
169
207
  headers: {
170
208
  'Content-Type': 'application/json',
171
209
  },
172
- body: JSON.stringify({ agent, model, thinkingLevel, applyRuntimeConfiguration }),
210
+ body: JSON.stringify({ harness, model, thinkingLevel, applyRuntimeConfiguration }),
173
211
  });
174
- const payload = (await response.json()) as CodeRunnersResponse;
212
+ const payload = (await response.json()) as HarnessAuthResponse;
175
213
 
176
214
  if (!response.ok) {
177
- throw new Error(payload.error || 'Failed to save code-runner configuration.');
215
+ throw new Error(payload.error || 'Failed to save harness configuration.');
178
216
  }
179
217
 
180
- setAgent(payload.agent || agent);
218
+ setHarness(payload.harness || harness);
181
219
  setModel(payload.model || model);
182
220
  setThinkingLevel(payload.thinkingLevel || thinkingLevel);
183
221
  setStatus(payload.status || '');
184
222
  setApplyOutput(payload.applyResult?.output || null);
185
223
  setConfigurationSuccessMessage(
186
224
  applyRuntimeConfiguration
187
- ? 'Code-runner configuration was saved and applied.'
188
- : 'Code-runner configuration was saved.',
225
+ ? 'Harness configuration was saved and applied.'
226
+ : 'Harness configuration was saved.',
189
227
  );
190
228
  } catch (error) {
191
229
  setConfigurationErrorMessage(
192
- error instanceof Error ? error.message : 'Failed to save code-runner configuration.',
230
+ error instanceof Error ? error.message : 'Failed to save harness configuration.',
193
231
  );
194
232
  } finally {
195
233
  setIsSaving(false);
@@ -199,9 +237,10 @@ export function CodeRunnersClient() {
199
237
  return (
200
238
  <div className="container mx-auto space-y-6 px-4 py-8">
201
239
  <div className="mt-20">
202
- <h1 className="text-3xl font-light text-gray-900">Code runners</h1>
240
+ <h1 className="text-3xl font-light text-gray-900">Harness Auth</h1>
203
241
  <p className="mt-1 text-sm text-gray-500">
204
- Configure the local runner used by the standalone Agents Server durable chat worker.
242
+ Configure the harness used by the standalone Agents Server durable chat worker and sign in to
243
+ subscription-backed CLIs such as OpenAI Codex.
205
244
  </p>
206
245
  </div>
207
246
 
@@ -219,14 +258,14 @@ export function CodeRunnersClient() {
219
258
  <Card className="hover:border-gray-200 hover:shadow-md">
220
259
  <div className="grid gap-5 md:grid-cols-3">
221
260
  <label className="space-y-2">
222
- <span className="text-sm font-semibold text-slate-700">Runner</span>
261
+ <span className="text-sm font-semibold text-slate-700">Harness</span>
223
262
  <select
224
- value={agent}
225
- onChange={(event) => setAgent(event.target.value)}
263
+ value={harness}
264
+ onChange={(event) => setHarness(event.target.value)}
226
265
  disabled={isLoading || isSaving || isStartingAuthentication}
227
266
  className={INPUT_CLASS_NAME}
228
267
  >
229
- {RUNNER_OPTIONS.map((option) => (
268
+ {HARNESS_OPTIONS.map((option) => (
230
269
  <option key={option.value} value={option.value}>
231
270
  {option.label}
232
271
  </option>
@@ -263,7 +302,7 @@ export function CodeRunnersClient() {
263
302
  className="inline-flex items-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60"
264
303
  >
265
304
  {isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
266
- Save runner
305
+ Save harness
267
306
  </button>
268
307
  <button
269
308
  type="button"
@@ -272,26 +311,29 @@ export function CodeRunnersClient() {
272
311
  className="inline-flex items-center gap-2 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60"
273
312
  >
274
313
  {isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <ServerCog className="h-4 w-4" />}
275
- Save and apply runner
314
+ Save and apply harness
276
315
  </button>
277
316
  </div>
317
+
318
+ <div className="mt-6">
319
+ <HarnessPlainOutputBlock
320
+ title="Harness status"
321
+ output={status || 'Harness status was not available.'}
322
+ />
323
+ </div>
278
324
  </Card>
279
325
 
280
326
  {applyOutput ? (
281
- <AdminXtermTerminal
282
- terminalId="code-runner-apply-output"
327
+ <HarnessPlainOutputBlock
328
+ title="Harness apply output"
283
329
  output={applyOutput}
284
- emptyState="No apply output returned."
285
- isReadOnly
286
- isPlainTextOutput
287
- heightClassName="h-72"
288
- ariaLabel="Code-runner apply output"
330
+ outputSizeClassName="max-h-72 overflow-auto"
289
331
  />
290
332
  ) : null}
291
333
 
292
334
  <AdminTerminalCard
293
335
  title="Authentication"
294
- description="Save runner changes first if you want to authenticate a different CLI, then start the saved-runner terminal here instead of SSHing into the VPS."
336
+ description="Save harness changes first if you want to authenticate a different CLI, then start the saved-harness terminal here instead of SSHing into the VPS."
295
337
  hint={authenticationHint}
296
338
  session={authenticationSession}
297
339
  onStart={() => void startAuthenticationSession()}
@@ -301,23 +343,14 @@ export function CodeRunnersClient() {
301
343
  isStarting={isStartingAuthentication}
302
344
  isSending={isSendingAuthenticationInput}
303
345
  isStopping={isStoppingAuthentication}
304
- startLabel="Authenticate saved runner"
346
+ startLabel="Authenticate saved harness"
305
347
  runningLabel="Authentication running"
306
348
  stopLabel="Stop terminal"
307
349
  outputLabel="Live authentication terminal"
308
- outputEmptyState="No authentication session output yet. Start the saved-runner terminal to see the live authentication log here."
350
+ outputEmptyState="No authentication session output yet. Start the saved-harness terminal to see the live authentication log here."
351
+ isOutputVisible={Boolean(authenticationSession?.isRunning)}
309
352
  quickActions={[{ label: 'Send Enter', input: '\n' }]}
310
- >
311
- <AdminXtermTerminal
312
- terminalId="code-runner-status"
313
- output={status || 'Runner status was not available.'}
314
- emptyState="Runner status was not available."
315
- isReadOnly
316
- isPlainTextOutput
317
- heightClassName="h-64"
318
- ariaLabel="Runner status"
319
- />
320
- </AdminTerminalCard>
353
+ />
321
354
  </div>
322
355
  );
323
356
  }
@@ -1,14 +1,14 @@
1
1
  import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
2
2
  import { isUserGlobalAdmin } from '../../../utils/isUserGlobalAdmin';
3
- import { CodeRunnersClient } from './CodeRunnersClient';
3
+ import { HarnessAuthClient } from './HarnessAuthClient';
4
4
 
5
5
  /**
6
- * Super-admin page for configuring standalone code runners.
6
+ * Super-admin page for configuring standalone harness authentication.
7
7
  */
8
- export default async function CodeRunnersPage() {
8
+ export default async function HarnessAuthPage() {
9
9
  if (!(await isUserGlobalAdmin())) {
10
10
  return <ForbiddenPage />;
11
11
  }
12
12
 
13
- return <CodeRunnersClient />;
13
+ return <HarnessAuthClient />;
14
14
  }
@@ -1,34 +1,34 @@
1
1
  import {
2
- getCodeRunnerAuthenticationSession,
3
- getLatestCodeRunnerAuthenticationSession,
4
- startCodeRunnerAuthenticationSession,
5
- stopCodeRunnerAuthenticationSession,
6
- subscribeToCodeRunnerAuthenticationSession,
7
- writeCodeRunnerAuthenticationSessionInput,
8
- } from '@/src/utils/codeRunnerAuthentication';
2
+ getHarnessAuthenticationSession,
3
+ getLatestHarnessAuthenticationSession,
4
+ startHarnessAuthenticationSession,
5
+ stopHarnessAuthenticationSession,
6
+ subscribeToHarnessAuthenticationSession,
7
+ writeHarnessAuthenticationSessionInput,
8
+ } from '@/src/utils/harnessAuthentication';
9
9
  import { createAdminTerminalRouteHandlers } from '@/src/utils/createAdminTerminalRouteHandlers';
10
- import { readConfiguredCodeRunner } from '@/src/utils/codeRunnerConfiguration';
10
+ import { readConfiguredHarness } from '@/src/utils/harnessConfiguration';
11
11
 
12
12
  export const runtime = 'nodejs';
13
13
  export const dynamic = 'force-dynamic';
14
14
 
15
15
  /**
16
- * Shared route handlers for the code-runner authentication terminal.
16
+ * Shared route handlers for the harness authentication terminal.
17
17
  */
18
18
  const authenticationTerminalRouteHandlers = createAdminTerminalRouteHandlers(
19
19
  {
20
20
  async getLatestSession() {
21
- const { agent } = await readConfiguredCodeRunner();
22
- return getLatestCodeRunnerAuthenticationSession(agent);
21
+ const { harness } = await readConfiguredHarness();
22
+ return getLatestHarnessAuthenticationSession(harness);
23
23
  },
24
- getSession: getCodeRunnerAuthenticationSession,
24
+ getSession: getHarnessAuthenticationSession,
25
25
  async startSession() {
26
- const { agent } = await readConfiguredCodeRunner();
27
- return startCodeRunnerAuthenticationSession(agent);
26
+ const { harness } = await readConfiguredHarness();
27
+ return startHarnessAuthenticationSession(harness);
28
28
  },
29
- writeSessionInput: writeCodeRunnerAuthenticationSessionInput,
30
- stopSession: stopCodeRunnerAuthenticationSession,
31
- subscribeToSession: subscribeToCodeRunnerAuthenticationSession,
29
+ writeSessionInput: writeHarnessAuthenticationSessionInput,
30
+ stopSession: stopHarnessAuthenticationSession,
31
+ subscribeToSession: subscribeToHarnessAuthenticationSession,
32
32
  },
33
33
  {
34
34
  loadErrorMessage: 'Failed to load the authentication session.',
@@ -1,13 +1,13 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { isUserGlobalAdmin } from '@/src/utils/isUserGlobalAdmin';
3
- import { readConfiguredCodeRunner, resolveCodeRunnerStatus } from '@/src/utils/codeRunnerConfiguration';
3
+ import { readConfiguredHarness, resolveHarnessStatus } from '@/src/utils/harnessConfiguration';
4
4
  import {
5
- applyVpsCodeRunnerConfiguration,
5
+ applyVpsHarnessConfiguration,
6
6
  updateVpsEnvironmentVariables,
7
7
  } from '@/src/utils/vpsConfiguration';
8
8
 
9
9
  /**
10
- * Loads configured code-runner settings from the editable VPS environment.
10
+ * Loads configured harness settings from the editable VPS environment.
11
11
  */
12
12
  export async function GET() {
13
13
  if (!(await isUserGlobalAdmin())) {
@@ -15,22 +15,22 @@ export async function GET() {
15
15
  }
16
16
 
17
17
  try {
18
- const configuredCodeRunner = await readConfiguredCodeRunner();
18
+ const configuredHarness = await readConfiguredHarness();
19
19
 
20
20
  return NextResponse.json({
21
- ...configuredCodeRunner,
22
- status: await resolveCodeRunnerStatus(configuredCodeRunner.agent),
21
+ ...configuredHarness,
22
+ status: await resolveHarnessStatus(configuredHarness.harness),
23
23
  });
24
24
  } catch (error) {
25
25
  return NextResponse.json(
26
- { error: error instanceof Error ? error.message : 'Failed to load code-runner configuration.' },
26
+ { error: error instanceof Error ? error.message : 'Failed to load harness configuration.' },
27
27
  { status: 500 },
28
28
  );
29
29
  }
30
30
  }
31
31
 
32
32
  /**
33
- * Updates code-runner environment variables.
33
+ * Updates harness environment variables.
34
34
  */
35
35
  export async function PATCH(request: Request) {
36
36
  if (!(await isUserGlobalAdmin())) {
@@ -40,7 +40,7 @@ export async function PATCH(request: Request) {
40
40
  try {
41
41
  const body = (await request.json().catch(() => null)) as
42
42
  | {
43
- readonly agent?: string;
43
+ readonly harness?: string;
44
44
  readonly model?: string;
45
45
  readonly thinkingLevel?: string;
46
46
  readonly applyRuntimeConfiguration?: boolean;
@@ -48,11 +48,11 @@ export async function PATCH(request: Request) {
48
48
  | null;
49
49
 
50
50
  if (!body) {
51
- return NextResponse.json({ error: 'Code-runner payload is required.' }, { status: 400 });
51
+ return NextResponse.json({ error: 'Harness payload is required.' }, { status: 400 });
52
52
  }
53
53
 
54
54
  await updateVpsEnvironmentVariables({
55
- PTBK_HARNESS: body.agent || '',
55
+ PTBK_HARNESS: body.harness || '',
56
56
  PTBK_MODEL: body.model || '',
57
57
  PTBK_THINKING_LEVEL: body.thinkingLevel || '',
58
58
  });
@@ -62,11 +62,11 @@ export async function PATCH(request: Request) {
62
62
 
63
63
  return NextResponse.json({
64
64
  ...payload,
65
- applyResult: body.applyRuntimeConfiguration ? await applyVpsCodeRunnerConfiguration() : null,
65
+ applyResult: body.applyRuntimeConfiguration ? await applyVpsHarnessConfiguration() : null,
66
66
  });
67
67
  } catch (error) {
68
68
  return NextResponse.json(
69
- { error: error instanceof Error ? error.message : 'Failed to update code-runner configuration.' },
69
+ { error: error instanceof Error ? error.message : 'Failed to update harness configuration.' },
70
70
  { status: 500 },
71
71
  );
72
72
  }
@@ -23,6 +23,7 @@ import type { AgentOrganizationAgent, AgentOrganizationFolder } from '../utils/a
23
23
  import { getAgentNaming } from '../utils/getAgentNaming';
24
24
  import { getCurrentUser } from '../utils/getCurrentUser';
25
25
  import { getDefaultChatPreferences } from '../utils/chatPreferences';
26
+ import { formatServerLanguageHumanReadableDate } from '../utils/localization/formatServerLanguageHumanReadableDate';
26
27
  import {
27
28
  CHAT_VISUAL_MODE_COOKIE_NAME,
28
29
  CHAT_VISUAL_MODE_METADATA_KEY,
@@ -51,6 +52,7 @@ import {
51
52
  import { resolveFileUploadAvailability } from '../utils/upload/fileUploadAvailability';
52
53
  import { readServerResourceWarningStatus } from '../utils/resourceMonitor/readServerResourceMonitorSnapshot';
53
54
  import { RESOURCE_MONITOR_OK_WARNING_STATUS } from '../utils/resourceMonitor/resourceMonitorTypes';
55
+ import { readAgentsServerFooterVersion } from '../utils/vpsSelfUpdate/readAgentsServerFooterVersion';
54
56
  import '@prisma/studio-core/ui/index.css';
55
57
  import './globals.css';
56
58
 
@@ -288,6 +290,7 @@ export default async function RootLayout({
288
290
  );
289
291
  const chatPreferencesPromise = getDefaultChatPreferences();
290
292
  const defaultIsNotificationsOnPromise = getDefaultIsNotificationsOn();
293
+ const footerVersionMetadataPromise = readAgentsServerFooterVersion();
291
294
  const fileUploadAvailabilityPromise = providedServerPromise.then((providedServer) =>
292
295
  resolveFileUploadAvailability({
293
296
  serverId: providedServer.id,
@@ -364,6 +367,7 @@ export default async function RootLayout({
364
367
  serverVisibility,
365
368
  fileUploadAvailability,
366
369
  resourceMonitorWarningStatus,
370
+ footerVersionMetadata,
367
371
  ] = await Promise.all([
368
372
  isAdminPromise,
369
373
  isGlobalAdminPromise,
@@ -383,6 +387,7 @@ export default async function RootLayout({
383
387
  serverVisibilityPromise,
384
388
  fileUploadAvailabilityPromise,
385
389
  resourceMonitorWarningStatusPromise,
390
+ footerVersionMetadataPromise,
386
391
  ]);
387
392
 
388
393
  const serverName = layoutMetadata.SERVER_NAME || 'Promptbook Agents Server';
@@ -411,6 +416,16 @@ export default async function RootLayout({
411
416
  const defaultAgentAvatarVisualId = resolveDefaultAgentAvatarVisualId(rawDefaultAgentAvatarVisual);
412
417
  const preferredLanguageSource = isServerLanguageEnforced ? rawServerLanguage : cookieLanguage || rawServerLanguage;
413
418
  const serverLanguage = resolveServerLanguageCode(preferredLanguageSource);
419
+ const footerVersionReleasedAtLabel =
420
+ formatServerLanguageHumanReadableDate(footerVersionMetadata.releasedAt, serverLanguage, {
421
+ fallbackLabel: '',
422
+ }) || null;
423
+ const footerVersion = {
424
+ label: footerVersionMetadata.label,
425
+ url: footerVersionMetadata.versionUrl,
426
+ commitSha: footerVersionMetadata.currentCommitSha,
427
+ releasedAtLabel: footerVersionReleasedAtLabel,
428
+ };
414
429
  const controlPanelOptionAvailability = getControlPanelOptionAvailability({
415
430
  metadata: layoutMetadata,
416
431
  isPushNotificationsConfigured: Boolean(webPushPublicKey),
@@ -447,6 +462,7 @@ export default async function RootLayout({
447
462
  footerLinks={
448
463
  isPublicServer ? footerLinks : footerLinks.filter((link) => link.url !== '/sitemap.xml')
449
464
  }
465
+ footerVersion={footerVersion}
450
466
  federatedServers={federatedServers}
451
467
  defaultIsSoundsOn={chatPreferences.defaultIsSoundsOn}
452
468
  defaultIsVibrationOn={chatPreferences.defaultIsVibrationOn}
@@ -105,6 +105,11 @@ type AdminTerminalCardProps<TSession extends AdminTerminalSession> = {
105
105
  */
106
106
  readonly outputEmptyState: string;
107
107
 
108
+ /**
109
+ * Whether the terminal output area should be mounted.
110
+ */
111
+ readonly isOutputVisible?: boolean;
112
+
108
113
  /**
109
114
  * Optional shortcut buttons that send raw terminal input.
110
115
  */
@@ -136,6 +141,7 @@ export function AdminTerminalCard<TSession extends AdminTerminalSession>({
136
141
  stopLabel,
137
142
  outputLabel,
138
143
  outputEmptyState,
144
+ isOutputVisible = true,
139
145
  quickActions = [],
140
146
  children,
141
147
  }: AdminTerminalCardProps<TSession>) {
@@ -175,32 +181,34 @@ export function AdminTerminalCard<TSession extends AdminTerminalSession>({
175
181
 
176
182
  {children}
177
183
 
178
- <div className="space-y-2">
179
- <div className="flex flex-wrap items-center justify-between gap-3">
180
- <h3 className="text-sm font-semibold text-slate-700">{outputLabel}</h3>
181
- {session ? (
182
- <span className="text-xs text-slate-500">
183
- {session.isRunning
184
- ? 'Running'
185
- : session.exitCode === 0
186
- ? 'Finished successfully'
187
- : 'Finished with an error'}
188
- </span>
189
- ) : (
190
- <span className="text-xs text-slate-500">No session started yet.</span>
191
- )}
184
+ {isOutputVisible ? (
185
+ <div className="space-y-2">
186
+ <div className="flex flex-wrap items-center justify-between gap-3">
187
+ <h3 className="text-sm font-semibold text-slate-700">{outputLabel}</h3>
188
+ {session ? (
189
+ <span className="text-xs text-slate-500">
190
+ {session.isRunning
191
+ ? 'Running'
192
+ : session.exitCode === 0
193
+ ? 'Finished successfully'
194
+ : 'Finished with an error'}
195
+ </span>
196
+ ) : (
197
+ <span className="text-xs text-slate-500">No session started yet.</span>
198
+ )}
199
+ </div>
200
+ <AdminXtermTerminal
201
+ terminalId={session?.id || `${title}:empty`}
202
+ output={session?.output || ''}
203
+ emptyState={outputEmptyState}
204
+ isRunning={Boolean(session?.isRunning)}
205
+ ariaLabel={outputLabel}
206
+ onData={onSend}
207
+ />
192
208
  </div>
193
- <AdminXtermTerminal
194
- terminalId={session?.id || `${title}:empty`}
195
- output={session?.output || ''}
196
- emptyState={outputEmptyState}
197
- isRunning={Boolean(session?.isRunning)}
198
- ariaLabel={outputLabel}
199
- onData={onSend}
200
- />
201
- </div>
209
+ ) : null}
202
210
 
203
- {quickActions.length > 0 ? (
211
+ {isOutputVisible && quickActions.length > 0 ? (
204
212
  <div className="flex flex-wrap gap-3">
205
213
  {quickActions.map((quickAction) => (
206
214
  <button