@skylandnpm/octopus-proxy 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,6 +72,6 @@ npm run publish
72
72
 
73
73
  ## 编码工具账号与 OpenCode 服务
74
74
 
75
- OpenCode 使用 external HTTP 模式。Proxy 默认在启动时使用 `tool.command` 拉起未运行的服务;也可自行运行 `opencode serve --hostname 127.0.0.1 --port 4096`,或在 Claw 工作区点击“启动服务”重试。配置的 `tool.baseUrl` 必须与端口一致。启动日志保存在 `stateDirectory/opencode-serve.log`;如需只连接自行管理的服务,可显式设置 `autoStart: false`。旧 managed 配置必须迁移;Proxy 不停止或重启已有 OpenCode 服务。
75
+ OpenCode 2.0.19 使用 external HTTP 模式。`tool.command` 应为 `/Users/luhuili/.opencode/bin/opencode`,`tool.baseUrl` 明确指向回环服务,`tool.password` 必填并由 onboard 自动生成。Proxy 可拉起未监听的服务,也可连接以同一密码预先启动的服务;启动日志保存在 `stateDirectory/opencode-serve.log`。如需只连接自行管理的服务,可设置 `autoStart: false`。旧顶层配置、managed 模式和 v1 API 不再兼容;Proxy 不停止或重启已有 OpenCode 服务。
76
76
 
77
77
  Claw 的“编码工具账号”窗口支持 Codex API Key/设备码、ZCode 原生 Z.AI OAuth、OpenCode 原生 API Key/OAuth。凭据由编码工具保存,模型列表从原生接口读取。详细行为与验证边界见 [编码工具账号](../../docs/tool-authentication.md)。
@@ -11549,13 +11549,13 @@ var TOOL_DESCRIPTORS = {
11549
11549
  opencode: {
11550
11550
  kind: "opencode",
11551
11551
  label: "OpenCode",
11552
- defaultCommand: "opencode",
11552
+ defaultCommand: "/Users/luhuili/.opencode/bin/opencode",
11553
11553
  serverArgs: [],
11554
11554
  versionArgs: ["--version"],
11555
- baseline: "1.1.32",
11555
+ baseline: "2.0.19",
11556
11556
  versionPolicy: "exact",
11557
11557
  sessionPattern: /^ses_[A-Za-z0-9]+$/,
11558
- searchDirs: ["/opt/homebrew/bin", "/usr/local/bin"]
11558
+ searchDirs: ["/Users/luhuili/.opencode/bin"]
11559
11559
  },
11560
11560
  codex: {
11561
11561
  kind: "codex",
@@ -11604,10 +11604,12 @@ var ToolSchema = z.object({
11604
11604
  baseUrl: z.string().url().optional(),
11605
11605
  mode: z.enum(["managed", "external"]).optional(),
11606
11606
  autoStart: z.boolean().optional(),
11607
- password: z.string().optional()
11607
+ password: z.string().min(20).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\u5C06 tool.mode \u6539\u4E3A external\uFF0C\u7136\u540E\u53EF\u4ECE Claw \u8FDC\u7A0B\u542F\u52A8\u670D\u52A1" });
11610
- }).transform((tool) => ({ ...tool, mode: tool.kind === "opencode" ? "external" : tool.mode ?? "managed", autoStart: tool.autoStart ?? true }));
11609
+ if (tool.kind === "opencode" && !tool.password) context.addIssue({ code: z.ZodIssueCode.custom, path: ["password"], message: "OpenCode v2 \u5FC5\u987B\u914D\u7F6E\u670D\u52A1\u5BC6\u7801\uFF1B\u8BF7\u91CD\u65B0\u8FD0\u884C onboard" });
11610
+ if (tool.kind === "opencode" && tool.mode === "managed") context.addIssue({ code: z.ZodIssueCode.custom, path: ["mode"], message: "OpenCode v2 \u4EC5\u652F\u6301 external \u6A21\u5F0F" });
11611
+ if (tool.kind !== "opencode" && tool.mode === "external") context.addIssue({ code: z.ZodIssueCode.custom, path: ["mode"], message: "external mode is only supported for opencode" });
11612
+ }).transform((tool) => ({ ...tool, mode: tool.kind === "opencode" ? "external" : "managed", autoStart: tool.autoStart ?? true }));
11611
11613
  var ConfigSchema = z.object({
11612
11614
  hubUrl: z.string().url(),
11613
11615
  botId: z.string().uuid(),
@@ -11625,11 +11627,6 @@ async function loadConfig(path) {
11625
11627
  });
11626
11628
  if ((info.mode & 63) !== 0) throw new Error("Proxy configuration contains a secret; run chmod 600 on it");
11627
11629
  const raw = JSON.parse(await readFile(path, "utf8"));
11628
- if (raw.opencode instanceof Object && raw.tool === void 0) {
11629
- const { command, baseUrl, mode, autoStart, password } = raw.opencode;
11630
- raw.tool = { kind: "opencode", command, baseUrl, mode, autoStart, password };
11631
- delete raw.opencode;
11632
- }
11633
11630
  const config = ConfigSchema.parse(raw);
11634
11631
  config.stateDirectory = expandHome(config.stateDirectory);
11635
11632
  config.tool.command = expandHome(config.tool.command || describeTool(config.tool.kind).defaultCommand);
@@ -11698,10 +11695,6 @@ var Operations = [
11698
11695
  "sessions.revert",
11699
11696
  "sessions.unrevert",
11700
11697
  "sessions.summarize",
11701
- "sessions.share",
11702
- "sessions.unshare",
11703
- "sessions.todos",
11704
- "sessions.children",
11705
11698
  "sessions.command",
11706
11699
  "sessions.shell",
11707
11700
  "sessions.status",
@@ -11720,9 +11713,6 @@ var Operations = [
11720
11713
  "configuration.mcpAdd",
11721
11714
  "configuration.mcpConnect",
11722
11715
  "configuration.mcpDisconnect",
11723
- "configuration.auth",
11724
- "configuration.oauthAuthorize",
11725
- "configuration.oauthCallback",
11726
11716
  "terminal.list",
11727
11717
  "terminal.create",
11728
11718
  "terminal.resize",
@@ -12215,7 +12205,7 @@ var AuthFlows = class {
12215
12205
  }
12216
12206
  };
12217
12207
  function isAuthentication(operation) {
12218
- return operation.startsWith("auth.") || ["configuration.auth", "configuration.oauthAuthorize", "configuration.oauthCallback"].includes(operation);
12208
+ return operation.startsWith("auth.");
12219
12209
  }
12220
12210
 
12221
12211
  // src/journal.ts
@@ -14169,11 +14159,11 @@ var ProjectConfig = class {
14169
14159
  return work;
14170
14160
  }
14171
14161
  async apply(project, patch) {
14172
- const states = await this.request(project, "GET", "/session/status");
14173
- if (Object.values(states).some((s) => s.type !== "idle")) throw new Error("Wait for active tasks and approvals before changing project configuration");
14162
+ const states = unwrap(await this.request(project, "GET", "/api/session/active"));
14163
+ if (Object.keys(states ?? {}).length) throw new Error("Wait for active tasks and approvals before changing project configuration");
14174
14164
  if (!this.validator) {
14175
- const doc = await this.request(project, "GET", "/doc");
14176
- this.validator = new import__.Ajv2020({ strict: false, validateFormats: false }).compile({ $ref: "#/components/schemas/Config", components: doc.components });
14165
+ const doc = await this.request(project, "GET", "/openapi.json");
14166
+ this.validator = new import__.Ajv2020({ strict: false, validateFormats: false }).compile({ $ref: "#/components/schemas/Config.InfoEncoded", components: doc.components });
14177
14167
  }
14178
14168
  if (!this.validator(patch)) throw new Error("Invalid OpenCode configuration: " + JSON.stringify(this.validator.errors));
14179
14169
  let name = "opencode.json", original = "", exists = false;
@@ -14214,12 +14204,20 @@ var ProjectConfig = class {
14214
14204
  });
14215
14205
  }
14216
14206
  }
14217
- await this.request(project, "POST", "/instance/dispose");
14218
- return this.request(project, "GET", "/config");
14207
+ await this.request(project, "POST", "/api/location/reload");
14208
+ return unwrap(await this.request(project, "GET", "/api/config"));
14219
14209
  }
14220
14210
  };
14211
+ function unwrap(value) {
14212
+ return value && typeof value === "object" && "data" in value ? value.data : value;
14213
+ }
14221
14214
 
14222
14215
  // src/opencode.ts
14216
+ var UNSUPPORTED = /* @__PURE__ */ new Set([
14217
+ "runtime.stop",
14218
+ "runtime.restart",
14219
+ "runtime.logs"
14220
+ ]);
14223
14221
  var OpenCodeAdapter = class {
14224
14222
  constructor(runtime, workspace, uploads, emit) {
14225
14223
  this.runtime = runtime;
@@ -14230,23 +14228,26 @@ var OpenCodeAdapter = class {
14230
14228
  this.media = new RemoteMedia(workspace, () => uploads.limits.imageBytes);
14231
14229
  }
14232
14230
  supportedOperations() {
14233
- return Operations.filter((op) => !["runtime.stop", "runtime.restart", "runtime.logs"].includes(op));
14231
+ return Operations.filter((operation) => !UNSUPPORTED.has(operation));
14234
14232
  }
14235
14233
  auth = new AuthFlows();
14236
- authDirty = /* @__PURE__ */ new Set();
14237
- authRefreshing = /* @__PURE__ */ new Map();
14238
14234
  streams = /* @__PURE__ */ new Map();
14239
14235
  terminals = /* @__PURE__ */ new Map();
14240
- // OpenCode 1.1.32 only buffers output while no client is attached.
14241
- // Keep a bounded recent tail for panel remounts; never persist terminal text.
14242
14236
  terminalOutput = /* @__PURE__ */ new Map();
14243
14237
  resolving = /* @__PURE__ */ new Set();
14244
14238
  config;
14245
14239
  media;
14246
14240
  async request(project, method, path, body, signal) {
14247
- const root = await this.workspace.root(project), url = new URL(path, this.runtime.config.tool.baseUrl);
14248
- url.searchParams.set("directory", root);
14249
- 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) => {
14241
+ const root = await this.workspace.root(project);
14242
+ const url = new URL(path, this.runtime.config.tool.baseUrl);
14243
+ if (path === "/api/session") url.searchParams.set("directory", root);
14244
+ else if (/^\/api\/(?:agent|model|provider|mcp|command|config|pty|permission\/request|form|integration|location|experimental\/mcp)(?:\/|$)/.test(path)) url.searchParams.set("location[directory]", root);
14245
+ const response = await fetch(url, {
14246
+ method,
14247
+ headers: { ...this.runtime.headers(), ...body === void 0 ? {} : { "Content-Type": "application/json" } },
14248
+ body: body === void 0 ? void 0 : JSON.stringify(body),
14249
+ signal: signal ?? AbortSignal.timeout(35e3)
14250
+ }).catch((error) => {
14250
14251
  if (method !== "GET") {
14251
14252
  const unknown = new Error("Transport interrupted; inspect remote state before retrying");
14252
14253
  unknown.name = "UnknownResultError";
@@ -14259,11 +14260,15 @@ var OpenCodeAdapter = class {
14259
14260
  const text = await response.text();
14260
14261
  return text ? JSON.parse(text) : null;
14261
14262
  }
14263
+ async data(project, method, path, body, signal) {
14264
+ return unwrap2(await this.request(project, method, path, body, signal));
14265
+ }
14262
14266
  async session(project, id) {
14263
14267
  if (!/^ses_[A-Za-z0-9]+$/.test(id)) throw new Error("Invalid session id");
14264
- const session = await this.request(project, "GET", `/session/${encodeURIComponent(id)}`);
14265
- if (await realpath4(session.directory) !== await this.workspace.root(project)) throw new Error("Session belongs to another project");
14266
- return session;
14268
+ const session = await this.data(project, "GET", `/api/session/${encodeURIComponent(id)}`);
14269
+ const directory = session?.location?.directory ?? session?.directory;
14270
+ if (typeof directory !== "string" || await realpath4(directory) !== await this.workspace.root(project)) throw new Error("Session belongs to another project");
14271
+ return normalizeSession(session);
14267
14272
  }
14268
14273
  identifier(value) {
14269
14274
  if (typeof value !== "string" || !/^[A-Za-z0-9_.:-]+$/.test(value)) throw new Error("Invalid resource identifier");
@@ -14281,164 +14286,193 @@ var OpenCodeAdapter = class {
14281
14286
  if (!id) throw new Error("Session required");
14282
14287
  await this.session(project, id);
14283
14288
  }
14284
- const base = `/session/${encodeURIComponent(id ?? "")}`;
14289
+ const base = `/api/session/${encodeURIComponent(id ?? "")}`;
14290
+ if (op === "sessions.prompt") return this.prompt(project, id, p, base);
14291
+ if (op === "sessions.command" || op === "sessions.shell") {
14292
+ await this.switchSelection(project, base, p);
14293
+ if (op === "sessions.shell") return this.data(project, "POST", `${base}/shell`, { ...p.id ? { id: p.id } : {}, command: p.command });
14294
+ return this.data(project, "POST", `${base}/command`, { name: p.name ?? p.command, text: p.text ?? p.arguments ?? "", delivery: "queue", ...Array.isArray(p.files) ? { files: p.files } : {}, ...Array.isArray(p.agents) ? { agents: p.agents } : {}, ...Array.isArray(p.skills) ? { skills: p.skills } : {} });
14295
+ }
14296
+ if (op === "permissions.list") return this.permissions(project, id);
14297
+ if (op === "questions.list") return this.forms(project, id);
14298
+ if (op === "permissions.reply" || op === "questions.reply" || op === "questions.reject") return this.resolveInteraction(project, id, op, p);
14299
+ if (op === "configuration.models") return this.models(project);
14300
+ if (op === "configuration.patch") return redact(await this.config.update(project, p));
14301
+ if (op === "configuration.mcpAdd") {
14302
+ this.identifier(p.name);
14303
+ await this.config.update(project, { mcp: { servers: { [String(p.name)]: p.config } } });
14304
+ return this.data(project, "GET", "/api/mcp");
14305
+ }
14306
+ if (op === "configuration.mcpConnect" || op === "configuration.mcpDisconnect") {
14307
+ return this.data(project, "POST", `/api/experimental/mcp/${this.identifier(p.name)}/${op.endsWith("Disconnect") ? "disconnect" : "connect"}`);
14308
+ }
14309
+ if (op === "terminal.create") return this.data(project, "POST", "/api/pty", { ...p, cwd: await this.workspace.path(project, typeof p.cwd === "string" ? p.cwd : ".") });
14310
+ if (op.startsWith("terminal.") && !["terminal.list", "terminal.create"].includes(op)) return this.terminal(project, op, p);
14285
14311
  const routes = {
14286
- "sessions.list": ["GET", "/session"],
14287
- "sessions.status": ["GET", "/session/status"],
14288
- "sessions.create": ["POST", "/session"],
14312
+ "sessions.list": ["GET", "/api/session"],
14313
+ "sessions.status": ["GET", "/api/session/active"],
14314
+ "sessions.create": ["POST", "/api/session", p],
14289
14315
  "sessions.get": ["GET", base],
14290
14316
  "sessions.messages": ["GET", `${base}/message`],
14291
- "sessions.abort": ["POST", `${base}/abort`],
14317
+ "sessions.abort": ["POST", `${base}/interrupt`],
14292
14318
  "sessions.delete": ["DELETE", base],
14293
- "sessions.rename": ["PATCH", base],
14294
- "sessions.fork": ["POST", `${base}/fork`],
14319
+ "sessions.rename": ["PATCH", base, { title: p.title }],
14320
+ "sessions.fork": ["POST", `${base}/fork`, p.before || p.messageID ? { before: p.before ?? p.messageID } : {}],
14295
14321
  "sessions.diff": ["GET", `${base}/diff`],
14296
- "sessions.revert": ["POST", `${base}/revert`],
14297
- "sessions.unrevert": ["POST", `${base}/unrevert`],
14298
- "sessions.summarize": ["POST", `${base}/summarize`],
14299
- "sessions.share": ["POST", `${base}/share`],
14300
- "sessions.unshare": ["DELETE", `${base}/share`],
14301
- "sessions.todos": ["GET", `${base}/todo`],
14302
- "sessions.children": ["GET", `${base}/children`],
14303
- "sessions.command": ["POST", `${base}/command`],
14304
- "sessions.shell": ["POST", `${base}/shell`],
14305
- "permissions.list": ["GET", "/permission"],
14306
- "questions.list": ["GET", "/question"],
14307
- "configuration.get": ["GET", "/config"],
14308
- "configuration.patch": ["PATCH", "/config"],
14309
- "configuration.models": ["GET", "/config/providers"],
14310
- "configuration.providers": ["GET", "/provider"],
14311
- "configuration.agents": ["GET", "/agent"],
14312
- "configuration.commands": ["GET", "/command"],
14313
- "configuration.mcp": ["GET", "/mcp"],
14314
- "configuration.mcpAdd": ["POST", "/mcp"],
14315
- "terminal.list": ["GET", "/pty"],
14316
- "terminal.create": ["POST", "/pty"]
14322
+ "sessions.revert": ["POST", `${base}/revert/stage`, p],
14323
+ "sessions.unrevert": ["DELETE", `${base}/revert`],
14324
+ "sessions.summarize": ["POST", `${base}/compact`, {}],
14325
+ "configuration.get": ["GET", "/api/config"],
14326
+ "configuration.providers": ["GET", "/api/provider"],
14327
+ "configuration.agents": ["GET", "/api/agent"],
14328
+ "configuration.commands": ["GET", "/api/command"],
14329
+ "configuration.mcp": ["GET", "/api/mcp"],
14330
+ "terminal.list": ["GET", "/api/pty"]
14317
14331
  };
14318
- if (op === "configuration.models") {
14319
- await this.refreshAuth(project);
14320
- const result2 = await this.request(project, "GET", "/provider");
14321
- return redact({ providers: result2.all.filter((provider) => result2.connected.includes(provider.id)) });
14322
- }
14323
- if (op === "sessions.prompt") {
14324
- const parts = [];
14325
- if (typeof p.text === "string" && p.text.trim()) parts.push({ type: "text", text: screenshotPrompt(p.text) });
14326
- for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
14327
- const upload = await this.uploads.attachment(project, String(uploadId), id);
14328
- if (upload.mime.startsWith("image/") && upload.mime !== "image/svg+xml" || upload.mime === "application/pdf") parts.push({ type: "file", mime: upload.mime, filename: upload.name, url: pathToFileURL(await this.workspace.path(project, upload.destination)).href });
14329
- else parts.push({ type: "text", text: `Attached file: ${upload.destination}. Read this file if relevant to the task.` });
14330
- }
14331
- if (!parts.length) throw new Error("Message or attachments required");
14332
- return this.request(project, "POST", `${base}/prompt_async`, { parts, ...p.agent ? { agent: p.agent } : {}, ...p.model ? { model: p.model } : {}, ...p.messageID ? { messageID: p.messageID } : {} });
14333
- }
14334
- if (op === "permissions.reply" || op === "questions.reply" || op === "questions.reject") {
14335
- const prefix = op.startsWith("permissions") ? "permission" : "question";
14336
- const requestId = this.identifier(p.requestId), key = `${prefix}:${requestId}`;
14337
- if (this.resolving.has(key)) throw new Error("Request is already being resolved by another client");
14338
- this.resolving.add(key);
14339
- try {
14340
- const pending = await this.request(project, "GET", `/${prefix}`);
14341
- const item = pending.find((x) => x.id === p.requestId);
14342
- if (!item) throw new Error("Request is no longer pending");
14343
- if (id && id !== item.sessionID) throw new Error("Request belongs to another session");
14344
- await this.session(project, item.sessionID);
14345
- const body = prefix === "permission" ? { reply: p.reply, message: p.message } : { answers: p.answers };
14346
- return await this.request(project, "POST", `/${prefix}/${requestId}/${op.endsWith("reject") ? "reject" : "reply"}`, body);
14347
- } finally {
14348
- this.resolving.delete(key);
14349
- }
14350
- }
14351
- if (op === "terminal.create") return this.request(project, "POST", "/pty", { ...p, cwd: await this.workspace.path(project, typeof p.cwd === "string" ? p.cwd : ".") });
14352
- if (op === "configuration.patch") return redact(await this.config.update(project, p));
14353
- if (op === "configuration.mcpAdd") {
14354
- this.identifier(p.name);
14355
- await this.config.update(project, { mcp: { [String(p.name)]: p.config } });
14356
- return this.request(project, "GET", "/mcp");
14357
- }
14358
- if (op.startsWith("terminal.") && !routes[op]) return this.terminal(project, op, p);
14359
- if (op === "configuration.mcpConnect" || op === "configuration.mcpDisconnect") return this.request(project, "POST", `/mcp/${this.identifier(p.name)}/${op.endsWith("Disconnect") ? "disconnect" : "connect"}`);
14360
- if (op === "configuration.auth") return this.request(project, "PUT", `/auth/${this.identifier(p.providerId)}`, p.credentials);
14361
- if (op === "configuration.oauthAuthorize" || op === "configuration.oauthCallback") return this.request(project, "POST", `/provider/${this.identifier(p.providerId)}/oauth/${op.endsWith("Authorize") ? "authorize" : "callback"}`, p.input);
14362
14332
  const route = routes[op];
14363
14333
  if (!route) throw new Error(`Unsupported OpenCode operation: ${op}`);
14364
- const result = await this.request(project, route[0], route[1], route[0] === "GET" ? void 0 : p);
14365
- if (op === "sessions.list") {
14366
- const root = await this.workspace.root(project);
14367
- const matches = await Promise.all(result.map(async (s) => {
14368
- try {
14369
- return await realpath4(s.directory) === root;
14370
- } catch {
14371
- return false;
14372
- }
14373
- }));
14374
- return result.filter((_, index) => matches[index]);
14375
- }
14376
- if (["configuration.get", "configuration.models", "configuration.providers"].includes(op)) return redact(result);
14377
- if (op === "sessions.messages" && Array.isArray(result)) return this.media.messages(project, id, result);
14378
- if (op.startsWith("sessions.")) return this.media.normalize(project, result);
14334
+ const result = await this.data(project, route[0], route[1], route[2]);
14335
+ if (op === "sessions.list") return (Array.isArray(result) ? result : []).map(normalizeSession);
14336
+ if (op === "sessions.status") return Object.fromEntries(Object.entries(result ?? {}).map(([key, value]) => [key, { type: value?.type === "running" ? "busy" : value?.type ?? "idle" }]));
14337
+ if (op === "sessions.messages") return this.media.messages(project, id, (Array.isArray(result) ? result : []).map(normalizeMessage));
14338
+ if (["configuration.get", "configuration.providers"].includes(op)) return redact(result);
14339
+ if (op.startsWith("sessions.")) return this.media.normalize(project, normalizeSession(result));
14379
14340
  return result;
14380
14341
  }
14381
- async refreshAuth(project) {
14382
- if (!this.authDirty.has(project)) return;
14383
- const pending = this.authRefreshing.get(project);
14384
- if (pending) return pending;
14385
- const refresh = (async () => {
14386
- const statuses = await this.request(project, "GET", "/session/status");
14387
- if (Object.values(statuses).some((status) => status.type !== "idle")) return;
14388
- await this.request(project, "POST", "/instance/dispose");
14389
- this.authDirty.delete(project);
14390
- })();
14391
- this.authRefreshing.set(project, refresh);
14342
+ async prompt(project, session, p, base) {
14343
+ const text = typeof p.text === "string" ? screenshotPrompt(p.text) : "";
14344
+ const files = [];
14345
+ for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
14346
+ const upload = await this.uploads.attachment(project, String(uploadId), session);
14347
+ files.push({ uri: pathToFileURL(await this.workspace.path(project, upload.destination)).href, name: upload.name });
14348
+ }
14349
+ if (!text.trim() && !files.length) throw new Error("Message or attachments required");
14350
+ await this.switchSelection(project, base, p);
14351
+ return this.data(project, "POST", `${base}/prompt`, { text, files, delivery: "queue", ...p.messageID ? { id: p.messageID } : {} });
14352
+ }
14353
+ async switchSelection(project, base, payload) {
14354
+ if (payload.agent) await this.data(project, "POST", `${base}/agent`, { agent: payload.agent });
14355
+ if (!payload.model) return;
14356
+ let model;
14357
+ if (typeof payload.model === "string") {
14358
+ const split = payload.model.indexOf("/");
14359
+ if (split < 1 || split === payload.model.length - 1) throw new Error("Invalid model identifier");
14360
+ model = { providerID: payload.model.slice(0, split), id: payload.model.slice(split + 1) };
14361
+ } else if (typeof payload.model === "object") {
14362
+ const input = payload.model;
14363
+ model = { providerID: input.providerID, id: input.modelID ?? input.id, ...input.variant ? { variant: input.variant } : {} };
14364
+ } else throw new Error("Invalid model identifier");
14365
+ await this.data(project, "POST", `${base}/model`, { model });
14366
+ }
14367
+ async models(project) {
14368
+ const models = await this.data(project, "GET", "/api/model");
14369
+ const providers = /* @__PURE__ */ new Map();
14370
+ for (const model of Array.isArray(models) ? models : []) {
14371
+ if (model.enabled === false || model.status === "deprecated") continue;
14372
+ const modelId = model.id ?? model.modelID;
14373
+ if (typeof model.providerID !== "string" || typeof modelId !== "string") continue;
14374
+ const provider = providers.get(model.providerID) ?? { id: model.providerID, models: {} };
14375
+ provider.models[modelId] = { ...model, id: modelId };
14376
+ providers.set(model.providerID, provider);
14377
+ }
14378
+ return redact({ providers: [...providers.values()] });
14379
+ }
14380
+ async permissions(project, session) {
14381
+ const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/permission` : "/api/permission/request");
14382
+ return (Array.isArray(entries) ? entries : []).map((entry) => ({ ...entry, permission: entry.action, patterns: entry.resources ?? [], replies: ["once", "always", "reject"] }));
14383
+ }
14384
+ async forms(project, session) {
14385
+ const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/form` : "/api/form");
14386
+ return (Array.isArray(entries) ? entries : []).map(formToQuestion);
14387
+ }
14388
+ async resolveInteraction(project, session, operation, payload) {
14389
+ const kind = operation.startsWith("permissions") ? "permission" : "form";
14390
+ const requestId = this.identifier(payload.requestId);
14391
+ const key = `${kind}:${requestId}`;
14392
+ if (this.resolving.has(key)) throw new Error("Request is already being resolved by another client");
14393
+ this.resolving.add(key);
14392
14394
  try {
14393
- await refresh;
14395
+ const pending = kind === "permission" ? await this.permissions(project) : await this.forms(project);
14396
+ const item = pending.find((entry) => entry.id === payload.requestId);
14397
+ if (!item) throw new Error("Request is no longer pending");
14398
+ if (session && session !== item.sessionID) throw new Error("Request belongs to another session");
14399
+ await this.session(project, item.sessionID);
14400
+ const base = `/api/session/${encodeURIComponent(item.sessionID)}/${kind}/${requestId}`;
14401
+ if (kind === "permission") {
14402
+ const decision = payload.reply === "deny" ? "reject" : payload.reply;
14403
+ return this.data(project, "POST", `${base}/reply`, { decision, ...payload.message ? { message: payload.message } : {} });
14404
+ }
14405
+ if (operation === "questions.reject") return this.data(project, "DELETE", base);
14406
+ const fields = item.fields;
14407
+ const answers = Array.isArray(payload.answers) ? payload.answers : [];
14408
+ const answer = Object.fromEntries(fields.map((field, index) => [field.key, formValue(field, answers[index] ?? [])]));
14409
+ return this.data(project, "POST", `${base}/reply`, { answer });
14394
14410
  } finally {
14395
- this.authRefreshing.delete(project);
14411
+ this.resolving.delete(key);
14396
14412
  }
14397
14413
  }
14398
14414
  async authentication(project, operation, payload) {
14399
14415
  if (operation === "auth.poll") return this.auth.poll(project, payload);
14400
14416
  if (operation === "auth.cancel") return this.auth.cancel(project, payload.flowId);
14401
- await this.refreshAuth(project);
14402
- const methods = await this.request(project, "GET", "/provider/auth");
14403
- const providers = await this.request(project, "GET", "/provider");
14404
- for (const provider2 of providers.all) if (!methods[provider2.id]?.length) methods[provider2.id] = [{ type: "api", label: "API Key" }];
14417
+ const integrations = await this.data(project, "GET", "/api/integration");
14418
+ const available = Array.isArray(integrations) ? integrations : [];
14405
14419
  if (operation === "auth.status") {
14406
- const choices = Object.entries(methods).flatMap(([provider2, entries]) => entries.map((method2, index2) => ({
14407
- id: `${provider2}:${index2}`,
14408
- label: `${providers.all.find((p) => p.id === provider2)?.name ?? provider2} \xB7 ${method2.label}`,
14409
- 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 })) } : {} }))
14410
- })));
14411
- 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 };
14412
- }
14413
- const methodId = String(payload.method ?? ""), split = methodId.lastIndexOf(":");
14414
- const provider = methodId.slice(0, split), index = Number(methodId.slice(split + 1));
14415
- const method = methods[provider]?.[index];
14416
- if (split < 0 || !method || !["api", "oauth"].includes(method.type)) throw new Error("\u4E0D\u652F\u6301\u7684 OpenCode \u767B\u5F55\u65B9\u5F0F");
14420
+ const methods = available.flatMap((integration2) => (integration2.methods ?? []).flatMap((method2, index2) => {
14421
+ if (!["key", "oauth"].includes(method2.type)) return [];
14422
+ return [{ id: `${integration2.id}:${index2}`, label: `${integration2.name ?? integration2.id} \xB7 ${method2.label ?? (method2.type === "key" ? "API Key" : "OAuth")}`, fields: method2.type === "key" ? [{ id: "key", label: "API Key", secret: true }, ...formFields(method2.form) ?? []] : formFields(method2.form) }];
14423
+ }));
14424
+ const providers = available.filter((integration2) => integration2.connections?.length).map((integration2) => integration2.id);
14425
+ return { status: providers.length ? "authenticated" : "unauthenticated", message: providers.length ? "OpenCode \u5DF2\u914D\u7F6E\u4F9B\u5E94\u5546" : "OpenCode \u672A\u914D\u7F6E\u53EF\u7528\u4F9B\u5E94\u5546", providers, methods };
14426
+ }
14427
+ const methodId = String(payload.method ?? "");
14428
+ const split = methodId.lastIndexOf(":");
14429
+ const integration = available.find((entry) => entry.id === methodId.slice(0, split));
14430
+ const index = Number(methodId.slice(split + 1));
14431
+ const method = integration?.methods?.[index];
14432
+ if (split < 0 || !method || !["key", "oauth"].includes(method.type)) throw new Error("\u4E0D\u652F\u6301\u7684 OpenCode \u767B\u5F55\u65B9\u5F0F");
14417
14433
  const input = payload.input ?? {};
14418
14434
  const controller = new AbortController();
14419
- const flow = this.auth.create(project, () => controller.abort());
14435
+ let attemptId = "";
14436
+ const flow = this.auth.create(project, async () => {
14437
+ controller.abort();
14438
+ if (attemptId) await this.request(project, "DELETE", `/api/integration/${this.identifier(integration.id)}/connect/oauth/${this.identifier(attemptId)}`).catch(() => {
14439
+ });
14440
+ });
14441
+ const complete = () => this.auth.update(project, flow.flowId, { status: "completed", message: "\u8BA4\u8BC1\u4FE1\u606F\u5DF2\u7531 OpenCode \u4FDD\u5B58" });
14442
+ const wait = async () => {
14443
+ while (!controller.signal.aborted) {
14444
+ const state = await this.data(project, "GET", `/api/integration/${this.identifier(integration.id)}/connect/oauth/${this.identifier(attemptId)}`, void 0, controller.signal);
14445
+ if (state.status === "complete") {
14446
+ complete();
14447
+ return;
14448
+ }
14449
+ if (["failed", "expired"].includes(state.status)) throw new Error("OpenCode OAuth failed");
14450
+ await new Promise((resolve4, reject) => {
14451
+ const timer = setTimeout(resolve4, 1e3);
14452
+ controller.signal.addEventListener("abort", () => {
14453
+ clearTimeout(timer);
14454
+ reject(controller.signal.reason);
14455
+ }, { once: true });
14456
+ });
14457
+ }
14458
+ };
14420
14459
  const run3 = async () => {
14421
- if (method.type === "api") {
14460
+ if (method.type === "key") {
14422
14461
  if (typeof input.key !== "string" || !input.key.trim()) throw new Error("API Key required");
14423
- await this.request(project, "PUT", `/auth/${this.identifier(provider)}`, { type: "api", key: input.key }, controller.signal);
14424
- for (const entry of this.runtime.config.projects) this.authDirty.add(entry.id);
14425
- await this.refreshAuth(project);
14426
- 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" });
14462
+ await this.request(project, "POST", `/api/integration/${this.identifier(integration.id)}/connect/key`, { key: input.key, ...method.label ? { label: method.label } : {}, ...Object.keys(input).length > 1 ? { answer: input } : {} }, controller.signal);
14463
+ complete();
14427
14464
  return;
14428
14465
  }
14429
- const result = await this.request(project, "POST", `/provider/${this.identifier(provider)}/oauth/authorize`, { method: index, inputs: input }, controller.signal);
14430
- this.auth.update(project, flow.flowId, { url: result.url, message: result.instructions });
14431
- const callback = async (code) => {
14432
- await this.request(project, "POST", `/provider/${this.identifier(provider)}/oauth/callback`, { method: index, ...typeof code === "string" ? { code } : {} }, controller.signal);
14433
- for (const entry of this.runtime.config.projects) this.authDirty.add(entry.id);
14434
- await this.refreshAuth(project);
14435
- 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" });
14436
- };
14437
- if (result.method === "auto") await callback();
14438
- else {
14466
+ const attempt = await this.data(project, "POST", `/api/integration/${this.identifier(integration.id)}/connect/oauth`, { methodID: method.id, ...method.label ? { label: method.label } : {}, ...Object.keys(input).length ? { answer: input } : {} }, controller.signal);
14467
+ attemptId = attempt.attemptID;
14468
+ this.auth.update(project, flow.flowId, { url: attempt.url, message: attempt.instructions });
14469
+ if (attempt.mode === "code") {
14439
14470
  this.auth.update(project, flow.flowId, { status: "input", fields: [{ id: "code", label: "\u6388\u6743\u7801\u6216\u56DE\u8C03\u5730\u5740", secret: true }] });
14440
- this.auth.get(project, flow.flowId).submit = (values) => callback(values.code);
14441
- }
14471
+ this.auth.get(project, flow.flowId).submit = async (values) => {
14472
+ await this.request(project, "POST", `/api/integration/${this.identifier(integration.id)}/connect/oauth/${this.identifier(attemptId)}/complete`, { code: values.code }, controller.signal);
14473
+ await wait();
14474
+ };
14475
+ } else await wait();
14442
14476
  };
14443
14477
  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" }));
14444
14478
  return { ...flow };
@@ -14448,11 +14482,10 @@ var OpenCodeAdapter = class {
14448
14482
  const controller = new AbortController();
14449
14483
  this.streams.set(project, controller);
14450
14484
  const loop = async () => {
14485
+ const root = await this.workspace.root(project);
14451
14486
  while (!controller.signal.aborted) {
14452
14487
  try {
14453
- const url = new URL("/event", this.runtime.config.tool.baseUrl);
14454
- url.searchParams.set("directory", await this.workspace.root(project));
14455
- const response = await fetch(url, { headers: this.runtime.headers(), signal: controller.signal });
14488
+ const response = await fetch(new URL("/api/event", this.runtime.config.tool.baseUrl), { headers: this.runtime.headers(), signal: controller.signal });
14456
14489
  if (!response.ok || !response.body) throw new Error("Event connection unavailable");
14457
14490
  this.emit(project, "octopus.resync", {});
14458
14491
  let buffer = "";
@@ -14464,10 +14497,10 @@ var OpenCodeAdapter = class {
14464
14497
  while ((end = buffer.indexOf("\n\n")) >= 0) {
14465
14498
  const block = buffer.slice(0, end);
14466
14499
  buffer = buffer.slice(end + 2);
14467
- const data = block.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trimStart()).join("\n");
14500
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
14468
14501
  if (data) {
14469
14502
  const event = JSON.parse(data);
14470
- this.emit(project, event.type, await this.media.normalize(project, event.properties ?? event));
14503
+ if (!event.location?.directory || event.location.directory === root) this.emit(project, event.type === "server.connected" ? "octopus.resync" : event.type, await this.media.normalize(project, event.data ?? {}));
14471
14504
  }
14472
14505
  }
14473
14506
  if (buffer.length > Math.max(4 * 1024 * 1024, Math.ceil(this.uploads.limits.fileBytes * 4 / 3) + 1024 * 1024)) throw new Error("Event too large");
@@ -14482,15 +14515,15 @@ var OpenCodeAdapter = class {
14482
14515
  }
14483
14516
  async terminal(project, op, p) {
14484
14517
  const id = this.identifier(p.id), key = `${project}:${id}`;
14485
- await this.request(project, "GET", `/pty/${id}`);
14518
+ await this.data(project, "GET", `/api/pty/${id}`);
14486
14519
  if (op === "terminal.remove") {
14487
- const result = await this.request(project, "DELETE", `/pty/${id}`);
14520
+ const result = await this.data(project, "DELETE", `/api/pty/${id}`);
14488
14521
  this.terminals.get(key)?.close();
14489
14522
  this.terminals.delete(key);
14490
14523
  this.terminalOutput.delete(key);
14491
14524
  return result;
14492
14525
  }
14493
- if (op === "terminal.resize") return this.request(project, "PUT", `/pty/${id}`, { size: { cols: p.cols, rows: p.rows } });
14526
+ if (op === "terminal.resize") return this.data(project, "PUT", `/api/pty/${id}`, { size: { cols: p.cols, rows: p.rows } });
14494
14527
  if (op === "terminal.attach") {
14495
14528
  const history = this.terminalOutput.get(key) ?? "";
14496
14529
  this.emit(project, "terminal.reset", { id: p.id });
@@ -14498,19 +14531,18 @@ var OpenCodeAdapter = class {
14498
14531
  const previous = this.terminals.get(key);
14499
14532
  this.terminals.delete(key);
14500
14533
  previous?.close();
14501
- const url = new URL(`/pty/${id}/connect`, this.runtime.config.tool.baseUrl);
14502
- url.protocol = "ws:";
14503
- url.searchParams.set("directory", await this.workspace.root(project));
14534
+ const url = new URL(`/api/pty/${id}/connect`, this.runtime.config.tool.baseUrl);
14535
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
14536
+ url.searchParams.set("location[directory]", await this.workspace.root(project));
14504
14537
  const socket2 = new WebSocket(url, { headers: this.runtime.headers() });
14505
14538
  this.terminals.set(key, socket2);
14506
14539
  socket2.on("message", (data) => {
14507
- if (this.terminals.get(key) === socket2) {
14508
- const text = data.toString(), tail = ((this.terminalOutput.get(key) ?? "") + text).slice(-1048576);
14509
- this.terminalOutput.delete(key);
14510
- this.terminalOutput.set(key, tail);
14511
- if (this.terminalOutput.size > 16) this.terminalOutput.delete(this.terminalOutput.keys().next().value);
14512
- this.emit(project, "terminal.output", { id: p.id, data: text });
14513
- }
14540
+ if (this.terminals.get(key) !== socket2) return;
14541
+ const text = data.toString(), tail = ((this.terminalOutput.get(key) ?? "") + text).slice(-1048576);
14542
+ this.terminalOutput.delete(key);
14543
+ this.terminalOutput.set(key, tail);
14544
+ if (this.terminalOutput.size > 16) this.terminalOutput.delete(this.terminalOutput.keys().next().value);
14545
+ this.emit(project, "terminal.output", { id: p.id, data: text });
14514
14546
  });
14515
14547
  socket2.on("close", () => {
14516
14548
  if (this.terminals.get(key) === socket2) {
@@ -14534,13 +14566,76 @@ var OpenCodeAdapter = class {
14534
14566
  }
14535
14567
  close() {
14536
14568
  this.auth.close();
14537
- for (const c of this.streams.values()) c.abort();
14538
- for (const t of this.terminals.values()) t.close();
14569
+ for (const controller of this.streams.values()) controller.abort();
14570
+ for (const terminal of this.terminals.values()) terminal.close();
14539
14571
  }
14540
14572
  };
14573
+ function unwrap2(value) {
14574
+ return value && typeof value === "object" && "data" in value ? value.data : value;
14575
+ }
14576
+ function normalizeSession(value) {
14577
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
14578
+ return { ...value, ...value.location?.directory ? { directory: value.location.directory } : {} };
14579
+ }
14580
+ function normalizeMessage(message) {
14581
+ if (message?.info && Array.isArray(message.parts)) return message;
14582
+ const role = message?.type === "user" ? "user" : "assistant";
14583
+ const info = { ...message, role };
14584
+ delete info.type;
14585
+ delete info.text;
14586
+ delete info.files;
14587
+ delete info.content;
14588
+ if (message?.model) info.model = { ...message.model, modelID: message.model.id };
14589
+ const parts = [];
14590
+ if (typeof message?.text === "string" && message.text) parts.push({ type: "text", text: message.text });
14591
+ for (const file of message?.files ?? []) parts.push({ type: "file", filename: file.name, url: file.uri });
14592
+ if (message?.type === "shell") parts.push({ type: "tool", tool: "shell", state: { status: message.time?.completed ? "completed" : "running", input: { command: message.command }, output: message.output } });
14593
+ if (message?.type === "compaction") {
14594
+ info.summary = true;
14595
+ parts.push({ type: "text", text: message.summary });
14596
+ }
14597
+ if (message?.type === "agent-switched") parts.push({ type: "text", text: `Agent: ${message.agent}` });
14598
+ if (message?.type === "model-switched") parts.push({ type: "text", text: `Model: ${message.model?.providerID}/${message.model?.id}` });
14599
+ for (const part of message?.content ?? []) {
14600
+ if (part.type === "tool") {
14601
+ const state = { ...part.state };
14602
+ if (state.content !== void 0 && state.output === void 0) state.output = state.content;
14603
+ parts.push({ ...part, tool: part.name ?? part.tool, state });
14604
+ } else parts.push(part);
14605
+ }
14606
+ return { info, parts };
14607
+ }
14608
+ function formToQuestion(form) {
14609
+ const fields = (form.fields ?? []).filter((field) => !field.hidden);
14610
+ return {
14611
+ ...form,
14612
+ fields,
14613
+ questions: fields.map((field) => ({
14614
+ key: field.key,
14615
+ header: field.title ?? form.title ?? field.key,
14616
+ question: field.description ?? field.title ?? field.key,
14617
+ ...field.options ? { options: field.options } : {},
14618
+ multiple: field.type === "multiselect",
14619
+ custom: field.custom ?? !field.options,
14620
+ isSecret: false
14621
+ }))
14622
+ };
14623
+ }
14624
+ function formValue(field, values) {
14625
+ if (field.type === "multiselect") return values;
14626
+ const value = values[0];
14627
+ if (field.type === "boolean") return value === true || value === "true";
14628
+ if (field.type === "number" || field.type === "integer") return Number(value);
14629
+ return value ?? "";
14630
+ }
14631
+ function formFields(form) {
14632
+ if (!form) return void 0;
14633
+ const fields = Array.isArray(form) ? form : form.fields;
14634
+ return (fields ?? []).filter((field) => !field.hidden).map((field) => ({ id: field.key, label: field.title ?? field.description ?? field.key, secret: Boolean(field.secret), ...field.options ? { options: field.options.map((option) => ({ label: option.label ?? option.value ?? String(option), value: option.value ?? String(option) })) } : {} }));
14635
+ }
14541
14636
  function redact(value) {
14542
14637
  if (Array.isArray(value)) return value.map(redact);
14543
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, /password|apikey|token|secret|authorization|^key$/i.test(k.replace(/[-_]/g, "")) ? "[redacted]" : redact(v)]));
14638
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, /password|apikey|token|secret|authorization|^key$/i.test(key.replace(/[-_]/g, "")) ? "[redacted]" : redact(item)]));
14544
14639
  return value;
14545
14640
  }
14546
14641
 
@@ -14566,11 +14661,11 @@ var Runtime = class {
14566
14661
  }
14567
14662
  async health() {
14568
14663
  try {
14569
- const response = await fetch(new URL("/global/health", this.config.tool.baseUrl), { headers: this.headers(), signal: AbortSignal.timeout(2e3) });
14664
+ const response = await fetch(new URL("/api/info", this.config.tool.baseUrl), { headers: this.headers(), signal: AbortSignal.timeout(2e3) });
14570
14665
  if (!response.ok) return false;
14571
14666
  const data = await response.json();
14572
14667
  this.version = data.version ?? this.version;
14573
- return data.healthy === true;
14668
+ return true;
14574
14669
  } catch {
14575
14670
  return false;
14576
14671
  }
@@ -14593,7 +14688,7 @@ var Runtime = class {
14593
14688
  }
14594
14689
  async launch() {
14595
14690
  const url = new URL(this.config.tool.baseUrl);
14596
- const endpoint = new URL("/global/health", url);
14691
+ const endpoint = new URL("/api/info", url);
14597
14692
  let response;
14598
14693
  try {
14599
14694
  response = await fetch(endpoint, { headers: this.headers(), signal: AbortSignal.timeout(2e3) });
@@ -14601,20 +14696,13 @@ var Runtime = class {
14601
14696
  if (error instanceof Error && error.name === "TimeoutError") throw new Error(`OpenCode \u5730\u5740 ${url.origin} \u54CD\u5E94\u8D85\u65F6\uFF1B\u8BF7\u68C0\u67E5\u670D\u52A1\u540E\u91CD\u8BD5\uFF0CProxy \u4E0D\u4F1A\u91CD\u590D\u542F\u52A8`);
14602
14697
  }
14603
14698
  if (response) {
14604
- const health = await response.json().catch(() => null);
14605
- if (response.ok && health?.healthy === true) return this.status();
14699
+ if (response.ok) return this.status();
14606
14700
  throw new Error(`OpenCode \u5730\u5740 ${url.origin} \u5DF2\u6709 HTTP \u670D\u52A1\uFF0C\u4F46\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF08HTTP ${response.status}\uFF09\uFF1B\u8BF7\u68C0\u67E5\u670D\u52A1\u5BC6\u7801\u6216\u7AEF\u53E3`);
14607
14701
  }
14608
14702
  await mkdir4(this.config.stateDirectory, { recursive: true, mode: 448 });
14609
14703
  const logPath = join5(this.config.stateDirectory, "opencode-serve.log");
14610
14704
  const log = await open4(logPath, "a", 384);
14611
- const env = { ...process.env };
14612
- delete env.OPENCODE_SERVER_PASSWORD;
14613
- delete env.OPENCODE_SERVER_USERNAME;
14614
- if (this.config.tool.password) {
14615
- env.OPENCODE_SERVER_PASSWORD = this.config.tool.password;
14616
- env.OPENCODE_SERVER_USERNAME = "opencode";
14617
- }
14705
+ const env = { ...process.env, OPENCODE_SERVER_PASSWORD: this.config.tool.password, OPENCODE_SERVER_USERNAME: "opencode" };
14618
14706
  let child;
14619
14707
  try {
14620
14708
  child = spawn(this.config.tool.command, ["serve", "--hostname", url.hostname === "[::1]" ? "::1" : url.hostname, "--port", url.port || "80"], {
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  runDaemon,
23
23
  supportedPlatform,
24
24
  systemdUnitPath
25
- } from "./chunk-N4L5M6MC.js";
25
+ } from "./chunk-HWMHCHRB.js";
26
26
 
27
27
  // src/cli.ts
28
28
  import { spawn } from "node:child_process";
@@ -439,6 +439,7 @@ async function runDoctor(opts) {
439
439
 
440
440
  // src/onboard.ts
441
441
  import { createInterface } from "node:readline/promises";
442
+ import { randomBytes } from "node:crypto";
442
443
  import { stdin, stdout } from "node:process";
443
444
  import { access as access3, constants as constants2, mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
444
445
  import { execFile as execFile5 } from "node:child_process";
@@ -475,7 +476,7 @@ var validateProjectId = (value, previousProjects) => validId(value) ?? (previous
475
476
  var validBotId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ? void 0 : "\u8BF7\u8F93\u5165\u673A\u5668\u4EBA UUID";
476
477
  var run5 = promisify5(execFile5);
477
478
  var MACOS_TOOL_EXAMPLES = {
478
- opencode: "/opt/homebrew/bin/opencode",
479
+ opencode: "/Users/luhuili/.opencode/bin/opencode",
479
480
  codex: "/Applications/ChatGPT.app/Contents/Resources/codex",
480
481
  zcode: "/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs",
481
482
  pi: "/opt/homebrew/bin/pi"
@@ -625,7 +626,7 @@ async function runOnboard(opts) {
625
626
  stateDirectory,
626
627
  ...loopbackHttp ? { allowInsecureLocalhost: true } : {},
627
628
  projects: projects.map((project) => ({ id: project.id, name: project.name, directory: project.directory, specDirectories: project.specDirectories ?? ["specs", "docs"] })),
628
- tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl, mode: "external" } : {}, ...toolKind === existing?.tool.kind && existing.tool.password ? { password: existing.tool.password } : {}, autoStart: toolKind === existing?.tool.kind ? existing.tool.autoStart : true }
629
+ tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl, mode: "external", password: toolKind === existing?.tool.kind && existing.tool.password ? existing.tool.password : randomBytes(32).toString("base64url") } : {}, autoStart: toolKind === existing?.tool.kind ? existing.tool.autoStart : true }
629
630
  });
630
631
  await loadConfig(opts.configPath);
631
632
  console.log(`\u5DF2\u5199\u5165\u914D\u7F6E\uFF1A${opts.configPath}\uFF080600\uFF09`);
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-N4L5M6MC.js";
5
+ } from "./chunk-HWMHCHRB.js";
6
6
 
7
7
  // src/main.ts
8
8
  runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {
@@ -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": "external" }
7
+ "tool": { "kind": "opencode", "command": "/Users/luhuili/.opencode/bin/opencode", "baseUrl": "http://127.0.0.1:4096", "mode": "external", "password": "replace-with-a-random-secret-at-least-20-characters" }
8
8
  }