@skylandnpm/octopus-proxy 0.1.20 → 0.1.23
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 +3 -1
- package/dist/{chunk-HWMHCHRB.js → chunk-KYGWORYZ.js} +161 -23
- package/dist/cli.js +2 -2
- package/dist/main.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ 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 或反向代理把 `/ws/proxy-files` 错误路由到控制通道,Proxy 会在连续握手冲突后自动切换为单控制通道兼容模式,避免两个连接互相挤掉而导致 Claw 永久显示离线;日志会明确记录该降级。同一套 bot ID 和 Secret 被另一台 Proxy 主机连接后,Hub 会让新实例接管,旧实例会同时关闭两个通道并停止自动重连;若要让旧实例重新接管,必须显式重启其服务。
|
|
19
|
+
|
|
18
20
|
添加授权项目时,填写项目 ID 后会询问是否继续添加;直接回车即可结束添加并继续保存配置。
|
|
19
21
|
|
|
20
22
|
如果旧配置中的项目 ID 重复或有错误项,先列出项目,再按序号删除指定项。此命令直接读取原始配置,因此配置校验失败时也能使用;删除前会生成仅当前用户可读的备份。清理完成后检查配置并重启服务:
|
|
@@ -72,6 +74,6 @@ npm run publish
|
|
|
72
74
|
|
|
73
75
|
## 编码工具账号与 OpenCode 服务
|
|
74
76
|
|
|
75
|
-
OpenCode 2.0.19 使用 external HTTP 模式。`tool.command` 应为 `/Users/luhuili/.opencode/bin/opencode`,`tool.baseUrl` 明确指向回环服务,`tool.password` 必填并由 onboard 自动生成。Proxy
|
|
77
|
+
OpenCode 2.0.19 使用 external HTTP 模式。`tool.command` 应为 `/Users/luhuili/.opencode/bin/opencode`,`tool.baseUrl` 明确指向回环服务,`tool.password` 必填并由 onboard 自动生成。Proxy 可拉起未监听的服务,也可连接以同一密码预先启动的服务;若配置端口已被密码不同的服务占用,Proxy 会在随机回环端口启动自己的实例并在后续重启时复用,避免接管或停止未知进程。启动日志保存在 `stateDirectory/opencode-serve.log`。如需只连接自行管理的服务,可设置 `autoStart: false`。旧顶层配置、managed 模式和 v1 API 不再兼容;Proxy 不停止或重启已有 OpenCode 服务。
|
|
76
78
|
|
|
77
79
|
Claw 的“编码工具账号”窗口支持 Codex API Key/设备码、ZCode 原生 Z.AI OAuth、OpenCode 原生 API Key/OAuth。凭据由编码工具保存,模型列表从原生接口读取。详细行为与验证边界见 [编码工具账号](../../docs/tool-authentication.md)。
|
|
@@ -7481,6 +7481,7 @@ function daemonEntryPath() {
|
|
|
7481
7481
|
|
|
7482
7482
|
// src/config.ts
|
|
7483
7483
|
import { readFile, stat } from "node:fs/promises";
|
|
7484
|
+
import { randomBytes } from "node:crypto";
|
|
7484
7485
|
import { isAbsolute } from "node:path";
|
|
7485
7486
|
|
|
7486
7487
|
// ../../node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.mjs
|
|
@@ -11619,7 +11620,7 @@ var ConfigSchema = z.object({
|
|
|
11619
11620
|
projects: z.array(z.object({ id: z.string().regex(/^[a-zA-Z0-9_-]+$/), name: z.string().min(1), directory: z.string().min(1), specDirectories: z.array(z.string()).default(["specs", "docs"]) })).min(1),
|
|
11620
11621
|
tool: ToolSchema.default({ kind: "opencode" })
|
|
11621
11622
|
});
|
|
11622
|
-
async function loadConfig(path) {
|
|
11623
|
+
async function loadConfig(path, options = {}) {
|
|
11623
11624
|
try {
|
|
11624
11625
|
const info = await stat(path).catch((error) => {
|
|
11625
11626
|
if (error.code === "ENOENT") throw new Error(`Configuration not found: ${path}`);
|
|
@@ -11627,6 +11628,10 @@ async function loadConfig(path) {
|
|
|
11627
11628
|
});
|
|
11628
11629
|
if ((info.mode & 63) !== 0) throw new Error("Proxy configuration contains a secret; run chmod 600 on it");
|
|
11629
11630
|
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
11631
|
+
if (options.provisionOpenCodePassword) {
|
|
11632
|
+
const tool = raw.tool && typeof raw.tool === "object" && !Array.isArray(raw.tool) ? raw.tool : void 0;
|
|
11633
|
+
if ((!tool || tool.kind === void 0 || tool.kind === "opencode") && !tool?.password) raw.tool = { ...tool ?? {}, kind: "opencode", password: randomBytes(32).toString("base64url") };
|
|
11634
|
+
}
|
|
11630
11635
|
const config = ConfigSchema.parse(raw);
|
|
11631
11636
|
config.stateDirectory = expandHome(config.stateDirectory);
|
|
11632
11637
|
config.tool.command = expandHome(config.tool.command || describeTool(config.tool.kind).defaultCommand);
|
|
@@ -12500,7 +12505,7 @@ async function buildProxySnapshot(config, runtime, adapter, requestedProjectId)
|
|
|
12500
12505
|
command: config.tool.command,
|
|
12501
12506
|
mode: config.tool.mode,
|
|
12502
12507
|
autoStart: config.tool.autoStart,
|
|
12503
|
-
...config.tool.kind === "opencode" ? { baseUrl: config.tool.baseUrl } : {}
|
|
12508
|
+
...config.tool.kind === "opencode" ? { baseUrl: typeof status.baseUrl === "string" ? status.baseUrl : config.tool.baseUrl } : {}
|
|
12504
12509
|
}
|
|
12505
12510
|
},
|
|
12506
12511
|
projects,
|
|
@@ -14239,7 +14244,7 @@ var OpenCodeAdapter = class {
|
|
|
14239
14244
|
media;
|
|
14240
14245
|
async request(project, method, path, body, signal) {
|
|
14241
14246
|
const root = await this.workspace.root(project);
|
|
14242
|
-
const url = new URL(path, this.runtime.
|
|
14247
|
+
const url = new URL(path, this.runtime.baseUrl);
|
|
14243
14248
|
if (path === "/api/session") url.searchParams.set("directory", root);
|
|
14244
14249
|
else if (/^\/api\/(?:agent|model|provider|mcp|command|config|pty|permission\/request|form|integration|location|experimental\/mcp)(?:\/|$)/.test(path)) url.searchParams.set("location[directory]", root);
|
|
14245
14250
|
const response = await fetch(url, {
|
|
@@ -14485,7 +14490,7 @@ var OpenCodeAdapter = class {
|
|
|
14485
14490
|
const root = await this.workspace.root(project);
|
|
14486
14491
|
while (!controller.signal.aborted) {
|
|
14487
14492
|
try {
|
|
14488
|
-
const response = await fetch(new URL("/api/event", this.runtime.
|
|
14493
|
+
const response = await fetch(new URL("/api/event", this.runtime.baseUrl), { headers: this.runtime.headers(), signal: controller.signal });
|
|
14489
14494
|
if (!response.ok || !response.body) throw new Error("Event connection unavailable");
|
|
14490
14495
|
this.emit(project, "octopus.resync", {});
|
|
14491
14496
|
let buffer = "";
|
|
@@ -14531,7 +14536,7 @@ var OpenCodeAdapter = class {
|
|
|
14531
14536
|
const previous = this.terminals.get(key);
|
|
14532
14537
|
this.terminals.delete(key);
|
|
14533
14538
|
previous?.close();
|
|
14534
|
-
const url = new URL(`/api/pty/${id}/connect`, this.runtime.
|
|
14539
|
+
const url = new URL(`/api/pty/${id}/connect`, this.runtime.baseUrl);
|
|
14535
14540
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
14536
14541
|
url.searchParams.set("location[directory]", await this.workspace.root(project));
|
|
14537
14542
|
const socket2 = new WebSocket(url, { headers: this.runtime.headers() });
|
|
@@ -14641,17 +14646,32 @@ function redact(value) {
|
|
|
14641
14646
|
|
|
14642
14647
|
// src/runtime.ts
|
|
14643
14648
|
import { spawn } from "node:child_process";
|
|
14644
|
-
import {
|
|
14649
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
14650
|
+
import { mkdir as mkdir4, open as open4, readFile as readFile5, rm, writeFile as writeFile4 } from "node:fs/promises";
|
|
14651
|
+
import { createServer } from "node:net";
|
|
14645
14652
|
import { join as join5 } from "node:path";
|
|
14646
14653
|
var Runtime = class {
|
|
14647
14654
|
constructor(config) {
|
|
14648
14655
|
this.config = config;
|
|
14656
|
+
this.endpoint = config.tool.baseUrl;
|
|
14649
14657
|
}
|
|
14650
14658
|
kind = "opencode";
|
|
14651
14659
|
version = "";
|
|
14652
14660
|
state = "external";
|
|
14653
14661
|
starting;
|
|
14662
|
+
endpoint;
|
|
14663
|
+
endpointRestored = false;
|
|
14664
|
+
get baseUrl() {
|
|
14665
|
+
return this.endpoint;
|
|
14666
|
+
}
|
|
14667
|
+
get endpointStatePath() {
|
|
14668
|
+
return join5(this.config.stateDirectory, "opencode-endpoint.json");
|
|
14669
|
+
}
|
|
14670
|
+
get passwordHash() {
|
|
14671
|
+
return createHash7("sha256").update(this.config.tool.password).digest("hex");
|
|
14672
|
+
}
|
|
14654
14673
|
async init() {
|
|
14674
|
+
await this.restoreEndpoint();
|
|
14655
14675
|
if (this.config.tool.autoStart) await this.start();
|
|
14656
14676
|
else await this.health();
|
|
14657
14677
|
}
|
|
@@ -14661,7 +14681,7 @@ var Runtime = class {
|
|
|
14661
14681
|
}
|
|
14662
14682
|
async health() {
|
|
14663
14683
|
try {
|
|
14664
|
-
const response = await fetch(new URL("/api/info", this.
|
|
14684
|
+
const response = await fetch(new URL("/api/info", this.baseUrl), { headers: this.headers(), signal: AbortSignal.timeout(2e3) });
|
|
14665
14685
|
if (!response.ok) return false;
|
|
14666
14686
|
const data = await response.json();
|
|
14667
14687
|
this.version = data.version ?? this.version;
|
|
@@ -14678,7 +14698,9 @@ var Runtime = class {
|
|
|
14678
14698
|
state: this.state,
|
|
14679
14699
|
healthy,
|
|
14680
14700
|
version: this.version,
|
|
14681
|
-
|
|
14701
|
+
baseUrl: this.baseUrl,
|
|
14702
|
+
...this.baseUrl === this.config.tool.baseUrl ? {} : { configuredBaseUrl: this.config.tool.baseUrl },
|
|
14703
|
+
...healthy ? {} : { error: `\u65E0\u6CD5\u8FDE\u63A5 OpenCode\uFF1A${this.baseUrl}\u3002\u53EF\u5728 Claw \u4E2D\u70B9\u51FB\u201C\u542F\u52A8\u670D\u52A1\u201D\uFF0C\u6216\u5728\u8BE5\u4E3B\u673A\u542F\u52A8 opencode serve\uFF1B\u8BF7\u68C0\u67E5\u670D\u52A1\u5730\u5740\u548C\u5BC6\u7801\u3002` }
|
|
14682
14704
|
};
|
|
14683
14705
|
}
|
|
14684
14706
|
async start() {
|
|
@@ -14687,7 +14709,8 @@ var Runtime = class {
|
|
|
14687
14709
|
});
|
|
14688
14710
|
}
|
|
14689
14711
|
async launch() {
|
|
14690
|
-
|
|
14712
|
+
await this.restoreEndpoint();
|
|
14713
|
+
let url = new URL(this.baseUrl);
|
|
14691
14714
|
const endpoint = new URL("/api/info", url);
|
|
14692
14715
|
let response;
|
|
14693
14716
|
try {
|
|
@@ -14697,7 +14720,12 @@ var Runtime = class {
|
|
|
14697
14720
|
}
|
|
14698
14721
|
if (response) {
|
|
14699
14722
|
if (response.ok) return this.status();
|
|
14700
|
-
|
|
14723
|
+
if ((response.status === 401 || response.status === 403) && url.origin === new URL(this.config.tool.baseUrl).origin) {
|
|
14724
|
+
url = await this.reserveFallbackUrl(url);
|
|
14725
|
+
this.endpoint = url.origin;
|
|
14726
|
+
} else {
|
|
14727
|
+
throw new Error(`OpenCode \u5730\u5740 ${url.origin} \u5DF2\u6709 HTTP \u670D\u52A1\uFF0C\u4F46\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF08HTTP ${response.status}\uFF09\uFF1B\u8BF7\u68C0\u67E5\u670D\u52A1\u5BC6\u7801\u6216\u7AEF\u53E3`);
|
|
14728
|
+
}
|
|
14701
14729
|
}
|
|
14702
14730
|
await mkdir4(this.config.stateDirectory, { recursive: true, mode: 448 });
|
|
14703
14731
|
const logPath = join5(this.config.stateDirectory, "opencode-serve.log");
|
|
@@ -14719,7 +14747,20 @@ var Runtime = class {
|
|
|
14719
14747
|
});
|
|
14720
14748
|
child.unref();
|
|
14721
14749
|
} catch (error) {
|
|
14750
|
+
this.endpoint = this.config.tool.baseUrl;
|
|
14722
14751
|
throw new Error(`\u65E0\u6CD5\u6267\u884C OpenCode \u547D\u4EE4 ${this.config.tool.command}\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
14752
|
+
}
|
|
14753
|
+
try {
|
|
14754
|
+
await writeFile4(this.endpointStatePath, JSON.stringify({
|
|
14755
|
+
configuredBaseUrl: this.config.tool.baseUrl,
|
|
14756
|
+
baseUrl: url.origin,
|
|
14757
|
+
passwordHash: this.passwordHash,
|
|
14758
|
+
pid: child.pid
|
|
14759
|
+
}, null, 2), { mode: 384 });
|
|
14760
|
+
} catch (error) {
|
|
14761
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
14762
|
+
this.endpoint = this.config.tool.baseUrl;
|
|
14763
|
+
throw new Error(`\u65E0\u6CD5\u4FDD\u5B58 OpenCode \u8FD0\u884C\u7AEF\u70B9\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
14723
14764
|
} finally {
|
|
14724
14765
|
await log.close();
|
|
14725
14766
|
}
|
|
@@ -14730,8 +14771,40 @@ var Runtime = class {
|
|
|
14730
14771
|
await new Promise((resolve4) => setTimeout(resolve4, 250));
|
|
14731
14772
|
}
|
|
14732
14773
|
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
14774
|
+
await rm(this.endpointStatePath, { force: true }).catch(() => {
|
|
14775
|
+
});
|
|
14776
|
+
this.endpoint = this.config.tool.baseUrl;
|
|
14733
14777
|
throw new Error(`OpenCode \u542F\u52A8\u5931\u8D25\u6216\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF1B\u8BF7\u68C0\u67E5 ${logPath}\u3001\u670D\u52A1\u5730\u5740\u548C\u5BC6\u7801`);
|
|
14734
14778
|
}
|
|
14779
|
+
async restoreEndpoint() {
|
|
14780
|
+
if (this.endpointRestored) return;
|
|
14781
|
+
this.endpointRestored = true;
|
|
14782
|
+
this.endpoint = this.config.tool.baseUrl;
|
|
14783
|
+
try {
|
|
14784
|
+
const saved = JSON.parse(await readFile5(this.endpointStatePath, "utf8"));
|
|
14785
|
+
if (saved.configuredBaseUrl !== this.config.tool.baseUrl || saved.passwordHash !== this.passwordHash || typeof saved.baseUrl !== "string") return;
|
|
14786
|
+
const url = new URL(saved.baseUrl);
|
|
14787
|
+
if (url.protocol !== "http:" || !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)) return;
|
|
14788
|
+
this.endpoint = url.origin;
|
|
14789
|
+
if (!await this.health()) this.endpoint = this.config.tool.baseUrl;
|
|
14790
|
+
} catch {
|
|
14791
|
+
}
|
|
14792
|
+
}
|
|
14793
|
+
async reserveFallbackUrl(configured) {
|
|
14794
|
+
const host = configured.hostname === "[::1]" ? "::1" : configured.hostname;
|
|
14795
|
+
const server = createServer();
|
|
14796
|
+
await new Promise((resolve4, reject) => {
|
|
14797
|
+
server.once("error", reject);
|
|
14798
|
+
server.listen(0, host, resolve4);
|
|
14799
|
+
});
|
|
14800
|
+
const address = server.address();
|
|
14801
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
14802
|
+
await new Promise((resolve4, reject) => server.close((error) => error ? reject(error) : resolve4()));
|
|
14803
|
+
if (!port) throw new Error("\u65E0\u6CD5\u4E3A OpenCode \u5206\u914D\u5907\u7528\u56DE\u73AF\u7AEF\u53E3");
|
|
14804
|
+
const fallback = new URL(configured.origin);
|
|
14805
|
+
fallback.port = String(port);
|
|
14806
|
+
return fallback;
|
|
14807
|
+
}
|
|
14735
14808
|
async stop() {
|
|
14736
14809
|
throw new Error("OpenCode \u670D\u52A1\u7531\u5916\u90E8\u7BA1\u7406\uFF0CProxy \u4E0D\u652F\u6301\u505C\u6B62");
|
|
14737
14810
|
}
|
|
@@ -16014,7 +16087,7 @@ var ZcodeAdapter = class {
|
|
|
16014
16087
|
|
|
16015
16088
|
// src/tools/pi.ts
|
|
16016
16089
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
16017
|
-
import { readFile as
|
|
16090
|
+
import { readFile as readFile6, readdir as readdir3, rm as rm2 } from "node:fs/promises";
|
|
16018
16091
|
import { dirname as dirname5 } from "node:path";
|
|
16019
16092
|
var OPERATIONS3 = [
|
|
16020
16093
|
...DAEMON_OPERATIONS,
|
|
@@ -16260,8 +16333,8 @@ var PiAdapter = class {
|
|
|
16260
16333
|
this.deleting.add(this.key(project, id));
|
|
16261
16334
|
try {
|
|
16262
16335
|
await this.runtime.closeSession(project, id);
|
|
16263
|
-
await
|
|
16264
|
-
await
|
|
16336
|
+
await rm2(this.runtime.sessionFile(project, id), { force: true });
|
|
16337
|
+
await rm2(this.metadataPath(project, id));
|
|
16265
16338
|
this.clearQuestions(project, id);
|
|
16266
16339
|
} finally {
|
|
16267
16340
|
this.deleting.delete(this.key(project, id));
|
|
@@ -16294,7 +16367,7 @@ var PiAdapter = class {
|
|
|
16294
16367
|
for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
|
|
16295
16368
|
const upload = await this.uploads.attachment(project, String(uploadId), id);
|
|
16296
16369
|
if (["image/png", "image/jpeg", "image/webp", "image/gif"].includes(upload.mime)) {
|
|
16297
|
-
images.push({ type: "image", data: (await
|
|
16370
|
+
images.push({ type: "image", data: (await readFile6(await this.workspace.path(project, upload.destination))).toString("base64"), mimeType: upload.mime });
|
|
16298
16371
|
} else lines.push(`Attached file: ${upload.destination} (${upload.name}, ${upload.mime}). Read this file if relevant to the task.`);
|
|
16299
16372
|
}
|
|
16300
16373
|
if (!lines.length && !images.length) throw new Error("Message or attachments required");
|
|
@@ -16484,14 +16557,55 @@ function createAdapter(runtime, workspace, uploads, emit) {
|
|
|
16484
16557
|
return new OpenCodeAdapter(runtime, workspace, uploads, forward);
|
|
16485
16558
|
}
|
|
16486
16559
|
|
|
16560
|
+
// src/hub-channel-compatibility.ts
|
|
16561
|
+
var HubChannelCompatibility = class {
|
|
16562
|
+
enabled = false;
|
|
16563
|
+
filesAttemptedAt = 0;
|
|
16564
|
+
filesFailures = 0;
|
|
16565
|
+
filesAttempt(now = Date.now()) {
|
|
16566
|
+
this.filesAttemptedAt = now;
|
|
16567
|
+
}
|
|
16568
|
+
filesWelcome() {
|
|
16569
|
+
this.filesFailures = 0;
|
|
16570
|
+
}
|
|
16571
|
+
controlClosed(filesPending, now = Date.now()) {
|
|
16572
|
+
if (this.enabled || !filesPending || now - this.filesAttemptedAt >= 5e3) return false;
|
|
16573
|
+
this.enabled = true;
|
|
16574
|
+
return true;
|
|
16575
|
+
}
|
|
16576
|
+
filesClosedBeforeWelcome(stopping) {
|
|
16577
|
+
if (this.enabled || stopping || ++this.filesFailures < 3) return false;
|
|
16578
|
+
this.enabled = true;
|
|
16579
|
+
return true;
|
|
16580
|
+
}
|
|
16581
|
+
};
|
|
16582
|
+
|
|
16583
|
+
// src/proxy-binding.ts
|
|
16584
|
+
var PROXY_REPLACED_CLOSE_CODE = 4009;
|
|
16585
|
+
var ProxyBinding = class {
|
|
16586
|
+
replaced = false;
|
|
16587
|
+
get active() {
|
|
16588
|
+
return !this.replaced;
|
|
16589
|
+
}
|
|
16590
|
+
observeClose(code) {
|
|
16591
|
+
if (code !== PROXY_REPLACED_CLOSE_CODE) return false;
|
|
16592
|
+
const first = !this.replaced;
|
|
16593
|
+
this.replaced = true;
|
|
16594
|
+
return first;
|
|
16595
|
+
}
|
|
16596
|
+
};
|
|
16597
|
+
|
|
16487
16598
|
// src/daemon.ts
|
|
16488
16599
|
async function runDaemon(configPath) {
|
|
16489
16600
|
const config = await loadConfig(configPath);
|
|
16490
16601
|
await mkdir7(config.stateDirectory, { recursive: true, mode: 448 });
|
|
16491
16602
|
const workspace = new Workspace(config.projects), runtime = createRuntime(config), uploads = new Uploads(workspace, join10(config.stateDirectory, "uploads")), journal = new Journal(join10(config.stateDirectory, "journal"));
|
|
16492
16603
|
const epoch = randomUUID12();
|
|
16493
|
-
let stopping = false, botId = "";
|
|
16604
|
+
let stopping = false, botId = "", filesStarted = false;
|
|
16605
|
+
const hubChannels = new HubChannelCompatibility();
|
|
16606
|
+
const binding = new ProxyBinding();
|
|
16494
16607
|
const sockets = /* @__PURE__ */ new Set();
|
|
16608
|
+
let filesSocket;
|
|
16495
16609
|
const events = new EventStream(epoch, () => {
|
|
16496
16610
|
for (const p of config.projects) emit(p.id, "octopus.resync", {});
|
|
16497
16611
|
});
|
|
@@ -16539,13 +16653,17 @@ async function runDaemon(configPath) {
|
|
|
16539
16653
|
return adapter.dispatch(c);
|
|
16540
16654
|
}
|
|
16541
16655
|
function connect(files, attempt = 0) {
|
|
16542
|
-
if (stopping) return;
|
|
16656
|
+
if (stopping || !binding.active || files && hubChannels.enabled) return;
|
|
16543
16657
|
const url = new URL(files ? "/ws/proxy-files" : "/ws/proxy", config.hubUrl);
|
|
16544
16658
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
16545
|
-
const headers = { Authorization: `SecretKey ${config.secretKey}`, "X-Octopus-Bot-Id": config.botId };
|
|
16659
|
+
const headers = { Authorization: `SecretKey ${config.secretKey}`, "X-Octopus-Bot-Id": config.botId, "X-Octopus-Proxy-Instance-Id": epoch };
|
|
16546
16660
|
const socket = new WebSocket2(url, { headers, maxPayload: 3 * 1024 * 1024 });
|
|
16547
16661
|
sockets.add(socket);
|
|
16548
|
-
|
|
16662
|
+
if (files) {
|
|
16663
|
+
filesSocket = socket;
|
|
16664
|
+
hubChannels.filesAttempt();
|
|
16665
|
+
}
|
|
16666
|
+
let lastPong = Date.now(), welcomed = false;
|
|
16549
16667
|
const heartbeat = setInterval(() => {
|
|
16550
16668
|
if (Date.now() - lastPong > 45e3) socket.terminate();
|
|
16551
16669
|
else if (socket.readyState === WebSocket2.OPEN) socket.send(JSON.stringify({ type: "ping", proxyVersion }));
|
|
@@ -16566,11 +16684,17 @@ async function runDaemon(configPath) {
|
|
|
16566
16684
|
return;
|
|
16567
16685
|
}
|
|
16568
16686
|
if (value.type === "welcome") {
|
|
16687
|
+
welcomed = true;
|
|
16688
|
+
if (files) hubChannels.filesWelcome();
|
|
16569
16689
|
botId = value.botId;
|
|
16570
16690
|
uploads.limits = value.limits;
|
|
16571
16691
|
if (!files) {
|
|
16572
16692
|
events.bind(socket, value.resume);
|
|
16573
16693
|
for (const p of config.projects) emit(p.id, "octopus.resync", {});
|
|
16694
|
+
if (!filesStarted && !hubChannels.enabled) {
|
|
16695
|
+
filesStarted = true;
|
|
16696
|
+
connect(true);
|
|
16697
|
+
}
|
|
16574
16698
|
}
|
|
16575
16699
|
return;
|
|
16576
16700
|
}
|
|
@@ -16583,18 +16707,32 @@ async function runDaemon(configPath) {
|
|
|
16583
16707
|
console.error("Rejected message:", error instanceof Error ? error.message : "invalid message");
|
|
16584
16708
|
}
|
|
16585
16709
|
});
|
|
16586
|
-
socket.on("
|
|
16587
|
-
});
|
|
16588
|
-
socket.on("close", () => {
|
|
16710
|
+
socket.on("unexpected-response", (_request, response) => console.error(`Hub ${files ? "files" : "control"} handshake failed: HTTP ${response.statusCode}`));
|
|
16711
|
+
socket.on("error", (error) => console.error(`Hub ${files ? "files" : "control"} connection: ${error.message}`));
|
|
16712
|
+
socket.on("close", (code, reason) => {
|
|
16589
16713
|
sockets.delete(socket);
|
|
16590
16714
|
clearInterval(heartbeat);
|
|
16591
16715
|
events.unbind(socket);
|
|
16592
|
-
if (
|
|
16716
|
+
if (files && filesSocket === socket) filesSocket = void 0;
|
|
16717
|
+
const replaced = binding.observeClose(code);
|
|
16718
|
+
if (replaced) {
|
|
16719
|
+
console.error("This bot was claimed by another Proxy instance; automatic reconnection is disabled until this service is restarted");
|
|
16720
|
+
for (const peer of sockets) peer.close();
|
|
16721
|
+
}
|
|
16722
|
+
const collision = binding.active && !files && hubChannels.controlClosed(filesSocket !== void 0 && filesSocket.readyState !== WebSocket2.CLOSED);
|
|
16723
|
+
const rejectedFilesChannel = binding.active && files && !welcomed && hubChannels.filesClosedBeforeWelcome(stopping);
|
|
16724
|
+
if (collision || rejectedFilesChannel) {
|
|
16725
|
+
console.warn("Hub routes proxy-files as the control channel; using compatibility mode with one control connection");
|
|
16726
|
+
filesSocket?.close();
|
|
16727
|
+
filesSocket = void 0;
|
|
16728
|
+
} else if (!stopping && binding.active && code !== 1e3) {
|
|
16729
|
+
console.warn(`Hub ${files ? "files" : "control"} connection closed: ${code}${reason.length ? ` ${reason.toString()}` : ""}`);
|
|
16730
|
+
}
|
|
16731
|
+
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);
|
|
16593
16732
|
});
|
|
16594
16733
|
}
|
|
16595
16734
|
await runtime.init().catch((error) => console.error(`${runtime.kind} startup:`, error.message));
|
|
16596
16735
|
connect(false);
|
|
16597
|
-
connect(true);
|
|
16598
16736
|
const scan = setInterval(async () => {
|
|
16599
16737
|
for (const p of config.projects) await artifacts.scan(p.id).catch(() => {
|
|
16600
16738
|
});
|
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
runDaemon,
|
|
23
23
|
supportedPlatform,
|
|
24
24
|
systemdUnitPath
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-KYGWORYZ.js";
|
|
26
26
|
|
|
27
27
|
// src/cli.ts
|
|
28
28
|
import { spawn } from "node:child_process";
|
|
@@ -502,7 +502,7 @@ async function runOnboard(opts) {
|
|
|
502
502
|
if (error.code === "ENOENT") return false;
|
|
503
503
|
throw error;
|
|
504
504
|
});
|
|
505
|
-
const existing = opts.interactive && configExists ? await loadConfig(opts.configPath) : void 0;
|
|
505
|
+
const existing = opts.interactive && configExists ? await loadConfig(opts.configPath, { provisionOpenCodePassword: true }) : void 0;
|
|
506
506
|
let hubUrl = opts.hubUrl || existing?.hubUrl || DEFAULT_HUB_URL, botId = opts.botId ?? existing?.botId ?? "", secretKey = opts.secretKey ?? existing?.secretKey ?? "", stateDirectory = opts.stateDirectory ?? existing?.stateDirectory ?? defaultStateDirectory();
|
|
507
507
|
let toolKind = opts.toolKind ?? existing?.tool.kind ?? "opencode";
|
|
508
508
|
let toolCommand = opts.toolCommand ?? (toolKind === existing?.tool.kind ? existing.tool.command : "");
|
package/dist/main.js
CHANGED