@vellumai/cli 0.5.6 → 0.5.7
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/knip.json +3 -1
- package/package.json +1 -1
- package/src/commands/backup.ts +28 -13
- package/src/commands/hatch.ts +96 -60
- package/src/commands/retire.ts +5 -5
- package/src/commands/rollback.ts +298 -135
- package/src/commands/upgrade.ts +548 -200
- package/src/lib/assistant-config.ts +33 -6
- package/src/lib/aws.ts +2 -0
- package/src/lib/backup-ops.ts +213 -0
- package/src/lib/cli-error.ts +91 -0
- package/src/lib/config-utils.ts +59 -0
- package/src/lib/docker.ts +45 -37
- package/src/lib/doctor-client.ts +11 -1
- package/src/lib/gcp.ts +5 -1
- package/src/lib/local.ts +29 -9
- package/src/lib/platform-client.ts +17 -3
- package/src/lib/platform-releases.ts +112 -0
- package/src/lib/upgrade-lifecycle.ts +237 -0
- package/src/lib/workspace-git.ts +39 -0
package/src/commands/rollback.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
|
+
import { join } from "path";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
findAssistantByName,
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
import type { AssistantEntry } from "../lib/assistant-config";
|
|
10
11
|
import {
|
|
11
12
|
captureImageRefs,
|
|
12
|
-
clearSigningKeyBootstrapLock,
|
|
13
13
|
GATEWAY_INTERNAL_PORT,
|
|
14
14
|
dockerResourceNames,
|
|
15
15
|
migrateCesSecurityFiles,
|
|
@@ -18,12 +18,25 @@ import {
|
|
|
18
18
|
stopContainers,
|
|
19
19
|
} from "../lib/docker";
|
|
20
20
|
import type { ServiceName } from "../lib/docker";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
loadBootstrapSecret,
|
|
23
|
+
saveBootstrapSecret,
|
|
24
|
+
} from "../lib/guardian-token";
|
|
25
|
+
import { restoreBackup } from "../lib/backup-ops.js";
|
|
26
|
+
import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
|
|
22
27
|
import {
|
|
23
28
|
broadcastUpgradeEvent,
|
|
29
|
+
buildCompleteEvent,
|
|
30
|
+
buildProgressEvent,
|
|
31
|
+
buildStartingEvent,
|
|
32
|
+
buildUpgradeCommitMessage,
|
|
24
33
|
captureContainerEnv,
|
|
34
|
+
CONTAINER_ENV_EXCLUDE_KEYS,
|
|
35
|
+
rollbackMigrations,
|
|
36
|
+
UPGRADE_PROGRESS,
|
|
25
37
|
waitForReady,
|
|
26
|
-
} from "
|
|
38
|
+
} from "../lib/upgrade-lifecycle.js";
|
|
39
|
+
import { commitWorkspaceState } from "../lib/workspace-git.js";
|
|
27
40
|
|
|
28
41
|
function parseArgs(): { name: string | null } {
|
|
29
42
|
const args = process.argv.slice(3);
|
|
@@ -53,6 +66,7 @@ function parseArgs(): { name: string | null } {
|
|
|
53
66
|
name = arg;
|
|
54
67
|
} else {
|
|
55
68
|
console.error(`Error: Unknown option '${arg}'.`);
|
|
69
|
+
emitCliError("UNKNOWN", `Unknown option '${arg}'`);
|
|
56
70
|
process.exit(1);
|
|
57
71
|
}
|
|
58
72
|
}
|
|
@@ -84,6 +98,10 @@ function resolveTargetAssistant(nameArg: string | null): AssistantEntry {
|
|
|
84
98
|
const entry = findAssistantByName(nameArg);
|
|
85
99
|
if (!entry) {
|
|
86
100
|
console.error(`No assistant found with name '${nameArg}'.`);
|
|
101
|
+
emitCliError(
|
|
102
|
+
"ASSISTANT_NOT_FOUND",
|
|
103
|
+
`No assistant found with name '${nameArg}'.`,
|
|
104
|
+
);
|
|
87
105
|
process.exit(1);
|
|
88
106
|
}
|
|
89
107
|
return entry;
|
|
@@ -99,11 +117,14 @@ function resolveTargetAssistant(nameArg: string | null): AssistantEntry {
|
|
|
99
117
|
if (all.length === 1) return all[0];
|
|
100
118
|
|
|
101
119
|
if (all.length === 0) {
|
|
102
|
-
|
|
120
|
+
const msg = "No assistants found. Run 'vellum hatch' first.";
|
|
121
|
+
console.error(msg);
|
|
122
|
+
emitCliError("ASSISTANT_NOT_FOUND", msg);
|
|
103
123
|
} else {
|
|
104
|
-
|
|
105
|
-
"Multiple assistants found. Specify a name or set an active assistant with 'vellum use <name>'."
|
|
106
|
-
);
|
|
124
|
+
const msg =
|
|
125
|
+
"Multiple assistants found. Specify a name or set an active assistant with 'vellum use <name>'.";
|
|
126
|
+
console.error(msg);
|
|
127
|
+
emitCliError("ASSISTANT_NOT_FOUND", msg);
|
|
107
128
|
}
|
|
108
129
|
process.exit(1);
|
|
109
130
|
}
|
|
@@ -115,26 +136,29 @@ export async function rollback(): Promise<void> {
|
|
|
115
136
|
|
|
116
137
|
// Only Docker assistants support rollback
|
|
117
138
|
if (cloud !== "docker") {
|
|
118
|
-
|
|
119
|
-
"Rollback is only supported for Docker assistants. For managed assistants, use the version picker to upgrade to the previous version."
|
|
120
|
-
);
|
|
139
|
+
const msg =
|
|
140
|
+
"Rollback is only supported for Docker assistants. For managed assistants, use the version picker to upgrade to the previous version.";
|
|
141
|
+
console.error(msg);
|
|
142
|
+
emitCliError("UNSUPPORTED_TOPOLOGY", msg);
|
|
121
143
|
process.exit(1);
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
// Verify rollback state exists
|
|
125
147
|
if (!entry.previousServiceGroupVersion || !entry.previousContainerInfo) {
|
|
126
|
-
|
|
127
|
-
"No rollback state available. Run `vellum upgrade` first to create a rollback point."
|
|
128
|
-
);
|
|
148
|
+
const msg =
|
|
149
|
+
"No rollback state available. Run `vellum upgrade` first to create a rollback point.";
|
|
150
|
+
console.error(msg);
|
|
151
|
+
emitCliError("ROLLBACK_NO_STATE", msg);
|
|
129
152
|
process.exit(1);
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
// Verify all three digest fields are present
|
|
133
156
|
const prev = entry.previousContainerInfo;
|
|
134
157
|
if (!prev.assistantDigest || !prev.gatewayDigest || !prev.cesDigest) {
|
|
135
|
-
|
|
136
|
-
"Incomplete rollback state. Previous container digests are missing."
|
|
137
|
-
);
|
|
158
|
+
const msg =
|
|
159
|
+
"Incomplete rollback state. Previous container digests are missing.";
|
|
160
|
+
console.error(msg);
|
|
161
|
+
emitCliError("ROLLBACK_NO_STATE", msg);
|
|
138
162
|
process.exit(1);
|
|
139
163
|
}
|
|
140
164
|
|
|
@@ -148,133 +172,272 @@ export async function rollback(): Promise<void> {
|
|
|
148
172
|
const instanceName = entry.assistantId;
|
|
149
173
|
const res = dockerResourceNames(instanceName);
|
|
150
174
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
"PATH",
|
|
176
|
-
]);
|
|
177
|
-
for (const envVar of ["ANTHROPIC_API_KEY", "VELLUM_PLATFORM_URL"]) {
|
|
178
|
-
if (process.env[envVar]) {
|
|
179
|
-
envKeysSetByRunArgs.add(envVar);
|
|
175
|
+
try {
|
|
176
|
+
const workspaceDir = entry.resources
|
|
177
|
+
? join(entry.resources.instanceDir, ".vellum", "workspace")
|
|
178
|
+
: undefined;
|
|
179
|
+
|
|
180
|
+
// Record rollback start in workspace git history
|
|
181
|
+
if (workspaceDir) {
|
|
182
|
+
try {
|
|
183
|
+
await commitWorkspaceState(
|
|
184
|
+
workspaceDir,
|
|
185
|
+
buildUpgradeCommitMessage({
|
|
186
|
+
action: "rollback",
|
|
187
|
+
phase: "starting",
|
|
188
|
+
from: entry.serviceGroupVersion ?? "unknown",
|
|
189
|
+
to: entry.previousServiceGroupVersion ?? "unknown",
|
|
190
|
+
topology: "docker",
|
|
191
|
+
assistantId: entry.assistantId,
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
console.warn(
|
|
196
|
+
`⚠️ Failed to create pre-rollback workspace commit: ${err instanceof Error ? err.message : String(err)}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
180
199
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
200
|
+
|
|
201
|
+
console.log(
|
|
202
|
+
`🔄 Rolling back Docker assistant '${instanceName}' to ${entry.previousServiceGroupVersion}...\n`,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Capture current container env
|
|
206
|
+
console.log("💾 Capturing existing container environment...");
|
|
207
|
+
const capturedEnv = await captureContainerEnv(res.assistantContainer);
|
|
208
|
+
console.log(
|
|
209
|
+
` Captured ${Object.keys(capturedEnv).length} env var(s) from ${res.assistantContainer}\n`,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Extract CES_SERVICE_TOKEN from captured env, or generate fresh one
|
|
213
|
+
const cesServiceToken =
|
|
214
|
+
capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex");
|
|
215
|
+
|
|
216
|
+
// Retrieve or generate a bootstrap secret for the gateway.
|
|
217
|
+
const loadedSecret = loadBootstrapSecret(instanceName);
|
|
218
|
+
const bootstrapSecret = loadedSecret || randomBytes(32).toString("hex");
|
|
219
|
+
if (!loadedSecret) {
|
|
220
|
+
saveBootstrapSecret(instanceName, bootstrapSecret);
|
|
186
221
|
}
|
|
187
|
-
}
|
|
188
222
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
223
|
+
// Extract or generate the shared JWT signing key.
|
|
224
|
+
const signingKey =
|
|
225
|
+
capturedEnv["ACTOR_TOKEN_SIGNING_KEY"] || randomBytes(32).toString("hex");
|
|
226
|
+
|
|
227
|
+
// Build extra env vars, excluding keys managed by serviceDockerRunArgs
|
|
228
|
+
const envKeysSetByRunArgs = new Set(CONTAINER_ENV_EXCLUDE_KEYS);
|
|
229
|
+
for (const envVar of ["ANTHROPIC_API_KEY", "VELLUM_PLATFORM_URL"]) {
|
|
230
|
+
if (process.env[envVar]) {
|
|
231
|
+
envKeysSetByRunArgs.add(envVar);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const extraAssistantEnv: Record<string, string> = {};
|
|
235
|
+
for (const [key, value] of Object.entries(capturedEnv)) {
|
|
236
|
+
if (!envKeysSetByRunArgs.has(key)) {
|
|
237
|
+
extraAssistantEnv[key] = value;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Parse gateway port from entry's runtimeUrl, fall back to default
|
|
242
|
+
let gatewayPort = GATEWAY_INTERNAL_PORT;
|
|
243
|
+
try {
|
|
244
|
+
const parsed = new URL(entry.runtimeUrl);
|
|
245
|
+
const port = parseInt(parsed.port, 10);
|
|
246
|
+
if (!isNaN(port)) {
|
|
247
|
+
gatewayPort = port;
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
// use default
|
|
196
251
|
}
|
|
197
|
-
} catch {
|
|
198
|
-
// use default
|
|
199
|
-
}
|
|
200
252
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
// Notify connected clients that a rollback is about to begin (best-effort)
|
|
254
|
+
console.log("📢 Notifying connected clients...");
|
|
255
|
+
await broadcastUpgradeEvent(
|
|
256
|
+
entry.runtimeUrl,
|
|
257
|
+
entry.assistantId,
|
|
258
|
+
buildStartingEvent(entry.previousServiceGroupVersion),
|
|
259
|
+
);
|
|
260
|
+
// Brief pause to allow SSE delivery before containers stop.
|
|
261
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
262
|
+
|
|
263
|
+
// Roll back migrations to pre-upgrade state (must happen before containers stop)
|
|
264
|
+
if (
|
|
265
|
+
entry.previousDbMigrationVersion !== undefined ||
|
|
266
|
+
entry.previousWorkspaceMigrationId !== undefined
|
|
267
|
+
) {
|
|
268
|
+
console.log("🔄 Reverting database changes...");
|
|
269
|
+
await broadcastUpgradeEvent(
|
|
270
|
+
entry.runtimeUrl,
|
|
271
|
+
entry.assistantId,
|
|
272
|
+
buildProgressEvent(UPGRADE_PROGRESS.REVERTING_MIGRATIONS),
|
|
273
|
+
);
|
|
274
|
+
await rollbackMigrations(
|
|
275
|
+
entry.runtimeUrl,
|
|
276
|
+
entry.assistantId,
|
|
277
|
+
entry.previousDbMigrationVersion,
|
|
278
|
+
entry.previousWorkspaceMigrationId,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Progress: switching version (must be sent BEFORE stopContainers)
|
|
283
|
+
await broadcastUpgradeEvent(
|
|
284
|
+
entry.runtimeUrl,
|
|
285
|
+
entry.assistantId,
|
|
286
|
+
buildProgressEvent(UPGRADE_PROGRESS.SWITCHING),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
console.log("🛑 Stopping existing containers...");
|
|
290
|
+
await stopContainers(res);
|
|
291
|
+
console.log("✅ Containers stopped\n");
|
|
292
|
+
|
|
293
|
+
// Run security file migrations and signing key cleanup
|
|
294
|
+
console.log("🔄 Migrating security files to gateway volume...");
|
|
295
|
+
await migrateGatewaySecurityFiles(res, (msg) => console.log(msg));
|
|
296
|
+
|
|
297
|
+
console.log("🔄 Migrating credential files to CES security volume...");
|
|
298
|
+
await migrateCesSecurityFiles(res, (msg) => console.log(msg));
|
|
299
|
+
|
|
300
|
+
console.log("🚀 Starting containers with previous version...");
|
|
301
|
+
await startContainers(
|
|
302
|
+
{
|
|
303
|
+
signingKey,
|
|
304
|
+
bootstrapSecret,
|
|
305
|
+
cesServiceToken,
|
|
306
|
+
extraAssistantEnv,
|
|
307
|
+
gatewayPort,
|
|
308
|
+
imageTags: previousImageRefs,
|
|
309
|
+
instanceName,
|
|
310
|
+
res,
|
|
259
311
|
},
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
312
|
+
(msg) => console.log(msg),
|
|
313
|
+
);
|
|
314
|
+
console.log("✅ Containers started\n");
|
|
315
|
+
|
|
316
|
+
console.log("Waiting for assistant to become ready...");
|
|
317
|
+
const ready = await waitForReady(entry.runtimeUrl);
|
|
318
|
+
|
|
319
|
+
if (ready) {
|
|
320
|
+
// Restore data from the backup created for the specific upgrade being
|
|
321
|
+
// rolled back. We use the persisted preUpgradeBackupPath rather than
|
|
322
|
+
// scanning for the latest backup on disk — if the most recent upgrade's
|
|
323
|
+
// backup failed, a global scan would find a stale backup from a prior
|
|
324
|
+
// cycle and overwrite newer user data.
|
|
325
|
+
const backupPath = entry.preUpgradeBackupPath as string | undefined;
|
|
326
|
+
if (backupPath) {
|
|
327
|
+
// Progress: restoring data (gateway is back up at this point)
|
|
328
|
+
await broadcastUpgradeEvent(
|
|
329
|
+
entry.runtimeUrl,
|
|
330
|
+
entry.assistantId,
|
|
331
|
+
buildProgressEvent(UPGRADE_PROGRESS.RESTORING),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
console.log(`📦 Restoring data from pre-upgrade backup...`);
|
|
335
|
+
console.log(` Source: ${backupPath}`);
|
|
336
|
+
const restored = await restoreBackup(
|
|
337
|
+
entry.runtimeUrl,
|
|
338
|
+
entry.assistantId,
|
|
339
|
+
backupPath,
|
|
340
|
+
);
|
|
341
|
+
if (restored) {
|
|
342
|
+
console.log(" ✅ Data restored successfully\n");
|
|
343
|
+
} else {
|
|
344
|
+
console.warn(
|
|
345
|
+
" ⚠️ Data restore failed (rollback continues without data restoration)\n",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
console.log(
|
|
350
|
+
"ℹ️ No pre-upgrade backup was created for this upgrade, skipping data restoration\n",
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Capture new digests from the rolled-back containers
|
|
355
|
+
const newDigests = await captureImageRefs(res);
|
|
356
|
+
|
|
357
|
+
// Swap current/previous state to enable "rollback the rollback"
|
|
358
|
+
const updatedEntry: AssistantEntry = {
|
|
359
|
+
...entry,
|
|
360
|
+
serviceGroupVersion: entry.previousServiceGroupVersion,
|
|
361
|
+
containerInfo: {
|
|
362
|
+
assistantImage: prev.assistantImage ?? previousImageRefs.assistant,
|
|
363
|
+
gatewayImage: prev.gatewayImage ?? previousImageRefs.gateway,
|
|
364
|
+
cesImage: prev.cesImage ?? previousImageRefs["credential-executor"],
|
|
365
|
+
assistantDigest: newDigests?.assistant,
|
|
366
|
+
gatewayDigest: newDigests?.gateway,
|
|
367
|
+
cesDigest: newDigests?.["credential-executor"],
|
|
368
|
+
networkName: res.network,
|
|
369
|
+
},
|
|
370
|
+
previousServiceGroupVersion: entry.serviceGroupVersion,
|
|
371
|
+
previousContainerInfo: entry.containerInfo,
|
|
372
|
+
// Clear the backup path — it belonged to the upgrade we just rolled back
|
|
373
|
+
preUpgradeBackupPath: undefined,
|
|
374
|
+
previousDbMigrationVersion: undefined,
|
|
375
|
+
previousWorkspaceMigrationId: undefined,
|
|
376
|
+
};
|
|
377
|
+
saveAssistantEntry(updatedEntry);
|
|
378
|
+
|
|
379
|
+
// Notify clients that the rollback succeeded
|
|
380
|
+
await broadcastUpgradeEvent(
|
|
381
|
+
entry.runtimeUrl,
|
|
382
|
+
entry.assistantId,
|
|
383
|
+
buildCompleteEvent(entry.previousServiceGroupVersion, true),
|
|
384
|
+
);
|
|
271
385
|
|
|
272
|
-
|
|
273
|
-
|
|
386
|
+
// Record successful rollback in workspace git history
|
|
387
|
+
if (workspaceDir) {
|
|
388
|
+
try {
|
|
389
|
+
await commitWorkspaceState(
|
|
390
|
+
workspaceDir,
|
|
391
|
+
buildUpgradeCommitMessage({
|
|
392
|
+
action: "rollback",
|
|
393
|
+
phase: "complete",
|
|
394
|
+
from: entry.serviceGroupVersion ?? "unknown",
|
|
395
|
+
to: entry.previousServiceGroupVersion ?? "unknown",
|
|
396
|
+
topology: "docker",
|
|
397
|
+
assistantId: entry.assistantId,
|
|
398
|
+
result: "success",
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
console.warn(
|
|
403
|
+
`⚠️ Failed to create post-rollback workspace commit: ${err instanceof Error ? err.message : String(err)}`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
console.log(
|
|
409
|
+
`\n✅ Docker assistant '${instanceName}' rolled back to ${entry.previousServiceGroupVersion}.`,
|
|
410
|
+
);
|
|
411
|
+
} else {
|
|
412
|
+
console.error(
|
|
413
|
+
`\n❌ Containers failed to become ready within the timeout.`,
|
|
414
|
+
);
|
|
415
|
+
console.log(
|
|
416
|
+
` Check logs with: docker logs -f ${res.assistantContainer}`,
|
|
417
|
+
);
|
|
418
|
+
await broadcastUpgradeEvent(
|
|
419
|
+
entry.runtimeUrl,
|
|
420
|
+
entry.assistantId,
|
|
421
|
+
buildCompleteEvent(
|
|
422
|
+
entry.previousServiceGroupVersion ?? "unknown",
|
|
423
|
+
false,
|
|
424
|
+
),
|
|
425
|
+
);
|
|
426
|
+
emitCliError(
|
|
427
|
+
"READINESS_TIMEOUT",
|
|
428
|
+
"Rolled-back containers failed to become ready within the timeout.",
|
|
429
|
+
);
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
} catch (err) {
|
|
433
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
434
|
+
console.error(`\n❌ Rollback failed: ${detail}`);
|
|
435
|
+
await broadcastUpgradeEvent(
|
|
436
|
+
entry.runtimeUrl,
|
|
437
|
+
entry.assistantId,
|
|
438
|
+
buildCompleteEvent(entry.serviceGroupVersion ?? "unknown", false),
|
|
274
439
|
);
|
|
275
|
-
|
|
276
|
-
console.error(`\n❌ Containers failed to become ready within the timeout.`);
|
|
277
|
-
console.log(` Check logs with: docker logs -f ${res.assistantContainer}`);
|
|
440
|
+
emitCliError(categorizeUpgradeError(err), "Rollback failed", detail);
|
|
278
441
|
process.exit(1);
|
|
279
442
|
}
|
|
280
443
|
}
|