@vellumai/cli 0.5.7 → 0.5.8
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 +234 -103
- package/src/commands/upgrade.ts +231 -209
- 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,190 @@ 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 — Workspace commit (starting)
|
|
177
|
+
await commitWorkspaceViaGateway(
|
|
178
|
+
entry.runtimeUrl,
|
|
179
|
+
entry.assistantId,
|
|
180
|
+
buildUpgradeCommitMessage({
|
|
181
|
+
action: "rollback",
|
|
182
|
+
phase: "starting",
|
|
183
|
+
from: currentVersion ?? "unknown",
|
|
184
|
+
to: version ?? "previous",
|
|
185
|
+
topology: "managed",
|
|
186
|
+
assistantId: entry.assistantId,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Step 3 — Authenticate
|
|
191
|
+
const token = readPlatformToken();
|
|
192
|
+
if (!token) {
|
|
193
|
+
const msg =
|
|
194
|
+
"Error: Not logged in. Run `vellum login --token <token>` first.";
|
|
195
|
+
console.error(msg);
|
|
196
|
+
emitCliError("AUTH_FAILED", msg);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let orgId: string;
|
|
201
|
+
try {
|
|
202
|
+
orgId = await fetchOrganizationId(token);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
205
|
+
if (msg.includes("401") || msg.includes("403")) {
|
|
206
|
+
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
207
|
+
} else {
|
|
208
|
+
console.error(`Error: ${msg}`);
|
|
209
|
+
}
|
|
210
|
+
emitCliError("AUTH_FAILED", "Failed to authenticate with platform", msg);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Step 4 — Broadcast starting event
|
|
215
|
+
console.log("📢 Notifying connected clients...");
|
|
216
|
+
await broadcastUpgradeEvent(
|
|
217
|
+
entry.runtimeUrl,
|
|
218
|
+
entry.assistantId,
|
|
219
|
+
buildStartingEvent(version ?? "previous", 90),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Step 5 — Call rollback endpoint
|
|
223
|
+
if (version) {
|
|
224
|
+
console.log(`Rolling back to ${version}...`);
|
|
225
|
+
} else {
|
|
226
|
+
console.log("Rolling back to previous version...");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let result: { detail: string; version: string | null };
|
|
230
|
+
try {
|
|
231
|
+
result = await rollbackPlatformAssistant(token, orgId, version);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
234
|
+
|
|
235
|
+
// Map specific server error messages to actionable CLI output
|
|
236
|
+
if (detail.includes("No previous version")) {
|
|
237
|
+
console.error(
|
|
238
|
+
"No previous version available. A successful upgrade must have been performed first.",
|
|
239
|
+
);
|
|
240
|
+
} else if (detail.includes("not older")) {
|
|
241
|
+
console.error(
|
|
242
|
+
`Target version is not older than the current version. Use 'vellum upgrade --version' instead.`,
|
|
243
|
+
);
|
|
244
|
+
} else if (detail.includes("not found")) {
|
|
245
|
+
console.error(
|
|
246
|
+
version
|
|
247
|
+
? `Version ${version} not found.`
|
|
248
|
+
: `Rollback target not found.`,
|
|
249
|
+
);
|
|
250
|
+
} else if (
|
|
251
|
+
err instanceof TypeError ||
|
|
252
|
+
detail.includes("fetch failed") ||
|
|
253
|
+
detail.includes("ECONNREFUSED")
|
|
254
|
+
) {
|
|
255
|
+
console.error(
|
|
256
|
+
`Connection error: ${detail}\nIs the platform reachable? Try 'vellum wake' if the assistant is asleep.`,
|
|
257
|
+
);
|
|
258
|
+
} else {
|
|
259
|
+
console.error(`Error: ${detail}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
emitCliError("PLATFORM_API_ERROR", "Platform rollback failed", detail);
|
|
263
|
+
await broadcastUpgradeEvent(
|
|
264
|
+
entry.runtimeUrl,
|
|
265
|
+
entry.assistantId,
|
|
266
|
+
buildCompleteEvent(currentVersion ?? "unknown", false),
|
|
267
|
+
);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const rolledBackVersion = result.version ?? version ?? "unknown";
|
|
272
|
+
|
|
273
|
+
// Step 6 — Workspace commit (complete)
|
|
274
|
+
await commitWorkspaceViaGateway(
|
|
275
|
+
entry.runtimeUrl,
|
|
276
|
+
entry.assistantId,
|
|
277
|
+
buildUpgradeCommitMessage({
|
|
278
|
+
action: "rollback",
|
|
279
|
+
phase: "complete",
|
|
280
|
+
from: currentVersion ?? "unknown",
|
|
281
|
+
to: rolledBackVersion,
|
|
282
|
+
topology: "managed",
|
|
283
|
+
assistantId: entry.assistantId,
|
|
284
|
+
result: "success",
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Step 7 — Print success
|
|
289
|
+
console.log(`Rolled back to version ${rolledBackVersion}.`);
|
|
290
|
+
if (!version) {
|
|
291
|
+
console.log("Tip: Run 'vellum rollback' again to undo.");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
132
295
|
export async function rollback(): Promise<void> {
|
|
133
|
-
const { name } = parseArgs();
|
|
296
|
+
const { name, version } = parseArgs();
|
|
134
297
|
const entry = resolveTargetAssistant(name);
|
|
135
298
|
const cloud = resolveCloud(entry);
|
|
136
299
|
|
|
137
|
-
//
|
|
300
|
+
// ---------- Managed (Vellum platform) rollback ----------
|
|
301
|
+
if (cloud === "vellum") {
|
|
302
|
+
await rollbackPlatformViaEndpoint(entry, version ?? undefined);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ---------- Unsupported topologies ----------
|
|
138
307
|
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.";
|
|
308
|
+
const msg = "Rollback is only supported for Docker and managed assistants.";
|
|
141
309
|
console.error(msg);
|
|
142
310
|
emitCliError("UNSUPPORTED_TOPOLOGY", msg);
|
|
143
311
|
process.exit(1);
|
|
144
312
|
}
|
|
145
313
|
|
|
314
|
+
// ---------- Docker: Targeted version rollback (--version specified) ----------
|
|
315
|
+
if (version) {
|
|
316
|
+
try {
|
|
317
|
+
await performDockerRollback(entry, { targetVersion: version });
|
|
318
|
+
} catch (err) {
|
|
319
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
320
|
+
console.error(`\n❌ Rollback failed: ${detail}`);
|
|
321
|
+
await broadcastUpgradeEvent(
|
|
322
|
+
entry.runtimeUrl,
|
|
323
|
+
entry.assistantId,
|
|
324
|
+
buildCompleteEvent(entry.serviceGroupVersion ?? "unknown", false),
|
|
325
|
+
);
|
|
326
|
+
emitCliError(categorizeUpgradeError(err), "Rollback failed", detail);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ---------- Docker: Saved-state rollback (no --version) ----------
|
|
333
|
+
|
|
146
334
|
// Verify rollback state exists
|
|
147
335
|
if (!entry.previousServiceGroupVersion || !entry.previousContainerInfo) {
|
|
148
336
|
const msg =
|
|
@@ -173,30 +361,19 @@ export async function rollback(): Promise<void> {
|
|
|
173
361
|
const res = dockerResourceNames(instanceName);
|
|
174
362
|
|
|
175
363
|
try {
|
|
176
|
-
const workspaceDir = entry.resources
|
|
177
|
-
? join(entry.resources.instanceDir, ".vellum", "workspace")
|
|
178
|
-
: undefined;
|
|
179
|
-
|
|
180
364
|
// 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
|
-
}
|
|
365
|
+
await commitWorkspaceViaGateway(
|
|
366
|
+
entry.runtimeUrl,
|
|
367
|
+
entry.assistantId,
|
|
368
|
+
buildUpgradeCommitMessage({
|
|
369
|
+
action: "rollback",
|
|
370
|
+
phase: "starting",
|
|
371
|
+
from: entry.serviceGroupVersion ?? "unknown",
|
|
372
|
+
to: entry.previousServiceGroupVersion ?? "unknown",
|
|
373
|
+
topology: "docker",
|
|
374
|
+
assistantId: entry.assistantId,
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
200
377
|
|
|
201
378
|
console.log(
|
|
202
379
|
`🔄 Rolling back Docker assistant '${instanceName}' to ${entry.previousServiceGroupVersion}...\n`,
|
|
@@ -213,13 +390,6 @@ export async function rollback(): Promise<void> {
|
|
|
213
390
|
const cesServiceToken =
|
|
214
391
|
capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex");
|
|
215
392
|
|
|
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
393
|
// Extract or generate the shared JWT signing key.
|
|
224
394
|
const signingKey =
|
|
225
395
|
capturedEnv["ACTOR_TOKEN_SIGNING_KEY"] || randomBytes(32).toString("hex");
|
|
@@ -301,7 +471,6 @@ export async function rollback(): Promise<void> {
|
|
|
301
471
|
await startContainers(
|
|
302
472
|
{
|
|
303
473
|
signingKey,
|
|
304
|
-
bootstrapSecret,
|
|
305
474
|
cesServiceToken,
|
|
306
475
|
extraAssistantEnv,
|
|
307
476
|
gatewayPort,
|
|
@@ -317,40 +486,6 @@ export async function rollback(): Promise<void> {
|
|
|
317
486
|
const ready = await waitForReady(entry.runtimeUrl);
|
|
318
487
|
|
|
319
488
|
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
489
|
// Capture new digests from the rolled-back containers
|
|
355
490
|
const newDigests = await captureImageRefs(res);
|
|
356
491
|
|
|
@@ -384,30 +519,26 @@ export async function rollback(): Promise<void> {
|
|
|
384
519
|
);
|
|
385
520
|
|
|
386
521
|
// 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
|
-
}
|
|
522
|
+
await commitWorkspaceViaGateway(
|
|
523
|
+
entry.runtimeUrl,
|
|
524
|
+
entry.assistantId,
|
|
525
|
+
buildUpgradeCommitMessage({
|
|
526
|
+
action: "rollback",
|
|
527
|
+
phase: "complete",
|
|
528
|
+
from: entry.serviceGroupVersion ?? "unknown",
|
|
529
|
+
to: entry.previousServiceGroupVersion ?? "unknown",
|
|
530
|
+
topology: "docker",
|
|
531
|
+
assistantId: entry.assistantId,
|
|
532
|
+
result: "success",
|
|
533
|
+
}),
|
|
534
|
+
);
|
|
407
535
|
|
|
408
536
|
console.log(
|
|
409
537
|
`\n✅ Docker assistant '${instanceName}' rolled back to ${entry.previousServiceGroupVersion}.`,
|
|
410
538
|
);
|
|
539
|
+
console.log(
|
|
540
|
+
"\nTip: To also restore data from before the upgrade, use `vellum restore --from <backup-path>`.",
|
|
541
|
+
);
|
|
411
542
|
} else {
|
|
412
543
|
console.error(
|
|
413
544
|
`\n❌ Containers failed to become ready within the timeout.`,
|