@skylandnpm/octopus-proxy 0.1.65 → 0.1.67

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.
@@ -4,15 +4,16 @@ import {
4
4
  PlatformDesktop,
5
5
  REMOTE_DEFAULTS,
6
6
  RemoteConfigSchema,
7
+ RemotePlatformRegistry,
7
8
  RemoteQualitySchema,
8
9
  SignalSchema,
9
10
  __commonJS,
10
11
  __toESM,
11
12
  assertDirectSignal,
12
- probeRemote,
13
+ nativeDependencies,
13
14
  remoteSessionBudget,
14
15
  z
15
- } from "./chunk-GS3Q6EU5.js";
16
+ } from "./chunk-OTKNRBZ7.js";
16
17
 
17
18
  // ../../node_modules/.pnpm/ajv@8.17.1/node_modules/ajv/dist/compile/codegen/code.js
18
19
  var require_code = __commonJS({
@@ -7812,8 +7813,8 @@ async function startDeviceControl(directory, devices, connections) {
7812
7813
  }
7813
7814
 
7814
7815
  // src/daemon.ts
7815
- import { mkdir as mkdir9 } from "node:fs/promises";
7816
- import { join as join15 } from "node:path";
7816
+ import { mkdir as mkdir10 } from "node:fs/promises";
7817
+ import { join as join16 } from "node:path";
7817
7818
 
7818
7819
  // src/workspace-diff.ts
7819
7820
  import { execFile as execFile2 } from "node:child_process";
@@ -13921,17 +13922,30 @@ var TerminalManager = class {
13921
13922
  import { execFile as execFile6 } from "node:child_process";
13922
13923
  import { promisify as promisify6 } from "node:util";
13923
13924
  import { userInfo as userInfo4 } from "node:os";
13925
+ import { lstat as lstat3 } from "node:fs/promises";
13924
13926
  var exec = promisify6(execFile6);
13925
13927
  var PlatformDesktopSession = class {
13926
- constructor(system = process.platform, environment = process.env, execute3 = async (args) => (await exec("loginctl", ["--no-ask-password", ...args], { timeout: 5e3, maxBuffer: 16384 })).stdout, account = userInfo4()) {
13928
+ constructor(x11Session = "logind", system = process.platform, environment = process.env, execute3 = async (args) => (await exec("loginctl", ["--no-ask-password", ...args], { timeout: 5e3, maxBuffer: 16384 })).stdout, account = userInfo4(), inspectSocket = lstat3) {
13929
+ this.x11Session = x11Session;
13927
13930
  this.system = system;
13928
13931
  this.environment = environment;
13929
13932
  this.execute = execute3;
13930
13933
  this.account = account;
13934
+ this.inspectSocket = inspectSocket;
13931
13935
  }
13932
13936
  async sessionIdentity() {
13933
13937
  if (this.system === "darwin") return String(this.account.uid);
13934
13938
  if (this.system !== "linux") throw new Error("\u6B64\u5E73\u53F0\u4E0D\u652F\u6301\u8FDC\u7A0B\u684C\u9762\u4F1A\u8BDD");
13939
+ if (this.x11Session === "standalone") {
13940
+ const display = /^(?:unix)?:([0-9]+)(?:\.([0-9]+))?$/.exec(this.environment.DISPLAY ?? "");
13941
+ if (!display || this.account.uid === 0 || this.environment.WAYLAND_DISPLAY || this.environment.XDG_SESSION_TYPE === "wayland") throw new Error("\u72EC\u7ACB X11 \u8FDC\u63A7\u9700\u8981\u975E root \u7528\u6237\u548C\u672C\u5730 DISPLAY\uFF08\u5982 :99\uFF09\uFF0C\u4E0D\u652F\u6301 Wayland \u6216\u8FDC\u7A0B X11");
13942
+ const path = `/tmp/.X11-unix/X${Number(display[1])}`;
13943
+ const socket = await this.inspectSocket(path).catch(() => {
13944
+ throw new Error(`\u72EC\u7ACB X11 socket \u4E0D\u53EF\u7528\uFF1A${path}\uFF1B\u8BF7\u5148\u542F\u52A8 Xvfb/\u684C\u9762\u670D\u52A1`);
13945
+ });
13946
+ if (!socket.isSocket()) throw new Error(`\u72EC\u7ACB X11 \u8DEF\u5F84\u4E0D\u662F socket\uFF1A${path}`);
13947
+ return `x11:${this.account.uid}:${Number(display[1])}.${Number(display[2] ?? 0)}:${socket.dev}:${socket.ino}:${socket.ctimeMs}`;
13948
+ }
13935
13949
  const ids = this.environment.XDG_SESSION_ID ? [this.environment.XDG_SESSION_ID] : (await this.execute(["list-sessions", "--no-legend"])).trim().split("\n").map((line) => line.trim().split(/\s+/)[0]).filter(Boolean);
13936
13950
  const sessions = [];
13937
13951
  for (const id of ids) {
@@ -13946,8 +13960,49 @@ var PlatformDesktopSession = class {
13946
13960
 
13947
13961
  // src/remote-manager.ts
13948
13962
  import { fork } from "node:child_process";
13949
- import { randomBytes as randomBytes2, randomUUID as randomUUID18 } from "node:crypto";
13963
+ import { randomBytes as randomBytes2, randomUUID as randomUUID19 } from "node:crypto";
13964
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
13965
+
13966
+ // src/remote-native.ts
13967
+ import { chmod as chmod5, mkdir as mkdir9, readFile as readFile10, rename as rename5, rm as rm3, stat as stat10, writeFile as writeFile7 } from "node:fs/promises";
13968
+ import { join as join14 } from "node:path";
13950
13969
  import { fileURLToPath as fileURLToPath4 } from "node:url";
13970
+ import { createHash as createHash10, randomUUID as randomUUID18 } from "node:crypto";
13971
+ async function ensureRemoteHelper(stateDirectory, adapter = RemotePlatformRegistry.standard.resolve()) {
13972
+ await mkdir9(stateDirectory, { recursive: true, mode: 448 });
13973
+ const build = adapter.helperBuild();
13974
+ const source = fileURLToPath4(new URL(`../native/${build.source}`, import.meta.url));
13975
+ const target = join14(stateDirectory, "octopus-remote-desktop");
13976
+ const stamp = `${target}.sha256`;
13977
+ const fingerprint = createHash10("sha256").update(await readFile10(source)).update(JSON.stringify([adapter.context.platform, adapter.context.arch, build.flags])).digest("hex");
13978
+ const existing = await stat10(target).catch(() => void 0);
13979
+ if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile10(stamp, "utf8").catch(() => "") === fingerprint) return target;
13980
+ const temporary = `${target}.${randomUUID18()}`, temporaryStamp = `${temporary}.sha256`;
13981
+ const args = build.args(source, temporary);
13982
+ try {
13983
+ await nativeDependencies.execute(build.compiler, args, { timeout: 6e4, maxBuffer: 16384 });
13984
+ await chmod5(temporary, 448);
13985
+ await writeFile7(temporaryStamp, fingerprint, { mode: 384 });
13986
+ await rename5(temporary, target);
13987
+ await rename5(temporaryStamp, stamp);
13988
+ return target;
13989
+ } catch {
13990
+ throw new Error(build.error);
13991
+ } finally {
13992
+ await Promise.all([rm3(temporary, { force: true }), rm3(temporaryStamp, { force: true })]);
13993
+ }
13994
+ }
13995
+ async function probeRemote(stateDirectory, config, adapter = RemotePlatformRegistry.standard.resolve()) {
13996
+ const platformError = adapter.platformError();
13997
+ if (platformError) throw new Error(platformError);
13998
+ await import("@roamhq/wrtc").catch(() => {
13999
+ throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
14000
+ });
14001
+ const helper = await ensureRemoteHelper(stateDirectory, adapter);
14002
+ const result = JSON.parse((await nativeDependencies.execute(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
14003
+ await adapter.checkDependencies(config);
14004
+ return { helper, ...result };
14005
+ }
13951
14006
 
13952
14007
  // src/remote-stun.ts
13953
14008
  import { Resolver } from "node:dns/promises";
@@ -14014,13 +14069,13 @@ async function discoverRemoteStunUrls(hubUrl, configured, resolve5 = resolvePubl
14014
14069
 
14015
14070
  // src/remote-manager.ts
14016
14071
  function spawnWorker(message) {
14017
- const worker = fileURLToPath4(new URL(import.meta.url.endsWith(".ts") ? "./remote-worker.ts" : "./remote-worker.js", import.meta.url));
14072
+ const worker = fileURLToPath5(new URL(import.meta.url.endsWith(".ts") ? "./remote-worker.ts" : "./remote-worker.js", import.meta.url));
14018
14073
  const child = fork(worker, [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: import.meta.url.endsWith(".ts") ? ["--import", "tsx"] : [] });
14019
14074
  child.send({ kind: "init", ...message });
14020
14075
  return child;
14021
14076
  }
14022
14077
  var RemoteManager = class {
14023
- constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls) {
14078
+ constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls, platformRegistry = RemotePlatformRegistry.standard) {
14024
14079
  this.host = host;
14025
14080
  this.desktop = desktop;
14026
14081
  this.desktopSession = desktopSession;
@@ -14029,22 +14084,25 @@ var RemoteManager = class {
14029
14084
  this.workerFactory = workerFactory;
14030
14085
  this.probe = probe;
14031
14086
  this.discoverStun = discoverStun;
14087
+ this.platform = platformRegistry.resolve();
14032
14088
  this.config = RemoteConfigSchema.parse(host.desktopRemote ?? {});
14033
14089
  }
14034
14090
  current;
14035
14091
  stopping = false;
14036
14092
  config;
14093
+ platform;
14037
14094
  stunUrls() {
14038
14095
  return this.config.stunUrls ?? defaultRemoteStunUrls(this.host.hubUrl);
14039
14096
  }
14040
14097
  async status() {
14041
- const base = { qualityModes: RemoteQualitySchema.options, platform: process.platform, passwordInput: process.platform === "darwin", enabled: this.config.enabled, busy: !!this.current, stunUrls: this.stunUrls(), budget: { ...REMOTE_DEFAULTS, totalBitrate: this.config.totalBitrate, videoMaxBitrate: this.config.videoMaxBitrate } };
14098
+ const base = { qualityModes: RemoteQualitySchema.options, platform: this.platform.context.platform, passwordInput: this.platform.passwordInput, enabled: this.config.enabled, busy: !!this.current, stunUrls: this.stunUrls(), budget: { ...REMOTE_DEFAULTS, totalBitrate: this.config.totalBitrate, videoMaxBitrate: this.config.videoMaxBitrate } };
14042
14099
  if (!this.config.enabled) return { ...base, available: false, message: "\u4E3B\u673A\u5DF2\u5173\u95ED\u8FDC\u7A0B\u63A7\u5236" };
14043
14100
  try {
14044
- const permissions = await this.probe(this.host.stateDirectory, this.config);
14045
- const lockUnsupported = permissions.locked === true && permissions.lockCapture === false;
14046
- const available = permissions.session && permissions.capture && permissions.input && !lockUnsupported;
14047
- return { ...base, available, permissions, ...!available ? { message: lockUnsupported ? "\u9501\u5C4F\u8FDC\u63A7\u9700\u8981 macOS 14 \u53CA\u4EE5\u4E0A\u53CA\u65B0\u7248 Xcode Command Line Tools\uFF1B\u5F53\u524D\u4E3B\u673A\u8BF7\u5148\u672C\u5730\u89E3\u9501" : "\u8BF7\u68C0\u67E5\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u5E76\u4E3A\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u7A0B\u5E8F\u6388\u6743\u5C4F\u5E55\u5F55\u5236\u548C\u8F85\u52A9\u529F\u80FD" } : {} };
14101
+ const permissions = await this.probe(this.host.stateDirectory, this.config, this.platform);
14102
+ const message = this.platform.unavailableReason(permissions);
14103
+ const available = message === void 0;
14104
+ if (available) await this.desktopSession.sessionIdentity();
14105
+ return { ...base, available, permissions, ...!available ? { message } : {} };
14048
14106
  } catch (error) {
14049
14107
  return { ...base, available: false, message: error instanceof Error ? error.message : "\u8FDC\u63A7\u4F9D\u8D56\u4E0D\u53EF\u7528" };
14050
14108
  }
@@ -14080,15 +14138,16 @@ var RemoteManager = class {
14080
14138
  if (this.current) throw new Error("\u4E3B\u673A\u6B63\u5728\u88AB\u63A7\u5236");
14081
14139
  const quality = RemoteQualitySchema.parse(payload.quality ?? "hd");
14082
14140
  const budget = remoteSessionBudget(this.config, quality, payload.quality === void 0);
14083
- const session = { remoteId: randomUUID18(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
14141
+ const session = { remoteId: randomUUID19(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
14084
14142
  this.current = session;
14085
14143
  try {
14086
14144
  const [permissions, stunUrls] = await Promise.all([
14087
- this.probe(this.host.stateDirectory, this.config),
14145
+ this.probe(this.host.stateDirectory, this.config, this.platform),
14088
14146
  this.discoverStun(this.host.hubUrl, this.config.stunUrls)
14089
14147
  ]);
14090
14148
  if (!permissions.session || !permissions.capture || !permissions.input) throw new Error("\u8FDC\u63A7\u9700\u8981\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\u3001\u5C4F\u5E55\u5F55\u5236\u53CA\u8F85\u52A9\u529F\u80FD\u6743\u9650");
14091
- if (permissions.locked === true && permissions.lockCapture === false) throw new Error("\u9501\u5C4F\u8FDC\u63A7\u9700\u8981 macOS 14 \u53CA\u4EE5\u4E0A\u53CA\u65B0\u7248 Xcode Command Line Tools\uFF1B\u5F53\u524D\u4E3B\u673A\u8BF7\u5148\u672C\u5730\u89E3\u9501");
14149
+ const unavailable = this.platform.unavailableReason(permissions);
14150
+ if (unavailable) throw new Error(unavailable);
14092
14151
  const identity = await this.desktopSession.sessionIdentity();
14093
14152
  const display = (await this.desktop.list()).find((d) => d.id === payload.displayId);
14094
14153
  if (!display) throw new Error("\u663E\u793A\u5668\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u5237\u65B0\u663E\u793A\u5668\u5217\u8868");
@@ -14159,16 +14218,16 @@ var RemoteManager = class {
14159
14218
  };
14160
14219
 
14161
14220
  // src/tool-selection.ts
14162
- import { randomBytes as randomBytes3, randomUUID as randomUUID19 } from "node:crypto";
14163
- import { access as access3, constants as constants6, readFile as readFile10 } from "node:fs/promises";
14221
+ import { randomBytes as randomBytes3, randomUUID as randomUUID20 } from "node:crypto";
14222
+ import { access as access3, constants as constants6, readFile as readFile11 } from "node:fs/promises";
14164
14223
  import { platform as platform5 } from "node:os";
14165
14224
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
14166
14225
 
14167
14226
  // src/node-resolution.ts
14168
- import { realpath as realpath7, stat as stat10 } from "node:fs/promises";
14227
+ import { realpath as realpath7, stat as stat11 } from "node:fs/promises";
14169
14228
  import { execFile as execFile7 } from "node:child_process";
14170
14229
  import { promisify as promisify7 } from "node:util";
14171
- import { join as join14 } from "node:path";
14230
+ import { join as join15 } from "node:path";
14172
14231
  import { homedir as homedir2, platform as platform4 } from "node:os";
14173
14232
  var run4 = promisify7(execFile7);
14174
14233
  var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
@@ -14212,15 +14271,15 @@ async function resolveNodePath(options = {}) {
14212
14271
  return { nodePath: null, source: "exec" };
14213
14272
  }
14214
14273
  function toolSearchDirs(kind) {
14215
- const dirs = [...describeTool(kind).searchDirs, join14(homedir2(), ".local", "bin"), join14(homedir2(), ".opencode", "bin"), join14(homedir2(), ".codex", "bin")];
14274
+ const dirs = [...describeTool(kind).searchDirs, join15(homedir2(), ".local", "bin"), join15(homedir2(), ".opencode", "bin"), join15(homedir2(), ".codex", "bin")];
14216
14275
  return [...new Set(dirs)];
14217
14276
  }
14218
14277
  async function resolveCommandOnPath(name, extraDirs = []) {
14219
14278
  const dirs = [...(process.env.PATH ?? "").split(":"), ...extraDirs].filter(Boolean);
14220
14279
  for (const dir of new Set(dirs)) {
14221
- const full = join14(dir, name);
14280
+ const full = join15(dir, name);
14222
14281
  try {
14223
- const info = await stat10(full);
14282
+ const info = await stat11(full);
14224
14283
  if (info.isFile() && info.mode & 73) return full;
14225
14284
  } catch {
14226
14285
  }
@@ -14303,7 +14362,7 @@ var ToolSelection = class {
14303
14362
  });
14304
14363
  }
14305
14364
  modelCommand(projectId) {
14306
- return { type: "command", id: randomUUID19(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
14365
+ return { type: "command", id: randomUUID20(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
14307
14366
  }
14308
14367
  profiles(raw) {
14309
14368
  return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
@@ -14345,7 +14404,7 @@ var ToolSelection = class {
14345
14404
  if (!isDeepStrictEqual2(JSON.parse(JSON.stringify(disk.tool)), JSON.parse(JSON.stringify(this.config.tool))) || !isDeepStrictEqual2(disk.projects, this.config.projects) || disk.botId !== this.config.botId || disk.secretKey !== this.config.secretKey) {
14346
14405
  throw new Error("Proxy \u914D\u7F6E\u5DF2\u5728\u4E3B\u673A\u4E0A\u88AB\u4FEE\u6539\uFF1B\u8BF7\u91CD\u542F Proxy \u540E\u518D\u5207\u6362\u667A\u80FD\u4F53");
14347
14406
  }
14348
- return JSON.parse(await readFile10(this.configPath, "utf8"));
14407
+ return JSON.parse(await readFile11(this.configPath, "utf8"));
14349
14408
  }
14350
14409
  async release(instance) {
14351
14410
  instance.adapter.close();
@@ -14362,14 +14421,14 @@ var ToolSelection = class {
14362
14421
  // src/daemon.ts
14363
14422
  async function runDaemon(configPath, authenticate = verifyHostPassword) {
14364
14423
  const config = await loadConfig(configPath);
14365
- await mkdir9(config.stateDirectory, { recursive: true, mode: 448 });
14366
- const workspace = new Workspace(config.projects), uploads = new Uploads(workspace, join15(config.stateDirectory, "uploads")), journal = new Journal(join15(config.stateDirectory, "journal"));
14424
+ await mkdir10(config.stateDirectory, { recursive: true, mode: 448 });
14425
+ const workspace = new Workspace(config.projects), uploads = new Uploads(workspace, join16(config.stateDirectory, "uploads")), journal = new Journal(join16(config.stateDirectory, "journal"));
14367
14426
  let secureChat;
14368
14427
  function emit(projectId, event, data) {
14369
14428
  secureChat?.publish(projectId, event, data);
14370
14429
  }
14371
14430
  const terminals = new TerminalManager(workspace, (owner, project, event, data) => secureChat?.sendTo(owner, project, event, data));
14372
- const desktopBackend = new PlatformDesktop(config.stateDirectory), desktopSession = new PlatformDesktopSession();
14431
+ const desktopBackend = new PlatformDesktop(config.stateDirectory), desktopSession = new PlatformDesktopSession(config.desktopRemote?.x11Session);
14373
14432
  const desktop = new DesktopManager(desktopBackend);
14374
14433
  const remote = new RemoteManager(
14375
14434
  config,
@@ -14385,7 +14444,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
14385
14444
  });
14386
14445
  return { runtime, adapter };
14387
14446
  });
14388
- const artifacts = new Artifacts(workspace, join15(config.stateDirectory, "artifacts"), emit);
14447
+ const artifacts = new Artifacts(workspace, join16(config.stateDirectory, "artifacts"), emit);
14389
14448
  const projectRegistry = () => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter, (resetProjects) => {
14390
14449
  for (const id of resetProjects) terminals.closeProject(id);
14391
14450
  });
@@ -4104,7 +4104,8 @@ var RemoteConfigSchema = z.object({
4104
4104
  stunUrls: z.array(stunUrl).min(1).max(4).optional(),
4105
4105
  totalBitrate: z.number().int().min(1e5).max(REMOTE_DEFAULTS.totalBitrate).default(REMOTE_DEFAULTS.totalBitrate),
4106
4106
  videoMaxBitrate: z.number().int().min(5e4).max(REMOTE_DEFAULTS.videoMaxBitrate).default(REMOTE_DEFAULTS.videoMaxBitrate),
4107
- ffmpeg: z.string().min(1).default("ffmpeg")
4107
+ ffmpeg: z.string().min(1).default("ffmpeg"),
4108
+ x11Session: z.enum(["logind", "standalone"]).default("logind")
4108
4109
  }).strict().refine((c) => c.videoMaxBitrate <= c.totalBitrate * 0.8, "\u89C6\u9891\u989D\u5EA6\u4E0D\u5F97\u8D85\u8FC7\u603B\u9884\u7B97\u7684 80%");
4109
4110
  function remoteSessionBudget(config, quality, legacy = false) {
4110
4111
  const profile = legacy ? REMOTE_LEGACY_PROFILE : REMOTE_QUALITY_PROFILES[quality];
@@ -4448,61 +4449,16 @@ var DesktopManager = class {
4448
4449
  }
4449
4450
  };
4450
4451
 
4451
- // src/remote-native.ts
4452
+ // src/remote/native-process.ts
4452
4453
  import { execFile as execFile2, spawn } from "node:child_process";
4453
4454
  import { promisify as promisify2 } from "node:util";
4454
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
4455
- import { join as join2 } from "node:path";
4456
- import { fileURLToPath as fileURLToPath2 } from "node:url";
4457
- import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
4458
4455
  var exec = promisify2(execFile2);
4459
- function supportedRemotePlatform(system = process.platform, arch = process.arch, env = process.env) {
4460
- return system === "darwin" && ["arm64", "x64"].includes(arch) || system === "linux" && arch === "x64" && !!env.DISPLAY && !env.WAYLAND_DISPLAY && env.XDG_SESSION_TYPE !== "wayland";
4461
- }
4462
- async function ensureRemoteHelper(stateDirectory) {
4463
- await mkdir2(stateDirectory, { recursive: true, mode: 448 });
4464
- const mac = process.platform === "darwin";
4465
- const source = fileURLToPath2(new URL(mac ? "../native/remote-macos.m" : "../native/remote-x11.c", import.meta.url));
4466
- const target = join2(stateDirectory, "octopus-remote-desktop");
4467
- const stamp = `${target}.sha256`;
4468
- const flags = mac ? ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon", "-framework", "IOKit", "-framework", "VideoToolbox"] : ["-O2", "-lX11", "-lXtst"];
4469
- const fingerprint = createHash2("sha256").update(await readFile2(source)).update(JSON.stringify([process.platform, process.arch, flags])).digest("hex");
4470
- const existing = await stat2(target).catch(() => void 0);
4471
- if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile2(stamp, "utf8").catch(() => "") === fingerprint) return target;
4472
- const temporary = `${target}.${randomUUID2()}`, temporaryStamp = `${temporary}.sha256`;
4473
- const args = mac ? [...flags, source, "-o", temporary] : ["-O2", source, "-o", temporary, "-lX11", "-lXtst"];
4474
- try {
4475
- await exec(mac ? "/usr/bin/clang" : "cc", args, { timeout: 6e4, maxBuffer: 16384 });
4476
- await chmod2(temporary, 448);
4477
- await writeFile2(temporaryStamp, fingerprint, { mode: 384 });
4478
- await rename2(temporary, target);
4479
- await rename2(temporaryStamp, stamp);
4480
- return target;
4481
- } catch {
4482
- throw new Error(mac ? "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 Xcode Command Line Tools" : "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 cc\u3001libx11-dev\u3001libxtst-dev");
4483
- } finally {
4484
- await Promise.all([rm2(temporary, { force: true }), rm2(temporaryStamp, { force: true })]);
4485
- }
4486
- }
4487
- async function probeRemote(stateDirectory, config) {
4488
- if (!supportedRemotePlatform()) throw new Error("\u8FDC\u63A7\u9996\u7248\u652F\u6301 macOS 12.3+ \u4E0E Linux X11 x64\uFF1BWayland\u3001\u65E0\u56FE\u5F62\u4F1A\u8BDD\u6682\u4E0D\u652F\u6301");
4489
- await import("@roamhq/wrtc").catch(() => {
4490
- throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
4491
- });
4492
- const helper = await ensureRemoteHelper(stateDirectory);
4493
- const result = JSON.parse((await exec(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
4494
- if (process.platform === "linux") {
4495
- const version = await exec(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
4496
- throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
4497
- });
4498
- assertLgplFFmpeg(version.stdout);
4499
- const devices = await exec(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
4500
- if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
4501
- }
4502
- return { helper, ...result };
4503
- }
4504
- function nativeProcess(helper, displayId, video) {
4505
- return spawn(helper, ["serve", displayId, String(video.width), String(video.height), String(video.fps)], { stdio: ["pipe", "pipe", "pipe", "pipe"] });
4456
+ var nativeDependencies = {
4457
+ execute: async (file, args, options) => ({ stdout: String((await exec(file, args, options)).stdout) }),
4458
+ spawn
4459
+ };
4460
+ function nativeProcess(helper, displayId, video, dependencies = nativeDependencies) {
4461
+ return dependencies.spawn(helper, ["serve", displayId, String(video.width), String(video.height), String(video.fps)], { stdio: ["pipe", "pipe", "pipe", "pipe"] });
4506
4462
  }
4507
4463
  function terminateNative(child) {
4508
4464
  if (!child) return;
@@ -4513,6 +4469,307 @@ function terminateNative(child) {
4513
4469
  child.once("exit", () => clearTimeout(timer));
4514
4470
  }
4515
4471
 
4472
+ // src/remote/native-session.ts
4473
+ var NativeSession = class {
4474
+ constructor(options, dependencies = nativeDependencies) {
4475
+ this.options = options;
4476
+ this.dependencies = dependencies;
4477
+ this.video = options.video;
4478
+ }
4479
+ helper;
4480
+ stopped = false;
4481
+ video;
4482
+ starting;
4483
+ start() {
4484
+ return this.starting ??= this.startOnce().catch((error) => {
4485
+ this.stop();
4486
+ throw error;
4487
+ });
4488
+ }
4489
+ async startOnce() {
4490
+ if (this.stopped) return;
4491
+ await this.prepare();
4492
+ if (this.stopped) return;
4493
+ const child = nativeProcess(this.options.helper, this.options.display.id, this.video, this.dependencies);
4494
+ this.helper = child;
4495
+ child.on("error", () => this.fail("\u4E3B\u673A\u8F93\u5165\u8F85\u52A9\u7A0B\u5E8F\u4E0D\u53EF\u7528"));
4496
+ child.on("exit", () => this.fail("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316"));
4497
+ child.stdin?.on("error", () => this.fail("\u4E3B\u673A\u8F93\u5165\u8FDE\u63A5\u5DF2\u5173\u95ED"));
4498
+ let lines = "";
4499
+ child.stdout?.on("data", (chunk) => {
4500
+ if (this.stopped) return;
4501
+ lines += String(chunk);
4502
+ if (lines.length > 15e5) {
4503
+ this.fail("\u4E3B\u673A\u526A\u8D34\u677F\u8D85\u8FC7\u4E0A\u9650");
4504
+ return;
4505
+ }
4506
+ let end;
4507
+ while (!this.stopped && (end = lines.indexOf("\n")) >= 0) {
4508
+ const line = lines.slice(0, end);
4509
+ lines = lines.slice(end + 1);
4510
+ try {
4511
+ const message = JSON.parse(line);
4512
+ if (message.kind === "error") {
4513
+ this.fail("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316");
4514
+ return;
4515
+ }
4516
+ this.options.onMessage(message);
4517
+ } catch {
4518
+ this.fail("\u4E3B\u673A\u8F85\u52A9\u7A0B\u5E8F\u8FD4\u56DE\u65E0\u6548\u6570\u636E");
4519
+ }
4520
+ }
4521
+ });
4522
+ this.startCapture(child);
4523
+ }
4524
+ async prepare() {
4525
+ }
4526
+ stopCapture() {
4527
+ }
4528
+ fail(message) {
4529
+ if (this.stopped) return;
4530
+ this.stop();
4531
+ this.options.onError(message);
4532
+ }
4533
+ writeCommand(line) {
4534
+ if (this.stopped || !this.helper?.stdin || this.helper.stdin.destroyed) return;
4535
+ if (this.helper.stdin.writableLength > 2 * 1024 * 1024) {
4536
+ this.fail("\u4E3B\u673A\u8F93\u5165\u5904\u7406\u8D85\u65F6");
4537
+ return;
4538
+ }
4539
+ this.helper.stdin.write(`${line}
4540
+ `);
4541
+ }
4542
+ input(input) {
4543
+ switch (input.kind) {
4544
+ case "pointer":
4545
+ this.writeCommand(`pointer ${input.x} ${input.y}`);
4546
+ break;
4547
+ case "button":
4548
+ this.writeCommand(`button ${input.x} ${input.y} ${input.button} ${input.down ? 1 : 0}`);
4549
+ break;
4550
+ case "wheel":
4551
+ this.writeCommand(`wheel ${input.x} ${input.y} ${Math.round(input.dx)} ${Math.round(input.dy)}`);
4552
+ break;
4553
+ case "key":
4554
+ this.writeCommand(`key ${input.code} ${input.down ? 1 : 0}`);
4555
+ break;
4556
+ case "text":
4557
+ this.writeCommand(`text ${input.terminal ? 1 : 0} ${Buffer.from(input.text).toString("base64")}`);
4558
+ break;
4559
+ case "release":
4560
+ this.writeCommand("release");
4561
+ break;
4562
+ }
4563
+ }
4564
+ stop() {
4565
+ if (this.stopped) return;
4566
+ this.stopped = true;
4567
+ this.stopCapture();
4568
+ const helper = this.helper;
4569
+ this.helper = void 0;
4570
+ terminateNative(helper);
4571
+ }
4572
+ };
4573
+
4574
+ // src/remote/platform-adapter.ts
4575
+ function unsupportedPlatform(context) {
4576
+ return `\u8FDC\u63A7\u4E0D\u652F\u6301\u5F53\u524D\u5E73\u53F0 ${context.platform}/${context.arch}\uFF1B\u9700\u8981 macOS 12.3+ \u6216 Linux X11\uFF08x64/ARM64\uFF09`;
4577
+ }
4578
+ function missingPermissions(permissions) {
4579
+ if (!permissions.session || !permissions.capture || !permissions.input) return "\u8BF7\u68C0\u67E5\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u5E76\u4E3A\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u7A0B\u5E8F\u6388\u6743\u5C4F\u5E55\u5F55\u5236\u548C\u8F85\u52A9\u529F\u80FD";
4580
+ }
4581
+
4582
+ // src/remote/linux-x11-adapter.ts
4583
+ var LinuxX11RemoteAdapter = class {
4584
+ constructor(context, dependencies = nativeDependencies) {
4585
+ this.context = context;
4586
+ this.dependencies = dependencies;
4587
+ }
4588
+ passwordInput = false;
4589
+ platformError() {
4590
+ if (!["arm64", "x64"].includes(this.context.arch)) return unsupportedPlatform(this.context);
4591
+ const env = this.context.env;
4592
+ if (env.WAYLAND_DISPLAY || env.XDG_SESSION_TYPE === "wayland") return "\u8FDC\u63A7\u6682\u4E0D\u652F\u6301 Wayland\uFF0C\u8BF7\u4F7F\u7528 X11 \u684C\u9762\u4F1A\u8BDD";
4593
+ if (!env.DISPLAY) return "Proxy \u672A\u8FDE\u63A5 X11 \u56FE\u5F62\u4F1A\u8BDD\uFF1B\u8BF7\u4E3A\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u670D\u52A1\u8BBE\u7F6E\u684C\u9762\u7528\u6237\u7684 DISPLAY \u548C XAUTHORITY\uFF0C\u5BB9\u5668\u4E2D\u8BF7\u5148\u542F\u52A8 Xvfb/\u684C\u9762\u670D\u52A1";
4594
+ }
4595
+ helperBuild() {
4596
+ return { source: "remote-x11.c", compiler: "cc", flags: ["-O2", "-lX11", "-lXtst"], args: (source, target) => ["-O2", source, "-o", target, "-lX11", "-lXtst"], error: "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 cc\u3001libx11-dev\u3001libxtst-dev" };
4597
+ }
4598
+ async checkDependencies(config) {
4599
+ const execute = this.dependencies.execute;
4600
+ const version = await execute(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
4601
+ throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
4602
+ });
4603
+ assertLgplFFmpeg(version.stdout);
4604
+ const devices = await execute(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
4605
+ if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
4606
+ }
4607
+ unavailableReason = missingPermissions;
4608
+ createSession(options) {
4609
+ return new LinuxX11RemoteSession(options, this.context, this.dependencies);
4610
+ }
4611
+ };
4612
+ var LinuxX11RemoteSession = class extends NativeSession {
4613
+ constructor(options, context, dependencies) {
4614
+ super(options, dependencies);
4615
+ this.context = context;
4616
+ }
4617
+ capture;
4618
+ geometry;
4619
+ async prepare() {
4620
+ const displays = parseXrandr((await this.dependencies.execute("xrandr", ["--listactivemonitors"], { timeout: 5e3 })).stdout);
4621
+ if (this.stopped) return;
4622
+ const display = displays.find((d) => d.id === this.options.display.id);
4623
+ if (!display || display.width !== this.options.display.width || display.height !== this.options.display.height) throw new Error("display");
4624
+ this.geometry = { x: display.x, y: display.y, width: display.width, height: display.height };
4625
+ }
4626
+ startCapture(_child) {
4627
+ const g = this.geometry;
4628
+ this.writeCommand(`geometry ${g.x} ${g.y} ${g.width} ${g.height}`);
4629
+ this.captureFrames();
4630
+ }
4631
+ captureFrames() {
4632
+ this.stopCapture();
4633
+ if (this.stopped) return;
4634
+ const g = this.geometry, { width, height, fps } = this.video;
4635
+ const child = this.dependencies.spawn(this.options.config.ffmpeg, ["-nostdin", "-hide_banner", "-loglevel", "error", "-f", "x11grab", "-framerate", String(fps), "-video_size", `${g.width}x${g.height}`, "-grab_x", String(g.x), "-grab_y", String(g.y), "-draw_mouse", "1", "-i", this.context.env.DISPLAY, "-vf", `scale=${width}:${height}`, "-pix_fmt", "yuv420p", "-f", "rawvideo", "pipe:1"], { stdio: ["ignore", "pipe", "ignore"], env: this.context.env });
4636
+ this.capture = child;
4637
+ const current = () => !this.stopped && this.capture === child;
4638
+ let buffer = Buffer.alloc(0);
4639
+ const length = width * height * 1.5;
4640
+ child.stdout.on("data", (chunk) => {
4641
+ if (!current()) return;
4642
+ buffer = Buffer.concat([buffer, chunk]);
4643
+ if (buffer.length > length * 4) {
4644
+ this.fail("\u4E3B\u673A\u91C7\u96C6\u7F13\u51B2\u533A\u8D85\u9650");
4645
+ return;
4646
+ }
4647
+ if (buffer.length >= length) {
4648
+ const count = Math.floor(buffer.length / length), frame = buffer.subarray((count - 1) * length, count * length);
4649
+ buffer = buffer.subarray(count * length);
4650
+ this.options.onFrame({ width, height, data: new Uint8Array(frame) });
4651
+ }
4652
+ });
4653
+ child.on("error", () => {
4654
+ if (current()) this.fail("FFmpeg \u91C7\u96C6\u5931\u8D25");
4655
+ });
4656
+ child.on("exit", () => {
4657
+ if (current()) this.fail("\u663E\u793A\u5668\u5DF2\u53D8\u5316\u6216 FFmpeg \u91C7\u96C6\u5931\u8D25");
4658
+ });
4659
+ }
4660
+ setVideo(video) {
4661
+ this.video = video;
4662
+ if (this.helper && !this.stopped) this.captureFrames();
4663
+ }
4664
+ stopCapture() {
4665
+ const child = this.capture;
4666
+ this.capture = void 0;
4667
+ terminateNative(child);
4668
+ }
4669
+ };
4670
+
4671
+ // src/remote/macos-adapter.ts
4672
+ var MAC_LOCK_CAPTURE_ERROR = "\u9501\u5C4F\u8FDC\u63A7\u9700\u8981 macOS 14 \u53CA\u4EE5\u4E0A\u53CA\u65B0\u7248 Xcode Command Line Tools\uFF1B\u5F53\u524D\u4E3B\u673A\u8BF7\u5148\u672C\u5730\u89E3\u9501";
4673
+ var MacOSRemoteAdapter = class {
4674
+ constructor(context, dependencies = nativeDependencies) {
4675
+ this.context = context;
4676
+ this.dependencies = dependencies;
4677
+ }
4678
+ passwordInput = true;
4679
+ platformError() {
4680
+ if (!["arm64", "x64"].includes(this.context.arch)) return unsupportedPlatform(this.context);
4681
+ }
4682
+ helperBuild() {
4683
+ const flags = ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon", "-framework", "IOKit", "-framework", "VideoToolbox"];
4684
+ return { source: "remote-macos.m", compiler: "/usr/bin/clang", flags, args: (source, target) => [...flags, source, "-o", target], error: "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 Xcode Command Line Tools" };
4685
+ }
4686
+ async checkDependencies(_config) {
4687
+ }
4688
+ unavailableReason(permissions) {
4689
+ if (permissions.locked === true && permissions.lockCapture === false) return MAC_LOCK_CAPTURE_ERROR;
4690
+ return missingPermissions(permissions);
4691
+ }
4692
+ createSession(options) {
4693
+ return new MacOSRemoteSession(options, this.dependencies);
4694
+ }
4695
+ };
4696
+ var MacOSRemoteSession = class extends NativeSession {
4697
+ startCapture(child) {
4698
+ const stream = child.stdio[3];
4699
+ let buffer = Buffer.alloc(0);
4700
+ stream.on("data", (chunk) => {
4701
+ if (this.stopped) return;
4702
+ buffer = Buffer.concat([buffer, chunk]);
4703
+ if (buffer.length > REMOTE_DEFAULTS.maxWidth * REMOTE_DEFAULTS.maxHeight * 3 + 24) {
4704
+ this.fail("\u8FDC\u63A7\u753B\u9762\u7F13\u51B2\u533A\u8D85\u9650");
4705
+ return;
4706
+ }
4707
+ let latest;
4708
+ while (buffer.length >= 12) {
4709
+ const width = buffer.readUInt32BE(0), height = buffer.readUInt32BE(4), length = buffer.readUInt32BE(8);
4710
+ if (!width || !height || width > REMOTE_DEFAULTS.maxWidth || height > REMOTE_DEFAULTS.maxHeight || width % 2 || height % 2 || length !== width * height * 1.5) {
4711
+ this.fail("\u4E3B\u673A\u753B\u9762\u683C\u5F0F\u65E0\u6548");
4712
+ return;
4713
+ }
4714
+ if (buffer.length < length + 12) break;
4715
+ latest = { width, height, data: new Uint8Array(buffer.subarray(12, 12 + length)) };
4716
+ buffer = buffer.subarray(12 + length);
4717
+ }
4718
+ if (latest) this.options.onFrame(latest);
4719
+ });
4720
+ }
4721
+ setVideo(video) {
4722
+ this.video = video;
4723
+ this.writeCommand(`video ${video.width} ${video.height} ${video.fps}`);
4724
+ }
4725
+ input(input) {
4726
+ if (input.kind === "password") this.writeCommand(`password ${Buffer.from(input.text).toString("base64")}`);
4727
+ else super.input(input);
4728
+ }
4729
+ };
4730
+
4731
+ // src/remote/unsupported-adapter.ts
4732
+ var UnsupportedRemoteAdapter = class {
4733
+ constructor(context) {
4734
+ this.context = context;
4735
+ }
4736
+ passwordInput = false;
4737
+ platformError() {
4738
+ return unsupportedPlatform(this.context);
4739
+ }
4740
+ helperBuild() {
4741
+ throw new Error(this.platformError());
4742
+ }
4743
+ async checkDependencies(_config) {
4744
+ throw new Error(this.platformError());
4745
+ }
4746
+ unavailableReason(_permissions) {
4747
+ return this.platformError();
4748
+ }
4749
+ createSession(_options) {
4750
+ throw new Error(this.platformError());
4751
+ }
4752
+ };
4753
+
4754
+ // src/remote/platform-registry.ts
4755
+ var RemotePlatformRegistry = class _RemotePlatformRegistry {
4756
+ static standard = new _RemotePlatformRegistry({
4757
+ darwin: (context) => new MacOSRemoteAdapter(context),
4758
+ linux: (context) => new LinuxX11RemoteAdapter(context)
4759
+ });
4760
+ factories;
4761
+ constructor(factories) {
4762
+ this.factories = Object.freeze({ ...factories });
4763
+ }
4764
+ withAdapter(platform, factory) {
4765
+ return new _RemotePlatformRegistry({ ...this.factories, [platform]: factory });
4766
+ }
4767
+ resolve(context = { platform: process.platform, arch: process.arch, env: process.env }) {
4768
+ const factory = Object.hasOwn(this.factories, context.platform) ? this.factories[context.platform] : void 0;
4769
+ return factory?.(context) ?? new UnsupportedRemoteAdapter(context);
4770
+ }
4771
+ };
4772
+
4516
4773
  export {
4517
4774
  __commonJS,
4518
4775
  __toESM,
@@ -4533,10 +4790,8 @@ export {
4533
4790
  DESKTOP_PERMISSION_GUIDANCE,
4534
4791
  requestDesktopPermission,
4535
4792
  checkDesktop,
4536
- parseXrandr,
4537
4793
  PlatformDesktop,
4538
4794
  DesktopManager,
4539
- probeRemote,
4540
- nativeProcess,
4541
- terminateNative
4795
+ nativeDependencies,
4796
+ RemotePlatformRegistry
4542
4797
  };
package/dist/cli.js CHANGED
@@ -35,13 +35,13 @@ import {
35
35
  supportedPlatform,
36
36
  systemdUnitPath,
37
37
  toolSearchDirs
38
- } from "./chunk-6RJKEA3I.js";
38
+ } from "./chunk-4CKW4ICE.js";
39
39
  import {
40
40
  DESKTOP_PERMISSION_GUIDANCE,
41
41
  PlatformDesktop,
42
42
  checkDesktop,
43
43
  requestDesktopPermission
44
- } from "./chunk-GS3Q6EU5.js";
44
+ } from "./chunk-OTKNRBZ7.js";
45
45
 
46
46
  // src/cli.ts
47
47
  import { spawn as spawn2 } from "node:child_process";
@@ -364,7 +364,7 @@ async function runDoctor(opts) {
364
364
  }
365
365
  if (config) {
366
366
  results.push(...await checkDesktop(config.stateDirectory));
367
- const remote = new RemoteManager(config, new PlatformDesktop(config.stateDirectory), new PlatformDesktopSession(), () => {
367
+ const remote = new RemoteManager(config, new PlatformDesktop(config.stateDirectory), new PlatformDesktopSession(config.desktopRemote?.x11Session), () => {
368
368
  }, () => false);
369
369
  try {
370
370
  const status = await remote.status();
package/dist/main.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-6RJKEA3I.js";
6
- import "./chunk-GS3Q6EU5.js";
5
+ } from "./chunk-4CKW4ICE.js";
6
+ import "./chunk-OTKNRBZ7.js";
7
7
 
8
8
  // src/main.ts
9
9
  runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
@@ -1,25 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  InputSchema,
4
- REMOTE_DEFAULTS,
5
4
  REMOTE_LEGACY_PROFILE,
6
5
  REMOTE_QUALITY_PROFILES,
7
6
  RemoteInputOrder,
7
+ RemotePlatformRegistry,
8
8
  SignalSchema,
9
9
  assertDirectSignal,
10
10
  capVideoSdp,
11
11
  fitVideo,
12
- nativeProcess,
13
- parseXrandr,
14
12
  remoteResolutionTiers,
15
- remoteSessionBudget,
16
- terminateNative
17
- } from "./chunk-GS3Q6EU5.js";
13
+ remoteSessionBudget
14
+ } from "./chunk-OTKNRBZ7.js";
18
15
 
19
16
  // src/remote-worker.ts
20
17
  import rtc from "@roamhq/wrtc";
21
- import { spawn, execFile } from "node:child_process";
22
- import { promisify } from "node:util";
23
18
  import { timingSafeEqual } from "node:crypto";
24
19
 
25
20
  // src/remote-budget.ts
@@ -158,7 +153,6 @@ var RemoteLiveness = class {
158
153
  };
159
154
 
160
155
  // src/remote-worker.ts
161
- var exec = promisify(execFile);
162
156
  var post = (kind, data = {}) => {
163
157
  if (process.connected) process.send?.({ kind, ...data });
164
158
  };
@@ -207,8 +201,7 @@ var RemotePeer = class {
207
201
  source = new rtc.nonstandard.RTCVideoSource({ isScreencast: true });
208
202
  track = this.source.createTrack();
209
203
  sender;
210
- helper;
211
- capture;
204
+ native;
212
205
  channels = /* @__PURE__ */ new Map();
213
206
  ready = false;
214
207
  ended = false;
@@ -239,7 +232,6 @@ var RemotePeer = class {
239
232
  legacy;
240
233
  quality;
241
234
  budget;
242
- geometry;
243
235
  video;
244
236
  signal(raw) {
245
237
  let stage = "validation";
@@ -379,155 +371,54 @@ var RemotePeer = class {
379
371
  if (channel?.readyState === "open") channel.send(JSON.stringify({ ...value, remoteId: this.init.remoteId, revision: 1 }));
380
372
  }
381
373
  async startNative() {
382
- if (process.platform === "linux") {
383
- const displays = parseXrandr((await exec("xrandr", ["--listactivemonitors"], { timeout: 5e3 })).stdout);
384
- if (this.ended) return;
385
- const display = displays.find((d) => d.id === this.init.display.id);
386
- if (!display || display.width !== this.init.display.width || display.height !== this.init.display.height) throw new Error("display");
387
- this.geometry = { x: display.x, y: display.y, width: display.width, height: display.height };
388
- }
389
374
  if (this.ended) return;
390
- const child = nativeProcess(this.init.helper, this.init.display.id, this.video);
391
- this.helper = child;
392
- child.on("error", () => this.stop("\u4E3B\u673A\u8F93\u5165\u8F85\u52A9\u7A0B\u5E8F\u4E0D\u53EF\u7528"));
393
- child.on("exit", () => {
394
- if (this.helper === child) this.stop("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316");
395
- });
396
- child.stdin?.on("error", () => this.stop("\u4E3B\u673A\u8F93\u5165\u8FDE\u63A5\u5DF2\u5173\u95ED"));
397
- let lines = "";
398
- child.stdout?.on("data", (chunk) => {
399
- lines += String(chunk);
400
- if (lines.length > 15e5) {
401
- this.stop("\u4E3B\u673A\u526A\u8D34\u677F\u8D85\u8FC7\u4E0A\u9650");
402
- return;
403
- }
404
- let end;
405
- while ((end = lines.indexOf("\n")) >= 0) {
406
- const line = lines.slice(0, end);
407
- lines = lines.slice(end + 1);
408
- try {
409
- const message = JSON.parse(line);
410
- if (message.kind === "clipboard" && typeof message.id === "string" && typeof message.data === "string") {
411
- const callback = this.nativePending.get(message.id);
412
- this.nativePending.delete(message.id);
413
- callback?.(Buffer.from(message.data, "base64").toString("utf8"));
414
- }
415
- if (message.kind === "clipboard-written" && this.pendingClipboard?.id === message.id) {
416
- this.pendingClipboard = void 0;
417
- this.send("clipboard", { kind: "clipboard.done", id: message.id });
418
- }
419
- if (message.kind === "clipboard-error") {
420
- if (typeof message.id === "string") this.nativePending.delete(message.id);
421
- this.pendingClipboard = void 0;
422
- this.send("clipboard", { kind: "clipboard.error", id: message.id, message: "\u4E3B\u673A\u526A\u8D34\u677F\u8BFB\u53D6\u5931\u8D25\u6216\u8D85\u8FC7 1 MiB" });
423
- }
424
- if (message.kind === "input-error") this.send("control", { kind: "input.error", message: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\u5BC6\u7801\uFF1B\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u754C\u9762" });
425
- if (message.kind === "error") this.stop("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316");
426
- } catch {
427
- this.stop("\u4E3B\u673A\u8F85\u52A9\u7A0B\u5E8F\u8FD4\u56DE\u65E0\u6548\u6570\u636E");
428
- }
429
- }
430
- });
431
- if (process.platform === "darwin") this.macFrames(child.stdio[3]);
432
- else {
433
- const g = this.geometry;
434
- this.writeNative(`geometry ${g.x} ${g.y} ${g.width} ${g.height}`);
435
- this.linuxFrames();
436
- }
437
- }
438
- macFrames(stream) {
439
- let buffer = Buffer.alloc(0);
440
- stream.on("data", (chunk) => {
441
- if (this.ended) return;
442
- buffer = Buffer.concat([buffer, chunk]);
443
- if (buffer.length > REMOTE_DEFAULTS.maxWidth * REMOTE_DEFAULTS.maxHeight * 3 + 24) {
444
- this.stop("\u8FDC\u63A7\u753B\u9762\u7F13\u51B2\u533A\u8D85\u9650");
445
- return;
446
- }
447
- let latest;
448
- while (buffer.length >= 12) {
449
- const width = buffer.readUInt32BE(0), height = buffer.readUInt32BE(4), length = buffer.readUInt32BE(8);
450
- if (!width || !height || width > REMOTE_DEFAULTS.maxWidth || height > REMOTE_DEFAULTS.maxHeight || width % 2 || height % 2 || length !== width * height * 1.5) {
451
- this.stop("\u4E3B\u673A\u753B\u9762\u683C\u5F0F\u65E0\u6548");
452
- return;
453
- }
454
- if (buffer.length < length + 12) break;
455
- latest = { width, height, data: new Uint8Array(buffer.subarray(12, 12 + length)) };
456
- buffer = buffer.subarray(12 + length);
457
- }
458
- if (latest && this.direct) {
459
- this.firstFrameAt ||= Date.now();
460
- this.frame(latest);
461
- }
462
- });
463
- }
464
- linuxFrames() {
465
- const old = this.capture;
466
- this.capture = void 0;
467
- old?.kill("SIGTERM");
468
- const g = this.geometry, { width, height, fps } = this.video;
469
- const child = spawn(this.init.config.ffmpeg, ["-nostdin", "-hide_banner", "-loglevel", "error", "-f", "x11grab", "-framerate", String(fps), "-video_size", `${g.width}x${g.height}`, "-grab_x", String(g.x), "-grab_y", String(g.y), "-draw_mouse", "1", "-i", process.env.DISPLAY, "-vf", `scale=${width}:${height}`, "-pix_fmt", "yuv420p", "-f", "rawvideo", "pipe:1"], { stdio: ["ignore", "pipe", "ignore"] });
470
- this.capture = child;
471
- let buffer = Buffer.alloc(0);
472
- const length = width * height * 1.5;
473
- child.stdout.on("data", (chunk) => {
474
- if (this.capture !== child || this.ended) return;
475
- buffer = Buffer.concat([buffer, chunk]);
476
- if (buffer.length > length * 4) {
477
- this.stop("\u4E3B\u673A\u91C7\u96C6\u7F13\u51B2\u533A\u8D85\u9650");
478
- return;
479
- }
480
- if (buffer.length >= length) {
481
- const count = Math.floor(buffer.length / length), frame = buffer.subarray((count - 1) * length, count * length);
482
- if (this.direct) {
375
+ const adapter = RemotePlatformRegistry.standard.resolve();
376
+ const error = adapter.platformError();
377
+ if (error) throw new Error(error);
378
+ const session = adapter.createSession({
379
+ helper: this.init.helper,
380
+ display: this.init.display,
381
+ video: this.video,
382
+ config: this.init.config,
383
+ onFrame: (frame) => {
384
+ if (!this.ended && this.direct) {
483
385
  this.firstFrameAt ||= Date.now();
484
- this.frame({ width, height, data: new Uint8Array(frame) });
386
+ this.frame(frame);
485
387
  }
486
- buffer = buffer.subarray(count * length);
487
- }
488
- });
489
- child.on("error", () => this.stop("FFmpeg \u91C7\u96C6\u5931\u8D25"));
490
- child.on("exit", () => {
491
- if (this.capture === child) this.stop("\u663E\u793A\u5668\u5DF2\u53D8\u5316\u6216 FFmpeg \u91C7\u96C6\u5931\u8D25");
388
+ },
389
+ onMessage: (message) => {
390
+ if (!this.ended) this.nativeMessage(message);
391
+ },
392
+ onError: (message) => this.stop(message)
492
393
  });
394
+ this.native = session;
395
+ await session.start();
396
+ }
397
+ nativeMessage(message) {
398
+ if (message.kind === "clipboard" && typeof message.id === "string" && typeof message.data === "string") {
399
+ const callback = this.nativePending.get(message.id);
400
+ this.nativePending.delete(message.id);
401
+ callback?.(Buffer.from(message.data, "base64").toString("utf8"));
402
+ }
403
+ if (message.kind === "clipboard-written" && this.pendingClipboard?.id === message.id) {
404
+ this.pendingClipboard = void 0;
405
+ this.send("clipboard", { kind: "clipboard.done", id: message.id });
406
+ }
407
+ if (message.kind === "clipboard-error") {
408
+ if (typeof message.id === "string") this.nativePending.delete(message.id);
409
+ this.pendingClipboard = void 0;
410
+ this.send("clipboard", { kind: "clipboard.error", id: message.id, message: "\u4E3B\u673A\u526A\u8D34\u677F\u8BFB\u53D6\u5931\u8D25\u6216\u8D85\u8FC7 1 MiB" });
411
+ }
412
+ if (message.kind === "input-error") this.send("control", { kind: "input.error", message: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\u5BC6\u7801\uFF1B\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u754C\u9762" });
493
413
  }
494
414
  frame(frame) {
495
415
  if (this.legacy || this.frameGate.accept(frame, Date.now())) this.source.onFrame(frame);
496
416
  }
497
417
  writeNative(line) {
498
- if (!this.helper?.stdin || this.helper.stdin.destroyed) return;
499
- if (this.helper.stdin.writableLength > 2 * 1024 * 1024) {
500
- this.stop("\u4E3B\u673A\u8F93\u5165\u5904\u7406\u8D85\u65F6");
501
- return;
502
- }
503
- this.helper.stdin.write(`${line}
504
- `);
418
+ this.native?.writeCommand(line);
505
419
  }
506
420
  input(input) {
507
- switch (input.kind) {
508
- case "pointer":
509
- this.writeNative(`pointer ${input.x} ${input.y}`);
510
- break;
511
- case "button":
512
- this.writeNative(`button ${input.x} ${input.y} ${input.button} ${input.down ? 1 : 0}`);
513
- break;
514
- case "wheel":
515
- this.writeNative(`wheel ${input.x} ${input.y} ${Math.round(input.dx)} ${Math.round(input.dy)}`);
516
- break;
517
- case "key":
518
- this.writeNative(`key ${input.code} ${input.down ? 1 : 0}`);
519
- break;
520
- case "password":
521
- if (process.platform !== "darwin") return;
522
- this.writeNative(`password ${Buffer.from(input.text).toString("base64")}`);
523
- break;
524
- case "text":
525
- this.writeNative(`text ${input.terminal ? 1 : 0} ${Buffer.from(input.text).toString("base64")}`);
526
- break;
527
- case "release":
528
- this.writeNative("release");
529
- break;
530
- }
421
+ this.native?.input(input);
531
422
  }
532
423
  clipboard(value) {
533
424
  if (typeof value.id !== "string" || !/^[a-zA-Z0-9_-]{1,64}$/.test(value.id)) throw new Error("clipboard id");
@@ -609,10 +500,7 @@ var RemotePeer = class {
609
500
  const video = this.budget.video(this.init.display.width, this.init.display.height);
610
501
  const changed = video.width !== this.video.width || video.height !== this.video.height || video.fps !== this.video.fps;
611
502
  this.video = video;
612
- if (changed && this.ready) {
613
- if (process.platform === "darwin") this.writeNative(`video ${video.width} ${video.height} ${video.fps}`);
614
- else this.linuxFrames();
615
- }
503
+ if (changed && this.ready) this.native?.setVideo(video);
616
504
  }
617
505
  async encoding() {
618
506
  if (this.parameterMode === "sdp") return;
@@ -677,9 +565,8 @@ var RemotePeer = class {
677
565
  clearInterval(this.timer);
678
566
  clearInterval(this.clipboardTimer);
679
567
  clearTimeout(this.handshake);
680
- this.writeNative("release");
681
- terminateNative(this.helper);
682
- this.capture?.kill("SIGTERM");
568
+ this.native?.stop();
569
+ this.native = void 0;
683
570
  this.track.stop();
684
571
  for (const c of this.channels.values()) c.close();
685
572
  this.pc.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {