@skylandnpm/octopus-proxy 0.1.29 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@ octopus-proxy onboard
15
15
 
16
16
  向导默认使用 `https://octopus.wuyehao.com` 作为 Hub 地址;按需可输入其他地址,或用 `--hub-url` 指定。再次运行向导时会读取现有配置,直接回车即可保留各项设置;机器人 Secret 只提示已配置,不在终端显示原值。macOS 上选择编码工具时,向导优先显示已配置或探测到的命令路径,否则给出该工具的 macOS 路径示例;回车后会检查文件可执行性并运行版本命令,失败时要求重新输入。无交互环境可使用 `octopus-proxy onboard --non-interactive`,未提供 `--hub-url` 时也使用该默认地址;完整选项见 `octopus-proxy help`。安装服务后可运行 `octopus-proxy status` 和 `octopus-proxy doctor` 检查状态。
17
17
 
18
- Proxy 始终使用独立的控制与文件 WebSocket;文件读取和上传不会回退到控制通道。两个通道会同时连接并各自重连,Hub 只会把文件命令发送到同一 Proxy 实例的已就绪文件通道。文件连接同时携带经过 Proxy 凭据认证的通道标识,因此即使反向代理将 `/ws/proxy-files` 重写为上游 `/ws/proxy`,Hub 仍能正确路由文件命令;反向代理仍需转发 WebSocket Upgrade 与 `X-Octopus-Proxy-Channel` 请求头。若旧 Hub 无法识别该标识,Proxy 会保留控制通道以免影响对话,同时在日志中提示升级 Hub 路由。同一套 bot ID 和 Secret 被另一台 Proxy 主机连接后,Hub 会让新实例接管,旧实例会同时关闭两个通道并停止自动重连;若要让旧实例重新接管,必须显式重启其服务。
18
+ Proxy 始终使用独立的控制与文件 WebSocket;文件读取和上传不会回退到控制通道。两个通道会同时连接并各自重连,Hub 只会把文件命令发送到同一 Proxy 实例的已就绪文件通道。控制与文件连接在 `X-Octopus-Proxy-Instance-Id` 中分别使用 `:control`、`:files` 后缀;Hub 去除后缀再配对实例,因此即使反向代理把 `/ws/proxy-files` 重写为上游 `/ws/proxy`,也能正确识别文件通道。反向代理需转发 WebSocket Upgrade、Authorization、`X-Octopus-Bot-Id` 与 `X-Octopus-Proxy-Instance-Id`。同一套 bot ID 和 Secret 被另一台 Proxy 主机连接后,Hub 会让新实例接管,旧实例会同时关闭两个通道并停止自动重连;若要让旧实例重新接管,必须显式重启其服务。
19
19
 
20
20
  添加授权项目时,填写项目 ID 后会询问是否继续添加;直接回车即可结束添加并继续保存配置。
21
21
 
@@ -16630,29 +16630,6 @@ function createAdapter(runtime, workspace, uploads, emit) {
16630
16630
  return new OpenCodeAdapter(runtime, workspace, uploads, forward);
16631
16631
  }
16632
16632
 
16633
- // src/hub-channel-compatibility.ts
16634
- var HubChannelCompatibility = class {
16635
- enabled = false;
16636
- filesAttemptedAt = 0;
16637
- filesFailures = 0;
16638
- filesAttempt(now = Date.now()) {
16639
- this.filesAttemptedAt = now;
16640
- }
16641
- filesWelcome() {
16642
- this.filesFailures = 0;
16643
- }
16644
- controlClosed(filesPending, now = Date.now()) {
16645
- if (this.enabled || !filesPending || now - this.filesAttemptedAt >= 5e3) return false;
16646
- this.enabled = true;
16647
- return true;
16648
- }
16649
- filesClosedBeforeWelcome(stopping) {
16650
- if (this.enabled || stopping || ++this.filesFailures < 3) return false;
16651
- this.enabled = true;
16652
- return true;
16653
- }
16654
- };
16655
-
16656
16633
  // src/proxy-binding.ts
16657
16634
  var PROXY_REPLACED_CLOSE_CODE = 4009;
