@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
@@ -0,0 +1,335 @@
1
+ import { PROMPTBOOK_ENGINE_VERSION } from '../../../../../src/version';
2
+ import {
3
+ readConfiguredVpsSelfUpdateOriginRepositoryUrl,
4
+ VPS_SELF_UPDATE_DEFAULT_ORIGIN_REPOSITORY_URL,
5
+ } from './vpsSelfUpdateOriginRepository';
6
+ import { resolveManagedPromptbookRepositoryDirectory } from './vpsSelfUpdateConfiguration';
7
+ import { abbreviateCommitSha, readCommitMetadataFromRepository, runGitInRepository } from './vpsSelfUpdateRepository';
8
+
9
+ /**
10
+ * Git tag pattern used for Promptbook/Agents Server release tags.
11
+ *
12
+ * @private constant of `vpsSelfUpdate`
13
+ */
14
+ const AGENTS_SERVER_VERSION_TAG_PATTERN = /^v?[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/u;
15
+
16
+ /**
17
+ * Generated version tag used when the local Git checkout does not expose reachable tags.
18
+ *
19
+ * @private constant of `vpsSelfUpdate`
20
+ */
21
+ const DEFAULT_AGENTS_SERVER_VERSION_TAG = `v${PROMPTBOOK_ENGINE_VERSION}`;
22
+
23
+ /**
24
+ * How long the Git-derived footer version summary stays cached.
25
+ *
26
+ * @private constant of `vpsSelfUpdate`
27
+ */
28
+ const AGENTS_SERVER_FOOTER_VERSION_CACHE_TTL_MILLISECONDS = 60 * 1000;
29
+
30
+ /**
31
+ * Git tag match patterns accepted by `git describe`.
32
+ *
33
+ * @private constant of `vpsSelfUpdate`
34
+ */
35
+ const GIT_DESCRIBE_VERSION_TAG_MATCH_ARGUMENTS = ['--match', 'v[0-9]*', '--match', '[0-9]*'] as const;
36
+
37
+ /**
38
+ * Cached footer version promise shared by concurrent layout renders.
39
+ *
40
+ * @private variable of `vpsSelfUpdate`
41
+ */
42
+ let cachedAgentsServerFooterVersionPromise: Promise<AgentsServerFooterVersion> | null = null;
43
+
44
+ /**
45
+ * Expiration timestamp for {@link cachedAgentsServerFooterVersionPromise}.
46
+ *
47
+ * @private variable of `vpsSelfUpdate`
48
+ */
49
+ let cachedAgentsServerFooterVersionExpiresAt = 0;
50
+
51
+ /**
52
+ * Lightweight Agents Server version summary consumed by the shared footer.
53
+ *
54
+ * @private type of `vpsSelfUpdate`
55
+ */
56
+ export type AgentsServerFooterVersion = {
57
+ /**
58
+ * Release tag shown as the base Agents Server version.
59
+ */
60
+ readonly versionTag: string;
61
+ /**
62
+ * Display label, optionally including the deployed commit short hash.
63
+ */
64
+ readonly label: string;
65
+ /**
66
+ * Browser URL of the backing Git repository.
67
+ */
68
+ readonly repositoryUrl: string;
69
+ /**
70
+ * Browser URL linked from the footer version text.
71
+ */
72
+ readonly versionUrl: string;
73
+ /**
74
+ * Current deployed commit hash.
75
+ */
76
+ readonly currentCommitSha: string | null;
77
+ /**
78
+ * Current deployed commit short hash.
79
+ */
80
+ readonly currentCommitShortSha: string | null;
81
+ /**
82
+ * Commit hash that the release tag points to.
83
+ */
84
+ readonly versionCommitSha: string | null;
85
+ /**
86
+ * Release commit author timestamp in ISO format.
87
+ */
88
+ readonly releasedAt: string | null;
89
+ };
90
+
91
+ /**
92
+ * Reads the footer version summary while caching the Git work outside the client footer render path.
93
+ *
94
+ * @returns Git-derived Agents Server version information safe to pass into the client footer.
95
+ *
96
+ * @private function of `vpsSelfUpdate`
97
+ */
98
+ export async function readAgentsServerFooterVersion(): Promise<AgentsServerFooterVersion> {
99
+ const now = Date.now();
100
+ const cachedAgentsServerFooterVersion = cachedAgentsServerFooterVersionPromise;
101
+ const isCacheFresh = cachedAgentsServerFooterVersion !== null && cachedAgentsServerFooterVersionExpiresAt > now;
102
+
103
+ if (isCacheFresh) {
104
+ return cachedAgentsServerFooterVersion;
105
+ }
106
+
107
+ cachedAgentsServerFooterVersionPromise = readUncachedAgentsServerFooterVersion();
108
+ cachedAgentsServerFooterVersionExpiresAt = now + AGENTS_SERVER_FOOTER_VERSION_CACHE_TTL_MILLISECONDS;
109
+ return cachedAgentsServerFooterVersionPromise;
110
+ }
111
+
112
+ /**
113
+ * Formats the compact version label shown in the footer.
114
+ *
115
+ * @param options - Release tag and optional deployed commit short hash.
116
+ * @returns Footer version label.
117
+ *
118
+ * @private utility of `vpsSelfUpdate`
119
+ */
120
+ export function formatAgentsServerFooterVersionLabel(options: {
121
+ readonly versionTag: string;
122
+ readonly currentCommitShortSha: string | null;
123
+ }): string {
124
+ if (!options.currentCommitShortSha) {
125
+ return options.versionTag;
126
+ }
127
+
128
+ return `${options.versionTag} (${options.currentCommitShortSha})`;
129
+ }
130
+
131
+ /**
132
+ * Normalizes a Git repository clone URL to a browser URL.
133
+ *
134
+ * @param repositoryUrl - Git clone URL.
135
+ * @returns Browser URL for the repository.
136
+ *
137
+ * @private utility of `vpsSelfUpdate`
138
+ */
139
+ export function normalizeGitRepositoryWebUrl(repositoryUrl: string): string {
140
+ const normalizedRepositoryUrl = repositoryUrl
141
+ .trim()
142
+ .replace(/^git\+/u, '')
143
+ .replace(/\.git$/u, '');
144
+ return normalizedRepositoryUrl || VPS_SELF_UPDATE_DEFAULT_ORIGIN_REPOSITORY_URL.replace(/\.git$/u, '');
145
+ }
146
+
147
+ /**
148
+ * Normalizes a release version into the repository tag format.
149
+ *
150
+ * @param value - Raw tag or generated version.
151
+ * @returns Normalized version tag or `null` when the value is not a release tag.
152
+ *
153
+ * @private utility of `vpsSelfUpdate`
154
+ */
155
+ export function normalizeAgentsServerVersionTag(value: string | null | undefined): string | null {
156
+ const trimmedValue = value?.trim() || '';
157
+ if (!AGENTS_SERVER_VERSION_TAG_PATTERN.test(trimmedValue)) {
158
+ return null;
159
+ }
160
+
161
+ return trimmedValue.startsWith('v') ? trimmedValue : `v${trimmedValue}`;
162
+ }
163
+
164
+ /**
165
+ * Resolves the URL linked from the footer version text.
166
+ *
167
+ * @param options - Repository URL, tag, current commit and comparison state.
168
+ * @returns Browser URL for the most specific Git reference.
169
+ *
170
+ * @private utility of `vpsSelfUpdate`
171
+ */
172
+ export function resolveAgentsServerFooterVersionUrl(options: {
173
+ readonly repositoryUrl: string;
174
+ readonly versionTag: string;
175
+ readonly currentCommitSha: string | null;
176
+ readonly isCurrentCommitNewerThanVersionTag: boolean;
177
+ }): string {
178
+ if (!options.repositoryUrl.startsWith('https://github.com/')) {
179
+ return options.repositoryUrl;
180
+ }
181
+
182
+ if (options.isCurrentCommitNewerThanVersionTag && options.currentCommitSha) {
183
+ return `${options.repositoryUrl}/commit/${options.currentCommitSha}`;
184
+ }
185
+
186
+ return `${options.repositoryUrl}/releases/tag/${encodeURIComponent(options.versionTag)}`;
187
+ }
188
+
189
+ /**
190
+ * Reads the footer version summary without using the module-level cache.
191
+ *
192
+ * @returns Git-derived Agents Server version information.
193
+ */
194
+ async function readUncachedAgentsServerFooterVersion(): Promise<AgentsServerFooterVersion> {
195
+ const repositoryUrl = normalizeGitRepositoryWebUrl(await readFooterVersionOriginRepositoryUrl());
196
+ const repositoryDirectory = await resolveAgentsServerFooterRepositoryDirectory();
197
+
198
+ if (!repositoryDirectory) {
199
+ return createFallbackAgentsServerFooterVersion(repositoryUrl);
200
+ }
201
+
202
+ const [currentCommit, versionTag] = await Promise.all([
203
+ readCommitMetadataFromRepository(repositoryDirectory, 'HEAD'),
204
+ readAgentsServerVersionTagFromRepository(repositoryDirectory),
205
+ ]);
206
+ const versionCommit = await readCommitMetadataFromRepository(repositoryDirectory, `${versionTag}^{commit}`);
207
+ const currentCommitSha = currentCommit?.commitSha ?? readCurrentCommitShaFromEnvironment();
208
+ const currentCommitShortSha = abbreviateCommitSha(currentCommitSha);
209
+ const versionCommitSha = versionCommit?.commitSha ?? null;
210
+ const isCurrentCommitNewerThanVersionTag = Boolean(
211
+ currentCommitSha && versionCommitSha && currentCommitSha !== versionCommitSha,
212
+ );
213
+ const displayedCommitShortSha = isCurrentCommitNewerThanVersionTag ? currentCommitShortSha : null;
214
+
215
+ return {
216
+ versionTag,
217
+ label: formatAgentsServerFooterVersionLabel({
218
+ versionTag,
219
+ currentCommitShortSha: displayedCommitShortSha,
220
+ }),
221
+ repositoryUrl,
222
+ versionUrl: resolveAgentsServerFooterVersionUrl({
223
+ repositoryUrl,
224
+ versionTag,
225
+ currentCommitSha,
226
+ isCurrentCommitNewerThanVersionTag,
227
+ }),
228
+ currentCommitSha,
229
+ currentCommitShortSha,
230
+ versionCommitSha,
231
+ releasedAt: versionCommit?.authoredAt ?? null,
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Reads the configured origin URL without letting optional VPS configuration block footer rendering.
237
+ *
238
+ * @returns Configured origin URL or the default Promptbook repository.
239
+ */
240
+ async function readFooterVersionOriginRepositoryUrl(): Promise<string> {
241
+ try {
242
+ return await readConfiguredVpsSelfUpdateOriginRepositoryUrl();
243
+ } catch {
244
+ return VPS_SELF_UPDATE_DEFAULT_ORIGIN_REPOSITORY_URL;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Resolves a local Git checkout suitable for lightweight version metadata reads.
250
+ *
251
+ * @returns Repository directory or `null` when Git metadata is unavailable.
252
+ */
253
+ async function resolveAgentsServerFooterRepositoryDirectory(): Promise<string | null> {
254
+ const managedRepositoryDirectory = await resolveManagedPromptbookRepositoryDirectory().catch(() => null);
255
+ const candidateDirectories = [
256
+ managedRepositoryDirectory,
257
+ process.env.PTBK_REPOSITORY_DIR?.trim() || null,
258
+ process.cwd(),
259
+ ];
260
+
261
+ for (const candidateDirectory of candidateDirectories) {
262
+ if (!candidateDirectory) {
263
+ continue;
264
+ }
265
+
266
+ const gitRepositoryRoot = await runGitInRepository(candidateDirectory, ['rev-parse', '--show-toplevel']);
267
+ if (gitRepositoryRoot) {
268
+ return candidateDirectory;
269
+ }
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ /**
276
+ * Reads the nearest reachable release tag for the deployed checkout.
277
+ *
278
+ * @param repositoryDirectory - Repository checkout path.
279
+ * @returns Normalized release tag.
280
+ */
281
+ async function readAgentsServerVersionTagFromRepository(repositoryDirectory: string): Promise<string> {
282
+ const describedTag = await runGitInRepository(repositoryDirectory, [
283
+ 'describe',
284
+ '--tags',
285
+ '--abbrev=0',
286
+ ...GIT_DESCRIBE_VERSION_TAG_MATCH_ARGUMENTS,
287
+ 'HEAD',
288
+ ]);
289
+
290
+ return (
291
+ normalizeAgentsServerVersionTag(describedTag) ??
292
+ normalizeAgentsServerVersionTag(DEFAULT_AGENTS_SERVER_VERSION_TAG) ??
293
+ DEFAULT_AGENTS_SERVER_VERSION_TAG
294
+ );
295
+ }
296
+
297
+ /**
298
+ * Reads the deployed commit hash from public deployment metadata when local Git is unavailable.
299
+ *
300
+ * @returns Commit hash or `null`.
301
+ */
302
+ function readCurrentCommitShaFromEnvironment(): string | null {
303
+ return process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA?.trim() || null;
304
+ }
305
+
306
+ /**
307
+ * Creates a resilient fallback when the server cannot inspect a Git checkout.
308
+ *
309
+ * @param repositoryUrl - Browser URL of the configured Git repository.
310
+ * @returns Fallback footer version summary.
311
+ */
312
+ function createFallbackAgentsServerFooterVersion(repositoryUrl: string): AgentsServerFooterVersion {
313
+ const currentCommitSha = readCurrentCommitShaFromEnvironment();
314
+ const versionTag =
315
+ normalizeAgentsServerVersionTag(DEFAULT_AGENTS_SERVER_VERSION_TAG) ?? DEFAULT_AGENTS_SERVER_VERSION_TAG;
316
+
317
+ return {
318
+ versionTag,
319
+ label: formatAgentsServerFooterVersionLabel({
320
+ versionTag,
321
+ currentCommitShortSha: null,
322
+ }),
323
+ repositoryUrl,
324
+ versionUrl: resolveAgentsServerFooterVersionUrl({
325
+ repositoryUrl,
326
+ versionTag,
327
+ currentCommitSha,
328
+ isCurrentCommitNewerThanVersionTag: false,
329
+ }),
330
+ currentCommitSha,
331
+ currentCommitShortSha: abbreviateCommitSha(currentCommitSha),
332
+ versionCommitSha: null,
333
+ releasedAt: null,
334
+ };
335
+ }
@@ -3,10 +3,17 @@ import { dirname } from 'path';
3
3
  import { resolveVpsSelfUpdateEnvironment } from './vpsSelfUpdateEnvironment';
4
4
  import { resolveVpsSelfUpdateJobIdentity } from './vpsSelfUpdateJobIdentity';
5
5
  import { readPersistedVpsSelfUpdateJob } from './readPersistedVpsSelfUpdateJob';
6
+ import { resolveVpsSelfUpdateJobForOverview } from './resolveVpsSelfUpdateJobForOverview';
7
+ import {
8
+ readCurrentVpsSelfUpdateEnvironment,
9
+ resolveManagedPromptbookRepositoryDirectory,
10
+ } from './vpsSelfUpdateConfiguration';
11
+ import { readCommitMetadataFromRepository } from './vpsSelfUpdateRepository';
6
12
  import { resolveVpsSelfUpdateTaskHistoryFilePath } from './vpsSelfUpdateStateFiles';
7
13
  import type {
8
14
  VpsSelfUpdateDatabaseMigrationSnapshot,
9
15
  VpsSelfUpdateDatabaseMigrationStatus,
16
+ VpsSelfUpdateJobOverviewContext,
10
17
  VpsSelfUpdateJobSnapshot,
11
18
  VpsSelfUpdateJobStatus,
12
19
  VpsSelfUpdateJobTrigger,
@@ -55,34 +62,61 @@ type VpsSelfUpdateTaskHistoryFile = {
55
62
  readonly jobs: ReadonlyArray<VpsSelfUpdateJobSnapshot>;
56
63
  };
57
64
 
65
+ /**
66
+ * Options for reading task-manager self-update snapshots.
67
+ *
68
+ * @private type of `vpsSelfUpdate`
69
+ */
70
+ type ReadVpsSelfUpdateJobTaskSnapshotsOptions = {
71
+ /**
72
+ * Current runtime state used to reinterpret a stale restart as a successful update.
73
+ */
74
+ readonly overviewContext?: VpsSelfUpdateJobOverviewContext | null;
75
+ };
76
+
58
77
  /**
59
78
  * Archives one latest self-update snapshot before a new self-update overwrites the singleton status file.
60
79
  *
61
80
  * @param job - Latest job snapshot to preserve in task history.
81
+ * @param options - Optional current runtime state override.
62
82
  *
63
83
  * @private function of `vpsSelfUpdate`
64
84
  */
65
- export async function preserveVpsSelfUpdateJobInTaskHistory(job: VpsSelfUpdateJobSnapshot): Promise<void> {
85
+ export async function preserveVpsSelfUpdateJobInTaskHistory(
86
+ job: VpsSelfUpdateJobSnapshot,
87
+ options: ReadVpsSelfUpdateJobTaskSnapshotsOptions = {},
88
+ ): Promise<void> {
66
89
  if (job.status === 'idle') {
67
90
  return;
68
91
  }
69
92
 
93
+ const [resolvedJob] = await resolveVpsSelfUpdateJobTaskSnapshots([job], options);
70
94
  const history = await readVpsSelfUpdateJobTaskHistory();
71
- const jobs = collectUniqueVpsSelfUpdateJobs([sanitizeVpsSelfUpdateJobForTaskHistory(job), ...history]);
95
+ const jobs = collectUniqueVpsSelfUpdateJobs([
96
+ sanitizeVpsSelfUpdateJobForTaskHistory(resolvedJob ?? job),
97
+ ...history,
98
+ ]);
72
99
  await writeVpsSelfUpdateJobTaskHistory(jobs);
73
100
  }
74
101
 
75
102
  /**
76
103
  * Reads all self-update task snapshots that should be surfaced in the admin task manager.
77
104
  *
105
+ * A successful self-update can restart the old server before it writes the final succeeded status.
106
+ * Task-manager rows therefore use the same target-commit reconciliation as `/admin/update`.
107
+ *
108
+ * @param options - Optional current runtime state override.
78
109
  * @returns Latest singleton status followed by archived history, with duplicates removed.
79
110
  *
80
111
  * @private function of `vpsSelfUpdate`
81
112
  */
82
- export async function readVpsSelfUpdateJobTaskSnapshots(): Promise<Array<VpsSelfUpdateJobSnapshot>> {
113
+ export async function readVpsSelfUpdateJobTaskSnapshots(
114
+ options: ReadVpsSelfUpdateJobTaskSnapshotsOptions = {},
115
+ ): Promise<Array<VpsSelfUpdateJobSnapshot>> {
83
116
  const latestJob = await readPersistedVpsSelfUpdateJob({ isLogTailIncluded: false });
84
117
  const history = await readVpsSelfUpdateJobTaskHistory();
85
- return collectUniqueVpsSelfUpdateJobs([latestJob, ...history]).filter((job) => job.status !== 'idle');
118
+ const jobs = collectUniqueVpsSelfUpdateJobs([latestJob, ...history]).filter((job) => job.status !== 'idle');
119
+ return await resolveVpsSelfUpdateJobTaskSnapshots(jobs, options);
86
120
  }
87
121
 
88
122
  /**
@@ -155,6 +189,75 @@ function collectUniqueVpsSelfUpdateJobs(
155
189
  return [...jobsByIdentity.values()];
156
190
  }
157
191
 
192
+ /**
193
+ * Resolves restart-aware task snapshots through the shared update-page reconciliation.
194
+ *
195
+ * @param jobs - Self-update task snapshots.
196
+ * @param options - Optional current runtime state override.
197
+ * @returns Resolved task snapshots.
198
+ *
199
+ * @private function of `vpsSelfUpdate`
200
+ */
201
+ async function resolveVpsSelfUpdateJobTaskSnapshots(
202
+ jobs: ReadonlyArray<VpsSelfUpdateJobSnapshot>,
203
+ options: ReadVpsSelfUpdateJobTaskSnapshotsOptions,
204
+ ): Promise<Array<VpsSelfUpdateJobSnapshot>> {
205
+ if (!jobs.some(isRestartedVpsSelfUpdateCandidate)) {
206
+ return [...jobs];
207
+ }
208
+
209
+ const overviewContext =
210
+ options.overviewContext === undefined
211
+ ? await readCurrentVpsSelfUpdateJobOverviewContext()
212
+ : options.overviewContext;
213
+
214
+ if (!overviewContext) {
215
+ return [...jobs];
216
+ }
217
+
218
+ return jobs.map((job) => resolveVpsSelfUpdateJobForOverview(job, overviewContext));
219
+ }
220
+
221
+ /**
222
+ * Returns whether a job could be the stale old process left by a successful restart.
223
+ *
224
+ * The full success check stays inside `resolveVpsSelfUpdateJobForOverview`; this predicate only avoids
225
+ * local git reads for ordinary completed, failed, or running jobs.
226
+ *
227
+ * @param job - Self-update job snapshot.
228
+ * @returns `true` when current repository state may change how the job should be displayed.
229
+ *
230
+ * @private function of `vpsSelfUpdate`
231
+ */
232
+ function isRestartedVpsSelfUpdateCandidate(job: VpsSelfUpdateJobSnapshot): boolean {
233
+ return job.status === 'failed' && job.isStale;
234
+ }
235
+
236
+ /**
237
+ * Reads the lightweight current runtime state required for self-update restart reconciliation.
238
+ *
239
+ * @returns Current job overview context or `null` when local repository state is unavailable.
240
+ *
241
+ * @private function of `vpsSelfUpdate`
242
+ */
243
+ async function readCurrentVpsSelfUpdateJobOverviewContext(): Promise<VpsSelfUpdateJobOverviewContext | null> {
244
+ const currentEnvironment = await readCurrentVpsSelfUpdateEnvironment();
245
+ const repositoryDirectory = await resolveManagedPromptbookRepositoryDirectory();
246
+ if (!repositoryDirectory) {
247
+ return null;
248
+ }
249
+
250
+ const currentCommit = await readCommitMetadataFromRepository(repositoryDirectory, 'HEAD');
251
+ if (!currentCommit) {
252
+ return null;
253
+ }
254
+
255
+ return {
256
+ currentEnvironment,
257
+ currentCommitSha: currentCommit.commitSha,
258
+ };
259
+ }
260
+
158
261
  /**
159
262
  * Removes log text before persisting a task-history snapshot.
160
263
  *