@skylandnpm/octopus-proxy 0.1.23 → 0.1.27
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 +1 -1
- package/dist/{chunk-KYGWORYZ.js → chunk-X2VENWTX.js} +256 -58
- package/dist/cli.js +1 -1
- package/dist/main.js +1 -1
- package/package.json +1 -1
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
|
|
18
|
+
Proxy 始终使用独立的控制与文件 WebSocket;文件读取和上传不会回退到控制通道。两个通道会同时连接并各自重连,Hub 只会把文件命令发送到同一 Proxy 实例的已就绪文件通道。若旧版 Hub 或反向代理没有正确转发 `/ws/proxy-files`,Claw 会明确显示文件通道离线,此时应修复路由配置。同一套 bot ID 和 Secret 被另一台 Proxy 主机连接后,Hub 会让新实例接管,旧实例会同时关闭两个通道并停止自动重连;若要让旧实例重新接管,必须显式重启其服务。
|
|
19
19
|
|
|
20
20
|
添加授权项目时,填写项目 ID 后会询问是否继续添加;直接回车即可结束添加并继续保存配置。
|
|
21
21
|
|
|
@@ -11611,13 +11611,19 @@ var ToolSchema = z.object({
|
|
|
11611
11611
|
if (tool.kind === "opencode" && tool.mode === "managed") context.addIssue({ code: z.ZodIssueCode.custom, path: ["mode"], message: "OpenCode v2 \u4EC5\u652F\u6301 external \u6A21\u5F0F" });
|
|
11612
11612
|
if (tool.kind !== "opencode" && tool.mode === "external") context.addIssue({ code: z.ZodIssueCode.custom, path: ["mode"], message: "external mode is only supported for opencode" });
|
|
11613
11613
|
}).transform((tool) => ({ ...tool, mode: tool.kind === "opencode" ? "external" : "managed", autoStart: tool.autoStart ?? true }));
|
|
11614
|
+
var ProjectSchema = z.object({
|
|
11615
|
+
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
11616
|
+
name: z.string().min(1),
|
|
11617
|
+
directory: z.string().min(1),
|
|
11618
|
+
specDirectories: z.array(z.string()).default(["specs", "docs"])
|
|
11619
|
+
});
|
|
11614
11620
|
var ConfigSchema = z.object({
|
|
11615
11621
|
hubUrl: z.string().url(),
|
|
11616
11622
|
botId: z.string().uuid(),
|
|
11617
11623
|
secretKey: z.string().min(20),
|
|
11618
11624
|
stateDirectory: z.string().min(1),
|
|
11619
11625
|
allowInsecureLocalhost: z.boolean().default(false),
|
|
11620
|
-
projects: z.array(
|
|
11626
|
+
projects: z.array(ProjectSchema).min(1),
|
|
11621
11627
|
tool: ToolSchema.default({ kind: "opencode" })
|
|
11622
11628
|
});
|
|
11623
11629
|
async function loadConfig(path, options = {}) {
|
|
@@ -11682,6 +11688,9 @@ var Operations = [
|
|
|
11682
11688
|
"runtime.restart",
|
|
11683
11689
|
"runtime.logs",
|
|
11684
11690
|
"workspace.projects",
|
|
11691
|
+
"workspace.projects.add",
|
|
11692
|
+
"workspace.projects.update",
|
|
11693
|
+
"workspace.projects.remove",
|
|
11685
11694
|
"workspace.list",
|
|
11686
11695
|
"workspace.read",
|
|
11687
11696
|
"workspace.search",
|
|
@@ -12459,6 +12468,9 @@ var EventStream = class {
|
|
|
12459
12468
|
|
|
12460
12469
|
// src/proxy-snapshot.ts
|
|
12461
12470
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
12471
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
12472
|
+
import { promisify as promisify2 } from "node:util";
|
|
12473
|
+
var execute2 = promisify2(execFile2);
|
|
12462
12474
|
function record(value) {
|
|
12463
12475
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12464
12476
|
}
|
|
@@ -12478,10 +12490,32 @@ function publicModels(value) {
|
|
|
12478
12490
|
}
|
|
12479
12491
|
return result;
|
|
12480
12492
|
}
|
|
12493
|
+
async function gitBranch(directory) {
|
|
12494
|
+
const git = async (...args) => (await execute2("git", ["--no-optional-locks", ...args], {
|
|
12495
|
+
cwd: directory,
|
|
12496
|
+
encoding: "utf8",
|
|
12497
|
+
timeout: 3e3,
|
|
12498
|
+
maxBuffer: 64 * 1024
|
|
12499
|
+
})).stdout.trim();
|
|
12500
|
+
try {
|
|
12501
|
+
const branch = await git("branch", "--show-current");
|
|
12502
|
+
if (branch) return branch;
|
|
12503
|
+
const commit = await git("rev-parse", "--short", "HEAD");
|
|
12504
|
+
return commit ? `HEAD@${commit}` : void 0;
|
|
12505
|
+
} catch {
|
|
12506
|
+
return void 0;
|
|
12507
|
+
}
|
|
12508
|
+
}
|
|
12509
|
+
async function publicProjects(config) {
|
|
12510
|
+
return Promise.all(config.projects.map(async ({ id, name, directory, specDirectories }) => {
|
|
12511
|
+
const branch = await gitBranch(directory);
|
|
12512
|
+
return { id, name, directory, specDirectories, ...branch ? { gitBranch: branch } : {} };
|
|
12513
|
+
}));
|
|
12514
|
+
}
|
|
12481
12515
|
async function buildProxySnapshot(config, runtime, adapter, requestedProjectId) {
|
|
12482
12516
|
const capabilities = adapter.supportedOperations();
|
|
12483
12517
|
const status = record(await runtime.status());
|
|
12484
|
-
const projects =
|
|
12518
|
+
const projects = await publicProjects(config);
|
|
12485
12519
|
const projectId = projects.find((project) => project.id === requestedProjectId)?.id ?? projects[0]?.id;
|
|
12486
12520
|
const models = [];
|
|
12487
12521
|
let modelCatalogStatus = capabilities.includes("configuration.models") ? "unavailable" : "unsupported";
|
|
@@ -14518,6 +14552,16 @@ var OpenCodeAdapter = class {
|
|
|
14518
14552
|
};
|
|
14519
14553
|
void loop();
|
|
14520
14554
|
}
|
|
14555
|
+
removeProject(project) {
|
|
14556
|
+
this.streams.get(project)?.abort();
|
|
14557
|
+
this.streams.delete(project);
|
|
14558
|
+
for (const [key, terminal] of this.terminals) {
|
|
14559
|
+
if (!key.startsWith(`${project}:`)) continue;
|
|
14560
|
+
terminal.close();
|
|
14561
|
+
this.terminals.delete(key);
|
|
14562
|
+
this.terminalOutput.delete(key);
|
|
14563
|
+
}
|
|
14564
|
+
}
|
|
14521
14565
|
async terminal(project, op, p) {
|
|
14522
14566
|
const id = this.identifier(p.id), key = `${project}:${id}`;
|
|
14523
14567
|
await this.data(project, "GET", `/api/pty/${id}`);
|
|
@@ -14831,8 +14875,8 @@ function zcodeEnvironment(command) {
|
|
|
14831
14875
|
}
|
|
14832
14876
|
|
|
14833
14877
|
// src/tools/stdio-runtime.ts
|
|
14834
|
-
import { spawn as spawn2, execFile as
|
|
14835
|
-
import { promisify as
|
|
14878
|
+
import { spawn as spawn2, execFile as execFile3 } from "node:child_process";
|
|
14879
|
+
import { promisify as promisify3 } from "node:util";
|
|
14836
14880
|
import { mkdir as mkdir5, open as open5 } from "node:fs/promises";
|
|
14837
14881
|
import { join as join7 } from "node:path";
|
|
14838
14882
|
|
|
@@ -15026,7 +15070,7 @@ function wrapEnvelope(frame) {
|
|
|
15026
15070
|
}
|
|
15027
15071
|
|
|
15028
15072
|
// src/tools/stdio-runtime.ts
|
|
15029
|
-
var run =
|
|
15073
|
+
var run = promisify3(execFile3);
|
|
15030
15074
|
var StdioRuntime = class {
|
|
15031
15075
|
constructor(config) {
|
|
15032
15076
|
this.config = config;
|
|
@@ -15100,6 +15144,24 @@ var StdioRuntime = class {
|
|
|
15100
15144
|
this.refreshing.delete(projectId);
|
|
15101
15145
|
}
|
|
15102
15146
|
}
|
|
15147
|
+
async addProject(projectId) {
|
|
15148
|
+
if (!this.wanted) return;
|
|
15149
|
+
const project = this.config.projects.find((entry) => entry.id === projectId);
|
|
15150
|
+
if (project) await this.track(project.id, project.directory);
|
|
15151
|
+
}
|
|
15152
|
+
async removeProject(projectId) {
|
|
15153
|
+
await this.tracking.get(projectId)?.catch(() => {
|
|
15154
|
+
});
|
|
15155
|
+
this.refreshing.add(projectId);
|
|
15156
|
+
try {
|
|
15157
|
+
this.kill(projectId);
|
|
15158
|
+
} finally {
|
|
15159
|
+
this.refreshing.delete(projectId);
|
|
15160
|
+
this.failures.delete(projectId);
|
|
15161
|
+
this.attempts.delete(projectId);
|
|
15162
|
+
this.logPaths.delete(projectId);
|
|
15163
|
+
}
|
|
15164
|
+
}
|
|
15103
15165
|
async logs() {
|
|
15104
15166
|
const chunks = [];
|
|
15105
15167
|
for (const [projectId, logPath] of this.logPaths) {
|
|
@@ -15271,6 +15333,9 @@ var DAEMON_OPERATIONS = [
|
|
|
15271
15333
|
"runtime.restart",
|
|
15272
15334
|
"runtime.logs",
|
|
15273
15335
|
"workspace.projects",
|
|
15336
|
+
"workspace.projects.add",
|
|
15337
|
+
"workspace.projects.update",
|
|
15338
|
+
"workspace.projects.remove",
|
|
15274
15339
|
"workspace.list",
|
|
15275
15340
|
"workspace.read",
|
|
15276
15341
|
"workspace.search",
|
|
@@ -16384,11 +16449,11 @@ var PiAdapter = class {
|
|
|
16384
16449
|
};
|
|
16385
16450
|
|
|
16386
16451
|
// src/tools/pi-runtime.ts
|
|
16387
|
-
import { execFile as
|
|
16452
|
+
import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
|
|
16388
16453
|
import { mkdir as mkdir6, open as open6 } from "node:fs/promises";
|
|
16389
16454
|
import { join as join9 } from "node:path";
|
|
16390
|
-
import { promisify as
|
|
16391
|
-
var run2 =
|
|
16455
|
+
import { promisify as promisify4 } from "node:util";
|
|
16456
|
+
var run2 = promisify4(execFile4);
|
|
16392
16457
|
var PiRuntime = class {
|
|
16393
16458
|
constructor(config) {
|
|
16394
16459
|
this.config = config;
|
|
@@ -16428,6 +16493,14 @@ var PiRuntime = class {
|
|
|
16428
16493
|
}
|
|
16429
16494
|
detach() {
|
|
16430
16495
|
}
|
|
16496
|
+
async addProject(projectId) {
|
|
16497
|
+
if (this.wanted) await this.connect(projectId);
|
|
16498
|
+
}
|
|
16499
|
+
async removeProject(projectId) {
|
|
16500
|
+
const prefix = `${projectId}:`;
|
|
16501
|
+
await Promise.allSettled([...this.launching].filter(([key]) => key.startsWith(prefix)).map(([, launch]) => launch));
|
|
16502
|
+
await Promise.all([...this.processes].filter(([, entry]) => entry.projectId === projectId).map(([key]) => this.kill(key)));
|
|
16503
|
+
}
|
|
16431
16504
|
async status() {
|
|
16432
16505
|
const children = [...this.processes.values()].map(({ child, rpc, projectId, sessionId }) => ({
|
|
16433
16506
|
projectId,
|
|
@@ -16557,29 +16630,6 @@ function createAdapter(runtime, workspace, uploads, emit) {
|
|
|
16557
16630
|
return new OpenCodeAdapter(runtime, workspace, uploads, forward);
|
|
16558
16631
|
}
|
|
16559
16632
|
|
|
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
16633
|
// src/proxy-binding.ts
|
|
16584
16634
|
var PROXY_REPLACED_CLOSE_CODE = 4009;
|
|
16585
16635
|
var ProxyBinding = class {
|
|
@@ -16595,17 +16645,145 @@ var ProxyBinding = class {
|
|
|
16595
16645
|
}
|
|
16596
16646
|
};
|
|
16597
16647
|
|
|
16648
|
+
// src/project-registry.ts
|
|
16649
|
+
import { realpath as realpath6, readFile as readFile7, stat as stat5 } from "node:fs/promises";
|
|
16650
|
+
import { isAbsolute as isAbsolute4 } from "node:path";
|
|
16651
|
+
var ProjectMutationSchema = ProjectSchema.extend({
|
|
16652
|
+
name: ProjectSchema.shape.name.trim().max(120),
|
|
16653
|
+
specDirectories: ProjectSchema.shape.specDirectories.removeDefault().element.min(1).array().max(20).default(["specs", "docs"])
|
|
16654
|
+
}).strict();
|
|
16655
|
+
var ProjectUpdateSchema = ProjectMutationSchema.omit({ specDirectories: true });
|
|
16656
|
+
function rawConfig(value) {
|
|
16657
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !Array.isArray(value.projects)) {
|
|
16658
|
+
throw new Error("Proxy \u914D\u7F6E\u6587\u4EF6\u7F3A\u5C11 projects \u6570\u7EC4");
|
|
16659
|
+
}
|
|
16660
|
+
return value;
|
|
16661
|
+
}
|
|
16662
|
+
function rawProjectId(value) {
|
|
16663
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.id === "string" ? value.id : void 0;
|
|
16664
|
+
}
|
|
16665
|
+
var ProjectRegistry = class {
|
|
16666
|
+
constructor(configPath, config, runtime, adapter) {
|
|
16667
|
+
this.configPath = configPath;
|
|
16668
|
+
this.config = config;
|
|
16669
|
+
this.runtime = runtime;
|
|
16670
|
+
this.adapter = adapter;
|
|
16671
|
+
}
|
|
16672
|
+
pending = Promise.resolve();
|
|
16673
|
+
add(payload) {
|
|
16674
|
+
return this.serial(() => this.addUnlocked(payload));
|
|
16675
|
+
}
|
|
16676
|
+
remove(payload) {
|
|
16677
|
+
return this.serial(() => this.removeUnlocked(payload));
|
|
16678
|
+
}
|
|
16679
|
+
update(payload) {
|
|
16680
|
+
return this.serial(() => this.updateUnlocked(payload));
|
|
16681
|
+
}
|
|
16682
|
+
serial(work) {
|
|
16683
|
+
const result = this.pending.catch(() => {
|
|
16684
|
+
}).then(work);
|
|
16685
|
+
this.pending = result;
|
|
16686
|
+
return result;
|
|
16687
|
+
}
|
|
16688
|
+
async bestEffort(work) {
|
|
16689
|
+
try {
|
|
16690
|
+
await work();
|
|
16691
|
+
} catch {
|
|
16692
|
+
}
|
|
16693
|
+
}
|
|
16694
|
+
async readCurrent() {
|
|
16695
|
+
const current = rawConfig(JSON.parse(await readFile7(this.configPath, "utf8")));
|
|
16696
|
+
const diskProjects = current.projects.map((value) => {
|
|
16697
|
+
const project = ProjectSchema.parse(value);
|
|
16698
|
+
return { ...project, directory: expandHome(project.directory) };
|
|
16699
|
+
});
|
|
16700
|
+
if (JSON.stringify(diskProjects) !== JSON.stringify(this.config.projects)) {
|
|
16701
|
+
throw new Error("Proxy \u914D\u7F6E\u5DF2\u5728\u4E3B\u673A\u4E0A\u88AB\u4FEE\u6539\uFF1B\u8BF7\u91CD\u542F Proxy \u540E\u518D\u7BA1\u7406\u9879\u76EE");
|
|
16702
|
+
}
|
|
16703
|
+
return current;
|
|
16704
|
+
}
|
|
16705
|
+
async addUnlocked(payload) {
|
|
16706
|
+
const parsed = ProjectMutationSchema.parse(payload);
|
|
16707
|
+
const directory = expandHome(parsed.directory);
|
|
16708
|
+
if (!isAbsolute4(directory)) throw new Error("\u9879\u76EE\u76EE\u5F55\u5FC5\u987B\u662F Proxy \u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u8DEF\u5F84");
|
|
16709
|
+
const info = await stat5(directory).catch(() => void 0);
|
|
16710
|
+
if (!info?.isDirectory()) throw new Error(`\u9879\u76EE\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55\uFF1A${directory}`);
|
|
16711
|
+
const canonical = await realpath6(directory);
|
|
16712
|
+
if (this.config.projects.some((project2) => project2.id === parsed.id)) throw new Error(`\u9879\u76EE ID \u5DF2\u5B58\u5728\uFF1A${parsed.id}`);
|
|
16713
|
+
for (const project2 of this.config.projects) {
|
|
16714
|
+
if (await realpath6(project2.directory).catch(() => project2.directory) === canonical) throw new Error("\u8BE5\u76EE\u5F55\u5DF2\u7ECF\u662F\u6388\u6743\u9879\u76EE");
|
|
16715
|
+
}
|
|
16716
|
+
const project = { ...parsed, directory: canonical };
|
|
16717
|
+
const raw = await this.readCurrent();
|
|
16718
|
+
await atomicJson(this.configPath, { ...raw, projects: [...raw.projects, project] });
|
|
16719
|
+
this.config.projects.push(project);
|
|
16720
|
+
await this.bestEffort(() => this.runtime.addProject?.(project.id));
|
|
16721
|
+
return publicProjects(this.config);
|
|
16722
|
+
}
|
|
16723
|
+
async updateUnlocked(payload) {
|
|
16724
|
+
const parsed = ProjectUpdateSchema.parse(payload);
|
|
16725
|
+
const index = this.config.projects.findIndex((project) => project.id === parsed.id);
|
|
16726
|
+
if (index < 0) throw new Error(`\u672A\u77E5\u6388\u6743\u9879\u76EE\uFF1A${parsed.id}`);
|
|
16727
|
+
const directory = expandHome(parsed.directory);
|
|
16728
|
+
if (!isAbsolute4(directory)) throw new Error("\u9879\u76EE\u76EE\u5F55\u5FC5\u987B\u662F Proxy \u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u8DEF\u5F84");
|
|
16729
|
+
const info = await stat5(directory).catch(() => void 0);
|
|
16730
|
+
if (!info?.isDirectory()) throw new Error(`\u9879\u76EE\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55\uFF1A${directory}`);
|
|
16731
|
+
const canonical = await realpath6(directory);
|
|
16732
|
+
for (const project of this.config.projects) {
|
|
16733
|
+
if (project.id !== parsed.id && await realpath6(project.directory).catch(() => project.directory) === canonical) {
|
|
16734
|
+
throw new Error("\u8BE5\u76EE\u5F55\u5DF2\u7ECF\u662F\u6388\u6743\u9879\u76EE");
|
|
16735
|
+
}
|
|
16736
|
+
}
|
|
16737
|
+
const current = this.config.projects[index];
|
|
16738
|
+
const oldDirectory = await realpath6(current.directory).catch(() => current.directory);
|
|
16739
|
+
const directoryChanged = oldDirectory !== canonical;
|
|
16740
|
+
const raw = await this.readCurrent();
|
|
16741
|
+
await atomicJson(this.configPath, {
|
|
16742
|
+
...raw,
|
|
16743
|
+
projects: raw.projects.map((project) => rawProjectId(project) === parsed.id ? { ...project, name: parsed.name, directory: canonical } : project)
|
|
16744
|
+
});
|
|
16745
|
+
if (directoryChanged) {
|
|
16746
|
+
await Promise.all([
|
|
16747
|
+
this.bestEffort(() => this.adapter.removeProject?.(parsed.id)),
|
|
16748
|
+
this.bestEffort(() => this.runtime.removeProject?.(parsed.id))
|
|
16749
|
+
]);
|
|
16750
|
+
}
|
|
16751
|
+
this.config.projects[index] = { ...current, name: parsed.name, directory: canonical };
|
|
16752
|
+
if (directoryChanged) await this.bestEffort(() => this.runtime.addProject?.(parsed.id));
|
|
16753
|
+
return publicProjects(this.config);
|
|
16754
|
+
}
|
|
16755
|
+
async removeUnlocked(payload) {
|
|
16756
|
+
const keys = Object.keys(payload);
|
|
16757
|
+
if (keys.length !== 1 || keys[0] !== "id" || typeof payload.id !== "string" || !/^[a-zA-Z0-9_-]+$/.test(payload.id)) {
|
|
16758
|
+
throw new Error("\u5220\u9664\u9879\u76EE\u9700\u8981\u5408\u6CD5\u7684\u9879\u76EE ID");
|
|
16759
|
+
}
|
|
16760
|
+
const id = payload.id;
|
|
16761
|
+
if (this.config.projects.length === 1) throw new Error("\u81F3\u5C11\u9700\u8981\u4FDD\u7559\u4E00\u4E2A\u6388\u6743\u9879\u76EE");
|
|
16762
|
+
const index = this.config.projects.findIndex((project) => project.id === id);
|
|
16763
|
+
if (index < 0) throw new Error(`\u672A\u77E5\u6388\u6743\u9879\u76EE\uFF1A${id}`);
|
|
16764
|
+
const raw = await this.readCurrent();
|
|
16765
|
+
await atomicJson(this.configPath, { ...raw, projects: raw.projects.filter((project) => rawProjectId(project) !== id) });
|
|
16766
|
+
await Promise.all([
|
|
16767
|
+
this.bestEffort(() => this.adapter.removeProject?.(id)),
|
|
16768
|
+
this.bestEffort(() => this.runtime.removeProject?.(id))
|
|
16769
|
+
]);
|
|
16770
|
+
this.config.projects.splice(index, 1);
|
|
16771
|
+
return publicProjects(this.config);
|
|
16772
|
+
}
|
|
16773
|
+
};
|
|
16774
|
+
|
|
16598
16775
|
// src/daemon.ts
|
|
16599
16776
|
async function runDaemon(configPath) {
|
|
16600
16777
|
const config = await loadConfig(configPath);
|
|
16601
16778
|
await mkdir7(config.stateDirectory, { recursive: true, mode: 448 });
|
|
16602
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"));
|
|
16603
16780
|
const epoch = randomUUID12();
|
|
16604
|
-
let stopping = false, botId = ""
|
|
16605
|
-
const hubChannels = new HubChannelCompatibility();
|
|
16781
|
+
let stopping = false, botId = "";
|
|
16606
16782
|
const binding = new ProxyBinding();
|
|
16607
16783
|
const sockets = /* @__PURE__ */ new Set();
|
|
16608
|
-
let filesSocket;
|
|
16784
|
+
let controlSocket, filesSocket;
|
|
16785
|
+
let controlWelcome;
|
|
16786
|
+
let filesWelcomed = false, channelsActivated = false;
|
|
16609
16787
|
const events = new EventStream(epoch, () => {
|
|
16610
16788
|
for (const p of config.projects) emit(p.id, "octopus.resync", {});
|
|
16611
16789
|
});
|
|
@@ -16613,6 +16791,7 @@ async function runDaemon(configPath) {
|
|
|
16613
16791
|
events.publish({ type: "event", botId, projectId, event, data });
|
|
16614
16792
|
}
|
|
16615
16793
|
const adapter = createAdapter(runtime, workspace, uploads, emit), artifacts = new Artifacts(workspace, join10(config.stateDirectory, "artifacts"), emit);
|
|
16794
|
+
const projectRegistry = new ProjectRegistry(configPath, config, runtime, adapter);
|
|
16616
16795
|
async function dispatch(c) {
|
|
16617
16796
|
if (!adapter.supportedOperations().includes(c.operation)) throw new Error(`Unsupported ${runtime.kind} operation: ${c.operation}`);
|
|
16618
16797
|
const p = c.payload;
|
|
@@ -16622,7 +16801,22 @@ async function runDaemon(configPath) {
|
|
|
16622
16801
|
if (c.operation === "runtime.start") return runtime.start();
|
|
16623
16802
|
if (c.operation === "runtime.stop") return runtime.stop();
|
|
16624
16803
|
if (c.operation === "runtime.restart") return runtime.restart();
|
|
16625
|
-
if (c.operation === "workspace.projects") return config
|
|
16804
|
+
if (c.operation === "workspace.projects") return publicProjects(config);
|
|
16805
|
+
if (c.operation === "workspace.projects.add") {
|
|
16806
|
+
const result = await projectRegistry.add(p);
|
|
16807
|
+
emit(String(p.id), "octopus.resync", {});
|
|
16808
|
+
return result;
|
|
16809
|
+
}
|
|
16810
|
+
if (c.operation === "workspace.projects.update") {
|
|
16811
|
+
const result = await projectRegistry.update(p);
|
|
16812
|
+
emit(String(p.id), "octopus.resync", {});
|
|
16813
|
+
return result;
|
|
16814
|
+
}
|
|
16815
|
+
if (c.operation === "workspace.projects.remove") {
|
|
16816
|
+
const result = await projectRegistry.remove(p);
|
|
16817
|
+
emit(String(p.id), "octopus.resync", {});
|
|
16818
|
+
return result;
|
|
16819
|
+
}
|
|
16626
16820
|
const project = c.projectId;
|
|
16627
16821
|
if (!project) throw new Error("Project required");
|
|
16628
16822
|
workspace.project(project);
|
|
@@ -16652,8 +16846,14 @@ async function runDaemon(configPath) {
|
|
|
16652
16846
|
}
|
|
16653
16847
|
return adapter.dispatch(c);
|
|
16654
16848
|
}
|
|
16849
|
+
function activateChannels() {
|
|
16850
|
+
if (channelsActivated || !controlWelcome || !filesWelcomed || controlWelcome.socket.readyState !== WebSocket2.OPEN) return;
|
|
16851
|
+
channelsActivated = true;
|
|
16852
|
+
events.bind(controlWelcome.socket, controlWelcome.resume);
|
|
16853
|
+
for (const p of config.projects) emit(p.id, "octopus.resync", {});
|
|
16854
|
+
}
|
|
16655
16855
|
function connect(files, attempt = 0) {
|
|
16656
|
-
if (stopping || !binding.active
|
|
16856
|
+
if (stopping || !binding.active) return;
|
|
16657
16857
|
const url = new URL(files ? "/ws/proxy-files" : "/ws/proxy", config.hubUrl);
|
|
16658
16858
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
16659
16859
|
const headers = { Authorization: `SecretKey ${config.secretKey}`, "X-Octopus-Bot-Id": config.botId, "X-Octopus-Proxy-Instance-Id": epoch };
|
|
@@ -16661,9 +16861,9 @@ async function runDaemon(configPath) {
|
|
|
16661
16861
|
sockets.add(socket);
|
|
16662
16862
|
if (files) {
|
|
16663
16863
|
filesSocket = socket;
|
|
16664
|
-
|
|
16665
|
-
}
|
|
16666
|
-
let lastPong = Date.now()
|
|
16864
|
+
filesWelcomed = false;
|
|
16865
|
+
} else controlSocket = socket;
|
|
16866
|
+
let lastPong = Date.now();
|
|
16667
16867
|
const heartbeat = setInterval(() => {
|
|
16668
16868
|
if (Date.now() - lastPong > 45e3) socket.terminate();
|
|
16669
16869
|
else if (socket.readyState === WebSocket2.OPEN) socket.send(JSON.stringify({ type: "ping", proxyVersion }));
|
|
@@ -16684,18 +16884,11 @@ async function runDaemon(configPath) {
|
|
|
16684
16884
|
return;
|
|
16685
16885
|
}
|
|
16686
16886
|
if (value.type === "welcome") {
|
|
16687
|
-
welcomed = true;
|
|
16688
|
-
if (files) hubChannels.filesWelcome();
|
|
16689
16887
|
botId = value.botId;
|
|
16690
16888
|
uploads.limits = value.limits;
|
|
16691
|
-
if (
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
if (!filesStarted && !hubChannels.enabled) {
|
|
16695
|
-
filesStarted = true;
|
|
16696
|
-
connect(true);
|
|
16697
|
-
}
|
|
16698
|
-
}
|
|
16889
|
+
if (files) filesWelcomed = true;
|
|
16890
|
+
else controlWelcome = { socket, resume: value.resume };
|
|
16891
|
+
activateChannels();
|
|
16699
16892
|
return;
|
|
16700
16893
|
}
|
|
16701
16894
|
const command = CommandSchema.parse(value);
|
|
@@ -16713,26 +16906,31 @@ async function runDaemon(configPath) {
|
|
|
16713
16906
|
sockets.delete(socket);
|
|
16714
16907
|
clearInterval(heartbeat);
|
|
16715
16908
|
events.unbind(socket);
|
|
16716
|
-
if (files && filesSocket === socket)
|
|
16909
|
+
if (files && filesSocket === socket) {
|
|
16910
|
+
filesSocket = void 0;
|
|
16911
|
+
filesWelcomed = false;
|
|
16912
|
+
channelsActivated = false;
|
|
16913
|
+
events.unbind();
|
|
16914
|
+
}
|
|
16915
|
+
if (!files && controlSocket === socket) {
|
|
16916
|
+
controlSocket = void 0;
|
|
16917
|
+
controlWelcome = void 0;
|
|
16918
|
+
channelsActivated = false;
|
|
16919
|
+
}
|
|
16717
16920
|
const replaced = binding.observeClose(code);
|
|
16718
16921
|
if (replaced) {
|
|
16719
16922
|
console.error("This bot was claimed by another Proxy instance; automatic reconnection is disabled until this service is restarted");
|
|
16720
16923
|
for (const peer of sockets) peer.close();
|
|
16721
16924
|
}
|
|
16722
|
-
|
|
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) {
|
|
16925
|
+
if (!stopping && binding.active && code !== 1e3) {
|
|
16729
16926
|
console.warn(`Hub ${files ? "files" : "control"} connection closed: ${code}${reason.length ? ` ${reason.toString()}` : ""}`);
|
|
16730
16927
|
}
|
|
16731
|
-
if (!stopping && binding.active
|
|
16928
|
+
if (!stopping && binding.active) setTimeout(() => connect(files, attempt + 1), Math.min(3e4, 1e3 * 2 ** Math.min(attempt, 5)) + Math.random() * 500);
|
|
16732
16929
|
});
|
|
16733
16930
|
}
|
|
16734
16931
|
await runtime.init().catch((error) => console.error(`${runtime.kind} startup:`, error.message));
|
|
16735
16932
|
connect(false);
|
|
16933
|
+
connect(true);
|
|
16736
16934
|
const scan = setInterval(async () => {
|
|
16737
16935
|
for (const p of config.projects) await artifacts.scan(p.id).catch(() => {
|
|
16738
16936
|
});
|
package/dist/cli.js
CHANGED
package/dist/main.js
CHANGED