@skylandnpm/octopus-proxy 0.1.66 → 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-UFLXQ4HQ.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";
@@ -13959,8 +13960,49 @@ var PlatformDesktopSession = class {
13959
13960
 
13960
13961
  // src/remote-manager.ts
13961
13962
  import { fork } from "node:child_process";
13962
- 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";
13963
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
+ }
13964
14006
 
13965
14007
  // src/remote-stun.ts
13966
14008
  import { Resolver } from "node:dns/promises";
@@ -14027,13 +14069,13 @@ async function discoverRemoteStunUrls(hubUrl, configured, resolve5 = resolvePubl
14027
14069
 
14028
14070
  // src/remote-manager.ts
14029
14071
  function spawnWorker(message) {
14030
- 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));
14031
14073
  const child = fork(worker, [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: import.meta.url.endsWith(".ts") ? ["--import", "tsx"] : [] });
14032
14074
  child.send({ kind: "init", ...message });
14033
14075
  return child;
14034
14076
  }
14035
14077
  var RemoteManager = class {
14036
- 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) {
14037
14079
  this.host = host;
14038
14080
  this.desktop = desktop;
14039
14081
  this.desktopSession = desktopSession;
@@ -14042,23 +14084,25 @@ var RemoteManager = class {
14042
14084
  this.workerFactory = workerFactory;
14043
14085
  this.probe = probe;
14044
14086
  this.discoverStun = discoverStun;
14087
+ this.platform = platformRegistry.resolve();
14045
14088
  this.config = RemoteConfigSchema.parse(host.desktopRemote ?? {});
14046
14089
  }
14047
14090
  current;
14048
14091
  stopping = false;
14049
14092
  config;
14093
+ platform;
14050
14094
  stunUrls() {
14051
14095
  return this.config.stunUrls ?? defaultRemoteStunUrls(this.host.hubUrl);
14052
14096
  }
14053
14097
  async status() {
14054
- 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 } };
14055
14099
  if (!this.config.enabled) return { ...base, available: false, message: "\u4E3B\u673A\u5DF2\u5173\u95ED\u8FDC\u7A0B\u63A7\u5236" };
14056
14100
  try {
14057
- const permissions = await this.probe(this.host.stateDirectory, this.config);
14058
- const lockUnsupported = permissions.locked === true && permissions.lockCapture === false;
14059
- const available = permissions.session && permissions.capture && permissions.input && !lockUnsupported;
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;
14060
14104
  if (available) await this.desktopSession.sessionIdentity();
14061
- 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" } : {} };
14105
+ return { ...base, available, permissions, ...!available ? { message } : {} };
14062
14106
  } catch (error) {
14063
14107
  return { ...base, available: false, message: error instanceof Error ? error.message : "\u8FDC\u63A7\u4F9D\u8D56\u4E0D\u53EF\u7528" };
14064
14108
  }
@@ -14094,15 +14138,16 @@ var RemoteManager = class {
14094
14138
  if (this.current) throw new Error("\u4E3B\u673A\u6B63\u5728\u88AB\u63A7\u5236");
14095
14139
  const quality = RemoteQualitySchema.parse(payload.quality ?? "hd");
14096
14140
  const budget = remoteSessionBudget(this.config, quality, payload.quality === void 0);
14097
- 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 };
14098
14142
  this.current = session;
14099
14143
  try {
14100
14144
  const [permissions, stunUrls] = await Promise.all([
14101
- this.probe(this.host.stateDirectory, this.config),
14145
+ this.probe(this.host.stateDirectory, this.config, this.platform),
14102
14146
  this.discoverStun(this.host.hubUrl, this.config.stunUrls)
14103
14147
  ]);
14104
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");
14105
- 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);
14106
14151
  const identity = await this.desktopSession.sessionIdentity();
14107
14152
  const display = (await this.desktop.list()).find((d) => d.id === payload.displayId);
14108
14153
  if (!display) throw new Error("\u663E\u793A\u5668\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u5237\u65B0\u663E\u793A\u5668\u5217\u8868");
@@ -14173,16 +14218,16 @@ var RemoteManager = class {
14173
14218
  };
14174
14219
 
14175
14220
  // src/tool-selection.ts
14176
- import { randomBytes as randomBytes3, randomUUID as randomUUID19 } from "node:crypto";
14177
- 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";
14178
14223
  import { platform as platform5 } from "node:os";
14179
14224
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
14180
14225
 
14181
14226
  // src/node-resolution.ts
14182
- import { realpath as realpath7, stat as stat10 } from "node:fs/promises";
14227
+ import { realpath as realpath7, stat as stat11 } from "node:fs/promises";
14183
14228
  import { execFile as execFile7 } from "node:child_process";
14184
14229
  import { promisify as promisify7 } from "node:util";
14185
- import { join as join14 } from "node:path";
14230
+ import { join as join15 } from "node:path";
14186
14231
  import { homedir as homedir2, platform as platform4 } from "node:os";
14187
14232
  var run4 = promisify7(execFile7);
14188
14233
  var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
@@ -14226,15 +14271,15 @@ async function resolveNodePath(options = {}) {
14226
14271
  return { nodePath: null, source: "exec" };
14227
14272
  }
14228
14273
  function toolSearchDirs(kind) {
14229
- 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")];
14230
14275
  return [...new Set(dirs)];
14231
14276
  }
14232
14277
  async function resolveCommandOnPath(name, extraDirs = []) {
14233
14278
  const dirs = [...(process.env.PATH ?? "").split(":"), ...extraDirs].filter(Boolean);
14234
14279
  for (const dir of new Set(dirs)) {
14235
- const full = join14(dir, name);
14280
+ const full = join15(dir, name);
14236
14281
  try {
14237
- const info = await stat10(full);
14282
+ const info = await stat11(full);
14238
14283
  if (info.isFile() && info.mode & 73) return full;
14239
14284
  } catch {
14240
14285
  }
@@ -14317,7 +14362,7 @@ var ToolSelection = class {
14317
14362
  });
14318
14363
  }
14319
14364
  modelCommand(projectId) {
14320
- 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: {} };
14321
14366
  }
14322
14367
  profiles(raw) {
14323
14368
  return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
@@ -14359,7 +14404,7 @@ var ToolSelection = class {
14359
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) {
14360
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");
14361
14406
  }
14362
- return JSON.parse(await readFile10(this.configPath, "utf8"));
14407
+ return JSON.parse(await readFile11(this.configPath, "utf8"));
14363
14408
  }
14364
14409
  async release(instance) {
14365
14410
  instance.adapter.close();
@@ -14376,8 +14421,8 @@ var ToolSelection = class {
14376
14421
  // src/daemon.ts
14377
14422
  async function runDaemon(configPath, authenticate = verifyHostPassword) {
14378
14423
  const config = await loadConfig(configPath);
14379
- await mkdir9(config.stateDirectory, { recursive: true, mode: 448 });
14380
- 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"));
14381
14426
  let secureChat;
14382
14427
  function emit(projectId, event, data) {
14383
14428
  secureChat?.publish(projectId, event, data);
@@ -14399,7 +14444,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
14399
14444
  });
14400
14445
  return { runtime, adapter };
14401
14446
  });
14402
- const artifacts = new Artifacts(workspace, join15(config.stateDirectory, "artifacts"), emit);
14447
+ const artifacts = new Artifacts(workspace, join16(config.stateDirectory, "artifacts"), emit);
14403
14448
  const projectRegistry = () => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter, (resetProjects) => {
14404
14449
  for (const id of resetProjects) terminals.closeProject(id);
14405
14450
  });
@@ -4449,66 +4449,16 @@ var DesktopManager = class {
4449
4449
  }
4450
4450
  };
4451
4451
 
4452
- // src/remote-native.ts
4452
+ // src/remote/native-process.ts
4453
4453
  import { execFile as execFile2, spawn } from "node:child_process";
4454
4454
  import { promisify as promisify2 } from "node:util";
4455
- 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";
4456
- import { join as join2 } from "node:path";
4457
- import { fileURLToPath as fileURLToPath2 } from "node:url";
4458
- import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
4459
4455
  var exec = promisify2(execFile2);
