@beeuni/agent-tunnel 0.1.1 → 0.1.3
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 +18 -7
- package/dist/agent-cli.js +94 -41
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -68,7 +68,10 @@ It starts at login, and the supervisor restarts it after every exit (launchd
|
|
|
68
68
|
`KeepAlive` with a 10 s throttle, systemd `Restart=always`, a 5 s Windows loop).
|
|
69
69
|
On Linux, install enables `loginctl enable-linger` so the user service keeps
|
|
70
70
|
running after logout and starts at boot; when that is not allowed, install
|
|
71
|
-
prints a warning. The Windows task runs
|
|
71
|
+
prints a warning. The Windows task runs PowerShell under `conhost --headless`
|
|
72
|
+
(no console window, also where Windows Terminal is the default terminal), on
|
|
73
|
+
battery, with no time limit. Every process the agent starts on Windows sets
|
|
74
|
+
`windowsHide`, so no console window opens.
|
|
72
75
|
The agent itself never stops on its own:
|
|
73
76
|
|
|
74
77
|
- It reconnects with exponential backoff from 1 s to 30 s, ±20 % jitter, forever.
|
|
@@ -181,9 +184,10 @@ change to the agent as `tunnel.access.set`:
|
|
|
181
184
|
A pairing made from the Beeuni desktop app starts in `ask`: the app shows the
|
|
182
185
|
prompt. A pairing made with this CLI alone starts in `always`, because nothing
|
|
183
186
|
on the machine could answer a prompt. This is a deliberate deviation from
|
|
184
|
-
"new pairings default to ask".
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
"new pairings default to ask". The mode and keep awake are the owner's
|
|
188
|
+
machine settings: pairing again (`--reauth`, a rejected or stale credential)
|
|
189
|
+
keeps them, and only a grant or a denial from the earlier pairing is dropped.
|
|
190
|
+
`logout` deletes `access.json`, so the next pairing starts from the default. The agent home (`config.json`, `access.json`, `desktop-app.json`)
|
|
187
191
|
is always a blocked path: no file or shell call can change these files.
|
|
188
192
|
|
|
189
193
|
The agent reports its state to Beeuni as the signed notification
|
|
@@ -233,9 +237,16 @@ you trust with this computer.
|
|
|
233
237
|
|
|
234
238
|
## Computer Use driver
|
|
235
239
|
|
|
236
|
-
Agent Tunnel never downloads or executes a desktop driver.
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
Agent Tunnel never downloads or executes a desktop driver. The Beeuni desktop
|
|
241
|
+
app bundles `cua-driver` on macOS and Windows (x64), and the agent it runs uses
|
|
242
|
+
that copy. A CLI install uses an existing binary from `CUA_DRIVER_BIN`,
|
|
243
|
+
`~/.local/bin`, `/usr/local/bin`, or `/opt/homebrew/bin`.
|
|
244
|
+
|
|
245
|
+
An approved `desktop` registers whenever the driver is present. Missing macOS
|
|
246
|
+
grants do not unregister it: the agent reports `desktopPermissions` and the
|
|
247
|
+
setup asks for them. When the driver is missing or rejected (wrong owner,
|
|
248
|
+
group-writable), `desktop` does not register and the reason is reported as
|
|
249
|
+
`unavailableCapabilities.desktop` in `status --json` and in `machineInfo`.
|
|
239
250
|
Treat that binary as trusted local code. Agent Tunnel does not verify or update
|
|
240
251
|
it.
|
|
241
252
|
|
package/dist/agent-cli.js
CHANGED
|
@@ -3516,6 +3516,15 @@ function writePrivateJson(file, home, value) {
|
|
|
3516
3516
|
function writeAccess(state, home = agentTunnelHome()) {
|
|
3517
3517
|
writePrivateJson(accessFilePath(home), home, state);
|
|
3518
3518
|
}
|
|
3519
|
+
function accessAfterPairing(home, desktopApp) {
|
|
3520
|
+
const kept = existsSync2(accessFilePath(home)) ? readAccess(home) : null;
|
|
3521
|
+
return {
|
|
3522
|
+
mode: kept?.mode ?? (desktopApp ? "ask" : "always"),
|
|
3523
|
+
grantedUntil: null,
|
|
3524
|
+
deniedUntil: null,
|
|
3525
|
+
keepAwake: kept?.keepAwake ?? false
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3519
3528
|
function decideAccess(state, now = Date.now()) {
|
|
3520
3529
|
if (state.mode === "always")
|
|
3521
3530
|
return "run";
|
|
@@ -3803,7 +3812,7 @@ function machineDisplayName({
|
|
|
3803
3812
|
os = platform2(),
|
|
3804
3813
|
host = hostname(),
|
|
3805
3814
|
env = process.env,
|
|
3806
|
-
run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"] })
|
|
3815
|
+
run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"], windowsHide: true })
|
|
3807
3816
|
} = {}) {
|
|
3808
3817
|
const fallback = host.replace(/\.local$/i, "") || host;
|
|
3809
3818
|
try {
|
|
@@ -3815,7 +3824,7 @@ function machineDisplayName({
|
|
|
3815
3824
|
}
|
|
3816
3825
|
function machineId({
|
|
3817
3826
|
os = platform2(),
|
|
3818
|
-
run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] }),
|
|
3827
|
+
run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"], windowsHide: true }),
|
|
3819
3828
|
read = (path) => readFileSync4(path, "utf8")
|
|
3820
3829
|
} = {}) {
|
|
3821
3830
|
const attempt = (source) => {
|
|
@@ -4036,6 +4045,10 @@ function verifyMessageSignature(signingKey, payload, nonce, signature) {
|
|
|
4036
4045
|
|
|
4037
4046
|
// src/agent/agent.ts
|
|
4038
4047
|
var AGENT_VERSION = agentTunnelVersion();
|
|
4048
|
+
function openRelaySocket(url) {
|
|
4049
|
+
const RelaySocket = WebSocket;
|
|
4050
|
+
return new RelaySocket(url, { headers: { "User-Agent": `beeuni-agent-tunnel/${AGENT_VERSION}` } });
|
|
4051
|
+
}
|
|
4039
4052
|
var AUTH_REJECTED_CLOSE_CODES = [4001, 4003];
|
|
4040
4053
|
var RELAY_FAULT_4001_REASONS = new Set(["auth timeout", "authentication error", "authentication response failed"]);
|
|
4041
4054
|
function isCredentialRejection(code, reason = "") {
|
|
@@ -4137,7 +4150,7 @@ class TunnelAgent {
|
|
|
4137
4150
|
this.connectStartedAt = this.now();
|
|
4138
4151
|
this.setStatus("connecting");
|
|
4139
4152
|
try {
|
|
4140
|
-
this.ws =
|
|
4153
|
+
this.ws = openRelaySocket(new URL(wsUrl).href);
|
|
4141
4154
|
this.setupWsHandlers();
|
|
4142
4155
|
} catch (err) {
|
|
4143
4156
|
log(`${c.red}✗${c.reset}`, `Connection failed`);
|
|
@@ -4456,7 +4469,8 @@ class TunnelAgent {
|
|
|
4456
4469
|
osVersion: release(),
|
|
4457
4470
|
agentVersion: AGENT_VERSION,
|
|
4458
4471
|
homeDir: this.config.workingDir,
|
|
4459
|
-
allowedPaths: this.config.allowedPaths
|
|
4472
|
+
allowedPaths: this.config.allowedPaths,
|
|
4473
|
+
unavailableCapabilities: Object.fromEntries(this.registry.unavailable)
|
|
4460
4474
|
}
|
|
4461
4475
|
}
|
|
4462
4476
|
});
|
|
@@ -4581,7 +4595,7 @@ class TunnelAgent {
|
|
|
4581
4595
|
const blocker = keepAwakeCommand(platform3(), process.pid);
|
|
4582
4596
|
if (!blocker)
|
|
4583
4597
|
return;
|
|
4584
|
-
const child = spawn3(blocker.command, blocker.args, { stdio: "ignore" });
|
|
4598
|
+
const child = spawn3(blocker.command, blocker.args, { stdio: "ignore", windowsHide: true });
|
|
4585
4599
|
child.on("error", () => {
|
|
4586
4600
|
this.keepAwakeFailed = true;
|
|
4587
4601
|
log(`${c.yellow}!${c.reset}`, `Keep awake is unavailable: ${blocker.command} could not start`);
|
|
@@ -4749,6 +4763,7 @@ async function printStartupBanner({
|
|
|
4749
4763
|
// src/agent/capabilities/index.ts
|
|
4750
4764
|
class CapabilityRegistry {
|
|
4751
4765
|
capabilities = new Map;
|
|
4766
|
+
unavailable = new Map;
|
|
4752
4767
|
register(capability) {
|
|
4753
4768
|
this.capabilities.set(capability.name, capability);
|
|
4754
4769
|
}
|
|
@@ -4774,7 +4789,7 @@ class CapabilityRegistry {
|
|
|
4774
4789
|
// src/agent/capabilities/desktop/cua-driver.ts
|
|
4775
4790
|
import { spawn as spawn4 } from "child_process";
|
|
4776
4791
|
import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
4777
|
-
import { homedir as homedir3, platform as platform5, tmpdir } from "os";
|
|
4792
|
+
import { homedir as homedir3, platform as platform5, tmpdir, userInfo } from "os";
|
|
4778
4793
|
import { basename as basename2, dirname as dirname3, join as join5 } from "path";
|
|
4779
4794
|
var MAX_DRIVER_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
4780
4795
|
var DRIVER_ENV_KEYS = [
|
|
@@ -4789,7 +4804,19 @@ var DRIVER_ENV_KEYS = [
|
|
|
4789
4804
|
"WAYLAND_DISPLAY",
|
|
4790
4805
|
"XDG_RUNTIME_DIR",
|
|
4791
4806
|
"XAUTHORITY",
|
|
4792
|
-
"DBUS_SESSION_BUS_ADDRESS"
|
|
4807
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
4808
|
+
"SystemRoot",
|
|
4809
|
+
"windir",
|
|
4810
|
+
"USERPROFILE",
|
|
4811
|
+
"USERNAME",
|
|
4812
|
+
"USERDOMAIN",
|
|
4813
|
+
"APPDATA",
|
|
4814
|
+
"LOCALAPPDATA",
|
|
4815
|
+
"ProgramData",
|
|
4816
|
+
"TEMP",
|
|
4817
|
+
"TMP",
|
|
4818
|
+
"ComSpec",
|
|
4819
|
+
"PATHEXT"
|
|
4793
4820
|
];
|
|
4794
4821
|
function driverEnvironment(extra = {}) {
|
|
4795
4822
|
const env = { CUA_DRIVER_RS_TELEMETRY_ENABLED: "0" };
|
|
@@ -4852,7 +4879,8 @@ function execFile(cmd, args, timeoutMs = 30000, env = driverEnvironment()) {
|
|
|
4852
4879
|
return new Promise((resolve2, reject) => {
|
|
4853
4880
|
const proc = spawn4(cmd, args, {
|
|
4854
4881
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4855
|
-
env
|
|
4882
|
+
env,
|
|
4883
|
+
windowsHide: true
|
|
4856
4884
|
});
|
|
4857
4885
|
let stdout = "";
|
|
4858
4886
|
let stderr = "";
|
|
@@ -4939,9 +4967,8 @@ class CuaDriver {
|
|
|
4939
4967
|
constructor(options = {}) {
|
|
4940
4968
|
this.binary = options.binary ?? null;
|
|
4941
4969
|
this.embedded = options.embedded;
|
|
4942
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : "user";
|
|
4943
4970
|
const home = serviceLabelFor(agentTunnelHome()).slice(SERVICE_LABEL.length);
|
|
4944
|
-
this.socketPath = options.socketPath ?? join5(tmpdir(), `beeuni-cua-${
|
|
4971
|
+
this.socketPath = options.socketPath ?? (process.platform === "win32" ? `\\\\.\\pipe\\beeuni-cua-${userInfo().username}${home}` : join5(tmpdir(), `beeuni-cua-${typeof process.getuid === "function" ? process.getuid() : "user"}${home}.sock`));
|
|
4945
4972
|
this.hostBundleId = options.hostBundleId;
|
|
4946
4973
|
}
|
|
4947
4974
|
isEmbedded(binary) {
|
|
@@ -4950,10 +4977,11 @@ class CuaDriver {
|
|
|
4950
4977
|
mode(binary) {
|
|
4951
4978
|
if (!this.isEmbedded(binary))
|
|
4952
4979
|
return { env: driverEnvironment(), socket: [] };
|
|
4980
|
+
const bundleId = this.hostBundleId ?? hostBundleId();
|
|
4953
4981
|
return {
|
|
4954
4982
|
env: driverEnvironment({
|
|
4955
4983
|
CUA_DRIVER_EMBEDDED: "1",
|
|
4956
|
-
CUA_DRIVER_HOST_BUNDLE_ID:
|
|
4984
|
+
...bundleId ? { CUA_DRIVER_HOST_BUNDLE_ID: bundleId } : {}
|
|
4957
4985
|
}),
|
|
4958
4986
|
socket: ["--socket", this.socketPath]
|
|
4959
4987
|
};
|
|
@@ -5027,7 +5055,8 @@ class CuaDriver {
|
|
|
5027
5055
|
const child = spawn4("open", ["-n", "-g", "-a", "CuaDriver", "--args", "serve"], {
|
|
5028
5056
|
detached: true,
|
|
5029
5057
|
stdio: "ignore",
|
|
5030
|
-
env: driverEnvironment()
|
|
5058
|
+
env: driverEnvironment(),
|
|
5059
|
+
windowsHide: true
|
|
5031
5060
|
});
|
|
5032
5061
|
child.unref();
|
|
5033
5062
|
await sleep2(750);
|
|
@@ -5035,7 +5064,8 @@ class CuaDriver {
|
|
|
5035
5064
|
const child = spawn4(bin, ["serve"], {
|
|
5036
5065
|
detached: true,
|
|
5037
5066
|
stdio: "ignore",
|
|
5038
|
-
env: driverEnvironment()
|
|
5067
|
+
env: driverEnvironment(),
|
|
5068
|
+
windowsHide: true
|
|
5039
5069
|
});
|
|
5040
5070
|
child.unref();
|
|
5041
5071
|
await sleep2(750);
|
|
@@ -5052,7 +5082,11 @@ class CuaDriver {
|
|
|
5052
5082
|
if (this.daemon && this.daemon.exitCode === null)
|
|
5053
5083
|
return;
|
|
5054
5084
|
const { env, socket } = this.mode(bin);
|
|
5055
|
-
if (
|
|
5085
|
+
if (process.platform === "win32") {
|
|
5086
|
+
await execFile(bin, ["stop", ...socket], 1e4, env).catch(() => {
|
|
5087
|
+
return;
|
|
5088
|
+
});
|
|
5089
|
+
} else if (existsSync3(this.socketPath)) {
|
|
5056
5090
|
await execFile(bin, ["stop", ...socket], 1e4, env).catch(() => {
|
|
5057
5091
|
return;
|
|
5058
5092
|
});
|
|
@@ -5060,7 +5094,8 @@ class CuaDriver {
|
|
|
5060
5094
|
}
|
|
5061
5095
|
const child = spawn4(bin, ["serve", "--embedded", "--no-permissions-gate", ...socket], {
|
|
5062
5096
|
stdio: "ignore",
|
|
5063
|
-
env
|
|
5097
|
+
env,
|
|
5098
|
+
windowsHide: true
|
|
5064
5099
|
});
|
|
5065
5100
|
child.unref();
|
|
5066
5101
|
child.on("exit", () => {
|
|
@@ -5071,16 +5106,23 @@ class CuaDriver {
|
|
|
5071
5106
|
});
|
|
5072
5107
|
this.daemon = child;
|
|
5073
5108
|
process.once("exit", () => child.kill());
|
|
5074
|
-
|
|
5109
|
+
const started = Date.now();
|
|
5110
|
+
while (Date.now() - started < 1e4 && !await this.endpointUp(bin)) {
|
|
5075
5111
|
if (child.exitCode !== null)
|
|
5076
5112
|
break;
|
|
5077
5113
|
await sleep2(50);
|
|
5078
5114
|
}
|
|
5079
|
-
if (!
|
|
5115
|
+
if (!await this.endpointUp(bin)) {
|
|
5080
5116
|
child.kill();
|
|
5081
5117
|
throw new Error("cua-driver did not start (no socket after 10 s)");
|
|
5082
5118
|
}
|
|
5083
5119
|
}
|
|
5120
|
+
async endpointUp(bin) {
|
|
5121
|
+
if (process.platform !== "win32")
|
|
5122
|
+
return existsSync3(this.socketPath);
|
|
5123
|
+
const { env, socket } = this.mode(bin);
|
|
5124
|
+
return execFile(bin, ["status", ...socket], 5000, env).then(({ stdout }) => !/not\s+running|unhealthy/i.test(stdout), () => false);
|
|
5125
|
+
}
|
|
5084
5126
|
async permissionOwner() {
|
|
5085
5127
|
return this.isEmbedded(await this.ensureInstalled()) ? "Beeuni" : "CuaDriver";
|
|
5086
5128
|
}
|
|
@@ -5101,7 +5143,7 @@ class CuaDriver {
|
|
|
5101
5143
|
async ensureDaemonReady() {
|
|
5102
5144
|
const bin = await this.ensureInstalled();
|
|
5103
5145
|
if (this.isEmbedded(bin)) {
|
|
5104
|
-
if (this.daemon?.exitCode === null && existsSync3(this.socketPath))
|
|
5146
|
+
if (this.daemon?.exitCode === null && (process.platform === "win32" ? this.daemonReady : existsSync3(this.socketPath)))
|
|
5105
5147
|
return;
|
|
5106
5148
|
this.stop();
|
|
5107
5149
|
await this.startDaemon();
|
|
@@ -5547,7 +5589,8 @@ function createShellCapability(config) {
|
|
|
5547
5589
|
cwd,
|
|
5548
5590
|
shell: false,
|
|
5549
5591
|
timeout,
|
|
5550
|
-
env: safeEnv
|
|
5592
|
+
env: safeEnv,
|
|
5593
|
+
windowsHide: true
|
|
5551
5594
|
});
|
|
5552
5595
|
let stdout = "";
|
|
5553
5596
|
let stderr = "";
|
|
@@ -5603,6 +5646,14 @@ function createShellCapability(config) {
|
|
|
5603
5646
|
}
|
|
5604
5647
|
|
|
5605
5648
|
// src/agent/capabilities/enabled-registry.ts
|
|
5649
|
+
var DRIVER_NOT_FOUND = "cua-driver was not found. The Beeuni desktop app bundles it on macOS and Windows; a CLI install needs it in CUA_DRIVER_BIN, ~/.local/bin, /usr/local/bin or /opt/homebrew/bin.";
|
|
5650
|
+
function desktopUnavailableReason(findDesktopDriver = findCuaDriverBinary) {
|
|
5651
|
+
try {
|
|
5652
|
+
return findDesktopDriver() ? null : DRIVER_NOT_FOUND;
|
|
5653
|
+
} catch (error) {
|
|
5654
|
+
return error instanceof Error ? error.message : String(error);
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5606
5657
|
function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriverBinary, desktop = {}) {
|
|
5607
5658
|
const registry = new CapabilityRegistry;
|
|
5608
5659
|
const enabled = new Set(config.enabledCapabilities ?? ["filesystem", "shell", "desktop"]);
|
|
@@ -5610,8 +5661,12 @@ function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriv
|
|
|
5610
5661
|
registry.register(createFilesystemCapability(config));
|
|
5611
5662
|
if (enabled.has("shell"))
|
|
5612
5663
|
registry.register(createShellCapability(config));
|
|
5613
|
-
if (enabled.has("desktop")
|
|
5614
|
-
|
|
5664
|
+
if (enabled.has("desktop")) {
|
|
5665
|
+
const reason = desktopUnavailableReason(findDesktopDriver);
|
|
5666
|
+
if (reason)
|
|
5667
|
+
registry.unavailable.set("desktop", reason);
|
|
5668
|
+
else
|
|
5669
|
+
registry.register(createDesktopCapability(desktop));
|
|
5615
5670
|
}
|
|
5616
5671
|
return registry;
|
|
5617
5672
|
}
|
|
@@ -5677,17 +5732,17 @@ function probeCredentials(config, options = {}) {
|
|
|
5677
5732
|
return new Promise((resolve3) => {
|
|
5678
5733
|
let socket;
|
|
5679
5734
|
try {
|
|
5680
|
-
socket =
|
|
5735
|
+
socket = openRelaySocket(new URL(buildTunnelWsUrl(config)).href);
|
|
5681
5736
|
} catch {
|
|
5682
5737
|
resolve3("unreachable");
|
|
5683
5738
|
return;
|
|
5684
5739
|
}
|
|
5685
5740
|
const settle = (result) => {
|
|
5686
5741
|
clearTimeout(timer);
|
|
5742
|
+
resolve3(result);
|
|
5687
5743
|
try {
|
|
5688
5744
|
socket.close(1000, "probe complete");
|
|
5689
5745
|
} catch {}
|
|
5690
|
-
resolve3(result);
|
|
5691
5746
|
};
|
|
5692
5747
|
const timer = setTimeout(() => settle("unreachable"), options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS);
|
|
5693
5748
|
socket.addEventListener("open", () => {
|
|
@@ -5760,7 +5815,7 @@ import { join as join10 } from "path";
|
|
|
5760
5815
|
// src/agent/service-drivers.ts
|
|
5761
5816
|
import { spawnSync } from "child_process";
|
|
5762
5817
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
5763
|
-
import { homedir as homedir4, platform as platform6, userInfo } from "os";
|
|
5818
|
+
import { homedir as homedir4, platform as platform6, userInfo as userInfo2 } from "os";
|
|
5764
5819
|
import { dirname as dirname6, join as join9 } from "path";
|
|
5765
5820
|
|
|
5766
5821
|
// src/agent/service-quoting.ts
|
|
@@ -5783,7 +5838,7 @@ function posixShellCommand(runner) {
|
|
|
5783
5838
|
}
|
|
5784
5839
|
var supervisor = {
|
|
5785
5840
|
run(command, args) {
|
|
5786
|
-
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
5841
|
+
const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
|
|
5787
5842
|
return {
|
|
5788
5843
|
ok: result.status === 0,
|
|
5789
5844
|
detail: [result.stdout, result.stderr].filter(Boolean).join(`
|
|
@@ -5801,7 +5856,7 @@ function joinDetails(...results) {
|
|
|
5801
5856
|
`);
|
|
5802
5857
|
}
|
|
5803
5858
|
function launchdTarget() {
|
|
5804
|
-
const uid = typeof process.getuid === "function" ? process.getuid() :
|
|
5859
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : userInfo2().uid;
|
|
5805
5860
|
return `gui/${uid}`;
|
|
5806
5861
|
}
|
|
5807
5862
|
function logPaths(paths) {
|
|
@@ -5874,7 +5929,8 @@ function renderWindowsPowerShellScript(runner) {
|
|
|
5874
5929
|
const args = runner.args.map(powershellQuote).join(" ");
|
|
5875
5930
|
const env = Object.entries(runner.env ?? {}).map(([key, value]) => `$env:${key} = ${powershellQuote(value)}
|
|
5876
5931
|
`).join("");
|
|
5877
|
-
return
|
|
5932
|
+
return `# Run by the Beeuni task under conhost --headless: no console window.
|
|
5933
|
+
$ErrorActionPreference = 'Continue'
|
|
5878
5934
|
${env}while ($true) {
|
|
5879
5935
|
# The pipe makes PowerShell wait for a GUI-subsystem binary (the desktop
|
|
5880
5936
|
# app's Beeuni.exe) and set $LASTEXITCODE; a bare call returns at once.
|
|
@@ -5934,7 +5990,7 @@ var launchd = {
|
|
|
5934
5990
|
},
|
|
5935
5991
|
status(paths, installed) {
|
|
5936
5992
|
const disabled = run("launchctl", ["print-disabled", launchdTarget()]);
|
|
5937
|
-
const enabled = !
|
|
5993
|
+
const enabled = ![`"${paths.label}" => disabled`, `"${paths.label}" => true`].some((line) => disabled.detail.includes(line));
|
|
5938
5994
|
const status = run("launchctl", ["print", `${launchdTarget()}/${paths.label}`]);
|
|
5939
5995
|
return {
|
|
5940
5996
|
active: status.ok,
|
|
@@ -5989,7 +6045,7 @@ var systemd = {
|
|
|
5989
6045
|
}
|
|
5990
6046
|
};
|
|
5991
6047
|
function ensureLinger() {
|
|
5992
|
-
const user =
|
|
6048
|
+
const user = userInfo2().username;
|
|
5993
6049
|
const linger = run("loginctl", ["show-user", user, "-p", "Linger"]);
|
|
5994
6050
|
if (/Linger=yes/.test(linger.detail))
|
|
5995
6051
|
return { ok: true, detail: "" };
|
|
@@ -6014,11 +6070,11 @@ function renderWindowsTaskXml(paths, user) {
|
|
|
6014
6070
|
<StartWhenAvailable>true</StartWhenAvailable>
|
|
6015
6071
|
<Enabled>true</Enabled>
|
|
6016
6072
|
</Settings>
|
|
6017
|
-
<Actions Context="Author"><Exec><Command>
|
|
6073
|
+
<Actions Context="Author"><Exec><Command>conhost.exe</Command><Arguments>${xmlEscape(`--headless powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "${paths.windowsScript}"`)}</Arguments></Exec></Actions>
|
|
6018
6074
|
</Task>
|
|
6019
6075
|
`;
|
|
6020
6076
|
}
|
|
6021
|
-
var windowsUser = () => process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${
|
|
6077
|
+
var windowsUser = () => process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${userInfo2().username}` : userInfo2().username;
|
|
6022
6078
|
var windowsTaskXmlPath = (paths) => paths.windowsScript.replace(/\.ps1$/, ".xml");
|
|
6023
6079
|
var scheduledTask = {
|
|
6024
6080
|
unitPath: (paths) => paths.windowsScript,
|
|
@@ -6382,9 +6438,9 @@ function startAgent(config, options = {}) {
|
|
|
6382
6438
|
const registry = createEnabledCapabilityRegistry(config, undefined, {
|
|
6383
6439
|
onPermissionMissing: restart?.permissionMissing
|
|
6384
6440
|
});
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6441
|
+
const desktopReason = registry.unavailable.get("desktop");
|
|
6442
|
+
if (desktopReason)
|
|
6443
|
+
console.error(`[agent-tunnel] Computer Use is approved but unavailable: ${desktopReason}`);
|
|
6388
6444
|
if (options.service) {
|
|
6389
6445
|
process.stdout.write(`[agent-tunnel] service starting: ${config.tunnelId} -> ${config.apiUrl}
|
|
6390
6446
|
`);
|
|
@@ -6401,7 +6457,6 @@ function startAgent(config, options = {}) {
|
|
|
6401
6457
|
persistent: options.service === true,
|
|
6402
6458
|
reloadConfig: () => loadConfig({ apiUrl: config.apiUrl })
|
|
6403
6459
|
});
|
|
6404
|
-
agent.connect();
|
|
6405
6460
|
const shutdown = () => {
|
|
6406
6461
|
if (!options.service)
|
|
6407
6462
|
console.log(`
|
|
@@ -6411,6 +6466,7 @@ ${c.dim} Shutting down…${c.reset}`);
|
|
|
6411
6466
|
};
|
|
6412
6467
|
process.on("SIGTERM", shutdown);
|
|
6413
6468
|
process.on("SIGINT", shutdown);
|
|
6469
|
+
agent.connect();
|
|
6414
6470
|
}
|
|
6415
6471
|
function chooseBackgroundMode(flags) {
|
|
6416
6472
|
if (anyFlag(flags, BACKGROUND_FLAGS))
|
|
@@ -6521,12 +6577,7 @@ async function pairThisMachine(apiUrl, flags, lease) {
|
|
|
6521
6577
|
process.exit(1);
|
|
6522
6578
|
}
|
|
6523
6579
|
saveCredentials(outcome.tunnelId, outcome.token, apiUrl, outcome.capabilities);
|
|
6524
|
-
writeAccess(
|
|
6525
|
-
mode: existsSync7(desktopAppPath()) ? "ask" : "always",
|
|
6526
|
-
grantedUntil: null,
|
|
6527
|
-
deniedUntil: null,
|
|
6528
|
-
keepAwake: readAccess().keepAwake
|
|
6529
|
-
});
|
|
6580
|
+
writeAccess(accessAfterPairing(agentTunnelHome(), existsSync7(desktopAppPath())));
|
|
6530
6581
|
if (jsonMode) {
|
|
6531
6582
|
emit("approved", { tunnelId: outcome.tunnelId, capabilities: outcome.capabilities });
|
|
6532
6583
|
} else {
|
|
@@ -6633,12 +6684,14 @@ function commandStatus(flags) {
|
|
|
6633
6684
|
const service = getServiceStatus();
|
|
6634
6685
|
const paired = Boolean(config.token && config.tunnelId);
|
|
6635
6686
|
const approved = new Set(config.enabledCapabilities ?? []);
|
|
6687
|
+
const desktopReason = approved.has("desktop") ? desktopUnavailableReason() : null;
|
|
6636
6688
|
if (isTruthyFlag(flags.json)) {
|
|
6637
6689
|
console.log(JSON.stringify({
|
|
6638
6690
|
paired,
|
|
6639
6691
|
tunnelId: paired ? config.tunnelId : null,
|
|
6640
6692
|
apiUrl: config.apiUrl,
|
|
6641
6693
|
capabilities: [...approved],
|
|
6694
|
+
unavailableCapabilities: desktopReason ? { desktop: desktopReason } : {},
|
|
6642
6695
|
version: agentTunnelVersion(),
|
|
6643
6696
|
home: agentTunnelHome(),
|
|
6644
6697
|
serviceLabel: getServicePaths().label,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beeuni/agent-tunnel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Tunnel relay between cloud AI agents and local machines — server relay, local agent, client SDK, JSON-RPC, HMAC signing",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"dist/",
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "bun build src/agent/cli.ts --target=node --outfile=dist/agent-cli.js --banner='#!/usr/bin/env node' && bun build src/client/cli.ts --target=node --outfile=dist/client-cli.js --banner='#!/usr/bin/env node' && node -e \"const fs=require('node:fs'); fs.chmodSync('dist/agent-cli.js', 0o755); fs.chmodSync('dist/client-cli.js', 0o755);\"",
|
|
17
|
+
"prepack": "bun run build",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "bun test --timeout ${BEEUNI_TEST_TIMEOUT_MS:-15000}"
|
|
20
|
+
},
|
|
15
21
|
"dependencies": {
|
|
16
22
|
"hono": "^4.13.7",
|
|
17
23
|
"ws": "^8.18.0"
|