@vellumai/cli 0.8.5 → 0.8.7
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/AGENTS.md +6 -0
- package/bun.lock +8 -0
- package/knip.json +6 -1
- package/node_modules/@vellumai/environments/bun.lock +24 -0
- package/node_modules/@vellumai/environments/package.json +18 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
- package/node_modules/@vellumai/environments/src/index.ts +11 -0
- package/{src/lib/environments → node_modules/@vellumai/environments/src}/seeds.ts +5 -9
- package/node_modules/@vellumai/environments/tsconfig.json +20 -0
- package/node_modules/@vellumai/local-mode/bun.lock +29 -0
- package/node_modules/@vellumai/local-mode/package.json +21 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/hatch.test.ts +93 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/package-boundary.test.ts +104 -0
- package/node_modules/@vellumai/local-mode/src/config.ts +59 -0
- package/node_modules/@vellumai/local-mode/src/gateway-proxy.ts +67 -0
- package/node_modules/@vellumai/local-mode/src/guardian-token.ts +122 -0
- package/node_modules/@vellumai/local-mode/src/hatch.ts +74 -0
- package/node_modules/@vellumai/local-mode/src/index.ts +26 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.ts +131 -0
- package/node_modules/@vellumai/local-mode/src/retire.ts +58 -0
- package/node_modules/@vellumai/local-mode/src/util.ts +102 -0
- package/node_modules/@vellumai/local-mode/tsconfig.json +16 -0
- package/package.json +12 -1
- package/src/__tests__/backup.test.ts +38 -0
- package/src/__tests__/env-drift.test.ts +32 -44
- package/src/__tests__/flags.test.ts +248 -0
- package/src/__tests__/multi-local.test.ts +1 -1
- package/src/__tests__/orphan-detection.test.ts +8 -6
- package/src/__tests__/recover.test.ts +307 -0
- package/src/__tests__/segments-to-plain-text.test.ts +37 -0
- package/src/__tests__/wake.test.ts +215 -0
- package/src/commands/backup.ts +2 -0
- package/src/commands/client.ts +471 -30
- package/src/commands/env.ts +1 -1
- package/src/commands/flags.ts +269 -0
- package/src/commands/gateway/token.ts +73 -0
- package/src/commands/gateway.ts +29 -0
- package/src/commands/logs.ts +6 -18
- package/src/commands/ps.ts +41 -41
- package/src/commands/recover.ts +47 -9
- package/src/commands/restore.ts +8 -1
- package/src/commands/retire.ts +3 -23
- package/src/commands/rollback.ts +2 -14
- package/src/commands/ssh.ts +5 -24
- package/src/commands/teleport.ts +34 -26
- package/src/commands/upgrade.ts +8 -16
- package/src/commands/wake.ts +68 -45
- package/src/components/DefaultMainScreen.tsx +16 -1
- package/src/index.ts +6 -0
- package/src/lib/__tests__/lifecycle-reporter.test.ts +59 -0
- package/src/lib/__tests__/step-runner.test.ts +49 -1
- package/src/lib/assistant-config.ts +16 -3
- package/src/lib/config-utils.ts +24 -3
- package/src/lib/docker.ts +57 -7
- package/src/lib/environments/__tests__/paths.test.ts +2 -1
- package/src/lib/environments/__tests__/seeds.test.ts +2 -1
- package/src/lib/environments/paths.ts +1 -1
- package/src/lib/environments/resolve.ts +2 -5
- package/src/lib/guardian-token.ts +12 -5
- package/src/lib/hatch-local.ts +75 -33
- package/src/lib/http-client.ts +1 -3
- package/src/lib/lifecycle-reporter.ts +31 -0
- package/src/lib/local.ts +173 -292
- package/src/lib/orphan-detection.ts +9 -5
- package/src/lib/pgrep.ts +5 -1
- package/src/lib/platform-client.ts +97 -49
- package/src/lib/process.ts +109 -39
- package/src/lib/retire-local.ts +28 -14
- package/src/lib/segments-to-plain-text.ts +35 -0
- package/src/lib/step-runner.ts +67 -7
- package/src/lib/sync-cloud-assistants.ts +17 -0
- /package/{src/lib/environments → node_modules/@vellumai/environments/src}/types.ts +0 -0
|
@@ -2,64 +2,52 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
-
import { SEEDS } from "
|
|
5
|
+
import { SEEDS } from "@vellumai/environments";
|
|
6
6
|
|
|
7
|
-
// Drift guard
|
|
8
|
-
// environment names:
|
|
7
|
+
// Drift guard between the two language-level sources of truth for the set of
|
|
8
|
+
// known environment names:
|
|
9
9
|
//
|
|
10
|
-
// 1.
|
|
11
|
-
// 2.
|
|
10
|
+
// 1. packages/environments/src/seeds.ts — SEEDS record (TS source of truth)
|
|
11
|
+
// 2. clients/shared/App/VellumEnvironment.swift — Swift `VellumEnvironment` enum
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// FOLLOW-UP: split the env name list into a shared `packages/environments`
|
|
18
|
-
// package (mirroring `packages/service-contracts`, `credential-storage`) so
|
|
19
|
-
// both sites can `import { KNOWN_ENVIRONMENTS }` from one place and this
|
|
20
|
-
// drift guard becomes a compile-time check. Planned alongside CLI-driven
|
|
21
|
-
// context support — see the "Environments" design doc.
|
|
13
|
+
// The Swift client can't import the TypeScript package, so the two lists are
|
|
14
|
+
// maintained independently and must be kept in lockstep by hand. This test
|
|
15
|
+
// parses the enum cases out of the Swift source and asserts they agree with
|
|
16
|
+
// SEEDS. Adding an environment means updating both sites.
|
|
22
17
|
|
|
23
18
|
const REPO_ROOT = join(import.meta.dir, "..", "..", "..");
|
|
24
|
-
const
|
|
19
|
+
const SWIFT_ENVIRONMENT = join(
|
|
25
20
|
REPO_ROOT,
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
21
|
+
"clients",
|
|
22
|
+
"shared",
|
|
23
|
+
"App",
|
|
24
|
+
"VellumEnvironment.swift",
|
|
30
25
|
);
|
|
31
26
|
|
|
32
27
|
/**
|
|
33
|
-
* Extract the
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* the
|
|
28
|
+
* Extract the case names declared in the `VellumEnvironment` enum. Matches
|
|
29
|
+
* standalone `case <name>` declaration lines (one identifier, nothing else),
|
|
30
|
+
* which is the enum's own declaration syntax. Switch-statement arms like
|
|
31
|
+
* `case .local:` carry a leading dot and a trailing colon, so they're
|
|
32
|
+
* excluded — the match is anchored to a bare identifier at end of line.
|
|
38
33
|
*/
|
|
39
|
-
function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const match = source.match(pattern);
|
|
45
|
-
if (!match) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`Could not find Set literal for ${setName}. Update the drift-guard regex in env-drift.test.ts.`,
|
|
48
|
-
);
|
|
34
|
+
function extractSwiftEnumCases(source: string): string[] {
|
|
35
|
+
const names: string[] = [];
|
|
36
|
+
for (const line of source.split("\n")) {
|
|
37
|
+
const match = line.match(/^\s*case\s+([a-zA-Z][a-zA-Z0-9]*)\s*$/);
|
|
38
|
+
if (match) names.push(match[1]!);
|
|
49
39
|
}
|
|
50
|
-
|
|
51
|
-
const literals = body.match(/"([^"]+)"/g) ?? [];
|
|
52
|
-
return literals.map((lit) => lit.slice(1, -1));
|
|
40
|
+
return names;
|
|
53
41
|
}
|
|
54
42
|
|
|
55
|
-
describe("
|
|
43
|
+
describe("environment name drift guard (TS ↔ Swift)", () => {
|
|
56
44
|
const seedNames = new Set(Object.keys(SEEDS));
|
|
57
45
|
|
|
58
|
-
test("
|
|
59
|
-
const source = readFileSync(
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
expect([...
|
|
46
|
+
test("clients/shared/App/VellumEnvironment.swift matches SEEDS", () => {
|
|
47
|
+
const source = readFileSync(SWIFT_ENVIRONMENT, "utf8");
|
|
48
|
+
const swiftNames = new Set(extractSwiftEnumCases(source));
|
|
49
|
+
|
|
50
|
+
expect(swiftNames.size).toBeGreaterThan(0);
|
|
51
|
+
expect([...swiftNames].sort()).toEqual([...seedNames].sort());
|
|
64
52
|
});
|
|
65
53
|
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
spyOn,
|
|
8
|
+
test,
|
|
9
|
+
} from "bun:test";
|
|
10
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import { type AssistantEntry } from "../lib/assistant-config.js";
|
|
15
|
+
import { flags } from "../commands/flags.js";
|
|
16
|
+
|
|
17
|
+
const testDir = mkdtempSync(join(tmpdir(), "cli-flags-test-"));
|
|
18
|
+
const originalArgv = [...process.argv];
|
|
19
|
+
const originalExit = process.exit;
|
|
20
|
+
const originalFetch = globalThis.fetch;
|
|
21
|
+
const originalLockfileDir = process.env.VELLUM_LOCKFILE_DIR;
|
|
22
|
+
|
|
23
|
+
let consoleLogSpy: ReturnType<typeof spyOn>;
|
|
24
|
+
let consoleErrorSpy: ReturnType<typeof spyOn>;
|
|
25
|
+
let fetchCalls: Array<{ url: string; method: string }>;
|
|
26
|
+
|
|
27
|
+
function makeEntry(
|
|
28
|
+
assistantId: string,
|
|
29
|
+
extra: Partial<AssistantEntry> = {},
|
|
30
|
+
): AssistantEntry {
|
|
31
|
+
return {
|
|
32
|
+
assistantId,
|
|
33
|
+
runtimeUrl: `http://127.0.0.1:${7800 + assistantId.length}`,
|
|
34
|
+
cloud: "local",
|
|
35
|
+
...extra,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeLockfile(
|
|
40
|
+
entries: AssistantEntry[],
|
|
41
|
+
activeAssistant?: string,
|
|
42
|
+
): void {
|
|
43
|
+
mkdirSync(testDir, { recursive: true });
|
|
44
|
+
writeFileSync(
|
|
45
|
+
join(testDir, ".vellum.lock.json"),
|
|
46
|
+
JSON.stringify(
|
|
47
|
+
{
|
|
48
|
+
assistants: entries,
|
|
49
|
+
...(activeAssistant ? { activeAssistant } : {}),
|
|
50
|
+
},
|
|
51
|
+
null,
|
|
52
|
+
2,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build a Response stub that callers shape per subcommand. `setFlag` needs
|
|
59
|
+
* a 200 OK with the gateway's updated flag payload; `getFlag`/`listFlags`
|
|
60
|
+
* need a flag list. Body content is the minimal valid shape — the tests
|
|
61
|
+
* exercise URL routing, not response parsing.
|
|
62
|
+
*/
|
|
63
|
+
function jsonResponse(body: unknown, status = 200): Response {
|
|
64
|
+
return new Response(JSON.stringify(body), {
|
|
65
|
+
status,
|
|
66
|
+
headers: { "content-type": "application/json" },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe("vellum flags --assistant routing", () => {
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
process.env.VELLUM_LOCKFILE_DIR = testDir;
|
|
73
|
+
rmSync(join(testDir, ".vellum.lock.json"), { force: true });
|
|
74
|
+
fetchCalls = [];
|
|
75
|
+
// Capture every outgoing fetch and respond with a stub matching the
|
|
76
|
+
// subcommand's expected shape. The URL is what the test asserts on.
|
|
77
|
+
globalThis.fetch = (async (
|
|
78
|
+
input: RequestInfo | URL,
|
|
79
|
+
init?: RequestInit,
|
|
80
|
+
) => {
|
|
81
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
82
|
+
const method = init?.method ?? "GET";
|
|
83
|
+
fetchCalls.push({ url, method });
|
|
84
|
+
if (method === "PATCH") {
|
|
85
|
+
return jsonResponse({
|
|
86
|
+
key: "external-plugins",
|
|
87
|
+
enabled: true,
|
|
88
|
+
defaultEnabled: false,
|
|
89
|
+
label: "External Plugins",
|
|
90
|
+
description: "test",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return jsonResponse({ flags: [] });
|
|
94
|
+
}) as typeof globalThis.fetch;
|
|
95
|
+
process.exit = ((code?: number) => {
|
|
96
|
+
throw new Error(`process.exit:${code}`);
|
|
97
|
+
}) as typeof process.exit;
|
|
98
|
+
consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
99
|
+
consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
process.argv = originalArgv;
|
|
104
|
+
process.exit = originalExit;
|
|
105
|
+
globalThis.fetch = originalFetch;
|
|
106
|
+
consoleLogSpy.mockRestore();
|
|
107
|
+
consoleErrorSpy.mockRestore();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
afterAll(() => {
|
|
111
|
+
if (originalLockfileDir === undefined) {
|
|
112
|
+
delete process.env.VELLUM_LOCKFILE_DIR;
|
|
113
|
+
} else {
|
|
114
|
+
process.env.VELLUM_LOCKFILE_DIR = originalLockfileDir;
|
|
115
|
+
}
|
|
116
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("set --assistant <id> routes to the explicit instance's runtime URL, not the active one", async () => {
|
|
120
|
+
// Two assistants on different ports. The active one is "alice"; the
|
|
121
|
+
// explicit --assistant target is "bob". A correct routing impl hits
|
|
122
|
+
// bob's URL — a regression that silently uses the active assistant
|
|
123
|
+
// would hit alice's URL.
|
|
124
|
+
writeLockfile(
|
|
125
|
+
[
|
|
126
|
+
makeEntry("alice-1", { name: "Alice" }),
|
|
127
|
+
makeEntry("bob-2", { name: "Bob" }),
|
|
128
|
+
],
|
|
129
|
+
"alice-1",
|
|
130
|
+
);
|
|
131
|
+
process.argv = [
|
|
132
|
+
"bun",
|
|
133
|
+
"vellum",
|
|
134
|
+
"flags",
|
|
135
|
+
"set",
|
|
136
|
+
"external-plugins",
|
|
137
|
+
"true",
|
|
138
|
+
"--assistant",
|
|
139
|
+
"Bob",
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
await flags();
|
|
143
|
+
|
|
144
|
+
expect(fetchCalls.length).toBe(1);
|
|
145
|
+
expect(fetchCalls[0].method).toBe("PATCH");
|
|
146
|
+
// bob-2 has assistantId.length === 5, so port = 7800 + 5 = 7805.
|
|
147
|
+
expect(fetchCalls[0].url).toContain("http://127.0.0.1:7805");
|
|
148
|
+
expect(fetchCalls[0].url).toContain(
|
|
149
|
+
"/v1/assistants/bob-2/feature-flags/external-plugins",
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("set --assistant <id> placed BEFORE positional args still parses correctly", async () => {
|
|
154
|
+
// Eval harness composes `vellum flags set <key> <value> --assistant <id>`
|
|
155
|
+
// but human users might write `--assistant <id> set <key> <value>`.
|
|
156
|
+
// The extractor strips --assistant from anywhere in argv so positional
|
|
157
|
+
// parsing downstream sees the same shape either way.
|
|
158
|
+
writeLockfile([
|
|
159
|
+
makeEntry("alice-1", { name: "Alice" }),
|
|
160
|
+
makeEntry("bob-2", { name: "Bob" }),
|
|
161
|
+
]);
|
|
162
|
+
process.argv = [
|
|
163
|
+
"bun",
|
|
164
|
+
"vellum",
|
|
165
|
+
"flags",
|
|
166
|
+
"--assistant",
|
|
167
|
+
"Bob",
|
|
168
|
+
"set",
|
|
169
|
+
"external-plugins",
|
|
170
|
+
"true",
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
await flags();
|
|
174
|
+
|
|
175
|
+
expect(fetchCalls.length).toBe(1);
|
|
176
|
+
expect(fetchCalls[0].url).toContain(
|
|
177
|
+
"/v1/assistants/bob-2/feature-flags/external-plugins",
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("set without --assistant uses the active assistant", async () => {
|
|
182
|
+
// Backwards-compat: behavior unchanged for invocations that don't
|
|
183
|
+
// pass --assistant. The active assistant ("alice-1") wins.
|
|
184
|
+
writeLockfile(
|
|
185
|
+
[
|
|
186
|
+
makeEntry("alice-1", { name: "Alice" }),
|
|
187
|
+
makeEntry("bob-2", { name: "Bob" }),
|
|
188
|
+
],
|
|
189
|
+
"alice-1",
|
|
190
|
+
);
|
|
191
|
+
process.argv = [
|
|
192
|
+
"bun",
|
|
193
|
+
"vellum",
|
|
194
|
+
"flags",
|
|
195
|
+
"set",
|
|
196
|
+
"external-plugins",
|
|
197
|
+
"true",
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
await flags();
|
|
201
|
+
|
|
202
|
+
expect(fetchCalls.length).toBe(1);
|
|
203
|
+
// alice-1 has assistantId.length === 7, so port = 7800 + 7 = 7807.
|
|
204
|
+
expect(fetchCalls[0].url).toContain("http://127.0.0.1:7807");
|
|
205
|
+
expect(fetchCalls[0].url).toContain(
|
|
206
|
+
"/v1/assistants/alice-1/feature-flags/external-plugins",
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("set --assistant <name> exits with a lookup error when no assistant matches", async () => {
|
|
211
|
+
writeLockfile([makeEntry("alice-1", { name: "Alice" })]);
|
|
212
|
+
process.argv = [
|
|
213
|
+
"bun",
|
|
214
|
+
"vellum",
|
|
215
|
+
"flags",
|
|
216
|
+
"set",
|
|
217
|
+
"external-plugins",
|
|
218
|
+
"true",
|
|
219
|
+
"--assistant",
|
|
220
|
+
"Ghost",
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
// The Error thrown by createClient propagates out of flags().
|
|
224
|
+
// No fetch should ever fire because lookup fails before the
|
|
225
|
+
// AssistantClient is constructed.
|
|
226
|
+
await expect(flags()).rejects.toThrow(/Ghost/);
|
|
227
|
+
expect(fetchCalls.length).toBe(0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("--assistant without a value exits via the explicit missing-value branch", async () => {
|
|
231
|
+
writeLockfile([makeEntry("alice-1", { name: "Alice" })]);
|
|
232
|
+
process.argv = [
|
|
233
|
+
"bun",
|
|
234
|
+
"vellum",
|
|
235
|
+
"flags",
|
|
236
|
+
"set",
|
|
237
|
+
"external-plugins",
|
|
238
|
+
"true",
|
|
239
|
+
"--assistant",
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
await expect(flags()).rejects.toThrow(/process\.exit:1/);
|
|
243
|
+
expect(consoleErrorSpy.mock.calls.flat().join("\n")).toContain(
|
|
244
|
+
"Missing value for --assistant <name>",
|
|
245
|
+
);
|
|
246
|
+
expect(fetchCalls.length).toBe(0);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -156,7 +156,7 @@ describe("multi-local", () => {
|
|
|
156
156
|
|
|
157
157
|
test("allocation picks env-specific port bases for non-prod envs", async () => {
|
|
158
158
|
// Each non-prod env sits in its own 1000-port window (see
|
|
159
|
-
// environments
|
|
159
|
+
// @vellumai/environments seeds). Hatching under VELLUM_ENVIRONMENT=dev should
|
|
160
160
|
// produce ports in the dev block (18000+), not the production defaults.
|
|
161
161
|
const prevEnv = process.env.VELLUM_ENVIRONMENT;
|
|
162
162
|
const prevXdg = process.env.XDG_DATA_HOME;
|
|
@@ -3,6 +3,8 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
|
+
import type { EnvironmentDefinition } from "@vellumai/environments";
|
|
7
|
+
|
|
6
8
|
// Point lockfile operations at a temp directory before importing anything that
|
|
7
9
|
// would otherwise resolve real on-host paths.
|
|
8
10
|
const testDir = mkdtempSync(join(tmpdir(), "cli-orphan-detection-test-"));
|
|
@@ -16,7 +18,6 @@ import {
|
|
|
16
18
|
loadAllAssistantsAcrossEnvs,
|
|
17
19
|
type AssistantEntry,
|
|
18
20
|
} from "../lib/assistant-config.js";
|
|
19
|
-
import type { EnvironmentDefinition } from "../lib/environments/types.js";
|
|
20
21
|
|
|
21
22
|
afterAll(() => {
|
|
22
23
|
rmSync(testDir, { recursive: true, force: true });
|
|
@@ -74,11 +75,12 @@ describe("getKnownPidsFromAssistants", () => {
|
|
|
74
75
|
});
|
|
75
76
|
|
|
76
77
|
test("collects daemon, gateway, qdrant, and embed-worker PIDs", () => {
|
|
77
|
-
const entry = makeLocalEntry(
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
const entry = makeLocalEntry("alpha", join(perTestDir, "alpha"), {
|
|
79
|
+
daemon: "100",
|
|
80
|
+
gateway: "200",
|
|
81
|
+
qdrant: "300",
|
|
82
|
+
embed: "400",
|
|
83
|
+
});
|
|
82
84
|
const pids = getKnownPidsFromAssistants([entry]);
|
|
83
85
|
expect(pids).toEqual(new Set(["100", "200", "300", "400"]));
|
|
84
86
|
});
|