@vellumai/cli 0.5.7 → 0.5.9
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/package.json +1 -1
- package/src/commands/backup.ts +124 -0
- package/src/commands/hatch.ts +24 -5
- package/src/commands/restore.ts +359 -16
- package/src/commands/rollback.ts +197 -103
- package/src/commands/upgrade.ts +204 -220
- package/src/index.ts +4 -4
- package/src/lib/aws.ts +14 -9
- package/src/lib/cli-error.ts +2 -0
- package/src/lib/docker.ts +54 -13
- package/src/lib/gcp.ts +15 -10
- package/src/lib/guardian-token.ts +4 -42
- package/src/lib/local.ts +1 -0
- package/src/lib/platform-client.ts +206 -18
- package/src/lib/upgrade-lifecycle.ts +612 -5
- package/src/lib/workspace-git.ts +0 -39
package/src/commands/rollback.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
|
-
import { join } from "path";
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
findAssistantByName,
|
|
@@ -18,12 +17,12 @@ import {
|
|
|
18
17
|
stopContainers,
|
|
19
18
|
} from "../lib/docker";
|
|
20
19
|
import type { ServiceName } from "../lib/docker";
|
|
21
|
-
import {
|
|
22
|
-
loadBootstrapSecret,
|
|
23
|
-
saveBootstrapSecret,
|
|
24
|
-
} from "../lib/guardian-token";
|
|
25
|
-
import { restoreBackup } from "../lib/backup-ops.js";
|
|
26
20
|
import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
|
|
21
|
+
import {
|
|
22
|
+
fetchOrganizationId,
|
|
23
|
+
readPlatformToken,
|
|
24
|
+
rollbackPlatformAssistant,
|
|
25
|
+
} from "../lib/platform-client.js";
|
|
27
26
|
import {
|
|
28
27
|
broadcastUpgradeEvent,
|
|
29
28
|
buildCompleteEvent,
|
|
@@ -31,37 +30,56 @@ import {
|
|
|
31
30
|
buildStartingEvent,
|
|
32
31
|
buildUpgradeCommitMessage,
|
|
33
32
|
captureContainerEnv,
|
|
33
|
+
commitWorkspaceViaGateway,
|
|
34
34
|
CONTAINER_ENV_EXCLUDE_KEYS,
|
|
35
|
+
performDockerRollback,
|
|
35
36
|
rollbackMigrations,
|
|
36
37
|
UPGRADE_PROGRESS,
|
|
37
38
|
waitForReady,
|
|
38
39
|
} from "../lib/upgrade-lifecycle.js";
|
|
39
|
-
import {
|
|
40
|
+
import { parseVersion } from "../lib/version-compat.js";
|
|
40
41
|
|
|
41
|
-
function parseArgs(): { name: string | null } {
|
|
42
|
+
function parseArgs(): { name: string | null; version: string | null } {
|
|
42
43
|
const args = process.argv.slice(3);
|
|
43
44
|
let name: string | null = null;
|
|
45
|
+
let version: string | null = null;
|
|
44
46
|
|
|
45
47
|
for (let i = 0; i < args.length; i++) {
|
|
46
48
|
const arg = args[i];
|
|
47
49
|
if (arg === "--help" || arg === "-h") {
|
|
48
|
-
console.log("Usage: vellum rollback [<name>]");
|
|
50
|
+
console.log("Usage: vellum rollback [<name>] [--version <version>]");
|
|
49
51
|
console.log("");
|
|
50
|
-
console.log(
|
|
52
|
+
console.log(
|
|
53
|
+
"Roll back a Docker or managed assistant to a previous version.",
|
|
54
|
+
);
|
|
51
55
|
console.log("");
|
|
52
56
|
console.log("Arguments:");
|
|
53
57
|
console.log(
|
|
54
|
-
" <name>
|
|
58
|
+
" <name> Name of the assistant (default: active or only assistant)",
|
|
59
|
+
);
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log("Options:");
|
|
62
|
+
console.log(
|
|
63
|
+
" --version <version> Target version (optional for managed — omit to roll back to previous)",
|
|
55
64
|
);
|
|
56
65
|
console.log("");
|
|
57
66
|
console.log("Examples:");
|
|
58
67
|
console.log(
|
|
59
|
-
" vellum rollback
|
|
68
|
+
" vellum rollback my-assistant # Roll back to previous version (Docker or managed)",
|
|
60
69
|
);
|
|
61
70
|
console.log(
|
|
62
|
-
" vellum rollback my-assistant
|
|
71
|
+
" vellum rollback my-assistant --version v1.2.3 # Roll back to a specific version",
|
|
63
72
|
);
|
|
64
73
|
process.exit(0);
|
|
74
|
+
} else if (arg === "--version") {
|
|
75
|
+
const next = args[i + 1];
|
|
76
|
+
if (!next || next.startsWith("-")) {
|
|
77
|
+
console.error("Error: --version requires a value");
|
|
78
|
+
emitCliError("UNKNOWN", "--version requires a value");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
version = next;
|
|
82
|
+
i++;
|
|
65
83
|
} else if (!arg.startsWith("-")) {
|
|
66
84
|
name = arg;
|
|
67
85
|
} else {
|
|
@@ -71,7 +89,7 @@ function parseArgs(): { name: string | null } {
|
|
|
71
89
|
}
|
|
72
90
|
}
|
|
73
91
|
|
|
74
|
-
return { name };
|
|
92
|
+
return { name, version };
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
function resolveCloud(entry: AssistantEntry): string {
|
|
@@ -129,20 +147,153 @@ function resolveTargetAssistant(nameArg: string | null): AssistantEntry {
|
|
|
129
147
|
process.exit(1);
|
|
130
148
|
}
|
|
131
149
|
|
|
150
|
+
async function rollbackPlatformViaEndpoint(
|
|
151
|
+
entry: AssistantEntry,
|
|
152
|
+
version?: string,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const currentVersion = entry.serviceGroupVersion;
|
|
155
|
+
|
|
156
|
+
// Step 1 — Version validation (only if version provided)
|
|
157
|
+
if (version && currentVersion) {
|
|
158
|
+
const current = parseVersion(currentVersion);
|
|
159
|
+
const target = parseVersion(version);
|
|
160
|
+
if (current && target) {
|
|
161
|
+
const isOlder =
|
|
162
|
+
target.major < current.major ||
|
|
163
|
+
(target.major === current.major && target.minor < current.minor) ||
|
|
164
|
+
(target.major === current.major &&
|
|
165
|
+
target.minor === current.minor &&
|
|
166
|
+
target.patch < current.patch);
|
|
167
|
+
if (!isOlder) {
|
|
168
|
+
const msg = `Target version ${version} is not older than the current version ${currentVersion}. Use \`vellum upgrade --version ${version}\` to upgrade.`;
|
|
169
|
+
console.error(msg);
|
|
170
|
+
emitCliError("VERSION_DIRECTION", msg);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Step 2 — Authenticate
|
|
177
|
+
const token = readPlatformToken();
|
|
178
|
+
if (!token) {
|
|
179
|
+
const msg =
|
|
180
|
+
"Error: Not logged in. Run `vellum login --token <token>` first.";
|
|
181
|
+
console.error(msg);
|
|
182
|
+
emitCliError("AUTH_FAILED", msg);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let orgId: string;
|
|
187
|
+
try {
|
|
188
|
+
orgId = await fetchOrganizationId(token);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
191
|
+
if (msg.includes("401") || msg.includes("403")) {
|
|
192
|
+
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
193
|
+
} else {
|
|
194
|
+
console.error(`Error: ${msg}`);
|
|
195
|
+
}
|
|
196
|
+
emitCliError("AUTH_FAILED", "Failed to authenticate with platform", msg);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Step 3 — Call rollback endpoint
|
|
201
|
+
if (version) {
|
|
202
|
+
console.log(`Rolling back to ${version}...`);
|
|
203
|
+
} else {
|
|
204
|
+
console.log("Rolling back to previous version...");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let result: { detail: string; version: string | null };
|
|
208
|
+
try {
|
|
209
|
+
result = await rollbackPlatformAssistant(token, orgId, version);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
212
|
+
|
|
213
|
+
// Map specific server error messages to actionable CLI output
|
|
214
|
+
if (detail.includes("No previous version")) {
|
|
215
|
+
console.error(
|
|
216
|
+
"No previous version available. A successful upgrade must have been performed first.",
|
|
217
|
+
);
|
|
218
|
+
} else if (detail.includes("not older")) {
|
|
219
|
+
console.error(
|
|
220
|
+
`Target version is not older than the current version. Use 'vellum upgrade --version' instead.`,
|
|
221
|
+
);
|
|
222
|
+
} else if (detail.includes("not found")) {
|
|
223
|
+
console.error(
|
|
224
|
+
version
|
|
225
|
+
? `Version ${version} not found.`
|
|
226
|
+
: `Rollback target not found.`,
|
|
227
|
+
);
|
|
228
|
+
} else if (
|
|
229
|
+
err instanceof TypeError ||
|
|
230
|
+
detail.includes("fetch failed") ||
|
|
231
|
+
detail.includes("ECONNREFUSED")
|
|
232
|
+
) {
|
|
233
|
+
console.error(
|
|
234
|
+
`Connection error: ${detail}\nIs the platform reachable? Try 'vellum wake' if the assistant is asleep.`,
|
|
235
|
+
);
|
|
236
|
+
} else {
|
|
237
|
+
console.error(`Error: ${detail}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
emitCliError("PLATFORM_API_ERROR", "Platform rollback failed", detail);
|
|
241
|
+
await broadcastUpgradeEvent(
|
|
242
|
+
entry.runtimeUrl,
|
|
243
|
+
entry.assistantId,
|
|
244
|
+
buildCompleteEvent(currentVersion ?? "unknown", false),
|
|
245
|
+
);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const rolledBackVersion = result.version ?? version ?? "unknown";
|
|
250
|
+
|
|
251
|
+
// Step 4 — Print success
|
|
252
|
+
console.log(`Rolled back to version ${rolledBackVersion}.`);
|
|
253
|
+
if (!version) {
|
|
254
|
+
console.log("Tip: Run 'vellum rollback' again to undo.");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
132
258
|
export async function rollback(): Promise<void> {
|
|
133
|
-
const { name } = parseArgs();
|
|
259
|
+
const { name, version } = parseArgs();
|
|
134
260
|
const entry = resolveTargetAssistant(name);
|
|
135
261
|
const cloud = resolveCloud(entry);
|
|
136
262
|
|
|
137
|
-
//
|
|
263
|
+
// ---------- Managed (Vellum platform) rollback ----------
|
|
264
|
+
if (cloud === "vellum") {
|
|
265
|
+
await rollbackPlatformViaEndpoint(entry, version ?? undefined);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ---------- Unsupported topologies ----------
|
|
138
270
|
if (cloud !== "docker") {
|
|
139
|
-
const msg =
|
|
140
|
-
"Rollback is only supported for Docker assistants. For managed assistants, use the version picker to upgrade to the previous version.";
|
|
271
|
+
const msg = "Rollback is only supported for Docker and managed assistants.";
|
|
141
272
|
console.error(msg);
|
|
142
273
|
emitCliError("UNSUPPORTED_TOPOLOGY", msg);
|
|
143
274
|
process.exit(1);
|
|
144
275
|
}
|
|
145
276
|
|
|
277
|
+
// ---------- Docker: Targeted version rollback (--version specified) ----------
|
|
278
|
+
if (version) {
|
|
279
|
+
try {
|
|
280
|
+
await performDockerRollback(entry, { targetVersion: version });
|
|
281
|
+
} catch (err) {
|
|
282
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
283
|
+
console.error(`\n❌ Rollback failed: ${detail}`);
|
|
284
|
+
await broadcastUpgradeEvent(
|
|
285
|
+
entry.runtimeUrl,
|
|
286
|
+
entry.assistantId,
|
|
287
|
+
buildCompleteEvent(entry.serviceGroupVersion ?? "unknown", false),
|
|
288
|
+
);
|
|
289
|
+
emitCliError(categorizeUpgradeError(err), "Rollback failed", detail);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---------- Docker: Saved-state rollback (no --version) ----------
|
|
296
|
+
|
|
146
297
|
// Verify rollback state exists
|
|
147
298
|
if (!entry.previousServiceGroupVersion || !entry.previousContainerInfo) {
|
|
148
299
|
const msg =
|
|
@@ -173,30 +324,19 @@ export async function rollback(): Promise<void> {
|
|
|
173
324
|
const res = dockerResourceNames(instanceName);
|
|
174
325
|
|
|
175
326
|
try {
|
|
176
|
-
const workspaceDir = entry.resources
|
|
177
|
-
? join(entry.resources.instanceDir, ".vellum", "workspace")
|
|
178
|
-
: undefined;
|
|
179
|
-
|
|
180
327
|
// Record rollback start in workspace git history
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
}
|
|
199
|
-
}
|
|
328
|
+
await commitWorkspaceViaGateway(
|
|
329
|
+
entry.runtimeUrl,
|
|
330
|
+
entry.assistantId,
|
|
331
|
+
buildUpgradeCommitMessage({
|
|
332
|
+
action: "rollback",
|
|
333
|
+
phase: "starting",
|
|
334
|
+
from: entry.serviceGroupVersion ?? "unknown",
|
|
335
|
+
to: entry.previousServiceGroupVersion ?? "unknown",
|
|
336
|
+
topology: "docker",
|
|
337
|
+
assistantId: entry.assistantId,
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
200
340
|
|
|
201
341
|
console.log(
|
|
202
342
|
`🔄 Rolling back Docker assistant '${instanceName}' to ${entry.previousServiceGroupVersion}...\n`,
|
|
@@ -213,13 +353,6 @@ export async function rollback(): Promise<void> {
|
|
|
213
353
|
const cesServiceToken =
|
|
214
354
|
capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex");
|
|
215
355
|
|
|
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);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
356
|
// Extract or generate the shared JWT signing key.
|
|
224
357
|
const signingKey =
|
|
225
358
|
capturedEnv["ACTOR_TOKEN_SIGNING_KEY"] || randomBytes(32).toString("hex");
|
|
@@ -301,7 +434,6 @@ export async function rollback(): Promise<void> {
|
|
|
301
434
|
await startContainers(
|
|
302
435
|
{
|
|
303
436
|
signingKey,
|
|
304
|
-
bootstrapSecret,
|
|
305
437
|
cesServiceToken,
|
|
306
438
|
extraAssistantEnv,
|
|
307
439
|
gatewayPort,
|
|
@@ -317,40 +449,6 @@ export async function rollback(): Promise<void> {
|
|
|
317
449
|
const ready = await waitForReady(entry.runtimeUrl);
|
|
318
450
|
|
|
319
451
|
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
452
|
// Capture new digests from the rolled-back containers
|
|
355
453
|
const newDigests = await captureImageRefs(res);
|
|
356
454
|
|
|
@@ -384,30 +482,26 @@ export async function rollback(): Promise<void> {
|
|
|
384
482
|
);
|
|
385
483
|
|
|
386
484
|
// Record successful rollback in workspace git history
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
}
|
|
485
|
+
await commitWorkspaceViaGateway(
|
|
486
|
+
entry.runtimeUrl,
|
|
487
|
+
entry.assistantId,
|
|
488
|
+
buildUpgradeCommitMessage({
|
|
489
|
+
action: "rollback",
|
|
490
|
+
phase: "complete",
|
|
491
|
+
from: entry.serviceGroupVersion ?? "unknown",
|
|
492
|
+
to: entry.previousServiceGroupVersion ?? "unknown",
|
|
493
|
+
topology: "docker",
|
|
494
|
+
assistantId: entry.assistantId,
|
|
495
|
+
result: "success",
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
407
498
|
|
|
408
499
|
console.log(
|
|
409
500
|
`\n✅ Docker assistant '${instanceName}' rolled back to ${entry.previousServiceGroupVersion}.`,
|
|
410
501
|
);
|
|
502
|
+
console.log(
|
|
503
|
+
"\nTip: To also restore data from before the upgrade, use `vellum restore --from <backup-path>`.",
|
|
504
|
+
);
|
|
411
505
|
} else {
|
|
412
506
|
console.error(
|
|
413
507
|
`\n❌ Containers failed to become ready within the timeout.`,
|