@skylandnpm/octopus-proxy 0.1.7 → 0.1.11
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 +6 -0
- package/dist/{chunk-JWIM27KY.js → chunk-AGWRSSBQ.js} +376 -231
- package/dist/cli.js +53 -23
- package/dist/main.js +1 -1
- package/package.json +1 -1
- package/proxy.example.json +1 -1
package/README.md
CHANGED
|
@@ -67,3 +67,9 @@ npm run publish
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
预览包内容可运行 `pnpm build && npm pack --dry-run`。直接运行 `npm publish` 会提示使用 `npm run publish`,以防绕过版本递增和编译。
|
|
70
|
+
|
|
71
|
+
## 编码工具账号与 OpenCode 服务
|
|
72
|
+
|
|
73
|
+
OpenCode 只支持 external:先自行运行 `opencode serve --hostname 127.0.0.1 --port 4096`,配置 `tool.mode: "external"` 和 `tool.baseUrl`。旧 managed 配置必须迁移,Proxy 不再管理 OpenCode 生命周期。
|
|
74
|
+
|
|
75
|
+
Claw 的“编码工具账号”窗口支持 Codex API Key/设备码、ZCode 原生 Z.AI OAuth、OpenCode 原生 API Key/OAuth。凭据由编码工具保存,模型列表从原生接口读取。详细行为与验证边界见 [编码工具账号](../../docs/tool-authentication.md)。
|
|
@@ -11602,10 +11602,12 @@ var ToolSchema = z.object({
|
|
|
11602
11602
|
kind: z.enum(TOOL_KINDS).default("opencode"),
|
|
11603
11603
|
command: z.string().default(""),
|
|
11604
11604
|
baseUrl: z.string().url().optional(),
|
|
11605
|
-
mode: z.enum(["managed", "external"]).
|
|
11606
|
-
autoStart: z.boolean().
|
|
11605
|
+
mode: z.enum(["managed", "external"]).optional(),
|
|
11606
|
+
autoStart: z.boolean().optional(),
|
|
11607
11607
|
password: z.string().optional()
|
|
11608
|
-
})
|
|
11608
|
+
}).superRefine((tool, context) => {
|
|
11609
|
+
if (tool.kind === "opencode" && tool.mode === "managed") context.addIssue({ code: z.ZodIssueCode.custom, path: ["mode"], message: "OpenCode \u4EC5\u652F\u6301 external \u6A21\u5F0F\uFF1B\u8BF7\u5148\u81EA\u884C\u542F\u52A8 opencode serve\uFF0C\u518D\u5C06 tool.mode \u6539\u4E3A external" });
|
|
11610
|
+
}).transform((tool) => ({ ...tool, mode: tool.kind === "opencode" ? "external" : tool.mode ?? "managed", autoStart: tool.kind === "opencode" ? false : tool.autoStart ?? true }));
|
|
11609
11611
|
var ConfigSchema = z.object({
|
|
11610
11612
|
hubUrl: z.string().url(),
|
|
11611
11613
|
botId: z.string().uuid(),
|
|
@@ -11658,11 +11660,15 @@ async function loadConfig(path) {
|
|
|
11658
11660
|
import WebSocket2 from "ws";
|
|
11659
11661
|
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
11660
11662
|
import { join as join9 } from "node:path";
|
|
11661
|
-
import { randomUUID as
|
|
11663
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
11662
11664
|
|
|
11663
11665
|
// ../../packages/protocol/src/index.ts
|
|
11664
11666
|
var PROTOCOL_VERSION = "1.0";
|
|
11665
11667
|
var Operations = [
|
|
11668
|
+
"auth.status",
|
|
11669
|
+
"auth.login",
|
|
11670
|
+
"auth.poll",
|
|
11671
|
+
"auth.cancel",
|
|
11666
11672
|
"runtime.status",
|
|
11667
11673
|
"runtime.snapshot",
|
|
11668
11674
|
"runtime.start",
|
|
@@ -12145,6 +12151,69 @@ var Uploads = class {
|
|
|
12145
12151
|
}
|
|
12146
12152
|
};
|
|
12147
12153
|
|
|
12154
|
+
// src/auth.ts
|
|
12155
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
12156
|
+
var AUTH_OPERATIONS = ["auth.status", "auth.login", "auth.poll", "auth.cancel"];
|
|
12157
|
+
var AuthFlows = class {
|
|
12158
|
+
flows = /* @__PURE__ */ new Map();
|
|
12159
|
+
create(project, cancel) {
|
|
12160
|
+
if ([...this.flows.values()].some((f) => ["pending", "input"].includes(f.state.status))) throw new Error("\u5DF2\u6709\u767B\u5F55\u6B63\u5728\u8FDB\u884C\uFF0C\u8BF7\u5148\u5B8C\u6210\u6216\u53D6\u6D88");
|
|
12161
|
+
const state = { flowId: randomUUID4(), status: "pending" };
|
|
12162
|
+
const timer = setTimeout(() => {
|
|
12163
|
+
void this.cancel(project, state.flowId).catch(() => {
|
|
12164
|
+
});
|
|
12165
|
+
this.flows.delete(state.flowId);
|
|
12166
|
+
}, 10 * 6e4);
|
|
12167
|
+
timer.unref();
|
|
12168
|
+
this.flows.set(state.flowId, { project, state, cancel, timer });
|
|
12169
|
+
return state;
|
|
12170
|
+
}
|
|
12171
|
+
get(project, id) {
|
|
12172
|
+
const flow = this.flows.get(String(id));
|
|
12173
|
+
if (!flow || flow.project !== project) throw new Error("\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB");
|
|
12174
|
+
return flow;
|
|
12175
|
+
}
|
|
12176
|
+
update(project, id, patch) {
|
|
12177
|
+
const flow = this.flows.get(id);
|
|
12178
|
+
if (!flow || flow.project !== project || flow.state.status === "cancelled") return;
|
|
12179
|
+
Object.assign(flow.state, patch);
|
|
12180
|
+
if (["completed", "failed"].includes(flow.state.status)) {
|
|
12181
|
+
delete flow.state.url;
|
|
12182
|
+
delete flow.state.code;
|
|
12183
|
+
delete flow.state.fields;
|
|
12184
|
+
flow.submit = void 0;
|
|
12185
|
+
}
|
|
12186
|
+
}
|
|
12187
|
+
async poll(project, payload) {
|
|
12188
|
+
const flow = this.get(project, payload.flowId);
|
|
12189
|
+
if (payload.input && flow.submit && flow.state.status === "input") {
|
|
12190
|
+
flow.state.status = "pending";
|
|
12191
|
+
const submit = flow.submit;
|
|
12192
|
+
flow.submit = void 0;
|
|
12193
|
+
void submit(payload.input).catch(() => this.update(project, flow.state.flowId, { status: "failed", message: "\u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8BA4\u8BC1\u4FE1\u606F\u540E\u91CD\u8BD5" }));
|
|
12194
|
+
}
|
|
12195
|
+
return { ...flow.state };
|
|
12196
|
+
}
|
|
12197
|
+
async cancel(project, id) {
|
|
12198
|
+
const flow = this.get(project, id);
|
|
12199
|
+
if (["pending", "input"].includes(flow.state.status)) await flow.cancel?.();
|
|
12200
|
+
flow.state = { flowId: flow.state.flowId, status: "cancelled" };
|
|
12201
|
+
flow.submit = void 0;
|
|
12202
|
+
return flow.state;
|
|
12203
|
+
}
|
|
12204
|
+
close() {
|
|
12205
|
+
for (const flow of this.flows.values()) {
|
|
12206
|
+
clearTimeout(flow.timer);
|
|
12207
|
+
void Promise.resolve(flow.cancel?.()).catch(() => {
|
|
12208
|
+
});
|
|
12209
|
+
}
|
|
12210
|
+
this.flows.clear();
|
|
12211
|
+
}
|
|
12212
|
+
};
|
|
12213
|
+
function isAuthentication(operation) {
|
|
12214
|
+
return operation.startsWith("auth.") || ["configuration.auth", "configuration.oauthAuthorize", "configuration.oauthCallback"].includes(operation);
|
|
12215
|
+
}
|
|
12216
|
+
|
|
12148
12217
|
// src/journal.ts
|
|
12149
12218
|
import { join as join3 } from "node:path";
|
|
12150
12219
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -12162,7 +12231,7 @@ var Journal = class {
|
|
|
12162
12231
|
}
|
|
12163
12232
|
const run3 = async () => {
|
|
12164
12233
|
const path = join3(this.directory, `${command.id}.json`);
|
|
12165
|
-
const readOnly = isReadOperation(command.operation);
|
|
12234
|
+
const readOnly = isReadOperation(command.operation) || isAuthentication(command.operation);
|
|
12166
12235
|
if (!readOnly) {
|
|
12167
12236
|
const old = await readJson(path);
|
|
12168
12237
|
if (old) {
|
|
@@ -12175,7 +12244,7 @@ var Journal = class {
|
|
|
12175
12244
|
try {
|
|
12176
12245
|
response = { type: "response", id: command.id, botId: command.botId, status: "completed", data: await action() };
|
|
12177
12246
|
} catch (error) {
|
|
12178
|
-
response = this.failure(command, error instanceof Error ? error.message : "Operation failed");
|
|
12247
|
+
response = this.failure(command, isAuthentication(command.operation) ? "\u8BA4\u8BC1\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F16\u7801\u5DE5\u5177\u7684\u767B\u5F55\u72B6\u6001\u540E\u91CD\u8BD5" : error instanceof Error ? error.message : "Operation failed");
|
|
12179
12248
|
if (!readOnly && error instanceof Error && ["TimeoutError", "AbortError", "UnknownResultError"].includes(error.name)) response.status = "unknown";
|
|
12180
12249
|
}
|
|
12181
12250
|
if (!readOnly) await atomicJson(path, { hash, response, finishedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -12197,7 +12266,7 @@ var Journal = class {
|
|
|
12197
12266
|
return result;
|
|
12198
12267
|
}
|
|
12199
12268
|
queueKey(c) {
|
|
12200
|
-
if (isReadOperation(c.operation)) return void 0;
|
|
12269
|
+
if (isReadOperation(c.operation) || isAuthentication(c.operation)) return void 0;
|
|
12201
12270
|
const [group] = c.operation.split(".");
|
|
12202
12271
|
if (c.operation === "sessions.abort") return JSON.stringify(["abort", c.projectId, c.sessionId]);
|
|
12203
12272
|
if (group === "sessions") return JSON.stringify(["session", c.projectId, c.sessionId ?? c.id]);
|
|
@@ -12390,7 +12459,7 @@ var EventStream = class {
|
|
|
12390
12459
|
};
|
|
12391
12460
|
|
|
12392
12461
|
// src/proxy-snapshot.ts
|
|
12393
|
-
import { randomUUID as
|
|
12462
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
12394
12463
|
function record(value) {
|
|
12395
12464
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12396
12465
|
}
|
|
@@ -12419,34 +12488,13 @@ async function buildProxySnapshot(config, runtime, adapter, requestedProjectId)
|
|
|
12419
12488
|
let modelCatalogStatus = capabilities.includes("configuration.models") ? "unavailable" : "unsupported";
|
|
12420
12489
|
if (status.healthy === true && projectId && capabilities.includes("configuration.models")) {
|
|
12421
12490
|
try {
|
|
12422
|
-
const command = { type: "command", id:
|
|
12491
|
+
const command = { type: "command", id: randomUUID5(), botId: config.botId, operation: "configuration.models", projectId, payload: {} };
|
|
12423
12492
|
models.push(...publicModels(await adapter.dispatch(command)));
|
|
12424
|
-
modelCatalogStatus = "available";
|
|
12493
|
+
modelCatalogStatus = models.length ? "available" : "unavailable";
|
|
12425
12494
|
} catch {
|
|
12426
12495
|
modelCatalogStatus = "unavailable";
|
|
12427
12496
|
}
|
|
12428
12497
|
}
|
|
12429
|
-
let recentModel;
|
|
12430
|
-
if (status.healthy === true && projectId && modelCatalogStatus === "unsupported" && capabilities.includes("sessions.list")) {
|
|
12431
|
-
try {
|
|
12432
|
-
const command = { type: "command", id: randomUUID4(), botId: config.botId, operation: "sessions.list", projectId, payload: {} };
|
|
12433
|
-
const sessions = await adapter.dispatch(command);
|
|
12434
|
-
if (Array.isArray(sessions)) {
|
|
12435
|
-
const withModel = sessions.map(record).filter((session) => {
|
|
12436
|
-
const model = record(session.model);
|
|
12437
|
-
return typeof model.providerId === "string" && typeof model.modelId === "string";
|
|
12438
|
-
}).sort((left, right) => {
|
|
12439
|
-
const leftTime = record(left.time).updated, rightTime = record(right.time).updated;
|
|
12440
|
-
return (typeof rightTime === "number" ? rightTime : 0) - (typeof leftTime === "number" ? leftTime : 0);
|
|
12441
|
-
})[0];
|
|
12442
|
-
if (withModel) {
|
|
12443
|
-
const model = record(withModel.model);
|
|
12444
|
-
recentModel = { providerId: model.providerId, modelId: model.modelId };
|
|
12445
|
-
}
|
|
12446
|
-
}
|
|
12447
|
-
} catch {
|
|
12448
|
-
}
|
|
12449
|
-
}
|
|
12450
12498
|
return {
|
|
12451
12499
|
runtime: { ...status, capabilities },
|
|
12452
12500
|
config: {
|
|
@@ -12462,12 +12510,12 @@ async function buildProxySnapshot(config, runtime, adapter, requestedProjectId)
|
|
|
12462
12510
|
}
|
|
12463
12511
|
},
|
|
12464
12512
|
projects,
|
|
12465
|
-
modelCatalog: { projectId, status: modelCatalogStatus, models
|
|
12513
|
+
modelCatalog: { projectId, status: modelCatalogStatus, models }
|
|
12466
12514
|
};
|
|
12467
12515
|
}
|
|
12468
12516
|
|
|
12469
12517
|
// src/media.ts
|
|
12470
|
-
import { createHash as createHash5, randomUUID as
|
|
12518
|
+
import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
|
|
12471
12519
|
import { constants as constants3 } from "node:fs";
|
|
12472
12520
|
import { open as open3, unlink as unlink3, realpath as realpath3 } from "node:fs/promises";
|
|
12473
12521
|
import { basename as basename3, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3, sep as sep2 } from "node:path";
|
|
@@ -12684,7 +12732,7 @@ var RemoteMedia = class {
|
|
|
12684
12732
|
if (bytes.length === 0 || bytes.length > this.imageLimit()) throw new Error("Image is empty or exceeds size limit");
|
|
12685
12733
|
const extension = { "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif", "image/webp": "webp", "application/pdf": "pdf" }[mime] ?? "bin";
|
|
12686
12734
|
const directory = await this.workspace.attachmentDirectory(project, "media", sha256), path = directory + "/asset." + extension;
|
|
12687
|
-
const temp = await this.workspace.path(project, directory + "/.octopus-media-" +
|
|
12735
|
+
const temp = await this.workspace.path(project, directory + "/.octopus-media-" + randomUUID6(), true);
|
|
12688
12736
|
const file = await open3(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
|
|
12689
12737
|
try {
|
|
12690
12738
|
await file.writeFile(bytes);
|
|
@@ -14102,7 +14150,7 @@ function applyEdits(text, edits) {
|
|
|
14102
14150
|
|
|
14103
14151
|
// src/project-config.ts
|
|
14104
14152
|
import { readFile as readFile4, writeFile as writeFile3, unlink as unlink4 } from "node:fs/promises";
|
|
14105
|
-
import { createHash as createHash6, randomUUID as
|
|
14153
|
+
import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
|
|
14106
14154
|
var ProjectConfig = class {
|
|
14107
14155
|
constructor(workspace, request) {
|
|
14108
14156
|
this.workspace = workspace;
|
|
@@ -14153,7 +14201,7 @@ var ProjectConfig = class {
|
|
|
14153
14201
|
if (!this.validator(parse2(updated))) throw new Error("Merged OpenCode configuration is invalid: " + JSON.stringify(this.validator.errors));
|
|
14154
14202
|
if (exists) await this.workspace.write(project, name, updated, createHash6("sha256").update(original).digest("hex"));
|
|
14155
14203
|
else {
|
|
14156
|
-
const temporary = await this.workspace.path(project, `.octopus-config-${
|
|
14204
|
+
const temporary = await this.workspace.path(project, `.octopus-config-${randomUUID7()}.tmp`, true);
|
|
14157
14205
|
try {
|
|
14158
14206
|
await writeFile3(temporary, updated, { mode: 384, flag: "wx" });
|
|
14159
14207
|
await this.workspace.publish(project, temporary, name);
|
|
@@ -14178,8 +14226,11 @@ var OpenCodeAdapter = class {
|
|
|
14178
14226
|
this.media = new RemoteMedia(workspace, () => uploads.limits.imageBytes);
|
|
14179
14227
|
}
|
|
14180
14228
|
supportedOperations() {
|
|
14181
|
-
return Operations;
|
|
14229
|
+
return Operations.filter((op) => !["runtime.start", "runtime.stop", "runtime.restart", "runtime.logs"].includes(op));
|
|
14182
14230
|
}
|
|
14231
|
+
auth = new AuthFlows();
|
|
14232
|
+
authDirty = /* @__PURE__ */ new Set();
|
|
14233
|
+
authRefreshing = /* @__PURE__ */ new Map();
|
|
14183
14234
|
streams = /* @__PURE__ */ new Map();
|
|
14184
14235
|
terminals = /* @__PURE__ */ new Map();
|
|
14185
14236
|
// OpenCode 1.1.32 only buffers output while no client is attached.
|
|
@@ -14188,10 +14239,10 @@ var OpenCodeAdapter = class {
|
|
|
14188
14239
|
resolving = /* @__PURE__ */ new Set();
|
|
14189
14240
|
config;
|
|
14190
14241
|
media;
|
|
14191
|
-
async request(project, method, path, body) {
|
|
14242
|
+
async request(project, method, path, body, signal) {
|
|
14192
14243
|
const root = await this.workspace.root(project), url = new URL(path, this.runtime.config.tool.baseUrl);
|
|
14193
14244
|
url.searchParams.set("directory", root);
|
|
14194
|
-
const response = await fetch(url, { method, headers: { ...this.runtime.headers(), "Content-Type": "application/json" }, body: body === void 0 ? void 0 : JSON.stringify(body), signal: AbortSignal.timeout(35e3) }).catch((error) => {
|
|
14245
|
+
const response = await fetch(url, { method, headers: { ...this.runtime.headers(), "Content-Type": "application/json" }, body: body === void 0 ? void 0 : JSON.stringify(body), signal: signal ?? AbortSignal.timeout(35e3) }).catch((error) => {
|
|
14195
14246
|
if (method !== "GET") {
|
|
14196
14247
|
const unknown = new Error("Transport interrupted; inspect remote state before retrying");
|
|
14197
14248
|
unknown.name = "UnknownResultError";
|
|
@@ -14220,6 +14271,7 @@ var OpenCodeAdapter = class {
|
|
|
14220
14271
|
this.workspace.project(project);
|
|
14221
14272
|
this.subscribe(project);
|
|
14222
14273
|
const op = command.operation, p = command.payload, id = command.sessionId;
|
|
14274
|
+
if (op.startsWith("auth.")) return this.authentication(project, op, p);
|
|
14223
14275
|
const needsSession = op.startsWith("sessions.") && !["sessions.list", "sessions.create", "sessions.status"].includes(op);
|
|
14224
14276
|
if (needsSession) {
|
|
14225
14277
|
if (!id) throw new Error("Session required");
|
|
@@ -14259,6 +14311,11 @@ var OpenCodeAdapter = class {
|
|
|
14259
14311
|
"terminal.list": ["GET", "/pty"],
|
|
14260
14312
|
"terminal.create": ["POST", "/pty"]
|
|
14261
14313
|
};
|
|
14314
|
+
if (op === "configuration.models") {
|
|
14315
|
+
await this.refreshAuth(project);
|
|
14316
|
+
const result2 = await this.request(project, "GET", "/provider");
|
|
14317
|
+
return redact({ providers: result2.all.filter((provider) => result2.connected.includes(provider.id)) });
|
|
14318
|
+
}
|
|
14262
14319
|
if (op === "sessions.prompt") {
|
|
14263
14320
|
const parts = [];
|
|
14264
14321
|
if (typeof p.text === "string" && p.text.trim()) parts.push({ type: "text", text: screenshotPrompt(p.text) });
|
|
@@ -14317,6 +14374,71 @@ var OpenCodeAdapter = class {
|
|
|
14317
14374
|
if (op.startsWith("sessions.")) return this.media.normalize(project, result);
|
|
14318
14375
|
return result;
|
|
14319
14376
|
}
|
|
14377
|
+
async refreshAuth(project) {
|
|
14378
|
+
if (!this.authDirty.has(project)) return;
|
|
14379
|
+
const pending = this.authRefreshing.get(project);
|
|
14380
|
+
if (pending) return pending;
|
|
14381
|
+
const refresh = (async () => {
|
|
14382
|
+
const statuses = await this.request(project, "GET", "/session/status");
|
|
14383
|
+
if (Object.values(statuses).some((status) => status.type !== "idle")) return;
|
|
14384
|
+
await this.request(project, "POST", "/instance/dispose");
|
|
14385
|
+
this.authDirty.delete(project);
|
|
14386
|
+
})();
|
|
14387
|
+
this.authRefreshing.set(project, refresh);
|
|
14388
|
+
try {
|
|
14389
|
+
await refresh;
|
|
14390
|
+
} finally {
|
|
14391
|
+
this.authRefreshing.delete(project);
|
|
14392
|
+
}
|
|
14393
|
+
}
|
|
14394
|
+
async authentication(project, operation, payload) {
|
|
14395
|
+
if (operation === "auth.poll") return this.auth.poll(project, payload);
|
|
14396
|
+
if (operation === "auth.cancel") return this.auth.cancel(project, payload.flowId);
|
|
14397
|
+
await this.refreshAuth(project);
|
|
14398
|
+
const methods = await this.request(project, "GET", "/provider/auth");
|
|
14399
|
+
const providers = await this.request(project, "GET", "/provider");
|
|
14400
|
+
for (const provider2 of providers.all) if (!methods[provider2.id]?.length) methods[provider2.id] = [{ type: "api", label: "API Key" }];
|
|
14401
|
+
if (operation === "auth.status") {
|
|
14402
|
+
const choices = Object.entries(methods).flatMap(([provider2, entries]) => entries.map((method2, index2) => ({
|
|
14403
|
+
id: `${provider2}:${index2}`,
|
|
14404
|
+
label: `${providers.all.find((p) => p.id === provider2)?.name ?? provider2} \xB7 ${method2.label}`,
|
|
14405
|
+
fields: method2.type === "api" ? [{ id: "key", label: "API Key", secret: true }] : (method2.prompts ?? []).map((prompt) => ({ id: prompt.key, label: prompt.message, secret: prompt.type !== "select", ...prompt.options ? { options: prompt.options.map(({ label, value }) => ({ label, value })) } : {} }))
|
|
14406
|
+
})));
|
|
14407
|
+
return { status: providers.connected.length ? "authenticated" : "unauthenticated", message: providers.connected.length ? "OpenCode \u5DF2\u914D\u7F6E\u4F9B\u5E94\u5546" : "OpenCode \u672A\u914D\u7F6E\u53EF\u7528\u4F9B\u5E94\u5546", providers: providers.connected, methods: choices };
|
|
14408
|
+
}
|
|
14409
|
+
const methodId = String(payload.method ?? ""), split = methodId.lastIndexOf(":");
|
|
14410
|
+
const provider = methodId.slice(0, split), index = Number(methodId.slice(split + 1));
|
|
14411
|
+
const method = methods[provider]?.[index];
|
|
14412
|
+
if (split < 0 || !method || !["api", "oauth"].includes(method.type)) throw new Error("\u4E0D\u652F\u6301\u7684 OpenCode \u767B\u5F55\u65B9\u5F0F");
|
|
14413
|
+
const input = payload.input ?? {};
|
|
14414
|
+
const controller = new AbortController();
|
|
14415
|
+
const flow = this.auth.create(project, () => controller.abort());
|
|
14416
|
+
const run3 = async () => {
|
|
14417
|
+
if (method.type === "api") {
|
|
14418
|
+
if (typeof input.key !== "string" || !input.key.trim()) throw new Error("API Key required");
|
|
14419
|
+
await this.request(project, "PUT", `/auth/${this.identifier(provider)}`, { type: "api", key: input.key }, controller.signal);
|
|
14420
|
+
for (const entry of this.runtime.config.projects) this.authDirty.add(entry.id);
|
|
14421
|
+
await this.refreshAuth(project);
|
|
14422
|
+
this.auth.update(project, flow.flowId, { status: "completed", message: this.authDirty.has(project) ? "\u8BA4\u8BC1\u4FE1\u606F\u5DF2\u4FDD\u5B58\uFF1B\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u540E\u5237\u65B0\u6A21\u578B\u5217\u8868" : "\u8BA4\u8BC1\u4FE1\u606F\u5DF2\u7531 OpenCode \u4FDD\u5B58" });
|
|
14423
|
+
return;
|
|
14424
|
+
}
|
|
14425
|
+
const result = await this.request(project, "POST", `/provider/${this.identifier(provider)}/oauth/authorize`, { method: index, inputs: input }, controller.signal);
|
|
14426
|
+
this.auth.update(project, flow.flowId, { url: result.url, message: result.instructions });
|
|
14427
|
+
const callback = async (code) => {
|
|
14428
|
+
await this.request(project, "POST", `/provider/${this.identifier(provider)}/oauth/callback`, { method: index, ...typeof code === "string" ? { code } : {} }, controller.signal);
|
|
14429
|
+
for (const entry of this.runtime.config.projects) this.authDirty.add(entry.id);
|
|
14430
|
+
await this.refreshAuth(project);
|
|
14431
|
+
this.auth.update(project, flow.flowId, { status: "completed", message: this.authDirty.has(project) ? "\u767B\u5F55\u6210\u529F\uFF1B\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u540E\u5237\u65B0\u6A21\u578B\u5217\u8868" : "\u767B\u5F55\u6210\u529F" });
|
|
14432
|
+
};
|
|
14433
|
+
if (result.method === "auto") await callback();
|
|
14434
|
+
else {
|
|
14435
|
+
this.auth.update(project, flow.flowId, { status: "input", fields: [{ id: "code", label: "\u6388\u6743\u7801\u6216\u56DE\u8C03\u5730\u5740", secret: true }] });
|
|
14436
|
+
this.auth.get(project, flow.flowId).submit = (values) => callback(values.code);
|
|
14437
|
+
}
|
|
14438
|
+
};
|
|
14439
|
+
void run3().catch(() => this.auth.update(project, flow.flowId, { status: "failed", message: "OpenCode \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8BA4\u8BC1\u4FE1\u606F\u4E0E\u4F9B\u5E94\u5546\u914D\u7F6E\u540E\u91CD\u8BD5" }));
|
|
14440
|
+
return { ...flow };
|
|
14441
|
+
}
|
|
14320
14442
|
subscribe(project) {
|
|
14321
14443
|
if (this.streams.has(project)) return;
|
|
14322
14444
|
const controller = new AbortController();
|
|
@@ -14369,7 +14491,9 @@ var OpenCodeAdapter = class {
|
|
|
14369
14491
|
const history = this.terminalOutput.get(key) ?? "";
|
|
14370
14492
|
this.emit(project, "terminal.reset", { id: p.id });
|
|
14371
14493
|
for (let offset = 0; offset < history.length; offset += 65536) this.emit(project, "terminal.output", { id: p.id, data: history.slice(offset, offset + 65536) });
|
|
14372
|
-
this.terminals.get(key)
|
|
14494
|
+
const previous = this.terminals.get(key);
|
|
14495
|
+
this.terminals.delete(key);
|
|
14496
|
+
previous?.close();
|
|
14373
14497
|
const url = new URL(`/pty/${id}/connect`, this.runtime.config.tool.baseUrl);
|
|
14374
14498
|
url.protocol = "ws:";
|
|
14375
14499
|
url.searchParams.set("directory", await this.workspace.root(project));
|
|
@@ -14405,6 +14529,7 @@ var OpenCodeAdapter = class {
|
|
|
14405
14529
|
return true;
|
|
14406
14530
|
}
|
|
14407
14531
|
close() {
|
|
14532
|
+
this.auth.close();
|
|
14408
14533
|
for (const c of this.streams.values()) c.abort();
|
|
14409
14534
|
for (const t of this.terminals.values()) t.close();
|
|
14410
14535
|
}
|
|
@@ -14416,207 +14541,78 @@ function redact(value) {
|
|
|
14416
14541
|
}
|
|
14417
14542
|
|
|
14418
14543
|
// src/runtime.ts
|
|
14419
|
-
import { spawn, execFile as execFile2 } from "node:child_process";
|
|
14420
|
-
import { promisify as promisify2 } from "node:util";
|
|
14421
|
-
import { randomBytes } from "node:crypto";
|
|
14422
|
-
import { open as open4 } from "node:fs/promises";
|
|
14423
|
-
import { join as join5 } from "node:path";
|
|
14424
14544
|
var Runtime = class {
|
|
14425
14545
|
constructor(config) {
|
|
14426
14546
|
this.config = config;
|
|
14427
14547
|
}
|
|
14428
|
-
detached = false;
|
|
14429
|
-
child;
|
|
14430
|
-
wanted = false;
|
|
14431
|
-
attempts = 0;
|
|
14432
|
-
timer;
|
|
14433
|
-
lines = [];
|
|
14434
|
-
starting;
|
|
14435
|
-
owned;
|
|
14436
|
-
monitor;
|
|
14437
14548
|
kind = "opencode";
|
|
14438
|
-
password = "";
|
|
14439
14549
|
version = "";
|
|
14440
|
-
state = "
|
|
14441
|
-
async identity(pid) {
|
|
14442
|
-
try {
|
|
14443
|
-
return (await promisify2(execFile2)("ps", ["-p", String(pid), "-o", "lstart=", "-o", "command="])).stdout.trim();
|
|
14444
|
-
} catch {
|
|
14445
|
-
return "";
|
|
14446
|
-
}
|
|
14447
|
-
}
|
|
14448
|
-
async save() {
|
|
14449
|
-
await atomicJson(join5(this.config.stateDirectory, "runtime.json"), { password: this.password, wanted: this.wanted, ...this.owned, baseUrl: this.config.tool.baseUrl, command: this.config.tool.command });
|
|
14450
|
-
}
|
|
14451
|
-
async stillOwned() {
|
|
14452
|
-
const owned = this.owned;
|
|
14453
|
-
return !!owned && await this.identity(owned.pid) === owned.identity && this.owned === owned;
|
|
14454
|
-
}
|
|
14550
|
+
state = "external";
|
|
14455
14551
|
async init() {
|
|
14456
|
-
|
|
14457
|
-
this.password = this.config.tool.password ?? saved?.password ?? randomBytes(32).toString("hex");
|
|
14458
|
-
if (this.config.tool.mode === "external") {
|
|
14459
|
-
this.password = this.config.tool.password ?? "";
|
|
14460
|
-
this.state = "external";
|
|
14461
|
-
return;
|
|
14462
|
-
}
|
|
14463
|
-
this.wanted = saved?.wanted ?? this.config.tool.autoStart;
|
|
14464
|
-
if (saved?.pid && saved.identity && saved.baseUrl === this.config.tool.baseUrl && saved.command === this.config.tool.command) {
|
|
14465
|
-
this.owned = { pid: saved.pid, identity: saved.identity };
|
|
14466
|
-
if (await this.stillOwned() && await this.health()) this.state = "running";
|
|
14467
|
-
else this.owned = void 0;
|
|
14468
|
-
}
|
|
14469
|
-
await this.save();
|
|
14470
|
-
this.monitor = setInterval(() => {
|
|
14471
|
-
if (this.owned && !this.child) void this.stillOwned().then((alive) => {
|
|
14472
|
-
if (!alive) {
|
|
14473
|
-
this.owned = void 0;
|
|
14474
|
-
this.exited();
|
|
14475
|
-
}
|
|
14476
|
-
});
|
|
14477
|
-
}, 3e3);
|
|
14478
|
-
this.monitor.unref();
|
|
14479
|
-
if (this.wanted && !this.owned) await this.start();
|
|
14552
|
+
await this.health();
|
|
14480
14553
|
}
|
|
14481
14554
|
headers() {
|
|
14482
|
-
|
|
14555
|
+
const password = this.config.tool.password;
|
|
14556
|
+
return password ? { Authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` } : {};
|
|
14483
14557
|
}
|
|
14484
14558
|
async health() {
|
|
14485
14559
|
try {
|
|
14486
|
-
const
|
|
14487
|
-
if (!
|
|
14488
|
-
const data = await
|
|
14560
|
+
const response = await fetch(`${this.config.tool.baseUrl}/global/health`, { headers: this.headers(), signal: AbortSignal.timeout(2e3) });
|
|
14561
|
+
if (!response.ok) return false;
|
|
14562
|
+
const data = await response.json();
|
|
14489
14563
|
this.version = data.version ?? this.version;
|
|
14490
|
-
return true;
|
|
14564
|
+
return data.healthy === true;
|
|
14491
14565
|
} catch {
|
|
14492
14566
|
return false;
|
|
14493
14567
|
}
|
|
14494
14568
|
}
|
|
14495
14569
|
async status() {
|
|
14496
|
-
|
|
14497
|
-
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
this.
|
|
14504
|
-
}
|
|
14505
|
-
return this.starting;
|
|
14506
|
-
}
|
|
14507
|
-
exited() {
|
|
14508
|
-
if (this.detached) return;
|
|
14509
|
-
this.state = this.wanted ? "failed" : "stopped";
|
|
14510
|
-
void this.save();
|
|
14511
|
-
if (this.wanted && this.attempts < 5) {
|
|
14512
|
-
const delay = Math.min(3e4, 1e3 * 2 ** this.attempts++);
|
|
14513
|
-
this.timer = setTimeout(() => this.start().catch((e) => this.lines.push(String(e))), delay);
|
|
14514
|
-
}
|
|
14515
|
-
}
|
|
14516
|
-
// Detach supervision when Proxy exits; the independently running tool is kept alive.
|
|
14517
|
-
detach() {
|
|
14518
|
-
this.detached = true;
|
|
14519
|
-
clearTimeout(this.timer);
|
|
14520
|
-
clearInterval(this.monitor);
|
|
14570
|
+
const healthy = await this.health();
|
|
14571
|
+
return {
|
|
14572
|
+
tool: this.kind,
|
|
14573
|
+
mode: "external",
|
|
14574
|
+
state: this.state,
|
|
14575
|
+
healthy,
|
|
14576
|
+
version: this.version,
|
|
14577
|
+
...healthy ? {} : { error: `\u65E0\u6CD5\u8FDE\u63A5 OpenCode\uFF1A${this.config.tool.baseUrl}\u3002\u8BF7\u5728\u8BE5\u4E3B\u673A\u542F\u52A8 opencode serve\uFF0C\u5E76\u68C0\u67E5\u670D\u52A1\u5730\u5740\u548C\u5BC6\u7801\u3002` }
|
|
14578
|
+
};
|
|
14521
14579
|
}
|
|
14522
|
-
async
|
|
14523
|
-
|
|
14524
|
-
await this.save();
|
|
14525
|
-
if (await this.stillOwned()) return this.status();
|
|
14526
|
-
if (await this.health()) {
|
|
14527
|
-
this.state = "unowned";
|
|
14528
|
-
throw new Error("A server already occupies the endpoint; refusing implicit takeover");
|
|
14529
|
-
}
|
|
14530
|
-
const { stdout } = await promisify2(execFile2)(this.config.tool.command, ["--version"], { timeout: 15e3 });
|
|
14531
|
-
this.version = stdout.trim();
|
|
14532
|
-
if (this.version !== "1.1.32") throw new Error(`Unsupported OpenCode ${this.version}; verified baseline is 1.1.32`);
|
|
14533
|
-
const url = new URL(this.config.tool.baseUrl);
|
|
14534
|
-
this.state = "starting";
|
|
14535
|
-
const log = await open4(join5(this.config.stateDirectory, "opencode.log"), "a", 384);
|
|
14536
|
-
const child = spawn(this.config.tool.command, ["serve", "--hostname", "127.0.0.1", "--port", url.port || "4096"], { detached: true, cwd: this.config.projects[0].directory, env: { ...process.env, OPENCODE_SERVER_PASSWORD: this.password }, stdio: ["ignore", log.fd, log.fd] });
|
|
14537
|
-
await log.close();
|
|
14538
|
-
this.child = child;
|
|
14539
|
-
child.unref();
|
|
14540
|
-
child.once("error", (e) => {
|
|
14541
|
-
this.lines.push(e.message);
|
|
14542
|
-
if (this.child === child) {
|
|
14543
|
-
this.child = void 0;
|
|
14544
|
-
this.owned = void 0;
|
|
14545
|
-
this.exited();
|
|
14546
|
-
}
|
|
14547
|
-
});
|
|
14548
|
-
child.once("exit", () => {
|
|
14549
|
-
if (this.child === child) {
|
|
14550
|
-
this.child = void 0;
|
|
14551
|
-
this.owned = void 0;
|
|
14552
|
-
this.exited();
|
|
14553
|
-
}
|
|
14554
|
-
});
|
|
14555
|
-
if (child.pid) {
|
|
14556
|
-
const identity = await this.identity(child.pid);
|
|
14557
|
-
if (identity) {
|
|
14558
|
-
this.owned = { pid: child.pid, identity };
|
|
14559
|
-
await this.save();
|
|
14560
|
-
}
|
|
14561
|
-
}
|
|
14562
|
-
for (let i = 0; i < 100; i++) {
|
|
14563
|
-
if (await this.health()) {
|
|
14564
|
-
this.state = "running";
|
|
14565
|
-
return this.status();
|
|
14566
|
-
}
|
|
14567
|
-
if (!this.child) throw new Error("OpenCode exited during startup");
|
|
14568
|
-
await new Promise((resolve4) => setTimeout(resolve4, 200));
|
|
14569
|
-
}
|
|
14570
|
-
await this.stop();
|
|
14571
|
-
throw new Error("OpenCode startup timed out");
|
|
14580
|
+
async start() {
|
|
14581
|
+
throw new Error("OpenCode \u4EC5\u652F\u6301 external \u6A21\u5F0F\uFF0C\u8BF7\u5728\u4E3B\u673A\u4E0A\u8FD0\u884C opencode serve");
|
|
14572
14582
|
}
|
|
14573
14583
|
async stop() {
|
|
14574
|
-
|
|
14575
|
-
this.wanted = false;
|
|
14576
|
-
clearTimeout(this.timer);
|
|
14577
|
-
await this.save();
|
|
14578
|
-
if (this.owned && await this.stillOwned()) {
|
|
14579
|
-
const pid = this.owned.pid;
|
|
14580
|
-
process.kill(pid, "SIGTERM");
|
|
14581
|
-
for (let i = 0; i < 50 && await this.stillOwned(); i++) await new Promise((resolve4) => setTimeout(resolve4, 100));
|
|
14582
|
-
if (await this.stillOwned()) process.kill(pid, "SIGKILL");
|
|
14583
|
-
}
|
|
14584
|
-
this.child = void 0;
|
|
14585
|
-
this.owned = void 0;
|
|
14586
|
-
this.state = "stopped";
|
|
14587
|
-
await this.save();
|
|
14588
|
-
return this.status();
|
|
14584
|
+
throw new Error("OpenCode \u670D\u52A1\u7531\u5916\u90E8\u7BA1\u7406\uFF0CProxy \u4E0D\u652F\u6301\u505C\u6B62");
|
|
14589
14585
|
}
|
|
14590
14586
|
async restart() {
|
|
14591
|
-
|
|
14592
|
-
this.attempts = 0;
|
|
14593
|
-
return this.start();
|
|
14587
|
+
throw new Error("OpenCode \u670D\u52A1\u7531\u5916\u90E8\u7BA1\u7406\uFF0CProxy \u4E0D\u652F\u6301\u91CD\u542F");
|
|
14594
14588
|
}
|
|
14595
14589
|
async logs() {
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
try {
|
|
14600
|
-
const size = (await file.stat()).size, bytes = Buffer.alloc(Math.min(size, 1e5));
|
|
14601
|
-
await file.read(bytes, 0, bytes.length, Math.max(0, size - bytes.length));
|
|
14602
|
-
log = bytes.toString("utf8");
|
|
14603
|
-
} finally {
|
|
14604
|
-
await file.close();
|
|
14605
|
-
}
|
|
14606
|
-
}
|
|
14607
|
-
const result = log + this.lines.slice(-100).join("\n");
|
|
14608
|
-
return this.password ? result.replaceAll(this.password, "[redacted]") : result;
|
|
14590
|
+
throw new Error("OpenCode \u65E5\u5FD7\u7531\u5916\u90E8\u670D\u52A1\u7BA1\u7406");
|
|
14591
|
+
}
|
|
14592
|
+
detach() {
|
|
14609
14593
|
}
|
|
14610
14594
|
};
|
|
14611
14595
|
|
|
14596
|
+
// src/tools/zcode-env.ts
|
|
14597
|
+
import { existsSync } from "node:fs";
|
|
14598
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
14599
|
+
function zcodeEnvironment(command) {
|
|
14600
|
+
const env = { ...process.env };
|
|
14601
|
+
if (!env.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE) {
|
|
14602
|
+
const bundled = join5(dirname4(command), "..", "config", "provider", "zcode-builtin.json");
|
|
14603
|
+
if (existsSync(bundled)) env.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE = bundled;
|
|
14604
|
+
}
|
|
14605
|
+
return env;
|
|
14606
|
+
}
|
|
14607
|
+
|
|
14612
14608
|
// src/tools/stdio-runtime.ts
|
|
14613
|
-
import { spawn
|
|
14614
|
-
import { promisify as
|
|
14615
|
-
import { open as
|
|
14609
|
+
import { spawn, execFile as execFile2 } from "node:child_process";
|
|
14610
|
+
import { promisify as promisify2 } from "node:util";
|
|
14611
|
+
import { open as open4 } from "node:fs/promises";
|
|
14616
14612
|
import { join as join6 } from "node:path";
|
|
14617
14613
|
|
|
14618
14614
|
// src/tools/line-rpc.ts
|
|
14619
|
-
import { randomUUID as
|
|
14615
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
14620
14616
|
import { crc32 } from "node:zlib";
|
|
14621
14617
|
import { StringDecoder } from "node:string_decoder";
|
|
14622
14618
|
function interrupted(message) {
|
|
@@ -14801,11 +14797,11 @@ var LineRpc = class {
|
|
|
14801
14797
|
}
|
|
14802
14798
|
};
|
|
14803
14799
|
function wrapEnvelope(frame) {
|
|
14804
|
-
return { wireVersion: 3, kind: "complete", deliveryKind: "desktop-continuous", logicalFrameId:
|
|
14800
|
+
return { wireVersion: 3, kind: "complete", deliveryKind: "desktop-continuous", logicalFrameId: randomUUID8(), logicalFrameOrdinal: 1, topic: "outbound", subscriptionId: "outbound", frame };
|
|
14805
14801
|
}
|
|
14806
14802
|
|
|
14807
14803
|
// src/tools/stdio-runtime.ts
|
|
14808
|
-
var run =
|
|
14804
|
+
var run = promisify2(execFile2);
|
|
14809
14805
|
var StdioRuntime = class {
|
|
14810
14806
|
constructor(config) {
|
|
14811
14807
|
this.config = config;
|
|
@@ -14823,6 +14819,7 @@ var StdioRuntime = class {
|
|
|
14823
14819
|
launching = /* @__PURE__ */ new Map();
|
|
14824
14820
|
attempts = /* @__PURE__ */ new Map();
|
|
14825
14821
|
timers = /* @__PURE__ */ new Map();
|
|
14822
|
+
refreshing = /* @__PURE__ */ new Set();
|
|
14826
14823
|
wanted = false;
|
|
14827
14824
|
detached = false;
|
|
14828
14825
|
async init() {
|
|
@@ -14854,10 +14851,22 @@ var StdioRuntime = class {
|
|
|
14854
14851
|
await Promise.allSettled(this.config.projects.map((project) => this.track(project.id, project.directory)));
|
|
14855
14852
|
return this.status();
|
|
14856
14853
|
}
|
|
14854
|
+
async reconnectProject(projectId) {
|
|
14855
|
+
const child = this.children.get(projectId);
|
|
14856
|
+
if (!child) return;
|
|
14857
|
+
this.refreshing.add(projectId);
|
|
14858
|
+
try {
|
|
14859
|
+
const exited = child.exitCode !== null || child.signalCode !== null ? Promise.resolve() : new Promise((resolve4) => child.once("exit", () => resolve4()));
|
|
14860
|
+
this.kill(projectId);
|
|
14861
|
+
await exited;
|
|
14862
|
+
} finally {
|
|
14863
|
+
this.refreshing.delete(projectId);
|
|
14864
|
+
}
|
|
14865
|
+
}
|
|
14857
14866
|
async logs() {
|
|
14858
14867
|
const chunks = [];
|
|
14859
14868
|
for (const [projectId, logPath] of this.logPaths) {
|
|
14860
|
-
const file = await
|
|
14869
|
+
const file = await open4(logPath, "r").catch(() => void 0);
|
|
14861
14870
|
if (!file) continue;
|
|
14862
14871
|
try {
|
|
14863
14872
|
const size = (await file.stat()).size, bytes = Buffer.alloc(Math.min(size, 3e4));
|
|
@@ -14924,8 +14933,8 @@ ${bytes.toString("utf8")}`);
|
|
|
14924
14933
|
if (inFlight) return inFlight;
|
|
14925
14934
|
const spawnOnce = (async () => {
|
|
14926
14935
|
const logPath = join6(this.config.stateDirectory, `${this.kind}-${projectId}.log`);
|
|
14927
|
-
const log = await
|
|
14928
|
-
const child =
|
|
14936
|
+
const log = await open4(logPath, "a", 384).catch(() => void 0);
|
|
14937
|
+
const child = spawn(this.config.tool.command, [...this.descriptor.serverArgs], { cwd: directory, env: this.kind === "zcode" ? zcodeEnvironment(this.config.tool.command) : { ...process.env }, stdio: ["pipe", "pipe", log ? log.fd : "ignore"] });
|
|
14929
14938
|
if (log) child.once("exit", () => {
|
|
14930
14939
|
void log.close();
|
|
14931
14940
|
});
|
|
@@ -14941,7 +14950,7 @@ ${bytes.toString("utf8")}`);
|
|
|
14941
14950
|
return spawnOnce;
|
|
14942
14951
|
}
|
|
14943
14952
|
onExited(projectId, _reason) {
|
|
14944
|
-
if (this.detached || !this.wanted) return;
|
|
14953
|
+
if (this.detached || !this.wanted || this.refreshing.has(projectId)) return;
|
|
14945
14954
|
const attempts = (this.attempts.get(projectId) ?? 0) + 1;
|
|
14946
14955
|
if (attempts > 5) {
|
|
14947
14956
|
console.error(`${projectId}: app server exited; retry budget exhausted`);
|
|
@@ -15004,9 +15013,10 @@ function unsupportedOperation(kind, operation) {
|
|
|
15004
15013
|
}
|
|
15005
15014
|
|
|
15006
15015
|
// src/tools/codex.ts
|
|
15007
|
-
import { randomUUID as
|
|
15016
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
15008
15017
|
var OPERATIONS = [
|
|
15009
15018
|
...DAEMON_OPERATIONS,
|
|
15019
|
+
...AUTH_OPERATIONS,
|
|
15010
15020
|
"sessions.list",
|
|
15011
15021
|
"sessions.create",
|
|
15012
15022
|
"sessions.get",
|
|
@@ -15030,6 +15040,10 @@ var CodexAdapter = class {
|
|
|
15030
15040
|
this.emit = emit;
|
|
15031
15041
|
this.media = new RemoteMedia(workspace, () => uploads.limits.imageBytes);
|
|
15032
15042
|
}
|
|
15043
|
+
auth = new AuthFlows();
|
|
15044
|
+
authDirty = /* @__PURE__ */ new Set();
|
|
15045
|
+
authReloading = /* @__PURE__ */ new Map();
|
|
15046
|
+
logins = /* @__PURE__ */ new Map();
|
|
15033
15047
|
media;
|
|
15034
15048
|
liveTurns = /* @__PURE__ */ new Map();
|
|
15035
15049
|
approvals = /* @__PURE__ */ new Map();
|
|
@@ -15039,10 +15053,36 @@ var CodexAdapter = class {
|
|
|
15039
15053
|
supportedOperations() {
|
|
15040
15054
|
return OPERATIONS;
|
|
15041
15055
|
}
|
|
15056
|
+
accountChanged(project) {
|
|
15057
|
+
for (const entry of this.runtime.config.projects) if (entry.id !== project) this.authDirty.add(entry.id);
|
|
15058
|
+
}
|
|
15042
15059
|
async rpc(project) {
|
|
15043
15060
|
this.workspace.project(project);
|
|
15061
|
+
if (this.authDirty.has(project) && ![...this.activeTurns.keys()].some((id) => this.loadedThreads.get(project)?.has(id))) {
|
|
15062
|
+
let pending = this.authReloading.get(project);
|
|
15063
|
+
if (!pending) {
|
|
15064
|
+
pending = this.runtime.reconnectProject(project).then(() => {
|
|
15065
|
+
this.authDirty.delete(project);
|
|
15066
|
+
}).finally(() => {
|
|
15067
|
+
this.authReloading.delete(project);
|
|
15068
|
+
});
|
|
15069
|
+
this.authReloading.set(project, pending);
|
|
15070
|
+
}
|
|
15071
|
+
await pending;
|
|
15072
|
+
}
|
|
15044
15073
|
const rpc = await this.runtime.connect(project, {
|
|
15045
15074
|
onNotification: (method, params) => {
|
|
15075
|
+
if (method === "account/login/completed") {
|
|
15076
|
+
const result = params;
|
|
15077
|
+
if (result.success) this.accountChanged(project);
|
|
15078
|
+
const login = this.logins.get(project);
|
|
15079
|
+
if (login && result.loginId) {
|
|
15080
|
+
if (login.loginId === result.loginId) this.auth.update(project, login.flowId, { status: result.success ? "completed" : "failed", message: result.success ? "\u767B\u5F55\u6210\u529F" : "\u767B\u5F55\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5" });
|
|
15081
|
+
else if (!login.loginId && login.early.size < 8) login.early.set(result.loginId, result.success === true);
|
|
15082
|
+
}
|
|
15083
|
+
return;
|
|
15084
|
+
}
|
|
15085
|
+
if (method.startsWith("account/")) return;
|
|
15046
15086
|
if (method === "deprecationNotice") return;
|
|
15047
15087
|
this.trackNotification(project, method, params);
|
|
15048
15088
|
if (method === "turn/completed") {
|
|
@@ -15068,7 +15108,7 @@ var CodexAdapter = class {
|
|
|
15068
15108
|
}
|
|
15069
15109
|
onServerRequest(project, id, method, params, respond) {
|
|
15070
15110
|
const detail = params ?? {};
|
|
15071
|
-
const requestId =
|
|
15111
|
+
const requestId = randomUUID9();
|
|
15072
15112
|
const threadId = String(detail.threadId ?? "");
|
|
15073
15113
|
if (["item/permissions/requestApproval", "item/commandExecution/requestApproval", "item/fileChange/requestApproval"].includes(method)) {
|
|
15074
15114
|
this.approvals.set(requestId, { project, method, requestId, threadId, detail, respond });
|
|
@@ -15245,6 +15285,7 @@ var CodexAdapter = class {
|
|
|
15245
15285
|
await this.rpc(project);
|
|
15246
15286
|
}
|
|
15247
15287
|
close() {
|
|
15288
|
+
this.auth.close();
|
|
15248
15289
|
for (const approval of this.approvals.values()) approval.respond(this.approvalResponse(approval, "reject"));
|
|
15249
15290
|
for (const input of this.inputs.values()) input.respond({ answers: {} });
|
|
15250
15291
|
this.approvals.clear();
|
|
@@ -15256,8 +15297,47 @@ var CodexAdapter = class {
|
|
|
15256
15297
|
if (!project) throw new Error("Project required");
|
|
15257
15298
|
this.workspace.project(project);
|
|
15258
15299
|
const op = command.operation, p = command.payload, id = command.sessionId;
|
|
15300
|
+
if (op === "auth.poll") return this.auth.poll(project, p);
|
|
15301
|
+
if (op === "auth.cancel") return this.auth.cancel(project, p.flowId);
|
|
15259
15302
|
const rpc = await this.rpc(project);
|
|
15303
|
+
if (op === "auth.status") {
|
|
15304
|
+
const result = await rpc.request("account/read", { refreshToken: false });
|
|
15305
|
+
return {
|
|
15306
|
+
status: result.account ? "authenticated" : result.requiresOpenaiAuth ? "unauthenticated" : "notRequired",
|
|
15307
|
+
account: result.account ? { type: result.account.type, email: result.account.email, planType: result.account.planType } : null,
|
|
15308
|
+
methods: [{ id: "chatgptDeviceCode", label: "ChatGPT \u8BBE\u5907\u7801\u767B\u5F55" }, { id: "apiKey", label: "OpenAI API Key", fields: [{ id: "apiKey", label: "API Key", secret: true }] }]
|
|
15309
|
+
};
|
|
15310
|
+
}
|
|
15311
|
+
if (op === "auth.login") {
|
|
15312
|
+
if (!["apiKey", "chatgptDeviceCode"].includes(String(p.method))) throw new Error("\u4E0D\u652F\u6301\u7684 Codex \u767B\u5F55\u65B9\u5F0F");
|
|
15313
|
+
const input = p.input ?? {};
|
|
15314
|
+
if (p.method === "apiKey" && (typeof input.apiKey !== "string" || !input.apiKey.trim())) throw new Error("\u8BF7\u8F93\u5165 API Key");
|
|
15315
|
+
let loginId;
|
|
15316
|
+
const flow = this.auth.create(project, async () => {
|
|
15317
|
+
if (loginId) await rpc.request("account/login/cancel", { loginId });
|
|
15318
|
+
});
|
|
15319
|
+
const login = { flowId: flow.flowId, loginId: void 0, early: /* @__PURE__ */ new Map() };
|
|
15320
|
+
this.logins.set(project, login);
|
|
15321
|
+
void rpc.request("account/login/start", { type: p.method, ...p.method === "apiKey" ? { apiKey: input.apiKey } : {} }).then(async (result) => {
|
|
15322
|
+
if (result.type === "apiKey") this.accountChanged(project);
|
|
15323
|
+
loginId = result.loginId;
|
|
15324
|
+
login.loginId = loginId;
|
|
15325
|
+
if (this.auth.get(project, flow.flowId).state.status === "cancelled") {
|
|
15326
|
+
if (loginId) await rpc.request("account/login/cancel", { loginId });
|
|
15327
|
+
return;
|
|
15328
|
+
}
|
|
15329
|
+
if (loginId && login.early.has(loginId)) {
|
|
15330
|
+
this.auth.update(project, flow.flowId, { status: login.early.get(loginId) ? "completed" : "failed", message: login.early.get(loginId) ? "\u767B\u5F55\u6210\u529F" : "\u767B\u5F55\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5" });
|
|
15331
|
+
login.early.clear();
|
|
15332
|
+
return;
|
|
15333
|
+
}
|
|
15334
|
+
this.auth.update(project, flow.flowId, result.type === "apiKey" ? { status: "completed", message: "\u8BA4\u8BC1\u4FE1\u606F\u5DF2\u7531 Codex \u4FDD\u5B58" } : { url: result.verificationUrl, code: result.userCode, message: "\u6253\u5F00\u94FE\u63A5\u5E76\u8F93\u5165\u8BBE\u5907\u7801\u5B8C\u6210\u767B\u5F55" });
|
|
15335
|
+
}).catch(() => this.auth.update(project, flow.flowId, { status: "failed", message: "Codex \u767B\u5F55\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5\u767B\u5F55\u65B9\u5F0F\u662F\u5426\u53D7\u5F53\u524D\u7248\u672C\u53CA\u8D26\u53F7\u652F\u6301" }));
|
|
15336
|
+
return { ...flow };
|
|
15337
|
+
}
|
|
15260
15338
|
if (op === "configuration.models") {
|
|
15339
|
+
const account = await rpc.request("account/read", { refreshToken: false });
|
|
15340
|
+
if (!account.account && account.requiresOpenaiAuth) return { providers: [] };
|
|
15261
15341
|
const models = [];
|
|
15262
15342
|
let cursor = null;
|
|
15263
15343
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
@@ -15397,10 +15477,13 @@ var CodexAdapter = class {
|
|
|
15397
15477
|
};
|
|
15398
15478
|
|
|
15399
15479
|
// src/tools/zcode.ts
|
|
15400
|
-
import {
|
|
15480
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
15481
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
15401
15482
|
import { realpath as realpath5 } from "node:fs/promises";
|
|
15402
15483
|
var OPERATIONS2 = [
|
|
15403
15484
|
...DAEMON_OPERATIONS,
|
|
15485
|
+
...AUTH_OPERATIONS,
|
|
15486
|
+
"configuration.models",
|
|
15404
15487
|
"sessions.list",
|
|
15405
15488
|
"sessions.create",
|
|
15406
15489
|
"sessions.get",
|
|
@@ -15423,6 +15506,7 @@ var ZcodeAdapter = class {
|
|
|
15423
15506
|
this.emit = emit;
|
|
15424
15507
|
this.media = new RemoteMedia(workspace, () => uploads.limits.imageBytes);
|
|
15425
15508
|
}
|
|
15509
|
+
auth = new AuthFlows();
|
|
15426
15510
|
media;
|
|
15427
15511
|
permissions = /* @__PURE__ */ new Map();
|
|
15428
15512
|
inputs = /* @__PURE__ */ new Map();
|
|
@@ -15449,7 +15533,7 @@ var ZcodeAdapter = class {
|
|
|
15449
15533
|
}
|
|
15450
15534
|
onServerRequest(project, id, method, params, respond, respondError) {
|
|
15451
15535
|
const detail = params ?? {};
|
|
15452
|
-
const requestId =
|
|
15536
|
+
const requestId = randomUUID10();
|
|
15453
15537
|
const sessionId = String(detail.sessionId ?? "");
|
|
15454
15538
|
if (method === "session/requestRuntimePreferences") {
|
|
15455
15539
|
respond({ nativeSearchEnhancementsEnabled: false });
|
|
@@ -15550,6 +15634,7 @@ var ZcodeAdapter = class {
|
|
|
15550
15634
|
await this.rpc(project);
|
|
15551
15635
|
}
|
|
15552
15636
|
close() {
|
|
15637
|
+
this.auth.close();
|
|
15553
15638
|
for (const entry of this.permissions.values()) entry.respond({ decision: "deny" });
|
|
15554
15639
|
for (const entry of this.inputs.values()) entry.respond({ action: "decline" });
|
|
15555
15640
|
this.permissions.clear();
|
|
@@ -15560,7 +15645,54 @@ var ZcodeAdapter = class {
|
|
|
15560
15645
|
if (!project) throw new Error("Project required");
|
|
15561
15646
|
this.workspace.project(project);
|
|
15562
15647
|
const op = command.operation, p = command.payload, id = command.sessionId;
|
|
15648
|
+
if (op === "auth.poll") return this.auth.poll(project, p);
|
|
15649
|
+
if (op === "auth.cancel") return this.auth.cancel(project, p.flowId);
|
|
15563
15650
|
const rpc = await this.rpc(project);
|
|
15651
|
+
if (op === "configuration.models" || op === "auth.status") {
|
|
15652
|
+
const snapshot = await rpc.request("session/create", { workspace: await this.workspaceRef(project), persistence: "deferred", titleGenerationEnabled: false, toolAllowlist: [] });
|
|
15653
|
+
try {
|
|
15654
|
+
const model = snapshot.settings.model;
|
|
15655
|
+
if (op === "auth.status") return { status: model.available.length ? "authenticated" : "unauthenticated", message: model.available.length ? "ZCode \u5DF2\u914D\u7F6E\u53EF\u7528\u6A21\u578B\uFF08\u51ED\u636E\u6709\u6548\u6027\u7531\u5DE5\u5177\u5728\u8C03\u7528\u65F6\u68C0\u67E5\uFF09" : "ZCode \u672A\u914D\u7F6E\u53EF\u7528\u6A21\u578B", methods: [{ id: "zai", label: "Z.AI \u6D4F\u89C8\u5668\u767B\u5F55" }] };
|
|
15656
|
+
const providers = /* @__PURE__ */ new Map();
|
|
15657
|
+
for (const entry of model.available) {
|
|
15658
|
+
const provider = providers.get(entry.ref.providerId) ?? { id: entry.ref.providerId, name: entry.providerLabel, models: /* @__PURE__ */ Object.create(null) };
|
|
15659
|
+
provider.models[entry.ref.modelId] = { id: entry.ref.modelId, name: entry.label, isDefault: entry.ref.providerId === model.current?.providerId && entry.ref.modelId === model.current?.modelId };
|
|
15660
|
+
providers.set(provider.id, provider);
|
|
15661
|
+
}
|
|
15662
|
+
return { providers: [...providers.values()] };
|
|
15663
|
+
} finally {
|
|
15664
|
+
await rpc.request("session/close", { sessionId: snapshot.session.sessionId, expectedPersistence: "deferred" });
|
|
15665
|
+
}
|
|
15666
|
+
}
|
|
15667
|
+
if (op === "auth.login") {
|
|
15668
|
+
if (p.method !== "zai") throw new Error("\u4E0D\u652F\u6301\u7684 ZCode \u767B\u5F55\u65B9\u5F0F");
|
|
15669
|
+
let child;
|
|
15670
|
+
const flow = this.auth.create(project, () => {
|
|
15671
|
+
child?.kill("SIGTERM");
|
|
15672
|
+
});
|
|
15673
|
+
child = spawn2(this.runtime.config.tool.command, ["login", "--no-browser", "--json"], { cwd: await this.workspace.root(project), env: zcodeEnvironment(this.runtime.config.tool.command), stdio: ["ignore", "pipe", "pipe"] });
|
|
15674
|
+
let output = "", diagnostic = "";
|
|
15675
|
+
child.stdout?.on("data", (data) => {
|
|
15676
|
+
output = (output + data.toString()).slice(-65536);
|
|
15677
|
+
});
|
|
15678
|
+
child.stderr?.on("data", (data) => {
|
|
15679
|
+
diagnostic = (diagnostic + data.toString()).slice(-8192);
|
|
15680
|
+
const match = /Open this URL to sign in:\s*(https?:\/\/[^\s]+)/.exec(diagnostic);
|
|
15681
|
+
if (match) this.auth.update(project, flow.flowId, { url: match[1], message: "\u5728\u6D4F\u89C8\u5668\u5B8C\u6210 Z.AI \u6388\u6743\uFF0CZCode \u5C06\u81EA\u884C\u4FDD\u5B58\u8BA4\u8BC1\u4FE1\u606F" });
|
|
15682
|
+
});
|
|
15683
|
+
child.once("error", () => this.auth.update(project, flow.flowId, { status: "failed", message: "\u65E0\u6CD5\u542F\u52A8 ZCode \u539F\u751F\u767B\u5F55" }));
|
|
15684
|
+
child.once("exit", (code) => {
|
|
15685
|
+
let ready = false;
|
|
15686
|
+
try {
|
|
15687
|
+
ready = JSON.parse(output).status === "ready";
|
|
15688
|
+
} catch {
|
|
15689
|
+
}
|
|
15690
|
+
output = "";
|
|
15691
|
+
diagnostic = "";
|
|
15692
|
+
this.auth.update(project, flow.flowId, { status: code === 0 && ready ? "completed" : "failed", message: code === 0 && ready ? "ZCode \u767B\u5F55\u6210\u529F" : "ZCode \u767B\u5F55\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5" });
|
|
15693
|
+
});
|
|
15694
|
+
return { ...flow };
|
|
15695
|
+
}
|
|
15564
15696
|
if (op === "sessions.list") {
|
|
15565
15697
|
const result = await rpc.request("session/list", { workspace: await this.workspaceRef(project) });
|
|
15566
15698
|
return (result.sessions ?? []).map((session) => this.sessionProjection(session));
|
|
@@ -15599,6 +15731,8 @@ var ZcodeAdapter = class {
|
|
|
15599
15731
|
lines.push(`Attached file: ${upload.destination} (${upload.name}, ${upload.mime}). Read this file if relevant to the task.`);
|
|
15600
15732
|
}
|
|
15601
15733
|
if (!lines.length) throw new Error("Message or attachments required");
|
|
15734
|
+
const selected = p.model;
|
|
15735
|
+
if (selected?.providerID && selected.modelID) await rpc.request("session/setModel", { sessionId: id, model: { providerId: selected.providerID, modelId: selected.modelID } });
|
|
15602
15736
|
return await rpc.request("session/send", { sessionId: id, content: lines.join("\n") });
|
|
15603
15737
|
}
|
|
15604
15738
|
if (op === "sessions.abort") {
|
|
@@ -15674,9 +15808,9 @@ var ZcodeAdapter = class {
|
|
|
15674
15808
|
};
|
|
15675
15809
|
|
|
15676
15810
|
// src/tools/pi.ts
|
|
15677
|
-
import { randomUUID as
|
|
15811
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
15678
15812
|
import { readFile as readFile5, readdir as readdir3, rm } from "node:fs/promises";
|
|
15679
|
-
import { dirname as
|
|
15813
|
+
import { dirname as dirname5 } from "node:path";
|
|
15680
15814
|
var OPERATIONS3 = [
|
|
15681
15815
|
...DAEMON_OPERATIONS,
|
|
15682
15816
|
"sessions.list",
|
|
@@ -15687,6 +15821,7 @@ var OPERATIONS3 = [
|
|
|
15687
15821
|
"sessions.prompt",
|
|
15688
15822
|
"sessions.abort",
|
|
15689
15823
|
"sessions.delete",
|
|
15824
|
+
"auth.status",
|
|
15690
15825
|
"configuration.models",
|
|
15691
15826
|
"permissions.list",
|
|
15692
15827
|
"questions.list",
|
|
@@ -15768,7 +15903,7 @@ var PiAdapter = class {
|
|
|
15768
15903
|
}
|
|
15769
15904
|
onQuestion(project, sessionId, detail) {
|
|
15770
15905
|
if (!["select", "confirm", "input", "editor"].includes(String(detail.method))) return;
|
|
15771
|
-
const id =
|
|
15906
|
+
const id = randomUUID11();
|
|
15772
15907
|
void this.rpc(project, sessionId).then((rpc) => {
|
|
15773
15908
|
if (rpc.exited) return;
|
|
15774
15909
|
const question = { id, project, sessionId, detail, rpc };
|
|
@@ -15847,6 +15982,11 @@ var PiAdapter = class {
|
|
|
15847
15982
|
async execute(command, project) {
|
|
15848
15983
|
const op = command.operation, p = command.payload, id = command.sessionId;
|
|
15849
15984
|
if (!OPERATIONS3.includes(op)) unsupportedOperation("pi", op);
|
|
15985
|
+
if (op === "auth.status") {
|
|
15986
|
+
const rpc2 = await this.runtime.connect(project);
|
|
15987
|
+
const result = await rpc2.request("get_available_models");
|
|
15988
|
+
return { status: result.models.length ? "authenticated" : "unknown", methods: [], message: result.models.length ? "Pi \u5DF2\u914D\u7F6E\u53EF\u7528\u6A21\u578B" : "Pi RPC \u672A\u63D0\u4F9B\u8FDC\u7A0B\u767B\u5F55\u63A5\u53E3\uFF1B\u8BF7\u5728\u4E3B\u673A\u4F7F\u7528 Pi \u539F\u751F\u767B\u5F55\u540E\u5237\u65B0" };
|
|
15989
|
+
}
|
|
15850
15990
|
if (op === "configuration.models") {
|
|
15851
15991
|
const rpc2 = await this.runtime.connect(project);
|
|
15852
15992
|
const { models } = await rpc2.request("get_available_models");
|
|
@@ -15859,7 +15999,7 @@ var PiAdapter = class {
|
|
|
15859
15999
|
return { providers: [...providers.values()] };
|
|
15860
16000
|
}
|
|
15861
16001
|
if (op === "sessions.list") {
|
|
15862
|
-
const directory =
|
|
16002
|
+
const directory = dirname5(this.metadataPath(project, "00000000-0000-0000-0000-000000000000"));
|
|
15863
16003
|
const files = await readdir3(directory).catch((error) => {
|
|
15864
16004
|
if (error.code === "ENOENT") return [];
|
|
15865
16005
|
throw error;
|
|
@@ -15868,7 +16008,7 @@ var PiAdapter = class {
|
|
|
15868
16008
|
return sessions.sort((a, b) => b.time.updated - a.time.updated);
|
|
15869
16009
|
}
|
|
15870
16010
|
if (op === "sessions.create") {
|
|
15871
|
-
const session2 = { id:
|
|
16011
|
+
const session2 = { id: randomUUID11(), title: typeof p.title === "string" && p.title.trim() ? p.title : "\u65B0\u4EFB\u52A1", directory: await this.workspace.root(project), time: { created: Date.now(), updated: Date.now() } };
|
|
15872
16012
|
try {
|
|
15873
16013
|
const rpc2 = await this.rpc(project, session2.id);
|
|
15874
16014
|
await rpc2.request("set_session_name", { name: session2.title });
|
|
@@ -15966,11 +16106,11 @@ var PiAdapter = class {
|
|
|
15966
16106
|
};
|
|
15967
16107
|
|
|
15968
16108
|
// src/tools/pi-runtime.ts
|
|
15969
|
-
import { execFile as
|
|
15970
|
-
import { mkdir as mkdir4, open as
|
|
16109
|
+
import { execFile as execFile3, spawn as spawn3 } from "node:child_process";
|
|
16110
|
+
import { mkdir as mkdir4, open as open5 } from "node:fs/promises";
|
|
15971
16111
|
import { join as join8 } from "node:path";
|
|
15972
|
-
import { promisify as
|
|
15973
|
-
var run2 =
|
|
16112
|
+
import { promisify as promisify3 } from "node:util";
|
|
16113
|
+
var run2 = promisify3(execFile3);
|
|
15974
16114
|
var PiRuntime = class {
|
|
15975
16115
|
constructor(config) {
|
|
15976
16116
|
this.config = config;
|
|
@@ -16030,7 +16170,7 @@ var PiRuntime = class {
|
|
|
16030
16170
|
async logs() {
|
|
16031
16171
|
const chunks = [];
|
|
16032
16172
|
for (const path of this.logPaths) {
|
|
16033
|
-
const file = await
|
|
16173
|
+
const file = await open5(path, "r").catch(() => void 0);
|
|
16034
16174
|
if (!file) continue;
|
|
16035
16175
|
try {
|
|
16036
16176
|
const size = (await file.stat()).size, bytes = Buffer.alloc(Math.min(size, 3e4));
|
|
@@ -16079,7 +16219,7 @@ ${bytes.toString("utf8")}`);
|
|
|
16079
16219
|
const directory = this.project(projectId).directory;
|
|
16080
16220
|
await mkdir4(join8(this.config.stateDirectory, "pi-sessions", projectId), { recursive: true, mode: 448 });
|
|
16081
16221
|
const logPath = join8(this.config.stateDirectory, `pi-${key}.log`);
|
|
16082
|
-
const log = await
|
|
16222
|
+
const log = await open5(logPath, "a", 384);
|
|
16083
16223
|
if (generation !== this.generation) {
|
|
16084
16224
|
await log.close();
|
|
16085
16225
|
throw new Error("Pi runtime stopped during startup");
|
|
@@ -16144,7 +16284,7 @@ async function runDaemon(configPath) {
|
|
|
16144
16284
|
const config = await loadConfig(configPath);
|
|
16145
16285
|
await mkdir5(config.stateDirectory, { recursive: true, mode: 448 });
|
|
16146
16286
|
const workspace = new Workspace(config.projects), runtime = createRuntime(config), uploads = new Uploads(workspace, join9(config.stateDirectory, "uploads")), journal = new Journal(join9(config.stateDirectory, "journal"));
|
|
16147
|
-
const epoch =
|
|
16287
|
+
const epoch = randomUUID12();
|
|
16148
16288
|
let stopping = false, botId = "";
|
|
16149
16289
|
const sockets = /* @__PURE__ */ new Set();
|
|
16150
16290
|
const events = new EventStream(epoch, () => {
|
|
@@ -16155,6 +16295,7 @@ async function runDaemon(configPath) {
|
|
|
16155
16295
|
}
|
|
16156
16296
|
const adapter = createAdapter(runtime, workspace, uploads, emit), artifacts = new Artifacts(workspace, join9(config.stateDirectory, "artifacts"), emit);
|
|
16157
16297
|
async function dispatch(c) {
|
|
16298
|
+
if (!adapter.supportedOperations().includes(c.operation)) throw new Error(`Unsupported ${runtime.kind} operation: ${c.operation}`);
|
|
16158
16299
|
const p = c.payload;
|
|
16159
16300
|
if (c.operation === "runtime.status") return { ...await runtime.status(), capabilities: adapter.supportedOperations() };
|
|
16160
16301
|
if (c.operation === "runtime.snapshot") return buildProxySnapshot(config, runtime, adapter, c.projectId);
|
|
@@ -16284,6 +16425,10 @@ export {
|
|
|
16284
16425
|
describeTool,
|
|
16285
16426
|
ConfigError,
|
|
16286
16427
|
loadConfig,
|
|
16428
|
+
Workspace,
|
|
16287
16429
|
atomicJson,
|
|
16430
|
+
Uploads,
|
|
16431
|
+
createRuntime,
|
|
16432
|
+
createAdapter,
|
|
16288
16433
|
runDaemon
|
|
16289
16434
|
};
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,11 @@ import {
|
|
|
4
4
|
SERVICE_LABEL,
|
|
5
5
|
SYSTEMD_UNIT,
|
|
6
6
|
TOOL_KINDS,
|
|
7
|
+
Uploads,
|
|
8
|
+
Workspace,
|
|
7
9
|
atomicJson,
|
|
10
|
+
createAdapter,
|
|
11
|
+
createRuntime,
|
|
8
12
|
daemonEntryPath,
|
|
9
13
|
defaultStateDirectory,
|
|
10
14
|
describeTool,
|
|
@@ -17,7 +21,7 @@ import {
|
|
|
17
21
|
runDaemon,
|
|
18
22
|
supportedPlatform,
|
|
19
23
|
systemdUnitPath
|
|
20
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-AGWRSSBQ.js";
|
|
21
25
|
|
|
22
26
|
// src/cli.ts
|
|
23
27
|
import { spawn } from "node:child_process";
|
|
@@ -27,7 +31,6 @@ import { access as access4 } from "node:fs/promises";
|
|
|
27
31
|
// src/doctor.ts
|
|
28
32
|
import { access as access2, chmod, constants, mkdir as mkdir3, stat as stat2 } from "node:fs/promises";
|
|
29
33
|
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
30
|
-
import { homedir as homedir2 } from "node:os";
|
|
31
34
|
import { execFile as execFile4 } from "node:child_process";
|
|
32
35
|
import { promisify as promisify4 } from "node:util";
|
|
33
36
|
|
|
@@ -140,7 +143,12 @@ async function launchdStop() {
|
|
|
140
143
|
await ignoreMissing(() => run2("launchctl", ["bootout", `${guiDomain()}/${SERVICE_LABEL}`]));
|
|
141
144
|
}
|
|
142
145
|
async function launchdRestart() {
|
|
143
|
-
|
|
146
|
+
const target = `${guiDomain()}/${SERVICE_LABEL}`;
|
|
147
|
+
if ((await launchdStatus()).loaded) {
|
|
148
|
+
await run2("launchctl", ["kickstart", "-k", target]);
|
|
149
|
+
} else {
|
|
150
|
+
await run2("launchctl", ["bootstrap", guiDomain(), launchdPlistPath()]);
|
|
151
|
+
}
|
|
144
152
|
}
|
|
145
153
|
async function launchdUninstall() {
|
|
146
154
|
await launchdStop();
|
|
@@ -322,6 +330,7 @@ function logLocationHint() {
|
|
|
322
330
|
}
|
|
323
331
|
|
|
324
332
|
// src/doctor.ts
|
|
333
|
+
import { randomUUID } from "node:crypto";
|
|
325
334
|
var run4 = promisify4(execFile4);
|
|
326
335
|
async function runDoctor(opts) {
|
|
327
336
|
const results = [];
|
|
@@ -354,7 +363,9 @@ async function runDoctor(opts) {
|
|
|
354
363
|
const descriptor = describeTool(config.tool.kind);
|
|
355
364
|
const checkName = `${descriptor.label} \u547D\u4EE4`;
|
|
356
365
|
const command2 = config.tool.command;
|
|
357
|
-
if (
|
|
366
|
+
if (config.tool.kind === "opencode") {
|
|
367
|
+
record(checkName, "ok", "external \u6A21\u5F0F\u901A\u8FC7 HTTP \u68C0\u67E5\u670D\u52A1\uFF0C\u4E0D\u9700\u8981 Proxy \u6267\u884C OpenCode \u547D\u4EE4");
|
|
368
|
+
} else if (!isAbsolute2(command2)) {
|
|
358
369
|
record(checkName, "warn", `\u5F53\u524D\u4E3A\u300C${command2}\u300D\uFF0C\u4F9D\u8D56 PATH \u67E5\u627E\uFF1B\u670D\u52A1\u73AF\u5883\u5EFA\u8BAE\u6539\u7528\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u91CD\u65B0\u8FD0\u884C onboard \u6216\u624B\u5DE5\u4FEE\u6539\u914D\u7F6E\uFF09`);
|
|
359
370
|
} else if (!await access2(command2, constants.X_OK).then(() => true, () => false)) {
|
|
360
371
|
record(checkName, "fail", `${command2} \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C`);
|
|
@@ -363,22 +374,29 @@ async function runDoctor(opts) {
|
|
|
363
374
|
const { stdout: stdout2 } = await run4(command2, [...descriptor.versionArgs], { timeout: 15e3 });
|
|
364
375
|
const version = stdout2.trim().split("\n")[0].trim();
|
|
365
376
|
if (version === descriptor.baseline) record(checkName, "ok", `${command2}\uFF08${version}\uFF09`);
|
|
366
|
-
else if (descriptor.versionPolicy === "exact") record(checkName, "warn", `${command2} \u7248\u672C\u4E3A ${version}\uFF0C\u5DF2\u9A8C\u8BC1\u57FA\u7EBF\u662F ${descriptor.baseline}\uFF08\
|
|
377
|
+
else if (descriptor.versionPolicy === "exact") record(checkName, "warn", `${command2} \u7248\u672C\u4E3A ${version}\uFF0C\u5DF2\u9A8C\u8BC1\u57FA\u7EBF\u662F ${descriptor.baseline}\uFF08\u8BF7\u786E\u8BA4\u5916\u90E8\u670D\u52A1 API \u517C\u5BB9\uFF09`);
|
|
367
378
|
else record(checkName, "warn", `${command2} \u7248\u672C\u4E3A ${version}\uFF0C\u5DF2\u9A8C\u8BC1\u57FA\u7EBF\u662F ${descriptor.baseline}\uFF1Bapp-server \u534F\u8BAE\u53EF\u80FD\u6F02\u79FB`);
|
|
368
379
|
} catch (error) {
|
|
369
380
|
record(checkName, "warn", `\u6267\u884C ${command2} ${descriptor.versionArgs.join(" ")} \u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
370
381
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (config) {
|
|
385
|
+
const probeConfig = { ...config, tool: { ...config.tool, autoStart: false } };
|
|
386
|
+
const runtime = createRuntime(probeConfig), workspace = new Workspace(config.projects);
|
|
387
|
+
const adapter = createAdapter(runtime, workspace, new Uploads(workspace, join2(config.stateDirectory, "uploads")), () => {
|
|
388
|
+
});
|
|
389
|
+
try {
|
|
390
|
+
await runtime.init();
|
|
391
|
+
const result = await adapter.dispatch({ type: "command", id: randomUUID(), botId: config.botId, projectId: config.projects[0].id, operation: "auth.status", payload: {} });
|
|
392
|
+
const ready = ["authenticated", "notRequired"].includes(result.status);
|
|
393
|
+
record("\u7F16\u7801\u5DE5\u5177\u8D26\u53F7", ready ? "ok" : "warn", result.message ?? (ready ? "\u539F\u751F\u5DE5\u5177\u5DF2\u914D\u7F6E\u8D26\u53F7\u6216\u65E0\u9700\u8BA4\u8BC1" : "\u539F\u751F\u5DE5\u5177\u672A\u767B\u5F55\uFF1B\u53EF\u5728 Claw \u7684\u201C\u7F16\u7801\u5DE5\u5177\u8D26\u53F7\u201D\u4E2D\u767B\u5F55"));
|
|
394
|
+
} catch {
|
|
395
|
+
record("\u7F16\u7801\u5DE5\u5177\u8D26\u53F7", "warn", "\u65E0\u6CD5\u83B7\u53D6\u539F\u751F\u767B\u5F55\u72B6\u6001\uFF1B\u8BF7\u68C0\u67E5\u5DE5\u5177\u670D\u52A1\u3001\u7248\u672C\u4E0E\u914D\u7F6E\uFF0C\u518D\u5728 Claw \u4E2D\u91CD\u8BD5");
|
|
396
|
+
} finally {
|
|
397
|
+
adapter.close();
|
|
398
|
+
if (config.tool.kind !== "opencode") await runtime.stop();
|
|
399
|
+
runtime.detach();
|
|
382
400
|
}
|
|
383
401
|
}
|
|
384
402
|
try {
|
|
@@ -489,7 +507,7 @@ async function runOnboard(opts) {
|
|
|
489
507
|
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();
|
|
490
508
|
let toolKind = opts.toolKind ?? existing?.tool.kind ?? "opencode";
|
|
491
509
|
let toolCommand = opts.toolCommand ?? (toolKind === existing?.tool.kind ? existing.tool.command : "");
|
|
492
|
-
let
|
|
510
|
+
let baseUrl = existing?.tool.kind === "opencode" ? existing.tool.baseUrl : "http://127.0.0.1:4096";
|
|
493
511
|
let projects = [...existing?.projects ?? [], ...opts.projects].map((project) => ({ ...project, directory: expandHome(project.directory) }));
|
|
494
512
|
if (opts.interactive) {
|
|
495
513
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
@@ -506,9 +524,21 @@ async function runOnboard(opts) {
|
|
|
506
524
|
const descriptor = describeTool(toolKind);
|
|
507
525
|
const found = await resolveCommandOnPath(toolKind, toolSearchDirs(toolKind));
|
|
508
526
|
const example = platform2() === "darwin" ? MACOS_TOOL_EXAMPLES[toolKind] : void 0;
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
527
|
+
if (toolKind === "opencode") {
|
|
528
|
+
toolCommand = toolCommand || found || "opencode";
|
|
529
|
+
console.log(" OpenCode \u7531\u5916\u90E8\u7BA1\u7406\uFF1B\u8BF7\u5728\u8BE5\u4E3B\u673A\u9884\u5148\u542F\u52A8 opencode serve\u3002");
|
|
530
|
+
baseUrl = await ask(rl, "OpenCode \u670D\u52A1\u5730\u5740", { default: baseUrl || "http://127.0.0.1:4096", validate: (value) => {
|
|
531
|
+
try {
|
|
532
|
+
const url = new URL(value);
|
|
533
|
+
return url.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) ? void 0 : "\u8BF7\u8F93\u5165 HTTP loopback \u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:4096";
|
|
534
|
+
} catch {
|
|
535
|
+
return "\u8BF7\u8F93\u5165\u6709\u6548 URL";
|
|
536
|
+
}
|
|
537
|
+
} });
|
|
538
|
+
} else {
|
|
539
|
+
toolCommand = expandHome(await ask(rl, `${descriptor.label} \u547D\u4EE4\uFF08\u56DE\u8F66\u4F1A\u9A8C\u8BC1\u8DEF\u5F84\uFF09`, { default: toolCommand || found || example, validate: (value) => validateToolCommand(value, toolKind) }));
|
|
540
|
+
console.log(` \u5DF2\u786E\u8BA4 ${descriptor.label} \u547D\u4EE4\u53EF\u6267\u884C\uFF1A${toolCommand}`);
|
|
541
|
+
}
|
|
512
542
|
if (projects.length) console.log("\n\u5DF2\u6709\u6388\u6743\u9879\u76EE\uFF08\u76F4\u63A5\u56DE\u8F66\u4FDD\u7559\u5404\u9879\uFF09\uFF1A");
|
|
513
543
|
for (const [index, project] of projects.entries()) {
|
|
514
544
|
project.name = await ask(rl, " \u9879\u76EE\u540D\u79F0", { default: project.name });
|
|
@@ -597,7 +627,7 @@ async function runOnboard(opts) {
|
|
|
597
627
|
stateDirectory,
|
|
598
628
|
...loopbackHttp ? { allowInsecureLocalhost: true } : {},
|
|
599
629
|
projects: projects.map((project) => ({ id: project.id, name: project.name, directory: project.directory, specDirectories: project.specDirectories ?? ["specs", "docs"] })),
|
|
600
|
-
tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl
|
|
630
|
+
tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl, mode: "external" } : {}, ...toolKind === existing?.tool.kind && existing.tool.password ? { password: existing.tool.password } : {}, ...toolKind === "opencode" ? {} : { autoStart: toolKind === existing?.tool.kind ? existing.tool.autoStart : true } }
|
|
601
631
|
});
|
|
602
632
|
await loadConfig(opts.configPath);
|
|
603
633
|
console.log(`\u5DF2\u5199\u5165\u914D\u7F6E\uFF1A${opts.configPath}\uFF080600\uFF09`);
|
|
@@ -626,7 +656,7 @@ async function runOnboard(opts) {
|
|
|
626
656
|
}
|
|
627
657
|
|
|
628
658
|
// src/projects-command.ts
|
|
629
|
-
import { randomUUID } from "node:crypto";
|
|
659
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
630
660
|
import { readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
|
|
631
661
|
function asConfig(value) {
|
|
632
662
|
if (typeof value !== "object" || value === null || Array.isArray(value) || !Array.isArray(value.projects)) {
|
|
@@ -653,7 +683,7 @@ async function manageProjects(configPath2, remove) {
|
|
|
653
683
|
const index = Number(remove) - 1;
|
|
654
684
|
if (!Number.isSafeInteger(index) || index >= config.projects.length) throw new Error(`\u9879\u76EE\u5E8F\u53F7\u8D85\u51FA\u8303\u56F4\uFF1A${remove}`);
|
|
655
685
|
if (config.projects.length === 1) throw new Error("\u81F3\u5C11\u9700\u8981\u4FDD\u7559\u4E00\u4E2A\u6388\u6743\u9879\u76EE\uFF1B\u65E0\u6CD5\u5220\u9664\u6700\u540E\u4E00\u9879");
|
|
656
|
-
const backupPath = `${configPath2}.backup-${
|
|
686
|
+
const backupPath = `${configPath2}.backup-${randomUUID2()}`;
|
|
657
687
|
await writeFile3(backupPath, original, { mode: 384, flag: "wx" });
|
|
658
688
|
config.projects.splice(index, 1);
|
|
659
689
|
await atomicJson(configPath2, config);
|
package/dist/main.js
CHANGED
package/package.json
CHANGED
package/proxy.example.json
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
"botId": "00000000-0000-4000-8000-000000000000",
|
|
5
5
|
"stateDirectory": "/home/developer/.local/state/octopus",
|
|
6
6
|
"projects": [{ "id": "example", "name": "Example project", "directory": "/home/developer/projects/example", "specDirectories": ["specs", "docs"] }],
|
|
7
|
-
"tool": { "kind": "opencode", "command": "/usr/local/bin/opencode", "baseUrl": "http://127.0.0.1:4096", "mode": "
|
|
7
|
+
"tool": { "kind": "opencode", "command": "/usr/local/bin/opencode", "baseUrl": "http://127.0.0.1:4096", "mode": "external" }
|
|
8
8
|
}
|