agentroam 0.2.0-preview.8 → 0.2.0-preview.9
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 +42 -8
- package/bin/agentroam.mjs +10 -1
- package/bin/node-preflight.mjs +138 -0
- package/dist/args.js +14 -1
- package/dist/args.test.js +27 -0
- package/dist/cli.js +59 -7
- package/dist/cloudflared/bundled-asset.test.js +2 -2
- package/dist/codex-runtime-manager.js +293 -0
- package/dist/codex-runtime-manager.test.js +192 -0
- package/dist/install-script-contract.test.js +41 -0
- package/dist/node-preflight.test.js +109 -0
- package/dist/node-runtime-manager.js +251 -0
- package/dist/node-runtime-manager.test.js +184 -0
- package/dist/opencode-runtime-manager.js +207 -0
- package/dist/opencode-runtime-manager.test.js +79 -0
- package/dist/platform-packages.js +1 -1
- package/dist/platform.js +8 -4
- package/dist/platform.test.js +17 -2
- package/dist/power/sleep-inhibitor.js +151 -0
- package/dist/power/sleep-inhibitor.test.js +104 -0
- package/dist/runtime-manager.js +50 -2
- package/dist/runtime-manager.test.js +38 -0
- package/dist/service/macos-launch-agent.js +250 -0
- package/dist/service/macos-launch-agent.test.js +197 -0
- package/dist/service/runtime-state.js +40 -0
- package/dist/service/runtime-state.test.js +43 -0
- package/dist/service/service-command.js +100 -0
- package/dist/service/service-command.test.js +102 -0
- package/dist/service/service-controller.js +27 -0
- package/dist/service/service-files.js +78 -0
- package/dist/service/service-files.test.js +48 -0
- package/dist/service/windows-service-host.js +53 -0
- package/dist/service/windows-service-host.test.js +46 -0
- package/dist/service/windows-task-service.js +275 -0
- package/dist/service/windows-task-service.test.js +95 -0
- package/dist/tunnel/cloudflare-provider.js +4 -1
- package/dist/tunnel/cloudflare-provider.test.js +13 -1
- package/dist/tunnel/public-readiness.js +1 -1
- package/dist/tunnel/relay-orchestrator.js +4 -0
- package/dist/tunnel/relay-orchestrator.test.js +11 -0
- package/install/README.md +45 -0
- package/install/install-agentroam.ps1 +175 -0
- package/install/install-agentroam.sh +199 -0
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -20,20 +20,20 @@ npx agentroam@preview
|
|
|
20
20
|
Or install from tarball:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npm install ./agentroam-cloudflared-darwin-arm64-0.2.0-preview.
|
|
24
|
-
./agentroam-runtime-darwin-arm64-0.2.0-preview.
|
|
25
|
-
./agentroam-tui-darwin-arm64-0.2.0-preview.
|
|
26
|
-
./agentroam-0.2.0-preview.
|
|
23
|
+
npm install ./agentroam-cloudflared-darwin-arm64-0.2.0-preview.9.tgz \
|
|
24
|
+
./agentroam-runtime-darwin-arm64-0.2.0-preview.9.tgz \
|
|
25
|
+
./agentroam-tui-darwin-arm64-0.2.0-preview.9.tgz \
|
|
26
|
+
./agentroam-0.2.0-preview.9.tgz
|
|
27
27
|
npx agentroam
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Windows PowerShell:
|
|
31
31
|
|
|
32
32
|
```powershell
|
|
33
|
-
npm install ./agentroam-cloudflared-win32-x64-0.2.0-preview.
|
|
34
|
-
./agentroam-runtime-win32-x64-0.2.0-preview.
|
|
35
|
-
./caoqu-agentroam-tui-win32-x64-0.2.0-preview.
|
|
36
|
-
./agentroam-0.2.0-preview.
|
|
33
|
+
npm install ./agentroam-cloudflared-win32-x64-0.2.0-preview.9.tgz `
|
|
34
|
+
./agentroam-runtime-win32-x64-0.2.0-preview.9.tgz `
|
|
35
|
+
./caoqu-agentroam-tui-win32-x64-0.2.0-preview.9.tgz `
|
|
36
|
+
./agentroam-0.2.0-preview.9.tgz
|
|
37
37
|
npx agentroam
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -53,6 +53,40 @@ agentroam version
|
|
|
53
53
|
agent-tui # start the bundled terminal Agent UI
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
## Install and run in the background
|
|
57
|
+
|
|
58
|
+
Run the versioned Shell installer on macOS or PowerShell installer on Windows
|
|
59
|
+
from the project directory AgentRoam should expose. The installer verifies or
|
|
60
|
+
installs Node 22 and the matching CLI, registers the current-user service, starts
|
|
61
|
+
it, checks readiness, and prints the access URL. Set `AGENTROAM_ROOT` to select a
|
|
62
|
+
different root explicitly. An implicit root equal to the home directory is
|
|
63
|
+
rejected.
|
|
64
|
+
|
|
65
|
+
The service keeps running after the terminal closes, restarts after failure, and
|
|
66
|
+
starts again when the user logs in. macOS uses a user LaunchAgent; Windows uses a
|
|
67
|
+
current-user Task Scheduler task. Neither requires administrator privileges.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
agentroam service status
|
|
71
|
+
agentroam service start
|
|
72
|
+
agentroam service stop
|
|
73
|
+
agentroam service url
|
|
74
|
+
agentroam service logs
|
|
75
|
+
agentroam service restart
|
|
76
|
+
agentroam service uninstall
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The current phone URL is stored privately at `~/.agentroam/tunnel.url`.
|
|
80
|
+
Cloudflared keeps the same Quick Tunnel hostname while its process reconnects;
|
|
81
|
+
a full service or computer restart creates a new random hostname. Re-run the
|
|
82
|
+
versioned installer after moving or replacing Node/npm so the service receives
|
|
83
|
+
the new absolute executable paths.
|
|
84
|
+
|
|
85
|
+
Both foreground `agentroam start` and the background service prevent idle system
|
|
86
|
+
sleep for as long as they run. The display can still dim and turn off normally.
|
|
87
|
+
Stopping AgentRoam releases the assertion. Lid close, explicit sleep,
|
|
88
|
+
hibernation, shutdown, and low-battery forced sleep remain controlled by the OS.
|
|
89
|
+
|
|
56
90
|
## Options
|
|
57
91
|
|
|
58
92
|
| Flag | Description |
|
package/bin/agentroam.mjs
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import("
|
|
2
|
+
import("./node-preflight.mjs").then(async ({ runNodePreflight }) => {
|
|
3
|
+
const result = await runNodePreflight();
|
|
4
|
+
if (result.handled) {
|
|
5
|
+
if (result.signal) process.kill(process.pid, result.signal);
|
|
6
|
+
else process.exitCode = result.exitCode ?? 1;
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const { main } = await import("../dist/cli.js");
|
|
10
|
+
await main(process.argv.slice(2));
|
|
11
|
+
}).catch((error) => {
|
|
3
12
|
console.error(`agentroam: ${error?.message ?? error}`);
|
|
4
13
|
process.exitCode = error?.exitCode ?? 1;
|
|
5
14
|
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { delimiter, dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const MANAGED_NODE_VERSION = "22.22.0";
|
|
10
|
+
const RELEASE_VERSION = "0.2.0-preview.9";
|
|
11
|
+
const RELEASE_BASE_URL = `https://gitee.com/caoqu/team-agent/releases/download/v${RELEASE_VERSION}`;
|
|
12
|
+
const FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
13
|
+
|
|
14
|
+
export async function runNodePreflight(options = {}) {
|
|
15
|
+
const argv = options.argv ?? process.argv.slice(2);
|
|
16
|
+
const nodeVersion = options.nodeVersion ?? process.versions.node;
|
|
17
|
+
const platform = options.platform ?? process.platform;
|
|
18
|
+
const arch = options.arch ?? process.arch;
|
|
19
|
+
const launcherPath = options.launcherPath ?? fileURLToPath(new URL("./agentroam.mjs", import.meta.url));
|
|
20
|
+
const environment = options.environment ?? process.env;
|
|
21
|
+
const processHost = options.processHost ?? process;
|
|
22
|
+
const major = Number(nodeVersion.split(".")[0]);
|
|
23
|
+
|
|
24
|
+
if (major === 22) return { handled: false };
|
|
25
|
+
if (environment.AGENTROAM_MANAGED_NODE) {
|
|
26
|
+
throw cliError(
|
|
27
|
+
`managed Node.js re-entry failed: expected 22, got ${nodeVersion} (${environment.AGENTROAM_MANAGED_NODE})`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isFinite(major) || major < 18) {
|
|
31
|
+
throw cliError(standaloneInstallerMessage(platform));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const target = detectManagedTarget(platform, arch);
|
|
35
|
+
const dataDir = parsePreflightDataDir(argv, environment, options.homeDir ?? homedir());
|
|
36
|
+
const findSystemNode22 = options.findSystemNode22 ?? defaultFindSystemNode22;
|
|
37
|
+
const systemNode = await findSystemNode22(environment, platform, options.currentExecutable ?? process.execPath);
|
|
38
|
+
let executable = systemNode;
|
|
39
|
+
|
|
40
|
+
if (!executable) {
|
|
41
|
+
const ensureNode = options.ensureNode ?? (async (installOptions) => {
|
|
42
|
+
const { ensureManagedNode } = await import("../dist/node-runtime-manager.js");
|
|
43
|
+
return ensureManagedNode(installOptions);
|
|
44
|
+
});
|
|
45
|
+
const resolution = await ensureNode({
|
|
46
|
+
dataDir,
|
|
47
|
+
target,
|
|
48
|
+
platform,
|
|
49
|
+
arch,
|
|
50
|
+
onProgress: options.onProgress ?? ((message) => processHost.stderr.write(`${message}\n`)),
|
|
51
|
+
});
|
|
52
|
+
executable = resolution.executable;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const spawnChild = options.spawnChild ?? defaultSpawnChild;
|
|
56
|
+
const child = spawnChild(executable, [launcherPath, ...argv], {
|
|
57
|
+
stdio: "inherit",
|
|
58
|
+
env: { ...environment, AGENTROAM_MANAGED_NODE: MANAGED_NODE_VERSION },
|
|
59
|
+
});
|
|
60
|
+
const result = await waitForChild(child, processHost);
|
|
61
|
+
return { handled: true, ...result };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parsePreflightDataDir(argv, environment = process.env, homeDir = homedir()) {
|
|
65
|
+
const index = argv.indexOf("--data-dir");
|
|
66
|
+
if (index >= 0) {
|
|
67
|
+
const value = argv[index + 1];
|
|
68
|
+
if (!value || value.startsWith("--")) throw cliError("missing value for --data-dir");
|
|
69
|
+
return resolve(value);
|
|
70
|
+
}
|
|
71
|
+
return resolve(environment.AGENTROAM_DATA_DIR?.trim() || homeDir, environment.AGENTROAM_DATA_DIR?.trim() ? "" : ".agentroam");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function detectManagedTarget(platform, arch) {
|
|
75
|
+
if (platform === "darwin" && arch === "arm64") return "darwin-arm64";
|
|
76
|
+
if (platform === "win32" && arch === "x64") return "windows-amd64";
|
|
77
|
+
throw cliError(`managed Node.js is unsupported on ${platform}-${arch}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function defaultFindSystemNode22(environment, platform, currentExecutable) {
|
|
81
|
+
const executableName = platform === "win32" ? "node.exe" : "node";
|
|
82
|
+
const pathDelimiter = platform === "win32" ? ";" : delimiter;
|
|
83
|
+
const currentDirectory = dirname(currentExecutable);
|
|
84
|
+
for (const directory of (environment.PATH ?? "").split(pathDelimiter).filter(Boolean)) {
|
|
85
|
+
if (resolve(directory) === resolve(currentDirectory)) continue;
|
|
86
|
+
const candidate = resolve(directory, executableName);
|
|
87
|
+
try {
|
|
88
|
+
await access(candidate);
|
|
89
|
+
const { stdout } = await execFileAsync(candidate, ["-p", "process.versions.node"], {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
timeout: 5_000,
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
});
|
|
94
|
+
if (Number(stdout.trim().split(".")[0]) === 22) return candidate;
|
|
95
|
+
} catch {
|
|
96
|
+
// Ignore broken or incompatible PATH entries and continue to the private runtime.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function defaultSpawnChild(executable, args, options) {
|
|
103
|
+
const { spawn } = await import("node:child_process");
|
|
104
|
+
return spawn(executable, args, options);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function waitForChild(childPromise, processHost) {
|
|
108
|
+
return Promise.resolve(childPromise).then((child) => new Promise((resolveChild, rejectChild) => {
|
|
109
|
+
const listeners = new Map();
|
|
110
|
+
const cleanup = () => {
|
|
111
|
+
for (const [signal, listener] of listeners) processHost.off(signal, listener);
|
|
112
|
+
};
|
|
113
|
+
for (const signal of FORWARDED_SIGNALS) {
|
|
114
|
+
const listener = () => child.kill(signal);
|
|
115
|
+
listeners.set(signal, listener);
|
|
116
|
+
processHost.on(signal, listener);
|
|
117
|
+
}
|
|
118
|
+
child.once("error", (error) => {
|
|
119
|
+
cleanup();
|
|
120
|
+
rejectChild(error);
|
|
121
|
+
});
|
|
122
|
+
child.once("exit", (exitCode, signal) => {
|
|
123
|
+
cleanup();
|
|
124
|
+
resolveChild({ exitCode, signal });
|
|
125
|
+
});
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function standaloneInstallerMessage(platform) {
|
|
130
|
+
if (platform === "win32") {
|
|
131
|
+
return `Node.js 18+ is required for automatic bootstrap. Run: irm ${RELEASE_BASE_URL}/install-agentroam.ps1 | iex`;
|
|
132
|
+
}
|
|
133
|
+
return `Node.js 18+ is required for automatic bootstrap. Run: curl -fsSL ${RELEASE_BASE_URL}/install-agentroam.sh | sh`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function cliError(message) {
|
|
137
|
+
return Object.assign(new Error(message), { exitCode: 2 });
|
|
138
|
+
}
|
package/dist/args.js
CHANGED
|
@@ -3,10 +3,23 @@ import { resolve } from "node:path";
|
|
|
3
3
|
export function parseArgs(argv) {
|
|
4
4
|
const values = [...argv];
|
|
5
5
|
let command = "start";
|
|
6
|
-
if (values[0] && ["start", "doctor", "version"].includes(values[0]))
|
|
6
|
+
if (values[0] && ["start", "doctor", "version", "service"].includes(values[0])) {
|
|
7
7
|
command = values.shift();
|
|
8
|
+
}
|
|
9
|
+
let serviceAction = null;
|
|
10
|
+
if (command === "service") {
|
|
11
|
+
const action = values.shift();
|
|
12
|
+
if (!action || !["install", "start", "stop", "status", "url", "logs", "restart", "uninstall"].includes(action)) {
|
|
13
|
+
throw cliError("service requires install, start, stop, status, url, logs, restart, or uninstall");
|
|
14
|
+
}
|
|
15
|
+
serviceAction = action;
|
|
16
|
+
if (serviceAction !== "install" && values.length > 0) {
|
|
17
|
+
throw cliError(`service ${serviceAction} does not accept options`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
const options = {
|
|
9
21
|
command,
|
|
22
|
+
serviceAction,
|
|
10
23
|
roots: [],
|
|
11
24
|
port: null,
|
|
12
25
|
relay: "auto",
|
package/dist/args.test.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import { describe, expect, it } from "vitest";
|
|
2
3
|
import { parseArgs } from "./args.js";
|
|
3
4
|
describe("parseArgs", () => {
|
|
4
5
|
it("uses auto relay and the current directory by default", () => {
|
|
5
6
|
const value = parseArgs([]);
|
|
6
7
|
expect(value.command).toBe("start");
|
|
8
|
+
expect(value.serviceAction).toBeNull();
|
|
7
9
|
expect(value.roots).toEqual([process.cwd()]);
|
|
8
10
|
expect(value.relay).toBe("auto");
|
|
9
11
|
});
|
|
@@ -24,4 +26,29 @@ describe("parseArgs", () => {
|
|
|
24
26
|
expect(() => parseArgs(["--port", "70000"])).toThrow();
|
|
25
27
|
expect(() => parseArgs(["--relay", "unknown"])).toThrow("relay must be auto");
|
|
26
28
|
});
|
|
29
|
+
it("parses every service action", () => {
|
|
30
|
+
for (const action of ["install", "start", "stop", "status", "url", "logs", "restart", "uninstall"]) {
|
|
31
|
+
const value = parseArgs(["service", action]);
|
|
32
|
+
expect(value.command).toBe("service");
|
|
33
|
+
expect(value.serviceAction).toBe(action);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
it("parses service install using the existing start options", () => {
|
|
37
|
+
const value = parseArgs([
|
|
38
|
+
"service", "install", "--root", ".", "--root", "..", "--port", "3210", "--relay", "cloudflare",
|
|
39
|
+
"--data-dir", "./service-data", "--no-qr",
|
|
40
|
+
]);
|
|
41
|
+
expect(value.roots).toHaveLength(2);
|
|
42
|
+
expect(value.port).toBe(3210);
|
|
43
|
+
expect(value.relay).toBe("cloudflare");
|
|
44
|
+
expect(value.dataDir).toBe(resolve(process.cwd(), "service-data"));
|
|
45
|
+
expect(value.qr).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
it("rejects missing service actions and options on non-install actions", () => {
|
|
48
|
+
expect(() => parseArgs(["service"])).toThrow("service requires");
|
|
49
|
+
expect(() => parseArgs(["service", "unknown"])).toThrow("service requires");
|
|
50
|
+
expect(() => parseArgs(["service", "status", "--root", "."])).toThrow("does not accept options");
|
|
51
|
+
expect(() => parseArgs(["service", "start", "--port", "3000"])).toThrow("does not accept options");
|
|
52
|
+
expect(() => parseArgs(["service", "stop", "--local-only"])).toThrow("does not accept options");
|
|
53
|
+
});
|
|
27
54
|
});
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import { parseArgs } from "./args.js";
|
|
3
4
|
import { ensureCloudflared } from "./cloudflared/installer.js";
|
|
5
|
+
import { resolveCodexRuntime } from "./codex-runtime-manager.js";
|
|
4
6
|
import { repairNativeRuntimePermissions } from "./native-runtime.js";
|
|
5
7
|
import { findLanUrl } from "./network.js";
|
|
6
8
|
import { createPairingSecret } from "./pairing.js";
|
|
7
9
|
import { detectPlatform } from "./platform.js";
|
|
8
10
|
import { AGENTROAM_VERSION, resolvePlatformRuntime, resolvePlatformTui } from "./platform-packages.js";
|
|
11
|
+
import { acquireSleepInhibitor } from "./power/sleep-inhibitor.js";
|
|
9
12
|
import { renderQr } from "./qr.js";
|
|
10
13
|
import { RuntimeManager } from "./runtime-manager.js";
|
|
14
|
+
import { ServiceRuntimeReporter } from "./service/runtime-state.js";
|
|
15
|
+
import { runServiceCommand } from "./service/service-command.js";
|
|
16
|
+
import { resolveServicePaths } from "./service/service-files.js";
|
|
11
17
|
import { selectRelay } from "./tunnel/relay-orchestrator.js";
|
|
12
18
|
const VERSION = AGENTROAM_VERSION;
|
|
13
19
|
export async function main(argv) {
|
|
14
20
|
const options = parseArgs(argv);
|
|
21
|
+
if (options.command === "service") {
|
|
22
|
+
await runServiceCommand(options, {
|
|
23
|
+
nodePath: process.execPath,
|
|
24
|
+
cliPath: fileURLToPath(new URL("../bin/agentroam.mjs", import.meta.url)),
|
|
25
|
+
version: VERSION,
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
15
29
|
const target = detectPlatform();
|
|
16
30
|
if (options.command === "version") {
|
|
17
31
|
console.log(`agentroam ${VERSION}`);
|
|
@@ -22,11 +36,15 @@ export async function main(argv) {
|
|
|
22
36
|
return;
|
|
23
37
|
}
|
|
24
38
|
console.log(`AgentRoam ${VERSION}\n✓ Node ${process.versions.node} · ${target}`);
|
|
39
|
+
const sleepInhibitor = await acquireSleepInhibitor();
|
|
25
40
|
const pairing = createPairingSecret();
|
|
26
41
|
const controller = new AbortController();
|
|
27
42
|
let runtime = null;
|
|
28
43
|
let relay = null;
|
|
29
44
|
let closing = false;
|
|
45
|
+
const serviceReporter = process.env.AGENTROAM_SERVICE === "1"
|
|
46
|
+
? new ServiceRuntimeReporter(resolveServicePaths(undefined, options.dataDir), VERSION)
|
|
47
|
+
: null;
|
|
30
48
|
const close = async () => {
|
|
31
49
|
if (closing)
|
|
32
50
|
return;
|
|
@@ -39,6 +57,7 @@ export async function main(argv) {
|
|
|
39
57
|
process.once("SIGINT", requestClose);
|
|
40
58
|
process.once("SIGTERM", requestClose);
|
|
41
59
|
try {
|
|
60
|
+
await serviceReporter?.starting();
|
|
42
61
|
runtime = await new RuntimeManager().start(options, pairing, target);
|
|
43
62
|
console.log(`✓ Local server: ${runtime.localUrl}/web`);
|
|
44
63
|
const lanUrl = findLanUrl(runtime.port) ?? runtime.localUrl;
|
|
@@ -52,6 +71,7 @@ export async function main(argv) {
|
|
|
52
71
|
log: (line) => process.stderr.write(`${line}\n`),
|
|
53
72
|
onAttempt: (provider) => console.log(`▲ Trying ${providerDisplayName(provider)} relay...`),
|
|
54
73
|
onFailure: (provider, message) => console.error(`⚠ ${providerDisplayName(provider)} unavailable: ${message}`),
|
|
74
|
+
allowLanFallback: serviceReporter === null,
|
|
55
75
|
});
|
|
56
76
|
if (controller.signal.aborted)
|
|
57
77
|
return;
|
|
@@ -69,18 +89,39 @@ export async function main(argv) {
|
|
|
69
89
|
console.log(" The phone browser may show a one-time Pinggy security confirmation before pairing.");
|
|
70
90
|
}
|
|
71
91
|
const accessUrl = `${relay.publicUrl}/web${runtime.needsSetup ? `?pair=${encodeURIComponent(pairing.token)}` : ""}`;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
await serviceReporter?.ready({
|
|
93
|
+
localUrl: runtime.localUrl,
|
|
94
|
+
publicUrl: relay.publicUrl,
|
|
95
|
+
accessUrl,
|
|
96
|
+
provider: relay.provider,
|
|
97
|
+
});
|
|
98
|
+
if (serviceReporter) {
|
|
99
|
+
console.log("✓ Background service URL written to the private state file.");
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log(`\nOpen: ${accessUrl}`);
|
|
103
|
+
if (options.qr)
|
|
104
|
+
console.log(`\n${await renderQr(accessUrl)}`);
|
|
105
|
+
if (runtime.needsSetup)
|
|
106
|
+
console.log("First pairing link expires in 5 minutes.");
|
|
107
|
+
console.log("Ctrl+C stops the tunnel and local server.");
|
|
108
|
+
}
|
|
109
|
+
await Promise.race([
|
|
110
|
+
runtime.exited,
|
|
111
|
+
relay.tunnel?.exited ?? new Promise(() => { }),
|
|
112
|
+
sleepInhibitor.lost.then(async (error) => {
|
|
113
|
+
await new Promise((resolveTurn) => setImmediate(resolveTurn));
|
|
114
|
+
if (!closing)
|
|
115
|
+
throw error;
|
|
116
|
+
}),
|
|
117
|
+
]);
|
|
79
118
|
}
|
|
80
119
|
finally {
|
|
81
120
|
process.removeListener("SIGINT", requestClose);
|
|
82
121
|
process.removeListener("SIGTERM", requestClose);
|
|
83
122
|
await close();
|
|
123
|
+
await serviceReporter?.stopped().catch(() => { });
|
|
124
|
+
await sleepInhibitor.release();
|
|
84
125
|
}
|
|
85
126
|
}
|
|
86
127
|
async function doctor(target, dataDir) {
|
|
@@ -111,6 +152,17 @@ async function doctor(target, dataDir) {
|
|
|
111
152
|
catch (error) {
|
|
112
153
|
reportDoctorFailure(error, "agent-tui: ");
|
|
113
154
|
}
|
|
155
|
+
try {
|
|
156
|
+
const codex = await resolveCodexRuntime({
|
|
157
|
+
dataDir,
|
|
158
|
+
target,
|
|
159
|
+
onProgress: (message) => console.log(`… ${message}`),
|
|
160
|
+
});
|
|
161
|
+
console.log(`✓ Codex ${codex.version} (${codex.source}) ${codex.executable}`);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
reportDoctorFailure(error, "Codex: ");
|
|
165
|
+
}
|
|
114
166
|
try {
|
|
115
167
|
const executable = await ensureCloudflared(target, dataDir);
|
|
116
168
|
console.log(`✓ cloudflared ${executable}`);
|
|
@@ -10,7 +10,7 @@ describe("resolveBundledCloudflared", () => {
|
|
|
10
10
|
const archivePath = resolve(root, "cloudflared.tgz");
|
|
11
11
|
await writeFile(archivePath, "archive");
|
|
12
12
|
await writeFile(manifestPath, JSON.stringify({
|
|
13
|
-
packageVersion: "0.2.0-preview.
|
|
13
|
+
packageVersion: "0.2.0-preview.9",
|
|
14
14
|
upstreamVersion: "2026.8.2",
|
|
15
15
|
target: "darwin-arm64",
|
|
16
16
|
assetFormat: "tgz",
|
|
@@ -51,7 +51,7 @@ describe("resolveBundledCloudflared", () => {
|
|
|
51
51
|
const assetPath = resolve(root, "cloudflared.exe");
|
|
52
52
|
await writeFile(assetPath, "binary");
|
|
53
53
|
await writeFile(manifestPath, JSON.stringify({
|
|
54
|
-
packageVersion: "0.2.0-preview.
|
|
54
|
+
packageVersion: "0.2.0-preview.9",
|
|
55
55
|
upstreamVersion: "2026.8.2",
|
|
56
56
|
target: "windows-amd64",
|
|
57
57
|
assetFormat: "executable",
|