16658
16635
  var ProxyBinding = class {
@@ -16802,7 +16779,6 @@ async function runDaemon(configPath) {
16802
16779
  const workspace = new Workspace(config.projects), runtime = createRuntime(config), uploads = new Uploads(workspace, join10(config.stateDirectory, "uploads")), journal = new Journal(join10(config.stateDirectory, "journal"));
16803
16780
  const epoch = randomUUID12();
16804
16781
  let stopping = false, botId = "";
16805
- const hubChannels = new HubChannelCompatibility();
16806
16782
  const binding = new ProxyBinding();
16807
16783
  const sockets = /* @__PURE__ */ new Set();
16808
16784
  let controlSocket, filesSocket;
@@ -16871,29 +16847,27 @@ async function runDaemon(configPath) {
16871
16847
  return adapter.dispatch(c);
16872
16848
  }
16873
16849
  function activateChannels() {
16874
- if (channelsActivated || !controlWelcome || !filesWelcomed && !hubChannels.enabled || controlWelcome.socket.readyState !== WebSocket2.OPEN) return;
16850
+ if (channelsActivated || !controlWelcome || !filesWelcomed || controlWelcome.socket.readyState !== WebSocket2.OPEN) return;
16875
16851
  channelsActivated = true;
16876
16852
  events.bind(controlWelcome.socket, controlWelcome.resume);
16877
16853
  for (const p of config.projects) emit(p.id, "octopus.resync", {});
16878
16854
  }
16879
16855
  function connect(files, attempt = 0) {
16880
- if (stopping || !binding.active || files && hubChannels.enabled) return;
16856
+ if (stopping || !binding.active) return;
16857
+ const current = files ? filesSocket : controlSocket;
16858
+ if (current && current.readyState !== WebSocket2.CLOSED) return;
16881
16859
  const url = new URL(files ? "/ws/proxy-files" : "/ws/proxy", config.hubUrl);
16882
16860
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
16883
16861
  const headers = {
16884
16862
  Authorization: `SecretKey ${config.secretKey}`,
16885
16863
  "X-Octopus-Bot-Id": config.botId,
16886
- "X-Octopus-Proxy-Instance-Id": epoch,
16887
- // Preserve the logical channel when a reverse proxy rewrites
16888
- // /ws/proxy-files to the generic /ws/proxy upstream endpoint.
16889
- "X-Octopus-Proxy-Channel": files ? "files" : "control"
16864
+ "X-Octopus-Proxy-Instance-Id": `${epoch}:${files ? "files" : "control"}`
16890
16865
  };
16891
16866
  const socket = new WebSocket2(url, { headers, maxPayload: 3 * 1024 * 1024 });
16892
16867
  sockets.add(socket);
16893
16868
  if (files) {
16894
16869
  filesSocket = socket;
16895
16870
  filesWelcomed = false;
16896
- hubChannels.filesAttempt();
16897
16871
  } else controlSocket = socket;
16898
16872
  let lastPong = Date.now();
16899
16873
  const heartbeat = setInterval(() => {
@@ -16918,10 +16892,8 @@ async function runDaemon(configPath) {
16918
16892
  if (value.type === "welcome") {
16919
16893
  botId = value.botId;
16920
16894
  uploads.limits = value.limits;
16921
- if (files) {
16922
- filesWelcomed = true;
16923
- hubChannels.filesWelcome();
16924
- } else controlWelcome = { socket, resume: value.resume };
16895
+ if (files) filesWelcomed = true;
16896
+ else controlWelcome = { socket, resume: value.resume };
16925
16897
  activateChannels();
16926
16898
  return;
16927
16899
  }
@@ -16956,17 +16928,10 @@ async function runDaemon(configPath) {
16956
16928
  console.error("This bot was claimed by another Proxy instance; automatic reconnection is disabled until this service is restarted");
16957
16929
  for (const peer of sockets) peer.close();
16958
16930
  }
16959
- const collision = binding.active && !files && hubChannels.controlClosed(filesSocket !== void 0 && filesSocket.readyState !== WebSocket2.CLOSED);
16960
- const rejectedFilesChannel = binding.active && files && !filesWelcomed && hubChannels.filesClosedBeforeWelcome(stopping);
16961
- if (collision || rejectedFilesChannel) {
16962
- console.warn("Hub does not preserve the Proxy files channel; keeping the control channel online until Hub routing is upgraded");
16963
- filesSocket?.close();
16964
- filesSocket = void 0;
16965
- activateChannels();
16966
- } else if (!stopping && binding.active && code !== 1e3) {
16931
+ if (!stopping && binding.active && code !== 1e3) {
16967
16932
  console.warn(`Hub ${files ? "files" : "control"} connection closed: ${code}${reason.length ? ` ${reason.toString()}` : ""}`);
16968
16933
  }
16969
- if (!stopping && binding.active && (!files || !hubChannels.enabled)) setTimeout(() => connect(files, attempt + 1), collision ? 100 : Math.min(3e4, 1e3 * 2 ** Math.min(attempt, 5)) + Math.random() * 500);
16934
+ if (!stopping && binding.active) setTimeout(() => connect(files, attempt + 1), Math.min(3e4, 1e3 * 2 ** Math.min(attempt, 5)) + Math.random() * 500);
16970
16935
  });
16971
16936
  }
16972
16937
  await runtime.init().catch((error) => console.error(`${runtime.kind} startup:`, error.message));
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  runDaemon,
23
23
  supportedPlatform,
24
24
  systemdUnitPath
25
- } from "./chunk-XHNRMXZT.js";
25
+ } from "./chunk-PYZSSOSD.js";
26
26
 
27
27
  // src/cli.ts
28
28
  import { spawn } from "node:child_process";
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-XHNRMXZT.js";
5
+ } from "./chunk-PYZSSOSD.js";
6
6
 
7
7
  // src/main.ts
8
8
  runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {