@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/lib/local.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync, spawn } from "child_process";
|
|
2
2
|
import {
|
|
3
|
+
closeSync,
|
|
3
4
|
existsSync,
|
|
4
5
|
mkdirSync,
|
|
5
6
|
readFileSync,
|
|
@@ -78,6 +79,18 @@ function findGatewaySourceFromCwd(): string | undefined {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function isOutboundProxySourceDir(dir: string): boolean {
|
|
83
|
+
const pkgPath = join(dir, "package.json");
|
|
84
|
+
if (!existsSync(pkgPath) || !existsSync(join(dir, "src", "main.ts")))
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
88
|
+
return pkg.name === "@vellumai/outbound-proxy";
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
81
94
|
function resolveAssistantIndexPath(): string | undefined {
|
|
82
95
|
// Source tree layout: cli/src/lib/ -> ../../.. -> repo root -> assistant/src/index.ts
|
|
83
96
|
const sourceTreeIndex = join(
|
|
@@ -153,6 +166,49 @@ function resolveDaemonMainPath(assistantIndex: string): string {
|
|
|
153
166
|
}
|
|
154
167
|
|
|
155
168
|
async function startDaemonFromSource(assistantIndex: string): Promise<void> {
|
|
169
|
+
const daemonMainPath = resolveDaemonMainPath(assistantIndex);
|
|
170
|
+
|
|
171
|
+
const vellumDir = join(homedir(), ".vellum");
|
|
172
|
+
mkdirSync(vellumDir, { recursive: true });
|
|
173
|
+
|
|
174
|
+
const pidFile = join(vellumDir, "vellum.pid");
|
|
175
|
+
const socketFile = join(vellumDir, "vellum.sock");
|
|
176
|
+
|
|
177
|
+
// --- Lifecycle guard: prevent split-brain daemon state ---
|
|
178
|
+
if (existsSync(pidFile)) {
|
|
179
|
+
try {
|
|
180
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
181
|
+
if (!isNaN(pid)) {
|
|
182
|
+
try {
|
|
183
|
+
process.kill(pid, 0);
|
|
184
|
+
console.log(` Assistant already running (pid ${pid})\n`);
|
|
185
|
+
return;
|
|
186
|
+
} catch {
|
|
187
|
+
try {
|
|
188
|
+
unlinkSync(pidFile);
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (await isSocketResponsive(socketFile)) {
|
|
196
|
+
const ownerPid = findSocketOwnerPid(socketFile);
|
|
197
|
+
if (ownerPid) {
|
|
198
|
+
writeFileSync(pidFile, String(ownerPid), "utf-8");
|
|
199
|
+
console.log(
|
|
200
|
+
` Assistant socket is responsive (pid ${ownerPid}) — skipping restart\n`,
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
console.log(" Assistant socket is responsive — skipping restart\n");
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
unlinkSync(socketFile);
|
|
210
|
+
} catch {}
|
|
211
|
+
|
|
156
212
|
const env: Record<string, string | undefined> = {
|
|
157
213
|
...process.env,
|
|
158
214
|
RUNTIME_HTTP_PORT: process.env.RUNTIME_HTTP_PORT || "7821",
|
|
@@ -163,28 +219,30 @@ async function startDaemonFromSource(assistantIndex: string): Promise<void> {
|
|
|
163
219
|
process.env.VELLUM_DAEMON_TCP_ENABLED || "1";
|
|
164
220
|
}
|
|
165
221
|
|
|
166
|
-
|
|
167
|
-
|
|
222
|
+
// Use fd inheritance instead of pipes so the daemon's stdout/stderr survive
|
|
223
|
+
// after the parent (hatch) exits. Bun does not ignore SIGPIPE, so piped
|
|
224
|
+
// stdio would kill the daemon on its first write after the parent closes.
|
|
225
|
+
const logFd = openLogFile("hatch.log");
|
|
226
|
+
const child = spawn("bun", ["run", daemonMainPath], {
|
|
227
|
+
detached: true,
|
|
228
|
+
stdio: ["ignore", logFd, logFd],
|
|
168
229
|
env,
|
|
169
230
|
});
|
|
231
|
+
if (typeof logFd === "number") closeSync(logFd);
|
|
232
|
+
child.unref();
|
|
170
233
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
resolve();
|
|
175
|
-
} else {
|
|
176
|
-
reject(new Error(`Daemon start exited with code ${code}`));
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
child.on("error", reject);
|
|
180
|
-
});
|
|
234
|
+
if (child.pid) {
|
|
235
|
+
writeFileSync(pidFile, String(child.pid), "utf-8");
|
|
236
|
+
}
|
|
181
237
|
}
|
|
182
238
|
|
|
183
239
|
// NOTE: startDaemonWatchFromSource() is the CLI-side watch-mode daemon
|
|
184
240
|
// launcher. Its lifecycle guards should eventually converge with
|
|
185
241
|
// assistant/src/daemon/daemon-control.ts::startDaemon which is the
|
|
186
242
|
// assistant-side equivalent.
|
|
187
|
-
async function startDaemonWatchFromSource(
|
|
243
|
+
async function startDaemonWatchFromSource(
|
|
244
|
+
assistantIndex: string,
|
|
245
|
+
): Promise<void> {
|
|
188
246
|
const mainPath = resolveDaemonMainPath(assistantIndex);
|
|
189
247
|
if (!existsSync(mainPath)) {
|
|
190
248
|
throw new Error(`Daemon main.ts not found at ${mainPath}`);
|
|
@@ -204,11 +262,13 @@ async function startDaemonWatchFromSource(assistantIndex: string): Promise<void>
|
|
|
204
262
|
if (!isNaN(pid)) {
|
|
205
263
|
try {
|
|
206
264
|
process.kill(pid, 0); // Check if alive
|
|
207
|
-
console.log(`
|
|
265
|
+
console.log(` Assistant already running (pid ${pid})\n`);
|
|
208
266
|
return;
|
|
209
267
|
} catch {
|
|
210
268
|
// Process doesn't exist, clean up stale PID file
|
|
211
|
-
try {
|
|
269
|
+
try {
|
|
270
|
+
unlinkSync(pidFile);
|
|
271
|
+
} catch {}
|
|
212
272
|
}
|
|
213
273
|
}
|
|
214
274
|
} catch {}
|
|
@@ -221,16 +281,18 @@ async function startDaemonWatchFromSource(assistantIndex: string): Promise<void>
|
|
|
221
281
|
if (ownerPid) {
|
|
222
282
|
writeFileSync(pidFile, String(ownerPid), "utf-8");
|
|
223
283
|
console.log(
|
|
224
|
-
`
|
|
284
|
+
` Assistant socket is responsive (pid ${ownerPid}) — skipping restart\n`,
|
|
225
285
|
);
|
|
226
286
|
} else {
|
|
227
|
-
console.log("
|
|
287
|
+
console.log(" Assistant socket is responsive — skipping restart\n");
|
|
228
288
|
}
|
|
229
289
|
return;
|
|
230
290
|
}
|
|
231
291
|
|
|
232
292
|
// Socket is unresponsive or missing — safe to clean up and start fresh.
|
|
233
|
-
try {
|
|
293
|
+
try {
|
|
294
|
+
unlinkSync(socketFile);
|
|
295
|
+
} catch {}
|
|
234
296
|
|
|
235
297
|
const env: Record<string, string | undefined> = {
|
|
236
298
|
...process.env,
|
|
@@ -251,7 +313,7 @@ async function startDaemonWatchFromSource(assistantIndex: string): Promise<void>
|
|
|
251
313
|
writeFileSync(pidFile, String(daemonPid), "utf-8");
|
|
252
314
|
}
|
|
253
315
|
|
|
254
|
-
console.log("
|
|
316
|
+
console.log(" Assistant started in watch mode (bun --watch)");
|
|
255
317
|
}
|
|
256
318
|
|
|
257
319
|
function resolveGatewayDir(): string {
|
|
@@ -292,6 +354,21 @@ function resolveGatewayDir(): string {
|
|
|
292
354
|
}
|
|
293
355
|
}
|
|
294
356
|
|
|
357
|
+
function resolveOutboundProxyDir(): string | undefined {
|
|
358
|
+
// Compiled binary: outbound-proxy/ bundled adjacent to the CLI executable.
|
|
359
|
+
const binProxy = join(dirname(process.execPath), "outbound-proxy");
|
|
360
|
+
if (isOutboundProxySourceDir(binProxy)) {
|
|
361
|
+
return binProxy;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
const pkgPath = _require.resolve("@vellumai/outbound-proxy/package.json");
|
|
366
|
+
return dirname(pkgPath);
|
|
367
|
+
} catch {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
295
372
|
function normalizeIngressUrl(value: unknown): string | undefined {
|
|
296
373
|
if (typeof value !== "string") return undefined;
|
|
297
374
|
const normalized = value.trim().replace(/\/+$/, "");
|
|
@@ -528,7 +605,7 @@ export async function startLocalDaemon(watch: boolean = false): Promise<void> {
|
|
|
528
605
|
try {
|
|
529
606
|
process.kill(pid, 0); // Check if alive
|
|
530
607
|
daemonAlive = true;
|
|
531
|
-
console.log(`
|
|
608
|
+
console.log(` Assistant already running (pid ${pid})\n`);
|
|
532
609
|
} catch {
|
|
533
610
|
// Process doesn't exist, clean up stale PID file
|
|
534
611
|
try {
|
|
@@ -550,10 +627,10 @@ export async function startLocalDaemon(watch: boolean = false): Promise<void> {
|
|
|
550
627
|
if (ownerPid) {
|
|
551
628
|
writeFileSync(pidFile, String(ownerPid), "utf-8");
|
|
552
629
|
console.log(
|
|
553
|
-
`
|
|
630
|
+
` Assistant socket is responsive (pid ${ownerPid}) — skipping restart\n`,
|
|
554
631
|
);
|
|
555
632
|
} else {
|
|
556
|
-
console.log("
|
|
633
|
+
console.log(" Assistant socket is responsive — skipping restart\n");
|
|
557
634
|
}
|
|
558
635
|
return;
|
|
559
636
|
}
|
|
@@ -563,7 +640,7 @@ export async function startLocalDaemon(watch: boolean = false): Promise<void> {
|
|
|
563
640
|
unlinkSync(socketFile);
|
|
564
641
|
} catch {}
|
|
565
642
|
|
|
566
|
-
console.log("🔨 Starting
|
|
643
|
+
console.log("🔨 Starting assistant...");
|
|
567
644
|
|
|
568
645
|
// Ensure ~/.vellum/ exists for PID/socket files
|
|
569
646
|
mkdirSync(vellumDir, { recursive: true });
|
|
@@ -625,7 +702,7 @@ export async function startLocalDaemon(watch: boolean = false): Promise<void> {
|
|
|
625
702
|
const assistantIndex = resolveAssistantIndexPath();
|
|
626
703
|
if (assistantIndex) {
|
|
627
704
|
console.log(
|
|
628
|
-
" Bundled
|
|
705
|
+
" Bundled assistant socket not ready after 60s — falling back to source assistant...",
|
|
629
706
|
);
|
|
630
707
|
// Kill the bundled daemon to avoid two processes competing for the same socket/port
|
|
631
708
|
await stopProcessByPidFile(pidFile, "bundled daemon", [socketFile]);
|
|
@@ -639,14 +716,14 @@ export async function startLocalDaemon(watch: boolean = false): Promise<void> {
|
|
|
639
716
|
}
|
|
640
717
|
|
|
641
718
|
if (socketReady) {
|
|
642
|
-
console.log("
|
|
719
|
+
console.log(" Assistant socket ready\n");
|
|
643
720
|
} else {
|
|
644
721
|
console.log(
|
|
645
|
-
" ⚠️
|
|
722
|
+
" ⚠️ Assistant socket did not appear within 60s — continuing anyway\n",
|
|
646
723
|
);
|
|
647
724
|
}
|
|
648
725
|
} else {
|
|
649
|
-
console.log("🔨 Starting local
|
|
726
|
+
console.log("🔨 Starting local assistant...");
|
|
650
727
|
|
|
651
728
|
const assistantIndex = resolveAssistantIndexPath();
|
|
652
729
|
if (!assistantIndex) {
|
|
@@ -662,17 +739,33 @@ export async function startLocalDaemon(watch: boolean = false): Promise<void> {
|
|
|
662
739
|
const socketFile = join(vellumDir, "vellum.sock");
|
|
663
740
|
const socketReady = await waitForSocketFile(socketFile, 60000);
|
|
664
741
|
if (socketReady) {
|
|
665
|
-
console.log("
|
|
742
|
+
console.log(" Assistant socket ready\n");
|
|
666
743
|
} else {
|
|
667
|
-
console.log(
|
|
744
|
+
console.log(
|
|
745
|
+
" ⚠️ Assistant socket did not appear within 60s — continuing anyway\n",
|
|
746
|
+
);
|
|
668
747
|
}
|
|
669
748
|
} else {
|
|
670
749
|
await startDaemonFromSource(assistantIndex);
|
|
750
|
+
|
|
751
|
+
const vellumDir = join(homedir(), ".vellum");
|
|
752
|
+
const socketFile = join(vellumDir, "vellum.sock");
|
|
753
|
+
const socketReady = await waitForSocketFile(socketFile, 60000);
|
|
754
|
+
if (socketReady) {
|
|
755
|
+
console.log(" Assistant socket ready\n");
|
|
756
|
+
} else {
|
|
757
|
+
console.log(
|
|
758
|
+
" ⚠️ Assistant socket did not appear within 60s — continuing anyway\n",
|
|
759
|
+
);
|
|
760
|
+
}
|
|
671
761
|
}
|
|
672
762
|
}
|
|
673
763
|
}
|
|
674
764
|
|
|
675
|
-
export async function startGateway(
|
|
765
|
+
export async function startGateway(
|
|
766
|
+
assistantId?: string,
|
|
767
|
+
watch: boolean = false,
|
|
768
|
+
): Promise<string> {
|
|
676
769
|
const publicUrl = await discoverPublicUrl();
|
|
677
770
|
if (publicUrl) {
|
|
678
771
|
console.log(` Public URL: ${publicUrl}`);
|
|
@@ -856,10 +949,130 @@ export async function startGateway(assistantId?: string, watch: boolean = false)
|
|
|
856
949
|
return gatewayUrl;
|
|
857
950
|
}
|
|
858
951
|
|
|
952
|
+
export async function startOutboundProxy(
|
|
953
|
+
watch: boolean = false,
|
|
954
|
+
): Promise<void> {
|
|
955
|
+
const proxyDir = resolveOutboundProxyDir();
|
|
956
|
+
if (!proxyDir) {
|
|
957
|
+
console.log(" ⚠️ Outbound proxy not found — skipping");
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
console.log("🔒 Starting outbound proxy...");
|
|
962
|
+
|
|
963
|
+
const vellumDir = join(homedir(), ".vellum");
|
|
964
|
+
mkdirSync(vellumDir, { recursive: true });
|
|
965
|
+
|
|
966
|
+
const pidFile = join(vellumDir, "outbound-proxy.pid");
|
|
967
|
+
|
|
968
|
+
// Check if already running
|
|
969
|
+
if (existsSync(pidFile)) {
|
|
970
|
+
try {
|
|
971
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
972
|
+
if (!isNaN(pid)) {
|
|
973
|
+
try {
|
|
974
|
+
process.kill(pid, 0);
|
|
975
|
+
console.log(` Outbound proxy already running (pid ${pid})\n`);
|
|
976
|
+
return;
|
|
977
|
+
} catch {
|
|
978
|
+
try {
|
|
979
|
+
unlinkSync(pidFile);
|
|
980
|
+
} catch {}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
} catch {}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const proxyEnv: Record<string, string> = {
|
|
987
|
+
...(process.env as Record<string, string>),
|
|
988
|
+
PROXY_PORT: process.env.PROXY_PORT || "7829",
|
|
989
|
+
PROXY_HEALTH_PORT: process.env.PROXY_HEALTH_PORT || "7828",
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
const proxyLogFd = openLogFile("hatch.log");
|
|
993
|
+
|
|
994
|
+
let proxy;
|
|
995
|
+
if (process.env.VELLUM_DESKTOP_APP && !watch) {
|
|
996
|
+
const proxyBinary = join(
|
|
997
|
+
dirname(process.execPath),
|
|
998
|
+
"vellum-outbound-proxy",
|
|
999
|
+
);
|
|
1000
|
+
if (!existsSync(proxyBinary)) {
|
|
1001
|
+
console.log(
|
|
1002
|
+
" ⚠️ Outbound proxy binary not found — falling back to source",
|
|
1003
|
+
);
|
|
1004
|
+
const bunArgs = watch
|
|
1005
|
+
? ["--watch", "run", "src/main.ts"]
|
|
1006
|
+
: ["run", "src/main.ts"];
|
|
1007
|
+
proxy = spawn("bun", bunArgs, {
|
|
1008
|
+
cwd: proxyDir,
|
|
1009
|
+
detached: true,
|
|
1010
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1011
|
+
env: proxyEnv,
|
|
1012
|
+
});
|
|
1013
|
+
} else {
|
|
1014
|
+
proxy = spawn(proxyBinary, [], {
|
|
1015
|
+
detached: true,
|
|
1016
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1017
|
+
env: proxyEnv,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
} else {
|
|
1021
|
+
const bunArgs = watch
|
|
1022
|
+
? ["--watch", "run", "src/main.ts"]
|
|
1023
|
+
: ["run", "src/main.ts"];
|
|
1024
|
+
proxy = spawn("bun", bunArgs, {
|
|
1025
|
+
cwd: proxyDir,
|
|
1026
|
+
detached: true,
|
|
1027
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1028
|
+
env: proxyEnv,
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
pipeToLogFile(proxy, proxyLogFd, "outbound-proxy");
|
|
1033
|
+
proxy.unref();
|
|
1034
|
+
|
|
1035
|
+
if (proxy.pid) {
|
|
1036
|
+
writeFileSync(pidFile, String(proxy.pid), "utf-8");
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (watch) {
|
|
1040
|
+
console.log(" Outbound proxy started in watch mode (bun --watch)");
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Wait for the health endpoint to respond
|
|
1044
|
+
const healthPort = Number(process.env.PROXY_HEALTH_PORT) || 7828;
|
|
1045
|
+
const start = Date.now();
|
|
1046
|
+
const timeoutMs = 15000;
|
|
1047
|
+
let ready = false;
|
|
1048
|
+
while (Date.now() - start < timeoutMs) {
|
|
1049
|
+
try {
|
|
1050
|
+
const res = await fetch(`http://localhost:${healthPort}/healthz`, {
|
|
1051
|
+
signal: AbortSignal.timeout(2000),
|
|
1052
|
+
});
|
|
1053
|
+
if (res.ok) {
|
|
1054
|
+
ready = true;
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
} catch {
|
|
1058
|
+
// Not ready yet
|
|
1059
|
+
}
|
|
1060
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (!ready) {
|
|
1064
|
+
console.warn(
|
|
1065
|
+
" ⚠️ Outbound proxy started but health check did not respond within 15s",
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
console.log("✅ Outbound proxy started\n");
|
|
1070
|
+
}
|
|
1071
|
+
|
|
859
1072
|
/**
|
|
860
|
-
* Stop any locally-running daemon
|
|
861
|
-
* PID/socket files. Called when hatch fails partway through
|
|
862
|
-
* leave orphaned processes with no lock file entry.
|
|
1073
|
+
* Stop any locally-running daemon, gateway, and outbound-proxy processes
|
|
1074
|
+
* and clean up PID/socket files. Called when hatch fails partway through
|
|
1075
|
+
* so we don't leave orphaned processes with no lock file entry.
|
|
863
1076
|
*/
|
|
864
1077
|
export async function stopLocalProcesses(): Promise<void> {
|
|
865
1078
|
const vellumDir = join(homedir(), ".vellum");
|
|
@@ -869,4 +1082,7 @@ export async function stopLocalProcesses(): Promise<void> {
|
|
|
869
1082
|
|
|
870
1083
|
const gatewayPidFile = join(vellumDir, "gateway.pid");
|
|
871
1084
|
await stopProcessByPidFile(gatewayPidFile, "gateway");
|
|
1085
|
+
|
|
1086
|
+
const outboundProxyPidFile = join(vellumDir, "outbound-proxy.pid");
|
|
1087
|
+
await stopProcessByPidFile(outboundProxyPidFile, "outbound-proxy");
|
|
872
1088
|
}
|
package/src/lib/ngrok.ts
CHANGED
|
@@ -158,9 +158,7 @@ export async function runNgrokTunnel(): Promise<void> {
|
|
|
158
158
|
console.error(" macOS: brew install ngrok/ngrok/ngrok");
|
|
159
159
|
console.error(" Linux: sudo snap install ngrok");
|
|
160
160
|
console.error("");
|
|
161
|
-
console.error(
|
|
162
|
-
"Then authenticate: ngrok config add-authtoken <your-token>",
|
|
163
|
-
);
|
|
161
|
+
console.error("Then authenticate: ngrok config add-authtoken <your-token>");
|
|
164
162
|
console.error(
|
|
165
163
|
" Get your token at: https://dashboard.ngrok.com/get-started/your-authtoken",
|
|
166
164
|
);
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
export async function buildOpenclawRuntimeServer(): Promise<string> {
|
|
7
7
|
try {
|
|
8
|
-
const serverSource = await Bun.file(
|
|
8
|
+
const serverSource = await Bun.file(
|
|
9
|
+
import.meta.dir + "/../adapters/openclaw-http-server.ts",
|
|
10
|
+
).text();
|
|
9
11
|
|
|
10
12
|
return `
|
|
11
13
|
cat > /opt/openclaw-runtime-server.ts << 'RUNTIME_SERVER_EOF'
|
|
@@ -17,7 +19,10 @@ nohup bun run /opt/openclaw-runtime-server.ts >> "\$HOME/.vellum/http-gateway.lo
|
|
|
17
19
|
echo "OpenClaw runtime server started (PID: \$!)"
|
|
18
20
|
`;
|
|
19
21
|
} catch (err) {
|
|
20
|
-
console.warn(
|
|
22
|
+
console.warn(
|
|
23
|
+
"⚠️ Could not embed openclaw runtime server (expected in compiled binary without --embed):",
|
|
24
|
+
(err as Error).message,
|
|
25
|
+
);
|
|
21
26
|
return "# openclaw-runtime-server: skipped (source files not available in compiled binary)";
|
|
22
27
|
}
|
|
23
28
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
writeFileSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
} from "fs";
|
|
2
9
|
import { homedir } from "os";
|
|
3
10
|
import { join, dirname } from "path";
|
|
4
11
|
|
|
@@ -63,10 +70,16 @@ export async function fetchCurrentUser(token: string): Promise<PlatformUser> {
|
|
|
63
70
|
});
|
|
64
71
|
|
|
65
72
|
if (!response.ok) {
|
|
66
|
-
if (
|
|
73
|
+
if (
|
|
74
|
+
response.status === 401 ||
|
|
75
|
+
response.status === 403 ||
|
|
76
|
+
response.status === 410
|
|
77
|
+
) {
|
|
67
78
|
throw new Error("Invalid or expired token. Please login again.");
|
|
68
79
|
}
|
|
69
|
-
throw new Error(
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Platform API error: ${response.status} ${response.statusText}`,
|
|
82
|
+
);
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
const body = (await response.json()) as AllauthSessionResponse;
|
package/src/lib/xdg-log.ts
CHANGED
|
@@ -26,14 +26,22 @@ export function openLogFile(name: string): number | "ignore" {
|
|
|
26
26
|
/** Close a file descriptor returned by openLogFile (no-op for "ignore"). */
|
|
27
27
|
export function closeLogFile(fd: number | "ignore"): void {
|
|
28
28
|
if (typeof fd === "number") {
|
|
29
|
-
try {
|
|
29
|
+
try {
|
|
30
|
+
closeSync(fd);
|
|
31
|
+
} catch {
|
|
32
|
+
/* best-effort */
|
|
33
|
+
}
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/** Write a string to a file descriptor returned by openLogFile (no-op for "ignore"). */
|
|
34
38
|
export function writeToLogFile(fd: number | "ignore", msg: string): void {
|
|
35
39
|
if (typeof fd === "number") {
|
|
36
|
-
try {
|
|
40
|
+
try {
|
|
41
|
+
writeSync(fd, msg);
|
|
42
|
+
} catch {
|
|
43
|
+
/* best-effort */
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -41,7 +49,11 @@ export function writeToLogFile(fd: number | "ignore", msg: string): void {
|
|
|
41
49
|
* prefixing each line with a tag (e.g. "[daemon]" or "[gateway]").
|
|
42
50
|
* Streams are unref'd so they don't prevent the parent from exiting.
|
|
43
51
|
* The fd is closed automatically when both streams end. */
|
|
44
|
-
export function pipeToLogFile(
|
|
52
|
+
export function pipeToLogFile(
|
|
53
|
+
child: ChildProcess,
|
|
54
|
+
fd: number | "ignore",
|
|
55
|
+
tag: string,
|
|
56
|
+
): void {
|
|
45
57
|
if (fd === "ignore") return;
|
|
46
58
|
const numFd: number = fd;
|
|
47
59
|
const tagLabel = `[${tag}]`;
|
|
@@ -51,7 +63,11 @@ export function pipeToLogFile(child: ChildProcess, fd: number | "ignore", tag: s
|
|
|
51
63
|
function onDone() {
|
|
52
64
|
ended++;
|
|
53
65
|
if (ended >= streams.length) {
|
|
54
|
-
try {
|
|
66
|
+
try {
|
|
67
|
+
closeSync(numFd);
|
|
68
|
+
} catch {
|
|
69
|
+
/* best-effort */
|
|
70
|
+
}
|
|
55
71
|
}
|
|
56
72
|
}
|
|
57
73
|
|
|
@@ -65,7 +81,11 @@ export function pipeToLogFile(child: ChildProcess, fd: number | "ignore", tag: s
|
|
|
65
81
|
if (i === lines.length - 1 && lines[i] === "") break;
|
|
66
82
|
const nl = i < lines.length - 1 ? "\n" : "";
|
|
67
83
|
const prefix = `${new Date().toISOString()} ${tagLabel} `;
|
|
68
|
-
try {
|
|
84
|
+
try {
|
|
85
|
+
writeSync(numFd, prefix + lines[i] + nl);
|
|
86
|
+
} catch {
|
|
87
|
+
/* best-effort */
|
|
88
|
+
}
|
|
69
89
|
}
|
|
70
90
|
});
|
|
71
91
|
stream.on("end", onDone);
|