@vellumai/cli 0.5.16 → 0.6.1
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/bun.lock +46 -52
- package/package.json +1 -1
- package/src/__tests__/teleport.test.ts +430 -4
- package/src/__tests__/version-compat.test.ts +206 -0
- package/src/commands/backup.ts +1 -15
- package/src/commands/events.ts +146 -0
- package/src/commands/message.ts +105 -0
- package/src/commands/restore.ts +1 -21
- package/src/commands/retire.ts +2 -7
- package/src/commands/rollback.ts +14 -37
- package/src/commands/teleport.ts +125 -65
- package/src/commands/upgrade.ts +50 -43
- package/src/index.ts +6 -0
- package/src/lib/arg-utils.ts +13 -0
- package/src/lib/assistant-client.ts +228 -0
- package/src/lib/aws.ts +2 -1
- package/src/lib/constants.ts +0 -11
- package/src/lib/docker.ts +168 -62
- package/src/lib/gcp.ts +2 -5
- package/src/lib/hatch-local.ts +5 -2
- package/src/lib/health-check.ts +3 -8
- package/src/lib/ngrok.ts +11 -1
- package/src/lib/platform-client.ts +191 -36
- package/src/lib/upgrade-lifecycle.ts +13 -15
- package/src/lib/version-compat.ts +67 -5
- package/src/shared/provider-env-vars.ts +19 -0
package/src/commands/backup.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { getBackupsDir, formatSize } from "../lib/backup-ops.js";
|
|
|
6
6
|
import { loadGuardianToken, leaseGuardianToken } from "../lib/guardian-token";
|
|
7
7
|
import {
|
|
8
8
|
readPlatformToken,
|
|
9
|
-
fetchOrganizationId,
|
|
10
9
|
platformInitiateExport,
|
|
11
10
|
platformPollExportStatus,
|
|
12
11
|
platformDownloadExport,
|
|
@@ -196,24 +195,11 @@ async function backupPlatform(
|
|
|
196
195
|
process.exit(1);
|
|
197
196
|
}
|
|
198
197
|
|
|
199
|
-
let orgId: string;
|
|
200
|
-
try {
|
|
201
|
-
orgId = await fetchOrganizationId(token, runtimeUrl);
|
|
202
|
-
} catch (err) {
|
|
203
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
204
|
-
if (msg.includes("401") || msg.includes("403")) {
|
|
205
|
-
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
throw err;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
198
|
// Step 2 — Initiate export job
|
|
212
199
|
let jobId: string;
|
|
213
200
|
try {
|
|
214
201
|
const result = await platformInitiateExport(
|
|
215
202
|
token,
|
|
216
|
-
orgId,
|
|
217
203
|
"CLI backup",
|
|
218
204
|
runtimeUrl,
|
|
219
205
|
);
|
|
@@ -244,7 +230,7 @@ async function backupPlatform(
|
|
|
244
230
|
while (Date.now() < deadline) {
|
|
245
231
|
let status: { status: string; downloadUrl?: string; error?: string };
|
|
246
232
|
try {
|
|
247
|
-
status = await platformPollExportStatus(jobId, token,
|
|
233
|
+
status = await platformPollExportStatus(jobId, token, runtimeUrl);
|
|
248
234
|
} catch (err) {
|
|
249
235
|
const msg = err instanceof Error ? err.message : String(err);
|
|
250
236
|
// Let non-transient errors (e.g. 404 "job not found") propagate immediately
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vellum events [assistant]`
|
|
3
|
+
*
|
|
4
|
+
* Subscribe to assistant events via the SSE endpoint and stream them
|
|
5
|
+
* to stdout. By default, events are rendered as human-readable
|
|
6
|
+
* markdown. Pass `--json` to emit one JSON object per event,
|
|
7
|
+
* separated by newlines.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { extractFlag } from "../lib/arg-utils.js";
|
|
11
|
+
import { AssistantClient } from "../lib/assistant-client.js";
|
|
12
|
+
|
|
13
|
+
function printUsage(): void {
|
|
14
|
+
console.log(`vellum events - Stream events from a running assistant
|
|
15
|
+
|
|
16
|
+
USAGE:
|
|
17
|
+
vellum events [assistant] [options]
|
|
18
|
+
|
|
19
|
+
ARGUMENTS:
|
|
20
|
+
[assistant] Instance name (default: active assistant)
|
|
21
|
+
|
|
22
|
+
OPTIONS:
|
|
23
|
+
--conversation-key <key> Scope to a single conversation
|
|
24
|
+
--json Output raw JSON events (one per line)
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
|
|
27
|
+
EXAMPLES:
|
|
28
|
+
vellum events
|
|
29
|
+
vellum events my-assistant
|
|
30
|
+
vellum events --json
|
|
31
|
+
vellum events --conversation-key my-thread
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface AssistantEvent {
|
|
36
|
+
id: string;
|
|
37
|
+
assistantId: string;
|
|
38
|
+
conversationId?: string;
|
|
39
|
+
emittedAt: string;
|
|
40
|
+
message: {
|
|
41
|
+
type: string;
|
|
42
|
+
text?: string;
|
|
43
|
+
thinking?: string;
|
|
44
|
+
toolName?: string;
|
|
45
|
+
input?: Record<string, unknown>;
|
|
46
|
+
result?: string;
|
|
47
|
+
isError?: boolean;
|
|
48
|
+
content?: string;
|
|
49
|
+
message?: string;
|
|
50
|
+
chunk?: string;
|
|
51
|
+
conversationId?: string;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Render an event as human-readable markdown to stdout. */
|
|
57
|
+
function renderMarkdown(event: AssistantEvent): void {
|
|
58
|
+
const msg = event.message;
|
|
59
|
+
switch (msg.type) {
|
|
60
|
+
case "assistant_text_delta":
|
|
61
|
+
process.stdout.write(msg.text ?? "");
|
|
62
|
+
break;
|
|
63
|
+
case "assistant_thinking_delta":
|
|
64
|
+
process.stdout.write(msg.thinking ?? "");
|
|
65
|
+
break;
|
|
66
|
+
case "tool_use_start":
|
|
67
|
+
console.log(`\n> **Tool call:** \`${msg.toolName}\``);
|
|
68
|
+
if (msg.input && Object.keys(msg.input).length > 0) {
|
|
69
|
+
console.log("```json");
|
|
70
|
+
console.log(JSON.stringify(msg.input, null, 2));
|
|
71
|
+
console.log("```");
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case "tool_input_delta":
|
|
75
|
+
process.stdout.write(msg.content ?? "");
|
|
76
|
+
break;
|
|
77
|
+
case "tool_result":
|
|
78
|
+
if (msg.isError) {
|
|
79
|
+
console.log(`\n> **Tool error** (\`${msg.toolName}\`): ${msg.result}`);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`\n> **Tool result** (\`${msg.toolName}\`): ${msg.result}`);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "tool_output_chunk":
|
|
85
|
+
process.stdout.write(msg.chunk ?? "");
|
|
86
|
+
break;
|
|
87
|
+
case "message_complete":
|
|
88
|
+
console.log("\n");
|
|
89
|
+
break;
|
|
90
|
+
case "error":
|
|
91
|
+
console.error(`\n**Error:** ${msg.message}`);
|
|
92
|
+
break;
|
|
93
|
+
case "user_message_echo":
|
|
94
|
+
console.log(`\n**You:** ${msg.text}`);
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
// Silently skip events that don't have a markdown representation
|
|
98
|
+
// (e.g. heartbeat comments, activity states, etc.)
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function events(): Promise<void> {
|
|
104
|
+
const rawArgs = process.argv.slice(3);
|
|
105
|
+
|
|
106
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
107
|
+
printUsage();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const jsonOutput = rawArgs.includes("--json");
|
|
112
|
+
let args = rawArgs.filter((a) => a !== "--json");
|
|
113
|
+
|
|
114
|
+
const [conversationKey, filteredArgs] = extractFlag(
|
|
115
|
+
args,
|
|
116
|
+
"--conversation-key",
|
|
117
|
+
);
|
|
118
|
+
args = filteredArgs;
|
|
119
|
+
|
|
120
|
+
const assistantId = args[0];
|
|
121
|
+
|
|
122
|
+
const client = new AssistantClient({ assistantId });
|
|
123
|
+
|
|
124
|
+
// Use an explicit AbortController so we can clean up on SIGINT
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
process.on("SIGINT", () => {
|
|
127
|
+
controller.abort();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const query: Record<string, string> = {};
|
|
132
|
+
if (conversationKey) {
|
|
133
|
+
query.conversationKey = conversationKey;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for await (const event of client.stream<AssistantEvent>("/events", {
|
|
137
|
+
signal: controller.signal,
|
|
138
|
+
query,
|
|
139
|
+
})) {
|
|
140
|
+
if (jsonOutput) {
|
|
141
|
+
console.log(JSON.stringify(event));
|
|
142
|
+
} else {
|
|
143
|
+
renderMarkdown(event);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vellum message <assistant> <message>`
|
|
3
|
+
*
|
|
4
|
+
* Send a message to a running assistant via its runtime HTTP API and
|
|
5
|
+
* print the result. This is a fire-and-send command — it does NOT
|
|
6
|
+
* subscribe to SSE events (use `vellum events` for that).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { extractFlag } from "../lib/arg-utils.js";
|
|
10
|
+
import { AssistantClient } from "../lib/assistant-client.js";
|
|
11
|
+
|
|
12
|
+
function printUsage(): void {
|
|
13
|
+
console.log(`vellum message - Send a message to a running assistant
|
|
14
|
+
|
|
15
|
+
USAGE:
|
|
16
|
+
vellum message [assistant] <message>
|
|
17
|
+
|
|
18
|
+
ARGUMENTS:
|
|
19
|
+
[assistant] Instance name (default: active assistant)
|
|
20
|
+
<message> Message content to send
|
|
21
|
+
|
|
22
|
+
OPTIONS:
|
|
23
|
+
--conversation-key <key> Conversation key (default: stable key per channel/interface)
|
|
24
|
+
--json Output raw JSON response
|
|
25
|
+
|
|
26
|
+
EXAMPLES:
|
|
27
|
+
vellum message "hello"
|
|
28
|
+
vellum message my-assistant "ping"
|
|
29
|
+
vellum message --conversation-key my-thread "hello"
|
|
30
|
+
vellum message --json "hello"
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function message(): Promise<void> {
|
|
35
|
+
const rawArgs = process.argv.slice(3);
|
|
36
|
+
|
|
37
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
38
|
+
printUsage();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const jsonOutput = rawArgs.includes("--json");
|
|
43
|
+
let args = rawArgs.filter((a) => a !== "--json");
|
|
44
|
+
|
|
45
|
+
const [conversationKey, filteredArgs] = extractFlag(
|
|
46
|
+
args,
|
|
47
|
+
"--conversation-key",
|
|
48
|
+
);
|
|
49
|
+
args = filteredArgs;
|
|
50
|
+
|
|
51
|
+
let assistantId: string | undefined;
|
|
52
|
+
let messageContent: string | undefined;
|
|
53
|
+
|
|
54
|
+
if (args.length >= 2) {
|
|
55
|
+
// vellum message <assistant> <message>
|
|
56
|
+
assistantId = args[0];
|
|
57
|
+
messageContent = args[1];
|
|
58
|
+
} else if (args.length === 1) {
|
|
59
|
+
// vellum message <message> (uses active/latest assistant)
|
|
60
|
+
messageContent = args[0];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!messageContent) {
|
|
64
|
+
console.error("Error: message content is required.");
|
|
65
|
+
console.error("");
|
|
66
|
+
printUsage();
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const client = new AssistantClient({ assistantId });
|
|
71
|
+
|
|
72
|
+
const payload: Record<string, string> = {
|
|
73
|
+
content: messageContent,
|
|
74
|
+
sourceChannel: "vellum",
|
|
75
|
+
interface: "cli",
|
|
76
|
+
};
|
|
77
|
+
if (conversationKey) {
|
|
78
|
+
payload.conversationKey = conversationKey;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const response = await client.post("/messages/", payload);
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const body = await response.text().catch(() => "");
|
|
85
|
+
console.error(
|
|
86
|
+
`Error: HTTP ${response.status}: ${body || response.statusText}`,
|
|
87
|
+
);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const result = (await response.json()) as {
|
|
92
|
+
accepted: boolean;
|
|
93
|
+
messageId: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (jsonOutput) {
|
|
97
|
+
console.log(JSON.stringify(result, null, 2));
|
|
98
|
+
} else {
|
|
99
|
+
if (result.accepted) {
|
|
100
|
+
console.log(`Message accepted (id: ${result.messageId})`);
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`Message rejected (id: ${result.messageId})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/commands/restore.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
} from "../lib/guardian-token.js";
|
|
9
9
|
import {
|
|
10
10
|
readPlatformToken,
|
|
11
|
-
fetchOrganizationId,
|
|
12
11
|
rollbackPlatformAssistant,
|
|
13
12
|
platformImportPreflight,
|
|
14
13
|
platformImportBundle,
|
|
@@ -177,18 +176,6 @@ async function restorePlatform(
|
|
|
177
176
|
process.exit(1);
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
let orgId: string;
|
|
181
|
-
try {
|
|
182
|
-
orgId = await fetchOrganizationId(token, entry.runtimeUrl);
|
|
183
|
-
} catch (err) {
|
|
184
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
185
|
-
if (msg.includes("401") || msg.includes("403")) {
|
|
186
|
-
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
throw err;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
179
|
// Step 2 — Dry-run path
|
|
193
180
|
if (opts.dryRun) {
|
|
194
181
|
if (opts.version) {
|
|
@@ -205,7 +192,6 @@ async function restorePlatform(
|
|
|
205
192
|
preflightResult = await platformImportPreflight(
|
|
206
193
|
new Uint8Array(bundleData),
|
|
207
194
|
token,
|
|
208
|
-
orgId,
|
|
209
195
|
entry.runtimeUrl,
|
|
210
196
|
);
|
|
211
197
|
} catch (err) {
|
|
@@ -316,12 +302,7 @@ async function restorePlatform(
|
|
|
316
302
|
);
|
|
317
303
|
|
|
318
304
|
try {
|
|
319
|
-
await rollbackPlatformAssistant(
|
|
320
|
-
token,
|
|
321
|
-
orgId,
|
|
322
|
-
opts.version,
|
|
323
|
-
entry.runtimeUrl,
|
|
324
|
-
);
|
|
305
|
+
await rollbackPlatformAssistant(token, opts.version, entry.runtimeUrl);
|
|
325
306
|
} catch (err) {
|
|
326
307
|
const msg = err instanceof Error ? err.message : String(err);
|
|
327
308
|
if (msg.includes("401") || msg.includes("403")) {
|
|
@@ -345,7 +326,6 @@ async function restorePlatform(
|
|
|
345
326
|
importResult = await platformImportBundle(
|
|
346
327
|
new Uint8Array(bundleData),
|
|
347
328
|
token,
|
|
348
|
-
orgId,
|
|
349
329
|
entry.runtimeUrl,
|
|
350
330
|
);
|
|
351
331
|
} catch (err) {
|
package/src/commands/retire.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "../lib/assistant-config";
|
|
5
5
|
import type { AssistantEntry } from "../lib/assistant-config";
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
authHeaders,
|
|
8
8
|
getPlatformUrl,
|
|
9
9
|
readPlatformToken,
|
|
10
10
|
} from "../lib/platform-client";
|
|
@@ -93,16 +93,11 @@ async function retireVellum(
|
|
|
93
93
|
process.exit(1);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const orgId = await fetchOrganizationId(token, runtimeUrl);
|
|
97
|
-
|
|
98
96
|
const platformUrl = runtimeUrl || getPlatformUrl();
|
|
99
97
|
const url = `${platformUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
|
|
100
98
|
const response = await fetch(url, {
|
|
101
99
|
method: "DELETE",
|
|
102
|
-
headers:
|
|
103
|
-
"X-Session-Token": token,
|
|
104
|
-
"Vellum-Organization-Id": orgId,
|
|
105
|
-
},
|
|
100
|
+
headers: await authHeaders(token, runtimeUrl),
|
|
106
101
|
});
|
|
107
102
|
|
|
108
103
|
if (!response.ok) {
|
package/src/commands/rollback.ts
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
import type { ServiceName } from "../lib/docker";
|
|
18
18
|
import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
|
|
19
19
|
import {
|
|
20
|
-
fetchOrganizationId,
|
|
21
20
|
readPlatformToken,
|
|
22
21
|
rollbackPlatformAssistant,
|
|
23
22
|
} from "../lib/platform-client.js";
|
|
@@ -35,7 +34,7 @@ import {
|
|
|
35
34
|
UPGRADE_PROGRESS,
|
|
36
35
|
waitForReady,
|
|
37
36
|
} from "../lib/upgrade-lifecycle.js";
|
|
38
|
-
import {
|
|
37
|
+
import { compareVersions } from "../lib/version-compat.js";
|
|
39
38
|
|
|
40
39
|
function parseArgs(): { name: string | null; version: string | null } {
|
|
41
40
|
const args = process.argv.slice(3);
|
|
@@ -153,21 +152,12 @@ async function rollbackPlatformViaEndpoint(
|
|
|
153
152
|
|
|
154
153
|
// Step 1 — Version validation (only if version provided)
|
|
155
154
|
if (version && currentVersion) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
(target.major === current.major &&
|
|
163
|
-
target.minor === current.minor &&
|
|
164
|
-
target.patch < current.patch);
|
|
165
|
-
if (!isOlder) {
|
|
166
|
-
const msg = `Target version ${version} is not older than the current version ${currentVersion}. Use \`vellum upgrade --version ${version}\` to upgrade.`;
|
|
167
|
-
console.error(msg);
|
|
168
|
-
emitCliError("VERSION_DIRECTION", msg);
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
155
|
+
const cmp = compareVersions(version, currentVersion);
|
|
156
|
+
if (cmp !== null && cmp >= 0) {
|
|
157
|
+
const msg = `Target version ${version} is not older than the current version ${currentVersion}. Use \`vellum upgrade --version ${version}\` to upgrade.`;
|
|
158
|
+
console.error(msg);
|
|
159
|
+
emitCliError("VERSION_DIRECTION", msg);
|
|
160
|
+
process.exit(1);
|
|
171
161
|
}
|
|
172
162
|
}
|
|
173
163
|
|
|
@@ -181,20 +171,6 @@ async function rollbackPlatformViaEndpoint(
|
|
|
181
171
|
process.exit(1);
|
|
182
172
|
}
|
|
183
173
|
|
|
184
|
-
let orgId: string;
|
|
185
|
-
try {
|
|
186
|
-
orgId = await fetchOrganizationId(token, entry.runtimeUrl);
|
|
187
|
-
} catch (err) {
|
|
188
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
189
|
-
if (msg.includes("401") || msg.includes("403")) {
|
|
190
|
-
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
191
|
-
} else {
|
|
192
|
-
console.error(`Error: ${msg}`);
|
|
193
|
-
}
|
|
194
|
-
emitCliError("AUTH_FAILED", "Failed to authenticate with platform", msg);
|
|
195
|
-
process.exit(1);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
174
|
// Step 3 — Call rollback endpoint
|
|
199
175
|
if (version) {
|
|
200
176
|
console.log(`Rolling back to ${version}...`);
|
|
@@ -204,12 +180,7 @@ async function rollbackPlatformViaEndpoint(
|
|
|
204
180
|
|
|
205
181
|
let result: { detail: string; version: string | null };
|
|
206
182
|
try {
|
|
207
|
-
result = await rollbackPlatformAssistant(
|
|
208
|
-
token,
|
|
209
|
-
orgId,
|
|
210
|
-
version,
|
|
211
|
-
entry.runtimeUrl,
|
|
212
|
-
);
|
|
183
|
+
result = await rollbackPlatformAssistant(token, version, entry.runtimeUrl);
|
|
213
184
|
} catch (err) {
|
|
214
185
|
const detail = err instanceof Error ? err.message : String(err);
|
|
215
186
|
|
|
@@ -352,6 +323,11 @@ export async function rollback(): Promise<void> {
|
|
|
352
323
|
` Captured ${Object.keys(capturedEnv).length} env var(s) from ${res.assistantContainer}\n`,
|
|
353
324
|
);
|
|
354
325
|
|
|
326
|
+
// Capture GUARDIAN_BOOTSTRAP_SECRET from the gateway container (it is only
|
|
327
|
+
// set on gateway, not assistant) so it persists across container restarts.
|
|
328
|
+
const gatewayEnv = await captureContainerEnv(res.gatewayContainer);
|
|
329
|
+
const bootstrapSecret = gatewayEnv["GUARDIAN_BOOTSTRAP_SECRET"];
|
|
330
|
+
|
|
355
331
|
// Extract CES_SERVICE_TOKEN from captured env, or generate fresh one
|
|
356
332
|
const cesServiceToken =
|
|
357
333
|
capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex");
|
|
@@ -430,6 +406,7 @@ export async function rollback(): Promise<void> {
|
|
|
430
406
|
await startContainers(
|
|
431
407
|
{
|
|
432
408
|
signingKey,
|
|
409
|
+
bootstrapSecret,
|
|
433
410
|
cesServiceToken,
|
|
434
411
|
extraAssistantEnv,
|
|
435
412
|
gatewayPort,
|