@skylandnpm/octopus-proxy 0.1.72 → 0.1.74

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.
@@ -11034,6 +11034,24 @@ var UNSUPPORTED = /* @__PURE__ */ new Set([
11034
11034
  "runtime.restart",
11035
11035
  "runtime.logs"
11036
11036
  ]);
11037
+ var SESSION_ID = /^ses_[A-Za-z0-9]+$/;
11038
+ var PERMISSION_ID = /^per_[A-Za-z0-9]+$/;
11039
+ var OpenCodeHttpError = class extends Error {
11040
+ constructor(status, body) {
11041
+ super(`OpenCode ${status}: ${body}`);
11042
+ this.status = status;
11043
+ this.body = body;
11044
+ }
11045
+ };
11046
+ function isPermissionMetadataError(error) {
11047
+ if (!(error instanceof OpenCodeHttpError) || error.status !== 400) return false;
11048
+ try {
11049
+ const body = JSON.parse(error.body);
11050
+ return body?._tag === "InvalidRequestError" && body.kind === "Body" && typeof body.message === "string" && /^Expected JSON value\s+at \["data"\]\[\d+\]\["metadata"\]\["[^"]+"\]$/.test(body.message);
11051
+ } catch {
11052
+ return false;
11053
+ }
11054
+ }
11037
11055
  var OpenCodeAdapter = class {
11038
11056
  constructor(runtime, workspace, uploads, emit) {
11039
11057
  this.runtime = runtime;
@@ -11050,7 +11068,9 @@ var OpenCodeAdapter = class {
11050
11068
  streams = /* @__PURE__ */ new Map();
11051
11069
  terminals = /* @__PURE__ */ new Map();
11052
11070
  terminalOutput = /* @__PURE__ */ new Map();
11071
+ permissionEvents = /* @__PURE__ */ new Map();
11053
11072
  resolving = /* @__PURE__ */ new Set();
11073
+ approveAll = /* @__PURE__ */ new Set();
11054
11074
  config;
11055
11075
  media;
11056
11076
  async request(project, method, path, body, signal) {
@@ -11072,7 +11092,7 @@ var OpenCodeAdapter = class {
11072
11092
  }
11073
11093
  throw error;
11074
11094
  });
11075
- if (!response.ok) throw new Error(`OpenCode ${response.status}: ${(await response.text()).slice(0, 1500)}`);
11095
+ if (!response.ok) throw new OpenCodeHttpError(response.status, (await response.text()).slice(0, 1500));
11076
11096
  if (response.status === 204) return null;
11077
11097
  const text = await response.text();
11078
11098
  return text ? JSON.parse(text) : null;
@@ -11081,7 +11101,7 @@ var OpenCodeAdapter = class {
11081
11101
  return unwrap2(await this.request(project, method, path, body, signal));
11082
11102
  }
11083
11103
  async session(project, id) {
11084
- if (!/^ses_[A-Za-z0-9]+$/.test(id)) throw new Error("Invalid session id");
11104
+ if (!SESSION_ID.test(id)) throw new Error("Invalid session id");
11085
11105
  const session = await this.data(project, "GET", `/api/session/${encodeURIComponent(id)}`);
11086
11106
  const directory = session?.location?.directory ?? session?.directory;
11087
11107
  if (typeof directory !== "string" || await realpath4(directory) !== await this.workspace.root(project)) throw new Error("Session belongs to another project");
@@ -11099,22 +11119,27 @@ var OpenCodeAdapter = class {
11099
11119
  const op = command.operation, p = command.payload, id = command.sessionId;
11100
11120
  if (op.startsWith("auth.")) return this.authentication(project, op, p);
11101
11121
  const needsSession = op.startsWith("sessions.") && !["sessions.list", "sessions.create"].includes(op);
11122
+ let session;
11102
11123
  if (needsSession) {
11103
11124
  if (!id) throw new Error("Session required");
11104
- await this.session(project, id);
11125
+ session = await this.session(project, id);
11105
11126
  }
11106
11127
  const base = `/api/session/${encodeURIComponent(id ?? "")}`;
11107
11128
  if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
11108
11129
  { id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u6BCF\u6B21\u4F7F\u7528\u5DE5\u5177\u524D\u8BF7\u6C42\u6279\u51C6" },
11109
11130
  { id: "full", label: "\u5B8C\u5168\u8BBF\u95EE", description: "\u81EA\u52A8\u5141\u8BB8\u5DE5\u5177\u64CD\u4F5C\uFF1B\u4ECD\u9075\u5B88\u4E3B\u673A\u548C\u5DE5\u5177\u81EA\u8EAB\u7684\u8BBF\u95EE\u9650\u5236" }
11110
11131
  ] };
11111
- if (op === "sessions.prompt") return this.prompt(project, id, p, base);
11132
+ if (id && ["sessions.prompt", "sessions.command", "sessions.shell", "sessions.abort", "sessions.delete"].includes(op)) this.approveAll.delete(`${project}\0${id}`);
11133
+ if (op === "sessions.prompt") return this.prompt(project, id, p, base, session?.model);
11112
11134
  if (op === "sessions.command" || op === "sessions.shell") {
11113
11135
  await this.switchSelection(project, base, p);
11114
11136
  if (op === "sessions.shell") return this.data(project, "POST", `${base}/shell`, { ...p.id ? { id: p.id } : {}, command: p.command });
11115
11137
  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 } : {} });
11116
11138
  }