4460
- function remotePlatformError(system = process.platform, arch = process.arch, env = process.env) {
4461
- if (!["darwin", "linux"].includes(system) || !["arm64", "x64"].includes(arch)) return `\u8FDC\u63A7\u4E0D\u652F\u6301\u5F53\u524D\u5E73\u53F0 ${system}/${arch}\uFF1B\u9700\u8981 macOS 12.3+ \u6216 Linux X11\uFF08x64/ARM64\uFF09`;
4462
- if (system === "linux") {
4463
- 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";
4464
- 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";
4465
- }
4466
- }
4467
- async function ensureRemoteHelper(stateDirectory) {
4468
- await mkdir2(stateDirectory, { recursive: true, mode: 448 });
4469
- const mac = process.platform === "darwin";
4470
- const source = fileURLToPath2(new URL(mac ? "../native/remote-macos.m" : "../native/remote-x11.c", import.meta.url));
4471
- const target = join2(stateDirectory, "octopus-remote-desktop");
4472
- const stamp = `${target}.sha256`;
4473
- 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"];
4474
- const fingerprint = createHash2("sha256").update(await readFile2(source)).update(JSON.stringify([process.platform, process.arch, flags])).digest("hex");
4475
- const existing = await stat2(target).catch(() => void 0);
4476
- if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile2(stamp, "utf8").catch(() => "") === fingerprint) return target;
4477
- const temporary = `${target}.${randomUUID2()}`, temporaryStamp = `${temporary}.sha256`;
4478
- const args = mac ? [...flags, source, "-o", temporary] : ["-O2", source, "-o", temporary, "-lX11", "-lXtst"];
4479
- try {
4480
- await exec(mac ? "/usr/bin/clang" : "cc", args, { timeout: 6e4, maxBuffer: 16384 });
4481
- await chmod2(temporary, 448);
4482
- await writeFile2(temporaryStamp, fingerprint, { mode: 384 });
4483
- await rename2(temporary, target);
4484
- await rename2(temporaryStamp, stamp);
4485
- return target;
4486
- } catch {
4487
- 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");
4488
- } finally {
4489
- await Promise.all([rm2(temporary, { force: true }), rm2(temporaryStamp, { force: true })]);
4490
- }
4491
- }
4492
- async function probeRemote(stateDirectory, config) {
4493
- const platformError = remotePlatformError();
4494
- if (platformError) throw new Error(platformError);
4495
- await import("@roamhq/wrtc").catch(() => {
4496
- throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
4497
- });
4498
- const helper = await ensureRemoteHelper(stateDirectory);
4499
- const result = JSON.parse((await exec(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
4500
- if (process.platform === "linux") {
4501
- const version = await exec(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
4502
- throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
4503
- });
4504
- assertLgplFFmpeg(version.stdout);
4505
- const devices = await exec(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
4506
- if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
4507
- }
4508
- return { helper, ...result };
4509
- }
4510
- function nativeProcess(helper, displayId, video) {
4511
- 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"] });
4512
4462
  }
4513
4463
  function terminateNative(child) {
4514
4464
  if (!child) return;
@@ -4519,6 +4469,307 @@ function terminateNative(child) {
4519
4469
  child.once("exit", () => clearTimeout(timer));
4520
4470
  }
4521
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
+
4522
4773
  export {
4523
4774
  __commonJS,
4524
4775
  __toESM,
@@ -4539,10 +4790,8 @@ export {
4539
4790
  DESKTOP_PERMISSION_GUIDANCE,
4540
4791
  requestDesktopPermission,
4541
4792
  checkDesktop,
4542
- parseXrandr,
4543
4793
  PlatformDesktop,
4544
4794
  DesktopManager,
4545
- probeRemote,
4546
- nativeProcess,
4547
- terminateNative
4795
+ nativeDependencies,
4796
+ RemotePlatformRegistry
4548
4797
  };
package/dist/cli.js CHANGED
@@ -35,13 +35,13 @@ import {
35
35
  supportedPlatform,
36
36
  systemdUnitPath,
37
37
  toolSearchDirs
38
- } from "./chunk-IV5ZGRAM.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-UFLXQ4HQ.js";
44
+ } from "./chunk-OTKNRBZ7.js";
45
45
 
46
46
  // src/cli.ts
47
47
  import { spawn as spawn2 } from "node:child_process";
package/dist/main.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-IV5ZGRAM.js";
6
- import "./chunk-UFLXQ4HQ.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-UFLXQ4HQ.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.66",
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": {