@vellumai/cli 0.4.25 → 0.4.29
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/README.md +24 -24
- package/package.json +1 -1
- package/src/__tests__/assistant-config.test.ts +17 -5
- package/src/__tests__/retire-archive.test.ts +6 -2
- package/src/adapters/openclaw-http-server.ts +22 -7
- package/src/commands/autonomy.ts +10 -11
- package/src/commands/client.ts +25 -9
- package/src/commands/config.ts +2 -6
- package/src/commands/contacts.ts +1 -4
- package/src/commands/hatch.ts +131 -36
- package/src/commands/login.ts +6 -2
- package/src/commands/pair.ts +26 -9
- package/src/commands/ps.ts +55 -23
- package/src/commands/recover.ts +4 -2
- package/src/commands/retire.ts +42 -14
- package/src/commands/sleep.ts +15 -3
- package/src/commands/ssh.ts +20 -13
- package/src/commands/tunnel.ts +6 -7
- package/src/commands/wake.ts +13 -4
- package/src/components/DefaultMainScreen.tsx +309 -99
- package/src/index.ts +2 -2
- package/src/lib/assistant-config.ts +9 -3
- package/src/lib/aws.ts +36 -11
- package/src/lib/constants.ts +3 -1
- package/src/lib/doctor-client.ts +23 -7
- package/src/lib/gcp.ts +74 -24
- package/src/lib/health-check.ts +14 -4
- package/src/lib/local.ts +249 -33
- package/src/lib/ngrok.ts +1 -3
- package/src/lib/openclaw-runtime-server.ts +7 -2
- package/src/lib/platform-client.ts +16 -3
- package/src/lib/xdg-log.ts +25 -5
package/src/index.ts
CHANGED
|
@@ -97,10 +97,10 @@ async function main() {
|
|
|
97
97
|
);
|
|
98
98
|
console.log(" recover Restore a previously retired local assistant");
|
|
99
99
|
console.log(" retire Delete an assistant instance");
|
|
100
|
-
console.log(" sleep Stop the
|
|
100
|
+
console.log(" sleep Stop the assistant process");
|
|
101
101
|
console.log(" ssh SSH into a remote assistant instance");
|
|
102
102
|
console.log(" tunnel Create a tunnel for a locally hosted assistant");
|
|
103
|
-
console.log(" wake Start the
|
|
103
|
+
console.log(" wake Start the assistant and gateway");
|
|
104
104
|
console.log(" whoami Show current logged-in user");
|
|
105
105
|
process.exit(0);
|
|
106
106
|
}
|
|
@@ -61,7 +61,8 @@ function readAssistants(): AssistantEntry[] {
|
|
|
61
61
|
return [];
|
|
62
62
|
}
|
|
63
63
|
return entries.filter(
|
|
64
|
-
(e) =>
|
|
64
|
+
(e) =>
|
|
65
|
+
typeof e.assistantId === "string" && typeof e.runtimeUrl === "string",
|
|
65
66
|
);
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -99,7 +100,9 @@ export function loadAllAssistants(): AssistantEntry[] {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
export function saveAssistantEntry(entry: AssistantEntry): void {
|
|
102
|
-
const entries = readAssistants().filter(
|
|
103
|
+
const entries = readAssistants().filter(
|
|
104
|
+
(e) => e.assistantId !== entry.assistantId,
|
|
105
|
+
);
|
|
103
106
|
entries.unshift(entry);
|
|
104
107
|
writeAssistants(entries);
|
|
105
108
|
}
|
|
@@ -114,7 +117,10 @@ export function syncConfigToLockfile(): void {
|
|
|
114
117
|
if (!existsSync(configPath)) return;
|
|
115
118
|
|
|
116
119
|
try {
|
|
117
|
-
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
|
|
120
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
|
|
121
|
+
string,
|
|
122
|
+
unknown
|
|
123
|
+
>;
|
|
118
124
|
const platform = raw.platform as Record<string, unknown> | undefined;
|
|
119
125
|
const data = readLockfile();
|
|
120
126
|
data.platformBaseUrl = (platform?.baseUrl as string) || undefined;
|
package/src/lib/aws.ts
CHANGED
|
@@ -43,7 +43,9 @@ export async function getDefaultVpcId(region: string): Promise<string> {
|
|
|
43
43
|
]);
|
|
44
44
|
const vpcId = output.trim();
|
|
45
45
|
if (!vpcId || vpcId === "None") {
|
|
46
|
-
throw new Error(
|
|
46
|
+
throw new Error(
|
|
47
|
+
"No default VPC found. Please create a default VPC or specify one.",
|
|
48
|
+
);
|
|
47
49
|
}
|
|
48
50
|
return vpcId;
|
|
49
51
|
}
|
|
@@ -342,15 +344,25 @@ async function pollAwsInstance(
|
|
|
342
344
|
const output = await awsSshExec(ip, keyPath, remoteCmd);
|
|
343
345
|
const sepIdx = output.indexOf("===HATCH_SEP===");
|
|
344
346
|
if (sepIdx === -1) {
|
|
345
|
-
return {
|
|
347
|
+
return {
|
|
348
|
+
lastLine: output.trim() || null,
|
|
349
|
+
done: false,
|
|
350
|
+
failed: false,
|
|
351
|
+
errorContent: "",
|
|
352
|
+
};
|
|
346
353
|
}
|
|
347
354
|
const errIdx = output.indexOf("===HATCH_ERR===");
|
|
348
355
|
const lastLine = output.substring(0, sepIdx).trim() || null;
|
|
349
356
|
const statusEnd = errIdx === -1 ? undefined : errIdx;
|
|
350
|
-
const status = output
|
|
357
|
+
const status = output
|
|
358
|
+
.substring(sepIdx + "===HATCH_SEP===".length, statusEnd)
|
|
359
|
+
.trim();
|
|
351
360
|
const errorContent =
|
|
352
|
-
errIdx === -1
|
|
353
|
-
|
|
361
|
+
errIdx === -1
|
|
362
|
+
? ""
|
|
363
|
+
: output.substring(errIdx + "===HATCH_ERR===".length).trim();
|
|
364
|
+
const done =
|
|
365
|
+
lastLine !== null && status !== "running" && status !== "pending";
|
|
354
366
|
const failed = errorContent.length > 0 || status === "error";
|
|
355
367
|
return { lastLine, done, failed, errorContent };
|
|
356
368
|
} catch {
|
|
@@ -394,7 +406,9 @@ export async function hatchAws(
|
|
|
394
406
|
}
|
|
395
407
|
} else {
|
|
396
408
|
while (await instanceExistsByName(instanceName, region)) {
|
|
397
|
-
console.log(
|
|
409
|
+
console.log(
|
|
410
|
+
`\u26a0\ufe0f Instance name ${instanceName} already exists, generating a new name...`,
|
|
411
|
+
);
|
|
398
412
|
const suffix = generateRandomSuffix();
|
|
399
413
|
instanceName = `${species}-${suffix}`;
|
|
400
414
|
}
|
|
@@ -405,7 +419,9 @@ export async function hatchAws(
|
|
|
405
419
|
const hatchedBy = process.env.VELLUM_HATCHED_BY;
|
|
406
420
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
407
421
|
if (!anthropicApiKey) {
|
|
408
|
-
console.error(
|
|
422
|
+
console.error(
|
|
423
|
+
"Error: ANTHROPIC_API_KEY environment variable is not set.",
|
|
424
|
+
);
|
|
409
425
|
process.exit(1);
|
|
410
426
|
}
|
|
411
427
|
|
|
@@ -464,7 +480,9 @@ export async function hatchAws(
|
|
|
464
480
|
try {
|
|
465
481
|
externalIp = await getInstancePublicIp(instanceId, region);
|
|
466
482
|
} catch {
|
|
467
|
-
console.log(
|
|
483
|
+
console.log(
|
|
484
|
+
"\u26a0\ufe0f Could not retrieve external IP yet (instance may still be starting)",
|
|
485
|
+
);
|
|
468
486
|
}
|
|
469
487
|
|
|
470
488
|
const runtimeUrl = externalIp
|
|
@@ -518,7 +536,9 @@ export async function hatchAws(
|
|
|
518
536
|
process.exit(1);
|
|
519
537
|
}
|
|
520
538
|
} else {
|
|
521
|
-
console.log(
|
|
539
|
+
console.log(
|
|
540
|
+
"\u26a0\ufe0f No external IP available for monitoring. Instance is still running.",
|
|
541
|
+
);
|
|
522
542
|
console.log(` Monitor with: vel logs ${instanceName}`);
|
|
523
543
|
console.log("");
|
|
524
544
|
}
|
|
@@ -532,7 +552,10 @@ export async function hatchAws(
|
|
|
532
552
|
}
|
|
533
553
|
}
|
|
534
554
|
} catch (error) {
|
|
535
|
-
console.error(
|
|
555
|
+
console.error(
|
|
556
|
+
"\u274c Error:",
|
|
557
|
+
error instanceof Error ? error.message : error,
|
|
558
|
+
);
|
|
536
559
|
process.exit(1);
|
|
537
560
|
}
|
|
538
561
|
}
|
|
@@ -610,7 +633,9 @@ export async function retireInstance(
|
|
|
610
633
|
}
|
|
611
634
|
}
|
|
612
635
|
|
|
613
|
-
console.log(
|
|
636
|
+
console.log(
|
|
637
|
+
`\u{1F5D1}\ufe0f Terminating AWS instance ${name} (${instanceId})\n`,
|
|
638
|
+
);
|
|
614
639
|
|
|
615
640
|
await exec("aws", [
|
|
616
641
|
"ec2",
|
package/src/lib/constants.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export const FIREWALL_TAG = "vellum-assistant";
|
|
2
|
-
export const GATEWAY_PORT = process.env.GATEWAY_PORT
|
|
2
|
+
export const GATEWAY_PORT = process.env.GATEWAY_PORT
|
|
3
|
+
? Number(process.env.GATEWAY_PORT)
|
|
4
|
+
: 7830;
|
|
3
5
|
export const VALID_REMOTE_HOSTS = ["local", "gcp", "aws", "custom"] as const;
|
|
4
6
|
export type RemoteHost = (typeof VALID_REMOTE_HOSTS)[number];
|
|
5
7
|
export const VALID_SPECIES = ["openclaw", "vellum"] as const;
|
package/src/lib/doctor-client.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const DOCTOR_URL = process.env.DOCTOR_URL || "https://doctor.vellum.ai";
|
|
2
2
|
|
|
3
|
-
export type ProgressPhase=
|
|
3
|
+
export type ProgressPhase =
|
|
4
|
+
| "invoking_prompt"
|
|
5
|
+
| "calling_tool"
|
|
6
|
+
| "processing_tool_result";
|
|
4
7
|
|
|
5
8
|
export interface ProgressEvent {
|
|
6
9
|
phase: ProgressPhase;
|
|
@@ -48,7 +51,10 @@ async function streamDoctorResponse(
|
|
|
48
51
|
|
|
49
52
|
for (const line of lines) {
|
|
50
53
|
if (!line.trim()) continue;
|
|
51
|
-
const parsed = JSON.parse(line) as { type: string } & Record<
|
|
54
|
+
const parsed = JSON.parse(line) as { type: string } & Record<
|
|
55
|
+
string,
|
|
56
|
+
unknown
|
|
57
|
+
>;
|
|
52
58
|
receivedEventTypes.push(parsed.type);
|
|
53
59
|
if (parsed.type === "progress") {
|
|
54
60
|
onProgress?.(parsed as unknown as ProgressEvent);
|
|
@@ -60,7 +66,8 @@ async function streamDoctorResponse(
|
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
} catch (streamErr) {
|
|
63
|
-
const detail =
|
|
69
|
+
const detail =
|
|
70
|
+
streamErr instanceof Error ? streamErr.message : String(streamErr);
|
|
64
71
|
throw new Error(
|
|
65
72
|
`Doctor stream interrupted after ${chunkCount} chunks ` +
|
|
66
73
|
`(received events: [${receivedEventTypes.join(", ")}]): ${detail}`,
|
|
@@ -68,7 +75,10 @@ async function streamDoctorResponse(
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
if (buffer.trim()) {
|
|
71
|
-
const parsed = JSON.parse(buffer) as { type: string } & Record<
|
|
78
|
+
const parsed = JSON.parse(buffer) as { type: string } & Record<
|
|
79
|
+
string,
|
|
80
|
+
unknown
|
|
81
|
+
>;
|
|
72
82
|
receivedEventTypes.push(parsed.type);
|
|
73
83
|
if (parsed.type === "result") {
|
|
74
84
|
result = parsed as unknown as DoctorResult;
|
|
@@ -105,14 +115,20 @@ async function callDoctorDaemon(
|
|
|
105
115
|
const response = await fetch(`${DOCTOR_URL}/doctor`, {
|
|
106
116
|
method: "POST",
|
|
107
117
|
headers: { "Content-Type": "application/json" },
|
|
108
|
-
body: JSON.stringify({
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
assistantId,
|
|
120
|
+
project,
|
|
121
|
+
zone,
|
|
122
|
+
userPrompt,
|
|
123
|
+
sessionId,
|
|
124
|
+
chatContext,
|
|
125
|
+
}),
|
|
109
126
|
});
|
|
110
127
|
return await streamDoctorResponse(response, onProgress, onLog);
|
|
111
128
|
} catch (err) {
|
|
112
129
|
lastError = err;
|
|
113
130
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
114
|
-
const logMsg =
|
|
115
|
-
`[doctor-client] Attempt ${attempt + 1}/${MAX_RETRIES} failed: ${errMsg}`;
|
|
131
|
+
const logMsg = `[doctor-client] Attempt ${attempt + 1}/${MAX_RETRIES} failed: ${errMsg}`;
|
|
116
132
|
onLog?.(logMsg);
|
|
117
133
|
if (attempt < MAX_RETRIES - 1) {
|
|
118
134
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
package/src/lib/gcp.ts
CHANGED
|
@@ -11,11 +11,7 @@ import { generateRandomSuffix } from "./random-name";
|
|
|
11
11
|
import { exec, execOutput } from "./step-runner";
|
|
12
12
|
|
|
13
13
|
export async function getActiveProject(): Promise<string> {
|
|
14
|
-
const output = await execOutput("gcloud", [
|
|
15
|
-
"config",
|
|
16
|
-
"get-value",
|
|
17
|
-
"project",
|
|
18
|
-
]);
|
|
14
|
+
const output = await execOutput("gcloud", ["config", "get-value", "project"]);
|
|
19
15
|
const project = output.trim();
|
|
20
16
|
if (!project || project === "(unset)") {
|
|
21
17
|
throw new Error(
|
|
@@ -87,7 +83,10 @@ async function describeFirewallRule(
|
|
|
87
83
|
}
|
|
88
84
|
}
|
|
89
85
|
|
|
90
|
-
function ruleNeedsUpdate(
|
|
86
|
+
function ruleNeedsUpdate(
|
|
87
|
+
spec: FirewallRuleSpec,
|
|
88
|
+
state: FirewallRuleState,
|
|
89
|
+
): boolean {
|
|
91
90
|
return (
|
|
92
91
|
spec.direction !== state.direction ||
|
|
93
92
|
spec.rules !== state.allowed ||
|
|
@@ -98,7 +97,11 @@ function ruleNeedsUpdate(spec: FirewallRuleSpec, state: FirewallRuleState): bool
|
|
|
98
97
|
);
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
async function createFirewallRule(
|
|
100
|
+
async function createFirewallRule(
|
|
101
|
+
spec: FirewallRuleSpec,
|
|
102
|
+
project: string,
|
|
103
|
+
account?: string,
|
|
104
|
+
): Promise<void> {
|
|
102
105
|
const args = [
|
|
103
106
|
"compute",
|
|
104
107
|
"firewall-rules",
|
|
@@ -121,7 +124,11 @@ async function createFirewallRule(spec: FirewallRuleSpec, project: string, accou
|
|
|
121
124
|
await exec("gcloud", args);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
async function deleteFirewallRule(
|
|
127
|
+
async function deleteFirewallRule(
|
|
128
|
+
ruleName: string,
|
|
129
|
+
project: string,
|
|
130
|
+
account?: string,
|
|
131
|
+
): Promise<void> {
|
|
125
132
|
const args = [
|
|
126
133
|
"compute",
|
|
127
134
|
"firewall-rules",
|
|
@@ -151,7 +158,10 @@ export async function syncFirewallRules(
|
|
|
151
158
|
];
|
|
152
159
|
if (account) listArgs.push(`--account=${account}`);
|
|
153
160
|
const output = await execOutput("gcloud", listArgs);
|
|
154
|
-
const allRules = JSON.parse(output) as Array<{
|
|
161
|
+
const allRules = JSON.parse(output) as Array<{
|
|
162
|
+
name: string;
|
|
163
|
+
targetTags?: string[];
|
|
164
|
+
}>;
|
|
155
165
|
existingNames = allRules
|
|
156
166
|
.filter((r) => r.targetTags?.includes(tag))
|
|
157
167
|
.map((r) => r.name);
|
|
@@ -211,7 +221,9 @@ export interface GcpInstance {
|
|
|
211
221
|
species: string | null;
|
|
212
222
|
}
|
|
213
223
|
|
|
214
|
-
export async function listAssistantInstances(
|
|
224
|
+
export async function listAssistantInstances(
|
|
225
|
+
project: string,
|
|
226
|
+
): Promise<GcpInstance[]> {
|
|
215
227
|
const output = await execOutput("gcloud", [
|
|
216
228
|
"compute",
|
|
217
229
|
"instances",
|
|
@@ -231,7 +243,8 @@ export async function listAssistantInstances(project: string): Promise<GcpInstan
|
|
|
231
243
|
return {
|
|
232
244
|
name: inst.name,
|
|
233
245
|
zone: zoneParts[zoneParts.length - 1] || "",
|
|
234
|
-
externalIp:
|
|
246
|
+
externalIp:
|
|
247
|
+
inst.networkInterfaces?.[0]?.accessConfigs?.[0]?.natIP ?? null,
|
|
235
248
|
species: inst.labels?.species ?? null,
|
|
236
249
|
};
|
|
237
250
|
});
|
|
@@ -258,7 +271,10 @@ export async function instanceExists(
|
|
|
258
271
|
return true;
|
|
259
272
|
} catch (error) {
|
|
260
273
|
const msg = error instanceof Error ? error.message.toLowerCase() : "";
|
|
261
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
msg.includes("was not found") ||
|
|
276
|
+
msg.includes("could not fetch resource")
|
|
277
|
+
) {
|
|
262
278
|
return false;
|
|
263
279
|
}
|
|
264
280
|
throw error;
|
|
@@ -407,15 +423,25 @@ async function pollInstance(
|
|
|
407
423
|
const output = await execOutput("gcloud", args);
|
|
408
424
|
const sepIdx = output.indexOf("===HATCH_SEP===");
|
|
409
425
|
if (sepIdx === -1) {
|
|
410
|
-
return {
|
|
426
|
+
return {
|
|
427
|
+
lastLine: output.trim() || null,
|
|
428
|
+
done: false,
|
|
429
|
+
failed: false,
|
|
430
|
+
errorContent: "",
|
|
431
|
+
};
|
|
411
432
|
}
|
|
412
433
|
const errIdx = output.indexOf("===HATCH_ERR===");
|
|
413
434
|
const lastLine = output.substring(0, sepIdx).trim() || null;
|
|
414
435
|
const statusEnd = errIdx === -1 ? undefined : errIdx;
|
|
415
|
-
const status = output
|
|
436
|
+
const status = output
|
|
437
|
+
.substring(sepIdx + "===HATCH_SEP===".length, statusEnd)
|
|
438
|
+
.trim();
|
|
416
439
|
const errorContent =
|
|
417
|
-
errIdx === -1
|
|
418
|
-
|
|
440
|
+
errIdx === -1
|
|
441
|
+
? ""
|
|
442
|
+
: output.substring(errIdx + "===HATCH_ERR===".length).trim();
|
|
443
|
+
const done =
|
|
444
|
+
lastLine !== null && status !== "active" && status !== "activating";
|
|
419
445
|
const failed = errorContent.length > 0 || status === "failed";
|
|
420
446
|
return { lastLine, done, failed, errorContent };
|
|
421
447
|
} catch {
|
|
@@ -483,7 +509,9 @@ async function recoverFromCurlFailure(
|
|
|
483
509
|
if (account) sshArgs.push(`--account=${account}`);
|
|
484
510
|
console.log("\ud83d\udd27 Running install script on instance...");
|
|
485
511
|
await exec("gcloud", sshArgs);
|
|
486
|
-
try {
|
|
512
|
+
try {
|
|
513
|
+
unlinkSync(installScriptPath);
|
|
514
|
+
} catch {}
|
|
487
515
|
}
|
|
488
516
|
|
|
489
517
|
export async function hatchGcp(
|
|
@@ -542,7 +570,9 @@ export async function hatchGcp(
|
|
|
542
570
|
}
|
|
543
571
|
} else {
|
|
544
572
|
while (await instanceExists(instanceName, project, zone, account)) {
|
|
545
|
-
console.log(
|
|
573
|
+
console.log(
|
|
574
|
+
`\u26a0\ufe0f Instance name ${instanceName} already exists, generating a new name...`,
|
|
575
|
+
);
|
|
546
576
|
const suffix = generateRandomSuffix();
|
|
547
577
|
instanceName = `${species}-${suffix}`;
|
|
548
578
|
}
|
|
@@ -553,7 +583,9 @@ export async function hatchGcp(
|
|
|
553
583
|
const hatchedBy = process.env.VELLUM_HATCHED_BY;
|
|
554
584
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
555
585
|
if (!anthropicApiKey) {
|
|
556
|
-
console.error(
|
|
586
|
+
console.error(
|
|
587
|
+
"Error: ANTHROPIC_API_KEY environment variable is not set.",
|
|
588
|
+
);
|
|
557
589
|
process.exit(1);
|
|
558
590
|
}
|
|
559
591
|
const startupScript = await buildStartupScript(
|
|
@@ -596,7 +628,12 @@ export async function hatchGcp(
|
|
|
596
628
|
}
|
|
597
629
|
|
|
598
630
|
console.log("\ud83d\udd12 Syncing firewall rules...");
|
|
599
|
-
await syncFirewallRules(
|
|
631
|
+
await syncFirewallRules(
|
|
632
|
+
DESIRED_FIREWALL_RULES,
|
|
633
|
+
project,
|
|
634
|
+
FIREWALL_TAG,
|
|
635
|
+
account,
|
|
636
|
+
);
|
|
600
637
|
|
|
601
638
|
console.log(`\u2705 Instance ${instanceName} created successfully\n`);
|
|
602
639
|
|
|
@@ -615,7 +652,9 @@ export async function hatchGcp(
|
|
|
615
652
|
const ipOutput = await execOutput("gcloud", describeArgs);
|
|
616
653
|
externalIp = ipOutput.trim() || null;
|
|
617
654
|
} catch {
|
|
618
|
-
console.log(
|
|
655
|
+
console.log(
|
|
656
|
+
"\u26a0\ufe0f Could not retrieve external IP yet (instance may still be starting)",
|
|
657
|
+
);
|
|
619
658
|
}
|
|
620
659
|
|
|
621
660
|
const runtimeUrl = externalIp
|
|
@@ -672,8 +711,16 @@ export async function hatchGcp(
|
|
|
672
711
|
(await checkCurlFailure(instanceName, project, zone, account))
|
|
673
712
|
) {
|
|
674
713
|
const installScriptUrl = `${process.env.VELLUM_ASSISTANT_PLATFORM_URL ?? "https://assistant.vellum.ai"}/install.sh`;
|
|
675
|
-
console.log(
|
|
676
|
-
|
|
714
|
+
console.log(
|
|
715
|
+
`\ud83d\udd04 Detected install script curl failure for ${installScriptUrl}, attempting recovery...`,
|
|
716
|
+
);
|
|
717
|
+
await recoverFromCurlFailure(
|
|
718
|
+
instanceName,
|
|
719
|
+
project,
|
|
720
|
+
zone,
|
|
721
|
+
sshUser,
|
|
722
|
+
account,
|
|
723
|
+
);
|
|
677
724
|
console.log("\u2705 Recovery successful!");
|
|
678
725
|
} else {
|
|
679
726
|
process.exit(1);
|
|
@@ -689,7 +736,10 @@ export async function hatchGcp(
|
|
|
689
736
|
}
|
|
690
737
|
}
|
|
691
738
|
} catch (error) {
|
|
692
|
-
console.error(
|
|
739
|
+
console.error(
|
|
740
|
+
"\u274c Error:",
|
|
741
|
+
error instanceof Error ? error.message : error,
|
|
742
|
+
);
|
|
693
743
|
process.exit(1);
|
|
694
744
|
}
|
|
695
745
|
}
|
package/src/lib/health-check.ts
CHANGED
|
@@ -10,11 +10,16 @@ export interface HealthCheckResult {
|
|
|
10
10
|
detail: string | null;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export async function checkHealth(
|
|
13
|
+
export async function checkHealth(
|
|
14
|
+
runtimeUrl: string,
|
|
15
|
+
): Promise<HealthCheckResult> {
|
|
14
16
|
try {
|
|
15
17
|
const url = `${runtimeUrl}/healthz`;
|
|
16
18
|
const controller = new AbortController();
|
|
17
|
-
const timeoutId = setTimeout(
|
|
19
|
+
const timeoutId = setTimeout(
|
|
20
|
+
() => controller.abort(),
|
|
21
|
+
HEALTH_CHECK_TIMEOUT_MS,
|
|
22
|
+
);
|
|
18
23
|
|
|
19
24
|
const response = await fetch(url, {
|
|
20
25
|
signal: controller.signal,
|
|
@@ -29,10 +34,15 @@ export async function checkHealth(runtimeUrl: string): Promise<HealthCheckResult
|
|
|
29
34
|
|
|
30
35
|
const data = (await response.json()) as HealthResponse;
|
|
31
36
|
const status = data.status || "unknown";
|
|
32
|
-
return {
|
|
37
|
+
return {
|
|
38
|
+
status,
|
|
39
|
+
detail: status !== "healthy" ? (data.message ?? null) : null,
|
|
40
|
+
};
|
|
33
41
|
} catch (error) {
|
|
34
42
|
const status =
|
|
35
|
-
error instanceof Error && error.name === "AbortError"
|
|
43
|
+
error instanceof Error && error.name === "AbortError"
|
|
44
|
+
? "timeout"
|
|
45
|
+
: "unreachable";
|
|
36
46
|
return { status, detail: null };
|
|
37
47
|
}
|
|
38
48
|
}
|