11117
- if (op === "permissions.list") return this.permissions(project, id);
11139
+ if (op === "permissions.list") {
11140
+ if (id) await this.session(project, id);
11141
+ return this.permissions(project, id);
11142
+ }
11118
11143
  if (op === "questions.list") return this.forms(project, id);
11119
11144
  if (op === "permissions.reply" || op === "questions.reply" || op === "questions.reject") return this.resolveInteraction(project, id, op, p);
11120
11145
  if (op === "configuration.models") return this.models(project);
@@ -11163,18 +11188,32 @@ var OpenCodeAdapter = class {
11163
11188
  if (op.startsWith("sessions.")) return this.media.normalize(project, normalizeSession(result));
11164
11189
  return result;
11165
11190
  }
11166
- async prompt(project, session, p, base) {
11191
+ async prompt(project, session, p, base, sessionModel) {
11167
11192
  const text = typeof p.text === "string" ? screenshotPrompt(p.text) : "";
11168
11193
  const files = [];
11194
+ let hasImage = false;
11169
11195
  for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
11170
11196
  const upload = await this.uploads.attachment(project, String(uploadId), session);
11171
11197
  files.push({ uri: pathToFileURL(await this.workspace.path(project, upload.destination)).href, name: upload.name });
11198
+ hasImage ||= upload.mime.startsWith("image/");
11172
11199
  }
11173
11200
  if (!text.trim() && !files.length) throw new Error("Message or attachments required");
11174
- await this.switchSelection(project, base, p);
11175
- return this.data(project, "POST", `${base}/prompt`, { text, files, delivery: "queue", ...p.messageID ? { id: p.messageID } : {} });
11176
- }
11177
- async switchSelection(project, base, payload) {
11201
+ const model = this.modelSelection(p);
11202
+ if (hasImage) await this.checkImageInput(project, model ?? sessionModel);
11203
+ await this.switchSelection(project, base, p, model);
11204
+ const admitted = await this.data(project, "POST", `${base}/prompt`, { text, files, delivery: "queue", ...p.messageID ? { id: p.messageID } : {} });
11205
+ return typeof admitted?.id === "string" ? { id: admitted.id } : null;
11206
+ }
11207
+ async checkImageInput(project, model) {
11208
+ if (typeof model?.providerID !== "string" || typeof model.id !== "string") return;
11209
+ const catalog = await this.data(project, "GET", "/api/model", void 0, AbortSignal.timeout(2e3)).catch(() => null);
11210
+ const entry = Array.isArray(catalog) ? catalog.find((item) => item.providerID === model.providerID && (item.id ?? item.modelID) === model.id) : void 0;
11211
+ const input = entry?.capabilities?.input;
11212
+ if (Array.isArray(input) && !input.includes("image")) {
11213
+ throw new Error(`OpenCode \u6A21\u578B ${model.providerID}/${model.id} \u4E0D\u652F\u6301\u56FE\u7247\u8F93\u5165\uFF0C\u8BF7\u5207\u6362\u6A21\u578B\u6216\u68C0\u67E5\u8BE5\u6A21\u578B\u7684\u8F93\u5165\u914D\u7F6E\u3002`);
11214
+ }
11215
+ }
11216
+ async switchSelection(project, base, payload, model = this.modelSelection(payload)) {
11178
11217
  if (payload.permissionMode !== void 0) {
11179
11218
  if (!["ask", "full"].includes(String(payload.permissionMode))) throw new Error("Unsupported permission mode");
11180
11219
  const states = await this.data(project, "GET", "/api/session/active");
@@ -11183,6 +11222,9 @@ var OpenCodeAdapter = class {
11183
11222
  await this.data(project, "PATCH", base, { permissions: [{ action: "*", resource: "*", effect: payload.permissionMode === "full" ? "allow" : "ask" }] });
11184
11223
  }
11185
11224
  if (payload.agent) await this.data(project, "POST", `${base}/agent`, { agent: payload.agent });
11225
+ if (model) await this.data(project, "POST", `${base}/model`, { model });
11226
+ }
11227
+ modelSelection(payload) {
11186
11228
  if (!payload.model) return;
11187
11229
  let model;
11188
11230
  if (typeof payload.model === "string") {
@@ -11195,7 +11237,7 @@ var OpenCodeAdapter = class {
11195
11237
  } else throw new Error("Invalid model identifier");
11196
11238
  const level = selectedReasoningLevel(payload, "model.variant");
11197
11239
  if (level) model.variant = level;
11198
- await this.data(project, "POST", `${base}/model`, { model });
11240
+ return model;
11199
11241
  }
11200
11242
  async models(project) {
11201
11243
  const models = await this.data(project, "GET", "/api/model");
@@ -11214,8 +11256,34 @@ var OpenCodeAdapter = class {
11214
11256
  return redact({ providers: [...providers.values()] });
11215
11257
  }
11216
11258
  async permissions(project, session) {
11217
- const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/permission` : "/api/permission/request");
11218
- return (Array.isArray(entries) ? entries : []).map((entry) => ({ ...entry, permission: entry.action, patterns: entry.resources ?? [], replies: ["once", "always", "reject"] }));
11259
+ let entries;
11260
+ try {
11261
+ entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/permission` : "/api/permission/request");
11262
+ } catch (error) {
11263
+ if (!isPermissionMetadataError(error)) throw error;
11264
+ entries = [...this.permissionEvents.get(project)?.values() ?? []].filter((entry) => !session || entry.sessionID === session);
11265
+ if (!entries.length) throw error;
11266
+ }
11267
+ return (Array.isArray(entries) ? entries : []).map((entry) => ({ ...entry, permission: entry.action, patterns: entry.resources ?? [], replies: ["once", "always", "all", "reject"] }));
11268
+ }
11269
+ trackPermission(project, type, data) {
11270
+ if (typeof data?.sessionID !== "string" || !SESSION_ID.test(data.sessionID)) return;
11271
+ if (type === "permission.asked") {
11272
+ if (typeof data.id !== "string" || !PERMISSION_ID.test(data.id) || typeof data.action !== "string" || !Array.isArray(data.resources) || !data.resources.every((resource) => typeof resource === "string")) return;
11273
+ let pending = this.permissionEvents.get(project);
11274
+ if (!pending) this.permissionEvents.set(project, pending = /* @__PURE__ */ new Map());
11275
+ pending.set(data.id, data);
11276
+ if (pending.size > 256) pending.delete(pending.keys().next().value);
11277
+ if (this.approveAll.has(`${project}\0${data.sessionID}`)) {
11278
+ void this.resolveInteraction(project, data.sessionID, "permissions.reply", { requestId: data.id, reply: "once" }).catch(() => {
11279
+ });
11280
+ }
11281
+ } else if (type === "session.idle" || type === "session.status" && data.status?.type === "idle") {
11282
+ this.approveAll.delete(`${project}\0${data.sessionID}`);
11283
+ } else if (type === "permission.replied") {
11284
+ const pending = this.permissionEvents.get(project);
11285
+ if (pending?.get(data.requestID)?.sessionID === data.sessionID) pending.delete(data.requestID);
11286
+ }
11219
11287
  }
11220
11288
  async forms(project, session) {
11221
11289
  const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/form` : "/api/form");
@@ -11228,15 +11296,25 @@ var OpenCodeAdapter = class {
11228
11296
  if (this.resolving.has(key)) throw new Error("Request is already being resolved by another client");
11229
11297
  this.resolving.add(key);
11230
11298
  try {
11231
- const pending = kind === "permission" ? await this.permissions(project) : await this.forms(project);
11299
+ if (kind === "permission" && session) await this.session(project, session);
11300
+ const pending = kind === "permission" ? await this.permissions(project, session) : await this.forms(project);
11232
11301
  const item = pending.find((entry) => entry.id === payload.requestId);
11233
11302
  if (!item) throw new Error("Request is no longer pending");
11234
11303
  if (session && session !== item.sessionID) throw new Error("Request belongs to another session");
11235
- await this.session(project, item.sessionID);
11304
+ if (kind !== "permission" || !session) await this.session(project, item.sessionID);
11236
11305
  const base = `/api/session/${encodeURIComponent(item.sessionID)}/${kind}/${requestId}`;
11237
11306
  if (kind === "permission") {
11238
- const decision = payload.reply === "deny" ? "reject" : payload.reply;
11239
- return this.data(project, "POST", `${base}/reply`, { decision, ...payload.message ? { message: payload.message } : {} });
11307
+ if (payload.reply === "all") {
11308
+ this.approveAll.add(`${project}\0${item.sessionID}`);
11309
+ for (const other of pending) if (other.sessionID === item.sessionID && other.id !== item.id) {
11310
+ await this.data(project, "POST", `/api/session/${encodeURIComponent(item.sessionID)}/permission/${this.identifier(other.id)}/reply`, { decision: "once" });
11311
+ this.permissionEvents.get(project)?.delete(other.id);
11312
+ }
11313
+ }
11314
+ const decision = payload.reply === "all" ? "once" : payload.reply === "deny" ? "reject" : payload.reply;
11315
+ const result = await this.data(project, "POST", `${base}/reply`, { decision, ...payload.message ? { message: payload.message } : {} });
11316
+ this.permissionEvents.get(project)?.delete(String(payload.requestId));
11317
+ return result;
11240
11318
  }
11241
11319
  if (operation === "questions.reject") return this.data(project, "DELETE", base);
11242
11320
  const fields = item.fields;
@@ -11336,7 +11414,10 @@ var OpenCodeAdapter = class {
11336
11414
  const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
11337
11415
  if (data) {
11338
11416
  const event = JSON.parse(data);
11339
- 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 ?? {}));
11417
+ if (!event.location?.directory || event.location.directory === root) {
11418
+ if (event.location?.directory === root) this.trackPermission(project, event.type, event.data);
11419
+ this.emit(project, event.type === "server.connected" ? "octopus.resync" : event.type, await this.media.normalize(project, event.data ?? {}));
11420
+ }
11340
11421
  }
11341
11422
  }
11342
11423
  if (buffer.length > Math.max(4 * 1024 * 1024, Math.ceil(this.uploads.limits.fileBytes * 4 / 3) + 1024 * 1024)) throw new Error("Event too large");
@@ -11344,6 +11425,7 @@ var OpenCodeAdapter = class {
11344
11425
  } catch {
11345
11426
  if (!controller.signal.aborted) this.emit(project, "octopus.disconnected", {});
11346
11427
  }
11428
+ if (!controller.signal.aborted) this.permissionEvents.delete(project);
11347
11429
  if (!controller.signal.aborted) await new Promise((resolve5) => setTimeout(resolve5, 2e3));
11348
11430
  }
11349
11431
  };
@@ -11352,6 +11434,8 @@ var OpenCodeAdapter = class {
11352
11434
  removeProject(project) {
11353
11435
  this.streams.get(project)?.abort();
11354
11436
  this.streams.delete(project);
11437
+ this.permissionEvents.delete(project);
11438
+ for (const key of this.approveAll) if (key.startsWith(`${project}\0`)) this.approveAll.delete(key);
11355
11439
  for (const [key, terminal] of this.terminals) {
11356
11440
  if (!key.startsWith(`${project}:`)) continue;
11357
11441
  terminal.close();
@@ -11412,6 +11496,8 @@ var OpenCodeAdapter = class {
11412
11496
  }
11413
11497
  close() {
11414
11498
  this.auth.close();
11499
+ this.permissionEvents.clear();
11500
+ this.approveAll.clear();
11415
11501
  for (const controller of this.streams.values()) controller.abort();
11416
11502
  for (const terminal of this.terminals.values()) terminal.close();
11417
11503
  }
@@ -11434,7 +11520,13 @@ function normalizeMessage(message) {
11434
11520
  if (message?.model) info.model = { ...message.model, modelID: message.model.id };
11435
11521
  const parts = [];
11436
11522
  if (typeof message?.text === "string" && message.text) parts.push({ type: "text", text: message.text });
11437
- for (const file of message?.files ?? []) parts.push({ type: "file", filename: file.name, url: file.uri });
11523
+ for (const file of message?.files ?? []) {
11524
+ if (typeof file.data === "string" && /^image\/(?:png|jpeg|gif|webp)$/.test(file.mime)) {
11525
+ parts.push({ type: "image", filename: file.name, mime: file.mime, base64: file.data });
11526
+ } else {
11527
+ parts.push({ type: "file", filename: file.name, mime: file.mime, url: file.uri });
11528
+ }
11529
+ }
11438
11530
  if (message?.type === "shell") parts.push({ type: "tool", tool: "shell", state: { status: message.time?.completed ? "completed" : "running", input: { command: message.command }, output: message.output } });
11439
11531
  if (message?.type === "compaction") {
11440
11532
  info.summary = true;
@@ -12241,6 +12333,7 @@ var CodexAdapter = class {
12241
12333
  media;
12242
12334
  liveTurns = /* @__PURE__ */ new Map();
12243
12335
  approvals = /* @__PURE__ */ new Map();
12336
+ approveAll = /* @__PURE__ */ new Set();
12244
12337
  inputs = /* @__PURE__ */ new Map();
12245
12338
  activeTurns = /* @__PURE__ */ new Map();
12246
12339
  loadedThreads = /* @__PURE__ */ new Map();
@@ -12281,13 +12374,17 @@ var CodexAdapter = class {
12281
12374
  this.trackNotification(project, method, params);
12282
12375
  if (method === "turn/completed") {
12283
12376
  const detail = params ?? {};
12284
- if (detail.threadId && this.activeTurns.get(detail.threadId) === detail.turn?.id) this.activeTurns.delete(detail.threadId);
12377
+ if (detail.threadId && this.activeTurns.get(detail.threadId) === detail.turn?.id) {
12378
+ this.activeTurns.delete(detail.threadId);
12379
+ this.approveAll.delete(`${project}\0${detail.threadId}`);
12380
+ }
12285
12381
  }
12286
12382
  this.emit(project, method, params);
12287
12383
  },
12288
12384
  onServerRequest: (id, method, params, respond) => this.onServerRequest(project, id, method, params, respond),
12289
12385
  onClose: () => {
12290
12386
  this.loadedThreads.delete(project);
12387
+ for (const key of this.approveAll) if (key.startsWith(`${project}\0`)) this.approveAll.delete(key);
12291
12388
  for (const key of this.liveTurns.keys()) if (key.startsWith(`${project}\0`)) this.liveTurns.delete(key);
12292
12389
  for (const [key, entry] of this.approvals) if (entry.project === project) this.approvals.delete(key);
12293
12390
  for (const [key, entry] of this.inputs) if (entry.project === project) this.inputs.delete(key);
@@ -12305,7 +12402,12 @@ var CodexAdapter = class {
12305
12402
  const requestId = randomUUID13();
12306
12403
  const threadId = String(detail.threadId ?? "");
12307
12404
  if (["item/permissions/requestApproval", "item/commandExecution/requestApproval", "item/fileChange/requestApproval"].includes(method)) {
12308
- this.approvals.set(requestId, { project, method, requestId, threadId, detail, respond });
12405
+ const entry = { project, method, requestId, threadId, detail, respond };
12406
+ if (this.approveAll.has(`${project}\0${threadId}`)) {
12407
+ respond(this.approvalResponse(entry, "once"));
12408
+ return;
12409
+ }
12410
+ this.approvals.set(requestId, entry);
12309
12411
  this.emit(project, "permission.request", { requestId, threadId });
12310
12412
  return;
12311
12413
  }
@@ -12536,6 +12638,7 @@ var CodexAdapter = class {
12536
12638
  for (const approval of this.approvals.values()) approval.respond(this.approvalResponse(approval, "reject"));
12537
12639
  for (const input of this.inputs.values()) input.respond({ answers: {} });
12538
12640
  this.approvals.clear();
12641
+ this.approveAll.clear();
12539
12642
  this.inputs.clear();
12540
12643
  this.liveTurns.clear();
12541
12644
  }
@@ -12662,6 +12765,7 @@ var CodexAdapter = class {
12662
12765
  }
12663
12766
  if (lines.length) input.push({ type: "text", text: lines.join("\n") });
12664
12767
  if (!input.length) throw new Error("Message or attachments required");
12768
+ this.approveAll.delete(`${project}\0${id}`);
12665
12769
  const selectedModel = p.model;
12666
12770
  const model = selectedModel?.providerID === "codex" && typeof selectedModel.modelID === "string" ? selectedModel.modelID : void 0;
12667
12771
  const effort = selectedReasoningLevel(p, "effort");
@@ -12671,6 +12775,7 @@ var CodexAdapter = class {
12671
12775
  }
12672
12776
  if (op === "sessions.abort") {
12673
12777
  if (!id) throw new Error("Session required");
12778
+ this.approveAll.delete(`${project}\0${id}`);
12674
12779
  await this.session(project, id);
12675
12780
  let turnId = this.activeTurns.get(id);
12676
12781
  if (!turnId) {
@@ -12695,15 +12800,23 @@ var CodexAdapter = class {
12695
12800
  permission: entry.detail.type ?? (entry.method.includes("fileChange") ? "\u6587\u4EF6\u4FEE\u6539" : entry.detail.permissions ? "\u6269\u5C55\u6743\u9650" : "\u547D\u4EE4\u6267\u884C"),
12696
12801
  patterns: [entry.detail.command ?? entry.detail.commandDraft ?? entry.detail.path ?? entry.detail.grantRoot ?? ""],
12697
12802
  metadata: entry.detail,
12698
- replies: ["once", "always", "reject"],
12699
- alwaysLabel: "\u672C\u4F1A\u8BDD\u5141\u8BB8"
12803
+ replies: ["once", "always", "all", "reject"],
12804
+ alwaysLabel: "\u5141\u8BB8\u7C7B\u4F3C\u8BF7\u6C42"
12700
12805
  }));
12701
12806
  }
12702
12807
  if (op === "permissions.reply") {
12703
12808
  const entry = this.approvals.get(String(p.requestId));
12704
12809
  if (!entry || entry.project !== project) throw new Error("Request is no longer pending in this project");
12705
12810
  if (id && id !== entry.threadId) throw new Error("Request belongs to another session");
12706
- if (!["accept", "allow", "yes", "once", "approve", "true", "always", "reject"].includes(String(p.reply))) throw new Error("Invalid permission reply");
12811
+ if (!["accept", "allow", "yes", "once", "approve", "true", "always", "all", "reject"].includes(String(p.reply))) throw new Error("Invalid permission reply");
12812
+ if (p.reply === "all") {
12813
+ this.approveAll.add(`${project}\0${entry.threadId}`);
12814
+ for (const [key, pending] of this.approvals) if (pending.project === project && pending.threadId === entry.threadId) {
12815
+ this.approvals.delete(key);
12816
+ pending.respond(this.approvalResponse(pending, "once"));
12817
+ }
12818
+ return { ok: true };
12819
+ }
12707
12820
  entry.respond(this.approvalResponse(entry, String(p.reply)));
12708
12821
  this.approvals.delete(String(p.requestId));
12709
12822
  return { ok: true };
@@ -12766,6 +12879,7 @@ var ZcodeAdapter = class {
12766
12879
  auth = new AuthFlows();
12767
12880
  media;
12768
12881
  permissions = /* @__PURE__ */ new Map();
12882
+ approveAll = /* @__PURE__ */ new Set();
12769
12883
  inputs = /* @__PURE__ */ new Map();
12770
12884
  subscribed = /* @__PURE__ */ new Set();
12771
12885
  resuming = /* @__PURE__ */ new Map();
@@ -12780,6 +12894,7 @@ var ZcodeAdapter = class {
12780
12894
  },
12781
12895
  onServerRequest: (id, method, params, respond, respondError) => this.onServerRequest(project, id, method, params, respond, respondError),
12782
12896
  onClose: () => {
12897
+ for (const key of this.approveAll) if (key.startsWith(`${project}\0`)) this.approveAll.delete(key);
12783
12898
  for (const key of this.subscribed) if (key.startsWith(`${project}\0`)) this.subscribed.delete(key);
12784
12899
  for (const [key, entry] of this.permissions) if (entry.project === project) this.permissions.delete(key);
12785
12900
  for (const [key, entry] of this.inputs) if (entry.project === project) this.inputs.delete(key);
@@ -12798,6 +12913,10 @@ var ZcodeAdapter = class {
12798
12913
  return;
12799
12914
  }
12800
12915
  if (method === "interaction/requestPermission" || method === "permission.request") {
12916
+ if (this.approveAll.has(`${project}\0${sessionId}`)) {
12917
+ respond({ decision: "allow" });
12918
+ return;
12919
+ }
12801
12920
  this.permissions.set(requestId, { project, requestId, sessionId, detail, respond });
12802
12921
  this.emit(project, "permission.request", { sessionID: sessionId, requestId });
12803
12922
  return;
@@ -12923,6 +13042,7 @@ var ZcodeAdapter = class {
12923
13042
  for (const entry of this.permissions.values()) entry.respond({ decision: "deny" });
12924
13043
  for (const entry of this.inputs.values()) entry.respond({ action: "decline" });
12925
13044
  this.permissions.clear();
13045
+ this.approveAll.clear();
12926
13046
  this.inputs.clear();
12927
13047
  }
12928
13048
  async dispatch(command) {
@@ -13043,10 +13163,16 @@ var ZcodeAdapter = class {
13043
13163
  const level = selectedReasoningLevel(p, "thoughtLevel");
13044
13164
  await rpc.request("session/setThoughtLevel", { sessionId: id, thoughtLevel: level });
13045
13165
  }
13046
- return await rpc.request("session/send", { sessionId: id, content: lines.join("\n") });
13166
+ this.approveAll.delete(`${project}\0${id}`);
13167
+ try {
13168
+ return await rpc.request("session/send", { sessionId: id, content: lines.join("\n") });
13169
+ } finally {
13170
+ this.approveAll.delete(`${project}\0${id}`);
13171
+ }
13047
13172
  }
13048
13173
  if (op === "sessions.abort") {
13049
13174
  if (!id) throw new Error("Session required");
13175
+ this.approveAll.delete(`${project}\0${id}`);
13050
13176
  return await rpc.request("session/stop", { sessionId: id });
13051
13177
  }
13052
13178
  if (op === "sessions.delete") {
@@ -13061,15 +13187,23 @@ var ZcodeAdapter = class {
13061
13187
  permission: entry.detail.type ?? entry.detail.toolName ?? "\u64CD\u4F5C\u786E\u8BA4",
13062
13188
  patterns: [entry.detail.command ?? entry.detail.pattern ?? ""],
13063
13189
  metadata: entry.detail,
13064
- replies: this.alwaysResponse(entry) ? ["once", "always", "reject"] : ["once", "reject"],
13065
- alwaysLabel: "\u6309\u8BF7\u6C42\u8303\u56F4\u5141\u8BB8"
13190
+ replies: this.alwaysResponse(entry) ? ["once", "always", "all", "reject"] : ["once", "all", "reject"],
13191
+ alwaysLabel: "\u5141\u8BB8\u7C7B\u4F3C\u8BF7\u6C42"
13066
13192
  }));
13067
13193
  }
13068
13194
  if (op === "permissions.reply") {
13069
13195
  const entry = this.permissions.get(String(p.requestId));
13070
13196
  if (!entry || entry.project !== project) throw new Error("Request is no longer pending in this project");
13071
13197
  if (id && id !== entry.sessionId) throw new Error("Request belongs to another session");
13072
- if (!["accept", "once", "always", "reject"].includes(String(p.reply))) throw new Error("Invalid permission reply");
13198
+ if (!["accept", "once", "always", "all", "reject"].includes(String(p.reply))) throw new Error("Invalid permission reply");
13199
+ if (p.reply === "all") {
13200
+ this.approveAll.add(`${project}\0${entry.sessionId}`);
13201
+ for (const [key, pending] of this.permissions) if (pending.project === project && pending.sessionId === entry.sessionId) {
13202
+ this.permissions.delete(key);
13203
+ pending.respond({ decision: "allow" });
13204
+ }
13205
+ return { ok: true };
13206
+ }
13073
13207
  const always = p.reply === "always" ? this.alwaysResponse(entry) : void 0;
13074
13208
  if (p.reply === "always" && !always) throw new Error("Persistent approval is not offered by this request");
13075
13209
  entry.respond(always ?? { decision: p.reply === "reject" ? "deny" : "allow" });
@@ -13169,6 +13303,7 @@ var PiAdapter = class {
13169
13303
  partials = /* @__PURE__ */ new Map();
13170
13304
  active = /* @__PURE__ */ new Set();
13171
13305
  questions = /* @__PURE__ */ new Map();
13306
+ approveAll = /* @__PURE__ */ new Set();
13172
13307
  mutations = /* @__PURE__ */ new Map();
13173
13308
  deleting = /* @__PURE__ */ new Set();
13174
13309
  reconfiguring = /* @__PURE__ */ new Set();
@@ -13212,7 +13347,10 @@ var PiAdapter = class {
13212
13347
  calls.set(detail.toolCallId, progress);
13213
13348
  this.toolProgress.set(key, calls);
13214
13349
  }
13215
- if (type === "agent_settled") this.active.delete(key);
13350
+ if (type === "agent_settled") {
13351
+ this.active.delete(key);
13352
+ this.approveAll.delete(key);
13353
+ }
13216
13354
  if (type === "message_update") this.partials.set(key, detail.message);
13217
13355
  if (type === "message_end" || type === "agent_end" || type === "agent_settled") this.partials.delete(key);
13218
13356
  if (type === "extension_ui_request") {
@@ -13223,6 +13361,7 @@ var PiAdapter = class {
13223
13361
  this.emit(project, event, { ...detail, sessionID: id });
13224
13362
  },
13225
13363
  onClose: () => {
13364
+ this.approveAll.delete(this.key(project, id));
13226
13365
  this.partials.delete(this.key(project, id));
13227
13366
  this.active.delete(this.key(project, id));
13228
13367
  this.clearQuestions(project, id);
@@ -13236,6 +13375,10 @@ var PiAdapter = class {
13236
13375
  void this.rpc(project, sessionId).then((rpc) => {
13237
13376
  if (rpc.exited) return;
13238
13377
  const question = { id, project, sessionId, detail, rpc };
13378
+ if (this.isPermission(question) && this.approveAll.has(this.key(project, sessionId))) {
13379
+ rpc.notify("extension_ui_response", { id: detail.id, confirmed: true });
13380
+ return;
13381
+ }
13239
13382
  if (typeof detail.timeout === "number" && detail.timeout > 0) {
13240
13383
  question.timer = setTimeout(() => {
13241
13384
  this.questions.delete(id);
@@ -13264,6 +13407,7 @@ var PiAdapter = class {
13264
13407
  if (!question.rpc.exited) question.rpc.notify("extension_ui_response", { id: question.detail.id, cancelled: true });
13265
13408
  }
13266
13409
  this.questions.clear();
13410
+ this.approveAll.clear();
13267
13411
  this.partials.clear();
13268
13412
  this.active.clear();
13269
13413
  this.toolProgress.clear();
@@ -13365,13 +13509,22 @@ var PiAdapter = class {
13365
13509
  sessionID: q.sessionId,
13366
13510
  permission: "Pi \u5DE5\u5177\u64CD\u4F5C",
13367
13511
  patterns: [q.detail.message ?? ""],
13368
- replies: ["once", "reject"]
13512
+ replies: ["once", "all", "reject"]
13369
13513
  }));
13370
13514
  }
13371
13515
  if (op === "permissions.reply") {
13372
13516
  const question = this.questions.get(String(p.requestId));
13373
13517
  if (!question || !this.isPermission(question) || question.project !== project || id && id !== question.sessionId) throw new Error("Request is no longer pending in this session");
13374
- if (p.reply !== "once" && p.reply !== "reject") throw new Error("Invalid permission reply");
13518
+ if (p.reply !== "once" && p.reply !== "all" && p.reply !== "reject") throw new Error("Invalid permission reply");
13519
+ if (p.reply === "all") {
13520
+ this.approveAll.add(this.key(project, question.sessionId));
13521
+ for (const [key, pending] of this.questions) if (pending.project === project && pending.sessionId === question.sessionId && this.isPermission(pending)) {
13522
+ clearTimeout(pending.timer);
13523
+ this.questions.delete(key);
13524
+ pending.rpc.notify("extension_ui_response", { id: pending.detail.id, confirmed: true });
13525
+ }
13526
+ return { ok: true };
13527
+ }
13375
13528
  question.rpc.notify("extension_ui_response", { id: question.detail.id, confirmed: p.reply === "once" });
13376
13529
  clearTimeout(question.timer);
13377
13530
  this.questions.delete(question.id);
@@ -13434,6 +13587,7 @@ var PiAdapter = class {
13434
13587
  return this.media.messages(project, id, this.messages(messages, this.key(project, id)));
13435
13588
  }
13436
13589
  if (op === "sessions.abort") {
13590
+ this.approveAll.delete(this.key(project, id));
13437
13591
  this.clearQuestions(project, id);
13438
13592
  await rpc.request("clear_queue");
13439
13593
  await rpc.request("abort");
@@ -13469,6 +13623,7 @@ var PiAdapter = class {
13469
13623
  const level = selectedReasoningLevel(p, "level");
13470
13624
  if (model && typeof model.providerID === "string" && typeof model.modelID === "string") await rpc.request("set_model", { provider: model.providerID, modelId: model.modelID });
13471
13625
  if (level) await rpc.request("set_thinking_level", { level });
13626
+ this.approveAll.delete(this.key(project, id));
13472
13627
  const result = await rpc.request("prompt", { message: lines.join("\n"), ...images.length ? { images } : {} });
13473
13628
  session.time.updated = Date.now();
13474
13629
  if (session.title === "\u65B0\u4EFB\u52A1" && typeof p.text === "string" && p.text.trim()) session.title = p.text.trim().slice(0, 100);
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  supportedPlatform,
36
36
  systemdUnitPath,
37
37
  toolSearchDirs
38
- } from "./chunk-CGHUM2TC.js";
38
+ } from "./chunk-JUUJGAHF.js";
39
39
  import {
40
40
  DESKTOP_PERMISSION_GUIDANCE,
41
41
  PlatformDesktop,
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-CGHUM2TC.js";
5
+ } from "./chunk-JUUJGAHF.js";
6
6
  import "./chunk-OTKNRBZ7.js";
7
7
 
8
8
  // src/main.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.72",
3
+ "version": "0.1.74",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {