@vellumai/cli 0.5.14 → 0.5.16
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/__tests__/teleport.test.ts +568 -397
- package/src/commands/hatch.ts +3 -387
- package/src/commands/retire.ts +2 -120
- package/src/commands/teleport.ts +595 -187
- package/src/commands/wake.ts +29 -4
- package/src/lib/hatch-local.ts +403 -0
- package/src/lib/local.ts +9 -120
- package/src/lib/retire-local.ts +124 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, renameSync, writeFileSync } from "fs";
|
|
3
|
+
import { basename, dirname, join } from "path";
|
|
4
|
+
|
|
5
|
+
import { getBaseDir, loadAllAssistants } from "./assistant-config.js";
|
|
6
|
+
import type { AssistantEntry } from "./assistant-config.js";
|
|
7
|
+
import {
|
|
8
|
+
stopOrphanedDaemonProcesses,
|
|
9
|
+
stopProcessByPidFile,
|
|
10
|
+
} from "./process.js";
|
|
11
|
+
import { getArchivePath, getMetadataPath } from "./retire-archive.js";
|
|
12
|
+
|
|
13
|
+
export async function retireLocal(
|
|
14
|
+
name: string,
|
|
15
|
+
entry: AssistantEntry,
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
console.log("\u{1F5D1}\ufe0f Stopping local assistant...\n");
|
|
18
|
+
|
|
19
|
+
if (!entry.resources) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Local assistant '${name}' is missing resource configuration. Re-hatch to fix.`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const resources = entry.resources;
|
|
25
|
+
const vellumDir = join(resources.instanceDir, ".vellum");
|
|
26
|
+
|
|
27
|
+
// Check whether another local assistant shares the same data directory.
|
|
28
|
+
const otherSharesDir = loadAllAssistants().some((other) => {
|
|
29
|
+
if (other.cloud !== "local") return false;
|
|
30
|
+
if (other.assistantId === name) return false;
|
|
31
|
+
if (!other.resources) return false;
|
|
32
|
+
const otherVellumDir = join(other.resources.instanceDir, ".vellum");
|
|
33
|
+
return otherVellumDir === vellumDir;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (otherSharesDir) {
|
|
37
|
+
console.log(
|
|
38
|
+
` Skipping process stop and archive — another local assistant shares ${vellumDir}.`,
|
|
39
|
+
);
|
|
40
|
+
console.log("\u2705 Local instance retired (config entry removed only).");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const daemonPidFile = resources.pidFile;
|
|
45
|
+
const daemonStopped = await stopProcessByPidFile(daemonPidFile, "daemon");
|
|
46
|
+
|
|
47
|
+
// Stop gateway via PID file — use a longer timeout because the gateway has a
|
|
48
|
+
// drain window (5s) before it exits.
|
|
49
|
+
const gatewayPidFile = join(vellumDir, "gateway.pid");
|
|
50
|
+
await stopProcessByPidFile(gatewayPidFile, "gateway", undefined, 7000);
|
|
51
|
+
|
|
52
|
+
// Stop Qdrant — the daemon's graceful shutdown tries to stop it via
|
|
53
|
+
// qdrantManager.stop(), but if the daemon was SIGKILL'd (after 2s timeout)
|
|
54
|
+
// Qdrant may still be running as an orphan. Check both the current PID file
|
|
55
|
+
// location and the legacy location.
|
|
56
|
+
const qdrantPidFile = join(
|
|
57
|
+
vellumDir,
|
|
58
|
+
"workspace",
|
|
59
|
+
"data",
|
|
60
|
+
"qdrant",
|
|
61
|
+
"qdrant.pid",
|
|
62
|
+
);
|
|
63
|
+
const qdrantLegacyPidFile = join(vellumDir, "qdrant.pid");
|
|
64
|
+
await stopProcessByPidFile(qdrantPidFile, "qdrant", undefined, 5000);
|
|
65
|
+
await stopProcessByPidFile(qdrantLegacyPidFile, "qdrant", undefined, 5000);
|
|
66
|
+
|
|
67
|
+
// If the PID file didn't track a running daemon, scan for orphaned
|
|
68
|
+
// daemon processes that may have been started without writing a PID.
|
|
69
|
+
if (!daemonStopped) {
|
|
70
|
+
await stopOrphanedDaemonProcesses();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// For named instances (instanceDir differs from the base directory),
|
|
74
|
+
// archive and remove the entire instance directory. For the default
|
|
75
|
+
// instance, archive only the .vellum subdirectory.
|
|
76
|
+
const isNamedInstance = resources.instanceDir !== getBaseDir();
|
|
77
|
+
const dirToArchive = isNamedInstance ? resources.instanceDir : vellumDir;
|
|
78
|
+
|
|
79
|
+
// Move the data directory out of the way so the path is immediately available
|
|
80
|
+
// for the next hatch, then kick off the tar archive in the background.
|
|
81
|
+
const archivePath = getArchivePath(name);
|
|
82
|
+
const metadataPath = getMetadataPath(name);
|
|
83
|
+
const stagingDir = `${archivePath}.staging`;
|
|
84
|
+
|
|
85
|
+
if (!existsSync(dirToArchive)) {
|
|
86
|
+
console.log(
|
|
87
|
+
` No data directory at ${dirToArchive} — nothing to archive.`,
|
|
88
|
+
);
|
|
89
|
+
console.log("\u2705 Local instance retired.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Ensure the retired archive directory exists before attempting the rename
|
|
94
|
+
mkdirSync(dirname(stagingDir), { recursive: true });
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
renameSync(dirToArchive, stagingDir);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
// Re-throw so the caller (and the desktop app) knows the archive failed.
|
|
100
|
+
// If the rename fails, old workspace data stays in place and a subsequent
|
|
101
|
+
// hatch would inherit stale SOUL.md, IDENTITY.md, and memories.
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Failed to archive ${dirToArchive}: ${err instanceof Error ? err.message : err}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
writeFileSync(metadataPath, JSON.stringify(entry, null, 2) + "\n");
|
|
108
|
+
|
|
109
|
+
// Spawn tar + cleanup in the background and detach so the CLI can exit
|
|
110
|
+
// immediately. The staging directory is removed once the archive is written.
|
|
111
|
+
const tarCmd = [
|
|
112
|
+
`tar czf ${JSON.stringify(archivePath)} -C ${JSON.stringify(dirname(stagingDir))} ${JSON.stringify(basename(stagingDir))}`,
|
|
113
|
+
`rm -rf ${JSON.stringify(stagingDir)}`,
|
|
114
|
+
].join(" && ");
|
|
115
|
+
|
|
116
|
+
const child = spawn("sh", ["-c", tarCmd], {
|
|
117
|
+
stdio: "ignore",
|
|
118
|
+
detached: true,
|
|
119
|
+
});
|
|
120
|
+
child.unref();
|
|
121
|
+
|
|
122
|
+
console.log(`📦 Archiving to ${archivePath} in the background.`);
|
|
123
|
+
console.log("\u2705 Local instance retired.");
|
|
124
|
+
}
|