@vellumai/cli 0.7.0 → 0.7.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/README.md +49 -0
- package/package.json +1 -1
- package/src/__tests__/backup.test.ts +475 -0
- package/src/__tests__/config-utils.test.ts +35 -48
- package/src/__tests__/teleport.test.ts +86 -28
- package/src/commands/backup.ts +117 -71
- package/src/commands/client.ts +10 -9
- package/src/commands/exec.ts +21 -8
- package/src/commands/hatch.ts +2 -6
- package/src/commands/login.ts +15 -33
- package/src/commands/logs.ts +2 -7
- package/src/commands/ps.ts +41 -6
- package/src/commands/restore.ts +26 -47
- package/src/commands/ssh.ts +2 -5
- package/src/commands/teleport.ts +38 -24
- package/src/commands/tunnel.ts +2 -7
- package/src/commands/upgrade.ts +108 -7
- package/src/components/DefaultMainScreen.tsx +25 -3
- package/src/index.ts +2 -7
- package/src/lib/__tests__/local-runtime-client.test.ts +122 -25
- package/src/lib/__tests__/platform-client-signed-url.test.ts +2 -2
- package/src/lib/__tests__/runtime-url.test.ts +87 -0
- package/src/lib/__tests__/terminal-session.test.ts +202 -0
- package/src/lib/assistant-client.ts +5 -21
- package/src/lib/assistant-config.ts +34 -16
- package/src/lib/cli-error.ts +1 -0
- package/src/lib/client-identity.ts +1 -1
- package/src/lib/config-utils.ts +1 -97
- package/src/lib/docker.ts +2 -2
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/local-runtime-client.ts +81 -28
- package/src/lib/local.ts +27 -58
- package/src/lib/platform-client.ts +1 -220
- package/src/lib/platform-releases.ts +23 -0
- package/src/lib/runtime-url.ts +30 -0
- package/src/lib/sync-cloud-assistants.ts +126 -0
- package/src/lib/terminal-client.ts +6 -1
- package/src/lib/terminal-session.ts +127 -48
- package/src/lib/tui-log.ts +60 -0
- package/src/lib/xdg-log.ts +10 -4
|
@@ -13,9 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
getActiveAssistant,
|
|
18
|
-
loadLatestAssistant,
|
|
16
|
+
resolveAssistant,
|
|
19
17
|
} from "./assistant-config.js";
|
|
20
18
|
import { GATEWAY_PORT } from "./constants.js";
|
|
21
19
|
import { loadGuardianToken } from "./guardian-token.js";
|
|
@@ -58,27 +56,13 @@ export class AssistantClient {
|
|
|
58
56
|
* @throws If no matching assistant is found.
|
|
59
57
|
*/
|
|
60
58
|
constructor(opts?: AssistantClientOpts) {
|
|
61
|
-
const
|
|
62
|
-
let entry = nameOrId ? findAssistantByName(nameOrId) : null;
|
|
63
|
-
|
|
64
|
-
if (nameOrId && !entry) {
|
|
65
|
-
throw new Error(`No assistant found with name '${nameOrId}'.`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!entry) {
|
|
69
|
-
const active = getActiveAssistant();
|
|
70
|
-
if (active) {
|
|
71
|
-
entry = findAssistantByName(active);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!entry) {
|
|
76
|
-
entry = loadLatestAssistant();
|
|
77
|
-
}
|
|
59
|
+
const entry = resolveAssistant(opts?.assistantId);
|
|
78
60
|
|
|
79
61
|
if (!entry) {
|
|
80
62
|
throw new Error(
|
|
81
|
-
|
|
63
|
+
opts?.assistantId
|
|
64
|
+
? `No assistant found with name '${opts.assistantId}'.`
|
|
65
|
+
: "No assistant found. Hatch one first with 'vellum hatch'.",
|
|
82
66
|
);
|
|
83
67
|
}
|
|
84
68
|
|
|
@@ -343,19 +343,17 @@ export function setActiveAssistant(assistantId: string): void {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
/**
|
|
346
|
-
*
|
|
346
|
+
* Best-effort resolution of the target assistant. Returns null when no
|
|
347
|
+
* match is found — callers decide how to handle the absence.
|
|
348
|
+
*
|
|
349
|
+
* Priority:
|
|
347
350
|
* 1. Explicit name argument
|
|
348
351
|
* 2. Active assistant set via `vellum use`
|
|
349
|
-
* 3. Sole
|
|
352
|
+
* 3. Sole lockfile entry (any cloud)
|
|
350
353
|
*/
|
|
351
|
-
export function
|
|
354
|
+
export function resolveAssistant(nameArg?: string): AssistantEntry | null {
|
|
352
355
|
if (nameArg) {
|
|
353
|
-
|
|
354
|
-
if (!entry) {
|
|
355
|
-
console.error(`No assistant found with name '${nameArg}'.`);
|
|
356
|
-
process.exit(1);
|
|
357
|
-
}
|
|
358
|
-
return entry;
|
|
356
|
+
return findAssistantByName(nameArg);
|
|
359
357
|
}
|
|
360
358
|
|
|
361
359
|
const active = getActiveAssistant();
|
|
@@ -366,15 +364,35 @@ export function resolveTargetAssistant(nameArg?: string): AssistantEntry {
|
|
|
366
364
|
}
|
|
367
365
|
|
|
368
366
|
const all = readAssistants();
|
|
369
|
-
|
|
370
|
-
|
|
367
|
+
if (all.length === 1) return all[0];
|
|
368
|
+
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
371
|
|
|
372
|
-
|
|
373
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Resolve which assistant to target for a command, exiting the process
|
|
374
|
+
* with a user-facing error when resolution fails.
|
|
375
|
+
*
|
|
376
|
+
* Priority:
|
|
377
|
+
* 1. Explicit name argument
|
|
378
|
+
* 2. Active assistant set via `vellum use`
|
|
379
|
+
* 3. Sole lockfile entry (any cloud)
|
|
380
|
+
*/
|
|
381
|
+
export function resolveTargetAssistant(nameArg?: string): AssistantEntry {
|
|
382
|
+
const entry = resolveAssistant(nameArg);
|
|
383
|
+
if (entry) return entry;
|
|
384
|
+
|
|
385
|
+
if (nameArg) {
|
|
386
|
+
console.error(`No assistant found with name '${nameArg}'.`);
|
|
374
387
|
} else {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
388
|
+
const all = readAssistants();
|
|
389
|
+
if (all.length === 0) {
|
|
390
|
+
console.error("No assistant found. Run 'vellum hatch' first.");
|
|
391
|
+
} else {
|
|
392
|
+
console.error(
|
|
393
|
+
`Multiple assistants found. Set an active assistant with 'vellum use <name>'.`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
378
396
|
}
|
|
379
397
|
process.exit(1);
|
|
380
398
|
}
|
package/src/lib/cli-error.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Stable per-install client identity for the CLI.
|
|
3
3
|
*
|
|
4
4
|
* Generates a UUID on first use and persists it to
|
|
5
|
-
* `~/.config/vellum/client-id` so the daemon's
|
|
5
|
+
* `~/.config/vellum/client-id` so the daemon's event hub can
|
|
6
6
|
* track this terminal across SSE reconnects and CLI restarts.
|
|
7
7
|
*/
|
|
8
8
|
|
package/src/lib/config-utils.ts
CHANGED
|
@@ -2,11 +2,6 @@ import { writeFileSync } from "fs";
|
|
|
2
2
|
import { tmpdir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
|
|
5
|
-
const ANTHROPIC_PROVIDER = "anthropic";
|
|
6
|
-
const ANTHROPIC_DEFAULT_MODEL = "claude-opus-4-7";
|
|
7
|
-
const MAIN_AGENT_OPUS_MODEL = "claude-opus-4-7";
|
|
8
|
-
const MAIN_AGENT_OPUS_MAX_TOKENS = 32000;
|
|
9
|
-
|
|
10
5
|
/**
|
|
11
6
|
* Convert flat dot-notation key=value pairs into a nested config object.
|
|
12
7
|
*
|
|
@@ -37,20 +32,6 @@ export function buildNestedConfig(
|
|
|
37
32
|
return config;
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
/**
|
|
41
|
-
* Build the first-boot workspace config overlay passed to the assistant during
|
|
42
|
-
* hatch. Anthropic onboarding sets `llm.default.model` to Sonnet so background
|
|
43
|
-
* fallback work stays cheaper, while the main conversation thread should remain
|
|
44
|
-
* on Opus via the same call-site override seeded by workspace migration 050.
|
|
45
|
-
*/
|
|
46
|
-
export function buildInitialConfig(
|
|
47
|
-
configValues: Record<string, string>,
|
|
48
|
-
): Record<string, unknown> {
|
|
49
|
-
const config = buildNestedConfig(configValues);
|
|
50
|
-
seedAnthropicMainAgentCallSite(config);
|
|
51
|
-
return config;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
35
|
/**
|
|
55
36
|
* Write arbitrary key-value pairs to a temporary JSON file and return its
|
|
56
37
|
* path. The caller passes this path to the daemon via the
|
|
@@ -68,7 +49,7 @@ export function writeInitialConfig(
|
|
|
68
49
|
): string | undefined {
|
|
69
50
|
if (Object.keys(configValues).length === 0) return undefined;
|
|
70
51
|
|
|
71
|
-
const config =
|
|
52
|
+
const config = buildNestedConfig(configValues);
|
|
72
53
|
const tempPath = join(
|
|
73
54
|
tmpdir(),
|
|
74
55
|
`vellum-default-workspace-config-${process.pid}-${Date.now()}.json`,
|
|
@@ -76,80 +57,3 @@ export function writeInitialConfig(
|
|
|
76
57
|
writeFileSync(tempPath, JSON.stringify(config, null, 2) + "\n");
|
|
77
58
|
return tempPath;
|
|
78
59
|
}
|
|
79
|
-
|
|
80
|
-
function seedAnthropicMainAgentCallSite(config: Record<string, unknown>): void {
|
|
81
|
-
const llm = ensureObject(config, "llm");
|
|
82
|
-
|
|
83
|
-
const existingCallSites = readObject(llm.callSites);
|
|
84
|
-
if (existingCallSites !== null && "mainAgent" in existingCallSites) return;
|
|
85
|
-
|
|
86
|
-
const { provider, model } = resolveInitialMainAgentBaseSelection(llm);
|
|
87
|
-
if (provider !== ANTHROPIC_PROVIDER) return;
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
model !== undefined &&
|
|
91
|
-
model !== ANTHROPIC_DEFAULT_MODEL &&
|
|
92
|
-
model !== MAIN_AGENT_OPUS_MODEL
|
|
93
|
-
) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const callSites = ensureObject(llm, "callSites");
|
|
98
|
-
|
|
99
|
-
callSites.mainAgent = {
|
|
100
|
-
model: MAIN_AGENT_OPUS_MODEL,
|
|
101
|
-
maxTokens: MAIN_AGENT_OPUS_MAX_TOKENS,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function resolveInitialMainAgentBaseSelection(llm: Record<string, unknown>): {
|
|
106
|
-
provider: string;
|
|
107
|
-
model?: string;
|
|
108
|
-
} {
|
|
109
|
-
const defaultBlock = readObject(llm.default);
|
|
110
|
-
let provider = readString(defaultBlock?.provider) ?? ANTHROPIC_PROVIDER;
|
|
111
|
-
let model = readString(defaultBlock?.model);
|
|
112
|
-
|
|
113
|
-
const profiles = readObject(llm.profiles);
|
|
114
|
-
const activeProfileName = readString(llm.activeProfile);
|
|
115
|
-
const activeProfile =
|
|
116
|
-
profiles !== null && activeProfileName !== undefined
|
|
117
|
-
? readObject(profiles[activeProfileName])
|
|
118
|
-
: null;
|
|
119
|
-
|
|
120
|
-
if (activeProfile !== null) {
|
|
121
|
-
provider = readString(activeProfile.provider) ?? provider;
|
|
122
|
-
model = readString(activeProfile.model) ?? model;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return model === undefined ? { provider } : { provider, model };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function ensureObject(
|
|
129
|
-
parent: Record<string, unknown>,
|
|
130
|
-
key: string,
|
|
131
|
-
): Record<string, unknown> {
|
|
132
|
-
const existing = parent[key];
|
|
133
|
-
if (
|
|
134
|
-
existing != null &&
|
|
135
|
-
typeof existing === "object" &&
|
|
136
|
-
!Array.isArray(existing)
|
|
137
|
-
) {
|
|
138
|
-
return existing as Record<string, unknown>;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const next: Record<string, unknown> = {};
|
|
142
|
-
parent[key] = next;
|
|
143
|
-
return next;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function readObject(value: unknown): Record<string, unknown> | null {
|
|
147
|
-
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
return value as Record<string, unknown>;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function readString(value: unknown): string | undefined {
|
|
154
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
155
|
-
}
|
package/src/lib/docker.ts
CHANGED
|
@@ -662,6 +662,8 @@ export function serviceDockerRunArgs(opts: {
|
|
|
662
662
|
"-e",
|
|
663
663
|
"IS_CONTAINERIZED=true",
|
|
664
664
|
"-e",
|
|
665
|
+
"DEBUG_STDOUT_LOGS=1",
|
|
666
|
+
"-e",
|
|
665
667
|
`VELLUM_ASSISTANT_NAME=${instanceName}`,
|
|
666
668
|
"-e",
|
|
667
669
|
"VELLUM_CLOUD=docker",
|
|
@@ -757,8 +759,6 @@ export function serviceDockerRunArgs(opts: {
|
|
|
757
759
|
"-e",
|
|
758
760
|
`RUNTIME_HTTP_PORT=${ASSISTANT_INTERNAL_PORT}`,
|
|
759
761
|
"-e",
|
|
760
|
-
"RUNTIME_PROXY_ENABLED=true",
|
|
761
|
-
"-e",
|
|
762
762
|
"CES_CREDENTIAL_URL=http://localhost:8090",
|
|
763
763
|
"-e",
|
|
764
764
|
"GATEWAY_IPC_SOCKET_DIR=/run/gateway-ipc",
|
package/src/lib/job-polling.ts
CHANGED
|
@@ -107,7 +107,7 @@ function isTransientPollError(err: unknown): boolean {
|
|
|
107
107
|
*
|
|
108
108
|
* Transient errors raised by `poll()` (5xx, network hiccups, rate-limits) are
|
|
109
109
|
* retried up to `maxTransientErrors` times before the last error propagates,
|
|
110
|
-
* matching the pre-rewrite
|
|
110
|
+
* matching the pre-rewrite migration-export polling loop's behavior so a
|
|
111
111
|
* single flaky poll doesn't abort a migration that may still be running.
|
|
112
112
|
*/
|
|
113
113
|
export async function pollJobUntilDone(
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { AssistantEntry } from "./assistant-config.js";
|
|
1
2
|
import {
|
|
3
|
+
authHeaders,
|
|
2
4
|
parseUnifiedJobStatus,
|
|
3
5
|
type UnifiedJobStatus,
|
|
4
6
|
} from "./platform-client.js";
|
|
7
|
+
import { resolveRuntimeMigrationUrl } from "./runtime-url.js";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Thrown when the local runtime returns 409 for an export/import request
|
|
@@ -34,6 +37,29 @@ function bearerHeaders(token: string): Record<string, string> {
|
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Build the auth + content headers for a runtime migration request.
|
|
42
|
+
*
|
|
43
|
+
* - For `cloud === "vellum"` we go through the platform's wildcard runtime
|
|
44
|
+
* proxy, which authenticates user-session / vak_ tokens via DRF's default
|
|
45
|
+
* authentication classes — `authHeaders()` produces the right combination
|
|
46
|
+
* (`X-Session-Token` + `Vellum-Organization-Id`, or `Authorization: Bearer
|
|
47
|
+
* vak_...`).
|
|
48
|
+
* - For local/docker the runtime endpoint expects a guardian-token bearer.
|
|
49
|
+
*/
|
|
50
|
+
async function migrationRequestHeaders(
|
|
51
|
+
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl">,
|
|
52
|
+
token: string,
|
|
53
|
+
): Promise<Record<string, string>> {
|
|
54
|
+
if (entry.cloud === "vellum") {
|
|
55
|
+
return {
|
|
56
|
+
...(await authHeaders(token, entry.runtimeUrl)),
|
|
57
|
+
Accept: "application/json",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return bearerHeaders(token);
|
|
61
|
+
}
|
|
62
|
+
|
|
37
63
|
interface Raw409Body {
|
|
38
64
|
detail?: string;
|
|
39
65
|
// The runtime's current 409 contract nests the payload under `error`:
|
|
@@ -69,13 +95,21 @@ async function throwIfInProgress(
|
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
/**
|
|
72
|
-
* Kick off an async export-to-GCS job on the
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* {
|
|
98
|
+
* Kick off an async export-to-GCS job on the assistant's runtime.
|
|
99
|
+
*
|
|
100
|
+
* For local/docker assistants this POSTs to
|
|
101
|
+
* `{runtimeUrl}/v1/migrations/export-to-gcs` with guardian-token bearer
|
|
102
|
+
* auth. For platform-managed (cloud="vellum") assistants the URL is rewritten
|
|
103
|
+
* to the wildcard-runtime-proxy shape
|
|
104
|
+
* `{platformUrl}/v1/assistants/<assistantId>/migrations/export-to-gcs` and
|
|
105
|
+
* authenticated via the platform-token header set the platform's DRF auth
|
|
106
|
+
* accepts (session / vak_).
|
|
107
|
+
*
|
|
108
|
+
* Returns the 202-accepted `job_id`. On 409 (another export in flight)
|
|
109
|
+
* throws {@link MigrationInProgressError} with the existing job_id.
|
|
76
110
|
*/
|
|
77
111
|
export async function localRuntimeExportToGcs(
|
|
78
|
-
|
|
112
|
+
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
|
|
79
113
|
token: string,
|
|
80
114
|
params: { uploadUrl: string; description?: string },
|
|
81
115
|
): Promise<{ jobId: string }> {
|
|
@@ -84,11 +118,14 @@ export async function localRuntimeExportToGcs(
|
|
|
84
118
|
body.description = params.description;
|
|
85
119
|
}
|
|
86
120
|
|
|
87
|
-
const response = await fetch(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
121
|
+
const response = await fetch(
|
|
122
|
+
resolveRuntimeMigrationUrl(entry, "export-to-gcs"),
|
|
123
|
+
{
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: await migrationRequestHeaders(entry, token),
|
|
126
|
+
body: JSON.stringify(body),
|
|
127
|
+
},
|
|
128
|
+
);
|
|
92
129
|
|
|
93
130
|
await throwIfInProgress(response, "export_in_progress");
|
|
94
131
|
|
|
@@ -110,20 +147,29 @@ export async function localRuntimeExportToGcs(
|
|
|
110
147
|
}
|
|
111
148
|
|
|
112
149
|
/**
|
|
113
|
-
* Kick off an async import-from-GCS job on the
|
|
114
|
-
*
|
|
115
|
-
*
|
|
150
|
+
* Kick off an async import-from-GCS job on the assistant's runtime.
|
|
151
|
+
*
|
|
152
|
+
* For local/docker assistants this POSTs to
|
|
153
|
+
* `{runtimeUrl}/v1/migrations/import-from-gcs` with guardian-token bearer
|
|
154
|
+
* auth. For platform-managed (cloud="vellum") assistants the URL is rewritten
|
|
155
|
+
* to the wildcard-runtime-proxy shape
|
|
156
|
+
* `{platformUrl}/v1/assistants/<assistantId>/migrations/import-from-gcs` and
|
|
157
|
+
* authenticated via the platform token. On 409 throws
|
|
158
|
+
* {@link MigrationInProgressError}.
|
|
116
159
|
*/
|
|
117
160
|
export async function localRuntimeImportFromGcs(
|
|
118
|
-
|
|
161
|
+
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
|
|
119
162
|
token: string,
|
|
120
163
|
params: { bundleUrl: string },
|
|
121
164
|
): Promise<{ jobId: string }> {
|
|
122
|
-
const response = await fetch(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
165
|
+
const response = await fetch(
|
|
166
|
+
resolveRuntimeMigrationUrl(entry, "import-from-gcs"),
|
|
167
|
+
{
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: await migrationRequestHeaders(entry, token),
|
|
170
|
+
body: JSON.stringify({ bundle_url: params.bundleUrl }),
|
|
171
|
+
},
|
|
172
|
+
);
|
|
127
173
|
|
|
128
174
|
await throwIfInProgress(response, "import_in_progress");
|
|
129
175
|
|
|
@@ -145,21 +191,28 @@ export async function localRuntimeImportFromGcs(
|
|
|
145
191
|
}
|
|
146
192
|
|
|
147
193
|
/**
|
|
148
|
-
* Poll the
|
|
149
|
-
*
|
|
150
|
-
*
|
|
194
|
+
* Poll the runtime's unified job-status endpoint.
|
|
195
|
+
*
|
|
196
|
+
* For local/docker assistants this GETs
|
|
197
|
+
* `{runtimeUrl}/v1/migrations/jobs/{jobId}` directly (guardian-token
|
|
198
|
+
* bearer). For platform-managed assistants it routes through the wildcard
|
|
199
|
+
* runtime proxy at
|
|
200
|
+
* `{platformUrl}/v1/assistants/<assistantId>/migrations/jobs/{jobId}` with
|
|
201
|
+
* platform-token auth — important: the platform's dedicated
|
|
202
|
+
* `/v1/migrations/jobs/{id}/` endpoint queries platform-side ImportJob
|
|
203
|
+
* records and would 404 on runtime-created job IDs.
|
|
151
204
|
*/
|
|
152
205
|
export async function localRuntimePollJobStatus(
|
|
153
|
-
|
|
206
|
+
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
|
|
154
207
|
token: string,
|
|
155
208
|
jobId: string,
|
|
156
209
|
): Promise<UnifiedJobStatus> {
|
|
157
|
-
const response = await fetch(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
210
|
+
const response = await fetch(
|
|
211
|
+
resolveRuntimeMigrationUrl(entry, `jobs/${jobId}`),
|
|
212
|
+
{
|
|
213
|
+
headers: await migrationRequestHeaders(entry, token),
|
|
161
214
|
},
|
|
162
|
-
|
|
215
|
+
);
|
|
163
216
|
|
|
164
217
|
if (response.status === 404) {
|
|
165
218
|
throw new Error("Migration job not found");
|
package/src/lib/local.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
writeFileSync,
|
|
9
9
|
} from "fs";
|
|
10
10
|
import { createRequire } from "module";
|
|
11
|
-
import { homedir,
|
|
11
|
+
import { homedir, networkInterfaces, platform, tmpdir } from "os";
|
|
12
12
|
import { dirname, join } from "path";
|
|
13
13
|
|
|
14
14
|
import {
|
|
@@ -392,7 +392,6 @@ async function startDaemonFromSource(
|
|
|
392
392
|
: {}),
|
|
393
393
|
};
|
|
394
394
|
if (resources) {
|
|
395
|
-
env.BASE_DATA_DIR = resources.instanceDir;
|
|
396
395
|
env.VELLUM_WORKSPACE_DIR = join(
|
|
397
396
|
resources.instanceDir,
|
|
398
397
|
".vellum",
|
|
@@ -403,6 +402,11 @@ async function startDaemonFromSource(
|
|
|
403
402
|
".vellum",
|
|
404
403
|
"protected",
|
|
405
404
|
);
|
|
405
|
+
env.CREDENTIAL_SECURITY_DIR = join(
|
|
406
|
+
resources.instanceDir,
|
|
407
|
+
".vellum",
|
|
408
|
+
"protected",
|
|
409
|
+
);
|
|
406
410
|
env.RUNTIME_HTTP_PORT = String(resources.daemonPort);
|
|
407
411
|
env.GATEWAY_PORT = String(resources.gatewayPort);
|
|
408
412
|
env.QDRANT_HTTP_PORT = String(resources.qdrantPort);
|
|
@@ -523,7 +527,6 @@ async function startDaemonWatchFromSource(
|
|
|
523
527
|
: {}),
|
|
524
528
|
};
|
|
525
529
|
if (resources) {
|
|
526
|
-
env.BASE_DATA_DIR = resources.instanceDir;
|
|
527
530
|
env.VELLUM_WORKSPACE_DIR = join(
|
|
528
531
|
resources.instanceDir,
|
|
529
532
|
".vellum",
|
|
@@ -534,6 +537,11 @@ async function startDaemonWatchFromSource(
|
|
|
534
537
|
".vellum",
|
|
535
538
|
"protected",
|
|
536
539
|
);
|
|
540
|
+
env.CREDENTIAL_SECURITY_DIR = join(
|
|
541
|
+
resources.instanceDir,
|
|
542
|
+
".vellum",
|
|
543
|
+
"protected",
|
|
544
|
+
);
|
|
537
545
|
env.RUNTIME_HTTP_PORT = String(resources.daemonPort);
|
|
538
546
|
env.GATEWAY_PORT = String(resources.gatewayPort);
|
|
539
547
|
env.QDRANT_HTTP_PORT = String(resources.qdrantPort);
|
|
@@ -675,51 +683,18 @@ export async function discoverPublicUrl(
|
|
|
675
683
|
return `http://${cloudIp}:${effectivePort}`;
|
|
676
684
|
}
|
|
677
685
|
|
|
678
|
-
// Log the local address source only when we actually use it.
|
|
679
|
-
if (localResult.source === "hostname") {
|
|
680
|
-
console.log(` Discovered macOS local hostname: ${localResult.label}`);
|
|
681
|
-
} else if (localResult.source === "lan") {
|
|
682
|
-
console.log(` Discovered LAN IP: ${localResult.label}`);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
686
|
return localResult.url;
|
|
686
687
|
}
|
|
687
688
|
|
|
688
689
|
/**
|
|
689
|
-
*
|
|
690
|
-
* address or falls back to localhost. Does not emit any logs — the caller
|
|
691
|
-
* decides whether to log based on which result is actually used.
|
|
690
|
+
* Returns the localhost URL for the gateway on the given port.
|
|
692
691
|
*/
|
|
693
692
|
function discoverLocalUrl(effectivePort: number): {
|
|
694
693
|
url: string;
|
|
695
|
-
source: "
|
|
696
|
-
label?: string;
|
|
694
|
+
source: "localhost";
|
|
697
695
|
} {
|
|
698
|
-
// On macOS, prefer the .local hostname (Bonjour/mDNS) so other devices on
|
|
699
|
-
// the same network can reach the gateway by name.
|
|
700
|
-
if (platform() === "darwin") {
|
|
701
|
-
const localHostname = getMacLocalHostname();
|
|
702
|
-
if (localHostname) {
|
|
703
|
-
return {
|
|
704
|
-
url: `http://${localHostname}:${effectivePort}`,
|
|
705
|
-
source: "hostname",
|
|
706
|
-
label: localHostname,
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
const lanIp = getLocalLanIPv4();
|
|
712
|
-
if (lanIp) {
|
|
713
|
-
return {
|
|
714
|
-
url: `http://${lanIp}:${effectivePort}`,
|
|
715
|
-
source: "lan",
|
|
716
|
-
label: lanIp,
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Final fallback to localhost when no LAN address could be discovered.
|
|
721
696
|
return {
|
|
722
|
-
url: `http://
|
|
697
|
+
url: `http://127.0.0.1:${effectivePort}`,
|
|
723
698
|
source: "localhost",
|
|
724
699
|
};
|
|
725
700
|
}
|
|
@@ -776,19 +751,6 @@ async function discoverCloudExternalIp(): Promise<string | undefined> {
|
|
|
776
751
|
return gcpIp ?? awsIp;
|
|
777
752
|
}
|
|
778
753
|
|
|
779
|
-
/**
|
|
780
|
-
* Returns the macOS Bonjour/mDNS `.local` hostname (e.g. "Vargass-Mac-Mini.local"),
|
|
781
|
-
* or undefined if not running on macOS or the hostname cannot be determined.
|
|
782
|
-
*/
|
|
783
|
-
export function getMacLocalHostname(): string | undefined {
|
|
784
|
-
const host = hostname();
|
|
785
|
-
if (!host) return undefined;
|
|
786
|
-
// macOS hostnames already end with .local when Bonjour is active
|
|
787
|
-
if (host.endsWith(".local")) return host;
|
|
788
|
-
// Otherwise, append .local — macOS resolves <ComputerName>.local via mDNS
|
|
789
|
-
return `${host}.local`;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
754
|
/**
|
|
793
755
|
* Returns the local IPv4 address most likely to be reachable from other
|
|
794
756
|
* devices on the same LAN.
|
|
@@ -988,8 +950,8 @@ export async function startLocalDaemon(
|
|
|
988
950
|
for (const key of [
|
|
989
951
|
"ANTHROPIC_API_KEY",
|
|
990
952
|
"APP_VERSION",
|
|
991
|
-
"BASE_DATA_DIR",
|
|
992
953
|
"GATEWAY_SECURITY_DIR",
|
|
954
|
+
"CREDENTIAL_SECURITY_DIR",
|
|
993
955
|
"VELLUM_ENVIRONMENT",
|
|
994
956
|
"VELLUM_PLATFORM_URL",
|
|
995
957
|
"QDRANT_HTTP_PORT",
|
|
@@ -1015,7 +977,6 @@ export async function startLocalDaemon(
|
|
|
1015
977
|
// When running a named instance, override env so the daemon resolves
|
|
1016
978
|
// all paths under the instance directory and listens on its own port.
|
|
1017
979
|
if (resources) {
|
|
1018
|
-
daemonEnv.BASE_DATA_DIR = resources.instanceDir;
|
|
1019
980
|
daemonEnv.VELLUM_WORKSPACE_DIR = join(
|
|
1020
981
|
resources.instanceDir,
|
|
1021
982
|
".vellum",
|
|
@@ -1026,6 +987,11 @@ export async function startLocalDaemon(
|
|
|
1026
987
|
".vellum",
|
|
1027
988
|
"protected",
|
|
1028
989
|
);
|
|
990
|
+
daemonEnv.CREDENTIAL_SECURITY_DIR = join(
|
|
991
|
+
resources.instanceDir,
|
|
992
|
+
".vellum",
|
|
993
|
+
"protected",
|
|
994
|
+
);
|
|
1029
995
|
daemonEnv.RUNTIME_HTTP_PORT = String(resources.daemonPort);
|
|
1030
996
|
daemonEnv.GATEWAY_PORT = String(resources.gatewayPort);
|
|
1031
997
|
daemonEnv.QDRANT_HTTP_PORT = String(resources.qdrantPort);
|
|
@@ -1165,7 +1131,7 @@ export async function startGateway(
|
|
|
1165
1131
|
|
|
1166
1132
|
const publicUrl = await discoverPublicUrl(effectiveGatewayPort);
|
|
1167
1133
|
if (publicUrl) {
|
|
1168
|
-
console.log(`
|
|
1134
|
+
console.log(` HTTP URL: ${publicUrl}`);
|
|
1169
1135
|
}
|
|
1170
1136
|
|
|
1171
1137
|
console.log("🌐 Starting gateway...");
|
|
@@ -1179,7 +1145,6 @@ export async function startGateway(
|
|
|
1179
1145
|
GATEWAY_PORT: String(effectiveGatewayPort),
|
|
1180
1146
|
// Pass gateway operational settings via env vars so the CLI does not
|
|
1181
1147
|
// need direct access to the workspace config file.
|
|
1182
|
-
RUNTIME_PROXY_ENABLED: "true",
|
|
1183
1148
|
RUNTIME_PROXY_REQUIRE_AUTH: "true",
|
|
1184
1149
|
UNMAPPED_POLICY: "default",
|
|
1185
1150
|
DEFAULT_ASSISTANT_ID: "self",
|
|
@@ -1197,7 +1162,6 @@ export async function startGateway(
|
|
|
1197
1162
|
// assistant DB directly for guardian bootstrap.
|
|
1198
1163
|
...(resources
|
|
1199
1164
|
? {
|
|
1200
|
-
BASE_DATA_DIR: resources.instanceDir,
|
|
1201
1165
|
VELLUM_WORKSPACE_DIR: join(
|
|
1202
1166
|
resources.instanceDir,
|
|
1203
1167
|
".vellum",
|
|
@@ -1208,6 +1172,11 @@ export async function startGateway(
|
|
|
1208
1172
|
".vellum",
|
|
1209
1173
|
"protected",
|
|
1210
1174
|
),
|
|
1175
|
+
CREDENTIAL_SECURITY_DIR: join(
|
|
1176
|
+
resources.instanceDir,
|
|
1177
|
+
".vellum",
|
|
1178
|
+
"protected",
|
|
1179
|
+
),
|
|
1211
1180
|
}
|
|
1212
1181
|
: {}),
|
|
1213
1182
|
};
|
|
@@ -1215,7 +1184,7 @@ export async function startGateway(
|
|
|
1215
1184
|
applyIpcSocketDirOverride(gatewayEnv);
|
|
1216
1185
|
|
|
1217
1186
|
if (publicUrl) {
|
|
1218
|
-
console.log(`
|
|
1187
|
+
console.log(` HTTP URL: ${publicUrl}`);
|
|
1219
1188
|
}
|
|
1220
1189
|
|
|
1221
1190
|
let gateway;
|