@skylandnpm/octopus-proxy 0.1.63 → 0.1.64

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.
@@ -8334,6 +8334,7 @@ var Operations = [
8334
8334
  "configuration.get",
8335
8335
  "configuration.patch",
8336
8336
  "configuration.models",
8337
+ "configuration.permissionModes",
8337
8338
  "configuration.agents",
8338
8339
  "configuration.commands",
8339
8340
  "configuration.providers",
@@ -8373,7 +8374,7 @@ var CommandSchema = z.object({
8373
8374
  sessionId: z.string().optional(),
8374
8375
  payload: z.record(z.unknown()).default({})
8375
8376
  }).strict();
8376
- var isReadOperation = (operation) => operation === "desktop.capture" || /\.(status|snapshot|list|get|messages|diff|todos|children|logs|projects|read|search|history|models|agents|commands|providers|mcp)$/.test(operation);
8377
+ var isReadOperation = (operation) => operation === "desktop.capture" || /\.(status|snapshot|list|get|messages|diff|todos|children|logs|projects|read|search|history|models|permissionModes|agents|commands|providers|mcp)$/.test(operation);
8377
8378
 
8378
8379
  // src/journal.ts
8379
8380
  var Journal = class {
@@ -11031,6 +11032,10 @@ var OpenCodeAdapter = class {
11031
11032
  await this.session(project, id);
11032
11033
  }
11033
11034
  const base = `/api/session/${encodeURIComponent(id ?? "")}`;
11035
+ if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
11036
+ { id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u6BCF\u6B21\u4F7F\u7528\u5DE5\u5177\u524D\u8BF7\u6C42\u6279\u51C6" },
11037
+ { 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" }
11038
+ ] };
11034
11039
  if (op === "sessions.prompt") return this.prompt(project, id, p, base);
11035
11040
  if (op === "sessions.command" || op === "sessions.shell") {
11036
11041
  await this.switchSelection(project, base, p);
@@ -11098,6 +11103,13 @@ var OpenCodeAdapter = class {
11098
11103
  return this.data(project, "POST", `${base}/prompt`, { text, files, delivery: "queue", ...p.messageID ? { id: p.messageID } : {} });
11099
11104
  }
11100
11105
  async switchSelection(project, base, payload) {
11106
+ if (payload.permissionMode !== void 0) {
11107
+ if (!["ask", "full"].includes(String(payload.permissionMode))) throw new Error("Unsupported permission mode");
11108
+ const states = await this.data(project, "GET", "/api/session/active");
11109
+ const state = states?.[decodeURIComponent(base.split("/").at(-1))];
11110
+ if (state && !["idle", "completed", "failed", "interrupted"].includes(state.type)) throw new Error("Wait for the active task before changing permission mode");
11111
+ await this.data(project, "PATCH", base, { permissions: [{ action: "*", resource: "*", effect: payload.permissionMode === "full" ? "allow" : "ask" }] });
11112
+ }
11101
11113
  if (payload.agent) await this.data(project, "POST", `${base}/agent`, { agent: payload.agent });
11102
11114
  if (!payload.model) return;
11103
11115
  let model;
@@ -12129,6 +12141,7 @@ var OPERATIONS = [
12129
12141
  "sessions.abort",
12130
12142
  "sessions.delete",
12131
12143
  "configuration.models",
12144
+ "configuration.permissionModes",
12132
12145
  "permissions.list",
12133
12146
  "permissions.reply",
12134
12147
  "questions.list",
@@ -12403,6 +12416,31 @@ var CodexAdapter = class {
12403
12416
  }
12404
12417
  return { decision: accept ? reply === "always" ? "acceptForSession" : "accept" : "decline" };
12405
12418
  }
12419
+ async permissionModes(project, rpc) {
12420
+ const result = await rpc.request("config/read", { cwd: await this.workspace.root(project), includeLayers: false }).catch(() => void 0);
12421
+ const autoReview = !!result?.config && Object.hasOwn(result.config, "approvals_reviewer");
12422
+ return {
12423
+ defaultMode: "ask",
12424
+ modes: [
12425
+ { id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u7F16\u8F91\u9879\u76EE\u5916\u7684\u6587\u4EF6\u548C\u4F7F\u7528\u4E92\u8054\u7F51\u65F6\u8BF7\u6C42\u6279\u51C6" },
12426
+ ...autoReview ? [{ id: "auto", label: "\u5E2E\u6211\u6279\u51C6", description: "\u7531 Codex \u81EA\u52A8\u5BA1\u6838\u64CD\u4F5C\u98CE\u9669\uFF0C\u9700\u8981\u4F60\u786E\u8BA4\u65F6\u518D\u8BF7\u6C42\u6279\u51C6" }] : [],
12427
+ { id: "full", label: "\u5B8C\u5168\u8BBF\u95EE", description: "\u5141\u8BB8\u8BBF\u95EE\u4E92\u8054\u7F51\u548C Proxy \u4E3B\u673A\u4E0A\u7684\u4EFB\u4F55\u6587\u4EF6\uFF0C\u65E0\u9700\u64CD\u4F5C\u6279\u51C6" }
12428
+ ],
12429
+ autoReview
12430
+ };
12431
+ }
12432
+ async permissionOverrides(project, rpc, mode) {
12433
+ if (mode === void 0) return {};
12434
+ const options = await this.permissionModes(project, rpc);
12435
+ if (!options.modes.some((entry) => entry.id === mode)) throw new Error("Unsupported permission mode");
12436
+ const reviewer = options.autoReview ? { approvalsReviewer: mode === "auto" ? "guardian_subagent" : "user" } : {};
12437
+ if (mode === "full") return { approvalPolicy: "never", sandboxPolicy: { type: "dangerFullAccess" }, ...reviewer };
12438
+ return {
12439
+ approvalPolicy: "on-request",
12440
+ sandboxPolicy: { type: "workspaceWrite", writableRoots: [await this.workspace.root(project)], networkAccess: false },
12441
+ ...reviewer
12442
+ };
12443
+ }
12406
12444
  async session(project, id) {
12407
12445
  if (!describeTool("codex").sessionPattern.test(id)) throw new Error("Invalid session id");
12408
12446
  const rpc = await this.rpc(project);
@@ -12431,6 +12469,10 @@ var CodexAdapter = class {
12431
12469
  if (op === "auth.poll") return this.auth.poll(project, p);
12432
12470
  if (op === "auth.cancel") return this.auth.cancel(project, p.flowId);
12433
12471
  const rpc = await this.rpc(project);
12472
+ if (op === "configuration.permissionModes") {
12473
+ const { autoReview: _, ...options } = await this.permissionModes(project, rpc);
12474
+ return options;
12475
+ }
12434
12476
  if (op === "auth.status") {
12435
12477
  const result = await rpc.request("account/read", { refreshToken: false });
12436
12478
  return {
@@ -12530,6 +12572,7 @@ var CodexAdapter = class {
12530
12572
  if (op === "sessions.prompt") {
12531
12573
  if (!id) throw new Error("Session required");
12532
12574
  await this.session(project, id);
12575
+ const permissions = await this.permissionOverrides(project, rpc, p.permissionMode);
12533
12576
  await this.resume(project, id, rpc);
12534
12577
  const input = [];
12535
12578
  const lines = [];
@@ -12544,7 +12587,7 @@ var CodexAdapter = class {
12544
12587
  const selectedModel = p.model;
12545
12588
  const model = selectedModel?.providerID === "codex" && typeof selectedModel.modelID === "string" ? selectedModel.modelID : void 0;
12546
12589
  const effort = selectedReasoningLevel(p, "effort");
12547
- const result = await rpc.request("turn/start", { threadId: id, input, ...model ? { model } : {}, ...effort ? { effort } : {} });
12590
+ const result = await rpc.request("turn/start", { threadId: id, input, ...model ? { model } : {}, ...effort ? { effort } : {}, ...permissions });
12548
12591
  this.activeTurns.set(id, result.turn.id);
12549
12592
  return result;
12550
12593
  }
@@ -12619,6 +12662,7 @@ var OPERATIONS2 = [
12619
12662
  ...DAEMON_OPERATIONS,
12620
12663
  ...AUTH_OPERATIONS,
12621
12664
  "configuration.models",
12665
+ "configuration.permissionModes",
12622
12666
  "sessions.list",
12623
12667
  "sessions.create",
12624
12668
  "sessions.get",
@@ -12811,6 +12855,12 @@ var ZcodeAdapter = class {
12811
12855
  if (op === "auth.poll") return this.auth.poll(project, p);
12812
12856
  if (op === "auth.cancel") return this.auth.cancel(project, p.flowId);
12813
12857
  const rpc = await this.rpc(project);
12858
+ if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
12859
+ { id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u4F7F\u7528 ZCode build \u6A21\u5F0F\uFF0C\u4FEE\u6539\u6587\u4EF6\u524D\u8BF7\u6C42\u6279\u51C6" },
12860
+ { id: "edit", label: "\u81EA\u52A8\u7F16\u8F91", description: "\u81EA\u52A8\u7F16\u8F91\u76F8\u5173\u6587\u4EF6\uFF0C\u5176\u4ED6\u64CD\u4F5C\u6309 ZCode \u539F\u751F\u89C4\u5219\u5BA1\u6279" },
12861
+ { id: "plan", label: "\u53EA\u8BFB\u89C4\u5212", description: "\u4F7F\u7528 ZCode plan \u6A21\u5F0F\uFF0C\u68C0\u67E5\u9879\u76EE\u5E76\u5236\u5B9A\u8BA1\u5212" },
12862
+ { id: "full", label: "\u5B8C\u5168\u8BBF\u95EE", description: "\u4F7F\u7528 ZCode yolo \u6A21\u5F0F\uFF0C\u51CF\u5C11\u5DE5\u5177\u64CD\u4F5C\u786E\u8BA4" }
12863
+ ] };
12814
12864
  if (op === "configuration.models" || op === "auth.status") {
12815
12865
  const snapshot = await rpc.request("session/create", { workspace: await this.workspaceRef(project), persistence: "deferred", titleGenerationEnabled: false, toolAllowlist: [] });
12816
12866
  try {
@@ -12886,7 +12936,8 @@ var ZcodeAdapter = class {
12886
12936
  }
12887
12937
  if (op === "sessions.prompt") {
12888
12938
  if (!id) throw new Error("Session required");
12889
- await this.session(project, id);
12939
+ const session = await this.session(project, id);
12940
+ if (p.permissionMode !== void 0 && typeof session.status === "string" && !["idle", "completed", "stopped", "closed", "failed", "error"].includes(session.status.toLowerCase())) throw new Error("Wait for the active task before changing permission mode");
12890
12941
  await this.watch(project, rpc, id);
12891
12942
  const lines = [];
12892
12943
  if (typeof p.text === "string" && p.text.trim()) lines.push(screenshotPrompt(p.text));
@@ -12895,6 +12946,12 @@ var ZcodeAdapter = class {
12895
12946
  lines.push(`Attached file: ${upload.destination} (${upload.name}, ${upload.mime}). Read this file if relevant to the task.`);
12896
12947
  }
12897
12948
  if (!lines.length) throw new Error("Message or attachments required");
12949
+ if (p.permissionMode !== void 0) {
12950
+ const modes = { ask: "build", edit: "edit", plan: "plan", full: "yolo" };
12951
+ const mode = typeof p.permissionMode === "string" && Object.hasOwn(modes, p.permissionMode) ? modes[p.permissionMode] : void 0;
12952
+ if (!mode) throw new Error("Unsupported permission mode");
12953
+ await rpc.request("session/setMode", { sessionId: id, mode });
12954
+ }
12898
12955
  const selected = p.model;
12899
12956
  if (selected?.providerID && selected.modelID) {
12900
12957
  let level = selectedReasoningLevel(p, "model.options.reasoningLevel");
@@ -12986,6 +13043,22 @@ var ZcodeAdapter = class {
12986
13043
  import { randomUUID as randomUUID15 } from "node:crypto";
12987
13044
  import { readFile as readFile8, readdir as readdir3, rm as rm2 } from "node:fs/promises";
12988
13045
  import { dirname as dirname5 } from "node:path";
13046
+
13047
+ // src/tools/pi-permissions.ts
13048
+ var PI_PERMISSION_TITLE = "Octopus \u5DE5\u5177\u6388\u6743";
13049
+ var PI_PERMISSION_EXTENSION = `
13050
+ export default function (pi) {
13051
+ const ready = process.env.OCTOPUS_PI_PERMISSION_READY;
13052
+ pi.on('session_start', (_event, ctx) => ctx.ui.notify(ready, 'info'));
13053
+ pi.on('tool_call', async (event, ctx) => {
13054
+ if (!ctx.hasUI) return { block: true, reason: '\u5DE5\u5177\u64CD\u4F5C\u9700\u8981\u7528\u6237\u6279\u51C6' };
13055
+ const allowed = await ctx.ui.confirm(${JSON.stringify(PI_PERMISSION_TITLE)}, JSON.stringify({ toolName: event.toolName, input: event.input }));
13056
+ if (allowed !== true) return { block: true, reason: '\u7528\u6237\u62D2\u7EDD\u4E86\u5DE5\u5177\u64CD\u4F5C' };
13057
+ });
13058
+ }
13059
+ `;
13060
+
13061
+ // src/tools/pi.ts
12989
13062
  var OPERATIONS3 = [
12990
13063
  ...DAEMON_OPERATIONS,
12991
13064
  "sessions.list",
@@ -12998,7 +13071,9 @@ var OPERATIONS3 = [
12998
13071
  "sessions.delete",
12999
13072
  "auth.status",
13000
13073
  "configuration.models",
13074
+ "configuration.permissionModes",
13001
13075
  "permissions.list",
13076
+ "permissions.reply",
13002
13077
  "questions.list",
13003
13078
  "questions.reply",
13004
13079
  "questions.reject"
@@ -13018,6 +13093,7 @@ var PiAdapter = class {
13018
13093
  questions = /* @__PURE__ */ new Map();
13019
13094
  mutations = /* @__PURE__ */ new Map();
13020
13095
  deleting = /* @__PURE__ */ new Set();
13096
+ reconfiguring = /* @__PURE__ */ new Set();
13021
13097
  supportedOperations() {
13022
13098
  return OPERATIONS3;
13023
13099
  }
@@ -13072,7 +13148,7 @@ var PiAdapter = class {
13072
13148
  this.partials.delete(this.key(project, id));
13073
13149
  this.active.delete(this.key(project, id));
13074
13150
  this.clearQuestions(project, id);
13075
- this.emit(project, "session.error", { sessionID: id, message: "Pi connection closed; reconnect to resume the saved session" });
13151
+ if (!this.reconfiguring.has(this.key(project, id))) this.emit(project, "session.error", { sessionID: id, message: "Pi connection closed; reconnect to resume the saved session" });
13076
13152
  }
13077
13153
  });
13078
13154
  }
@@ -13090,16 +13166,20 @@ var PiAdapter = class {
13090
13166
  question.timer.unref();
13091
13167
  }
13092
13168
  this.questions.set(id, question);
13093
- this.emit(project, "question.request", { sessionID: sessionId, requestId: id });
13169
+ this.emit(project, this.isPermission(question) ? "permission.request" : "question.request", { sessionID: sessionId, requestId: id });
13094
13170
  }).catch(() => {
13095
13171
  });
13096
13172
  }
13097
13173
  clearQuestions(project, id) {
13098
13174
  for (const [key, question] of this.questions) if (question.project === project && question.sessionId === id) {
13099
13175
  clearTimeout(question.timer);
13176
+ if (!question.rpc.exited) question.rpc.notify("extension_ui_response", { id: question.detail.id, cancelled: true });
13100
13177
  this.questions.delete(key);
13101
13178
  }
13102
13179
  }
13180
+ isPermission(question) {
13181
+ return question.detail.method === "confirm" && question.detail.title === PI_PERMISSION_TITLE;
13182
+ }
13103
13183
  close() {
13104
13184
  for (const question of this.questions.values()) {
13105
13185
  clearTimeout(question.timer);
@@ -13157,6 +13237,10 @@ var PiAdapter = class {
13157
13237
  async execute(command, project) {
13158
13238
  const op = command.operation, p = command.payload, id = command.sessionId;
13159
13239
  if (!OPERATIONS3.includes(op)) unsupportedOperation("pi", op);
13240
+ if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
13241
+ { id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u901A\u8FC7 Pi \u5DE5\u5177\u6388\u6743\u6269\u5C55\uFF0C\u5728\u6BCF\u6B21\u5DE5\u5177\u6267\u884C\u524D\u8BF7\u6C42\u6279\u51C6" },
13242
+ { id: "full", label: "\u5B8C\u5168\u8BBF\u95EE", description: "\u6309 Pi \u539F\u751F\u6743\u9650\u6267\u884C\u5DE5\u5177\uFF0C\u4E0D\u542F\u7528\u9010\u6B21\u5BA1\u6279\uFF1B\u4E0D\u63D0\u4F9B\u7CFB\u7EDF\u6C99\u7BB1" }
13243
+ ] };
13160
13244
  if (op === "auth.status") {
13161
13245
  const rpc2 = await this.runtime.connect(project);
13162
13246
  const result = await rpc2.request("get_available_models");
@@ -13198,11 +13282,26 @@ var PiAdapter = class {
13198
13282
  }
13199
13283
  if (op === "permissions.list") {
13200
13284
  if (id) await this.session(project, id);
13201
- return [];
13285
+ return [...this.questions.values()].filter((q) => q.project === project && (!id || q.sessionId === id) && this.isPermission(q)).map((q) => ({
13286
+ id: q.id,
13287
+ sessionID: q.sessionId,
13288
+ permission: "Pi \u5DE5\u5177\u64CD\u4F5C",
13289
+ patterns: [q.detail.message ?? ""],
13290
+ replies: ["once", "reject"]
13291
+ }));
13292
+ }
13293
+ if (op === "permissions.reply") {
13294
+ const question = this.questions.get(String(p.requestId));
13295
+ if (!question || !this.isPermission(question) || question.project !== project || id && id !== question.sessionId) throw new Error("Request is no longer pending in this session");
13296
+ if (p.reply !== "once" && p.reply !== "reject") throw new Error("Invalid permission reply");
13297
+ question.rpc.notify("extension_ui_response", { id: question.detail.id, confirmed: p.reply === "once" });
13298
+ clearTimeout(question.timer);
13299
+ this.questions.delete(question.id);
13300
+ return { ok: true };
13202
13301
  }
13203
13302
  if (op === "questions.list") {
13204
13303
  if (id) await this.session(project, id);
13205
- return [...this.questions.values()].filter((q) => q.project === project && (!id || q.sessionId === id)).map((q) => ({
13304
+ return [...this.questions.values()].filter((q) => q.project === project && (!id || q.sessionId === id) && !this.isPermission(q)).map((q) => ({
13206
13305
  id: q.id,
13207
13306
  sessionID: q.sessionId,
13208
13307
  questions: [{
@@ -13216,7 +13315,7 @@ var PiAdapter = class {
13216
13315
  }
13217
13316
  if (op === "questions.reply" || op === "questions.reject") {
13218
13317
  const question = this.questions.get(String(p.requestId));
13219
- if (!question || question.project !== project || id && id !== question.sessionId) throw new Error("Request is no longer pending in this session");
13318
+ if (!question || this.isPermission(question) || question.project !== project || id && id !== question.sessionId) throw new Error("Request is no longer pending in this session");
13220
13319
  const answers = Array.isArray(p.answers) ? p.answers : [];
13221
13320
  const first = Array.isArray(answers[0]) ? answers[0][0] : answers[0];
13222
13321
  const value = typeof first === "string" ? first : "";
@@ -13234,13 +13333,14 @@ var PiAdapter = class {
13234
13333
  await this.runtime.closeSession(project, id);
13235
13334
  await rm2(this.runtime.sessionFile(project, id), { force: true });
13236
13335
  await rm2(this.metadataPath(project, id));
13336
+ await rm2(this.runtime.sessionFile(project, id).replace(/\.jsonl$/, ".permission.ts"), { force: true });
13237
13337
  this.clearQuestions(project, id);
13238
13338
  } finally {
13239
13339
  this.deleting.delete(this.key(project, id));
13240
13340
  }
13241
13341
  return { ok: true };
13242
13342
  }
13243
- const rpc = await this.rpc(project, id);
13343
+ let rpc = await this.rpc(project, id);
13244
13344
  if (op === "sessions.status") {
13245
13345
  const state = await rpc.request("get_state");
13246
13346
  return { [id]: { type: this.active.has(this.key(project, id)) || state.isStreaming || state.isCompacting || (state.pendingMessageCount ?? 0) > 0 ? "busy" : "idle" } };
@@ -13256,6 +13356,7 @@ var PiAdapter = class {
13256
13356
  return this.media.messages(project, id, this.messages(messages, this.key(project, id)));
13257
13357
  }
13258
13358
  if (op === "sessions.abort") {
13359
+ this.clearQuestions(project, id);
13259
13360
  await rpc.request("clear_queue");
13260
13361
  await rpc.request("abort");
13261
13362
  return { ok: true };
@@ -13270,6 +13371,22 @@ var PiAdapter = class {
13270
13371
  } else lines.push(`Attached file: ${upload.destination} (${upload.name}, ${upload.mime}). Read this file if relevant to the task.`);
13271
13372
  }
13272
13373
  if (!lines.length && !images.length) throw new Error("Message or attachments required");
13374
+ if (p.permissionMode !== void 0) {
13375
+ if (p.permissionMode !== "ask" && p.permissionMode !== "full") throw new Error("Unsupported permission mode");
13376
+ if (p.permissionMode !== (session.permissionMode ?? "full")) {
13377
+ const state = await rpc.request("get_state");
13378
+ if (this.active.has(this.key(project, id)) || state.isStreaming || state.isCompacting || (state.pendingMessageCount ?? 0) > 0) throw new Error("Wait for the active task before changing permission mode");
13379
+ session.permissionMode = p.permissionMode;
13380
+ await atomicJson(this.metadataPath(project, id), session);
13381
+ this.reconfiguring.add(this.key(project, id));
13382
+ try {
13383
+ await this.runtime.closeSession(project, id);
13384
+ rpc = await this.rpc(project, id);
13385
+ } finally {
13386
+ this.reconfiguring.delete(this.key(project, id));
13387
+ }
13388
+ }
13389
+ }
13273
13390
  const model = p.model;
13274
13391
  const level = selectedReasoningLevel(p, "level");
13275
13392
  if (model && typeof model.providerID === "string" && typeof model.modelID === "string") await rpc.request("set_model", { provider: model.providerID, modelId: model.modelID });
@@ -13286,7 +13403,8 @@ var PiAdapter = class {
13286
13403
 
13287
13404
  // src/tools/pi-runtime.ts
13288
13405
  import { execFile as execFile5, spawn as spawn5 } from "node:child_process";
13289
- import { mkdir as mkdir8, open as open8 } from "node:fs/promises";
13406
+ import { mkdir as mkdir8, open as open8, writeFile as writeFile6 } from "node:fs/promises";
13407
+ import { randomUUID as randomUUID16 } from "node:crypto";
13290
13408
  import { join as join13 } from "node:path";
13291
13409
  import { promisify as promisify5 } from "node:util";
13292
13410
  var run3 = promisify5(execFile5);
@@ -13405,32 +13523,58 @@ ${bytes.toString("utf8")}`);
13405
13523
  await this.kill(key);
13406
13524
  const directory = this.project(projectId).directory;
13407
13525
  await mkdir8(join13(this.config.stateDirectory, "pi-sessions", projectId), { recursive: true, mode: 448 });
13526
+ const sessionFile = sessionId ? this.sessionFile(projectId, sessionId) : void 0;
13527
+ const policy = sessionFile ? await readJson(sessionFile.replace(/\.jsonl$/, ".json")) : void 0;
13528
+ const gated = policy?.permissionMode === "ask";
13529
+ const extension = sessionFile?.replace(/\.jsonl$/, ".permission.ts");
13530
+ if (gated) await writeFile6(extension, PI_PERMISSION_EXTENSION, { mode: 384 });
13531
+ const readyToken = gated ? `octopus-permissions-ready:${randomUUID16()}` : "";
13408
13532
  const logPath = join13(this.config.stateDirectory, `pi-${key}.log`);
13409
13533
  const log = await open8(logPath, "a", 384);
13410
13534
  if (generation !== this.generation) {
13411
13535
  await log.close();
13412
13536
  throw new Error("Pi runtime stopped during startup");
13413
13537
  }
13414
- const args = [...describeTool("pi").serverArgs, ...sessionId ? ["--session", this.sessionFile(projectId, sessionId)] : ["--no-session"]];
13415
- const child = spawn5(this.config.tool.command, args, { cwd: directory, env: { ...process.env }, stdio: ["pipe", "pipe", log.fd] });
13538
+ const args = [...describeTool("pi").serverArgs, ...sessionFile ? ["--session", sessionFile] : ["--no-session"], ...gated ? ["--extension", extension] : []];
13539
+ const child = spawn5(this.config.tool.command, args, { cwd: directory, env: { ...process.env, OCTOPUS_PI_PERMISSION_READY: readyToken }, stdio: ["pipe", "pipe", log.fd] });
13416
13540
  child.once("close", () => {
13417
13541
  void log.close();
13418
13542
  });
13419
13543
  this.logPaths.add(logPath);
13544
+ let resolveReady = () => {
13545
+ }, rejectReady = (_error) => {
13546
+ };
13547
+ let readyTimeout;
13548
+ const ready = gated ? new Promise((resolve5, reject) => {
13549
+ resolveReady = resolve5;
13550
+ rejectReady = reject;
13551
+ readyTimeout = setTimeout(() => reject(new Error("Pi \u5DE5\u5177\u6388\u6743\u6269\u5C55\u672A\u5C31\u7EEA\uFF0C\u5DF2\u505C\u6B62\u4F1A\u8BDD\u4EE5\u907F\u514D\u8DF3\u8FC7\u5BA1\u6279")), 1e4);
13552
+ }) : Promise.resolve();
13420
13553
  const rpc = new LineRpc(child, {
13421
13554
  ...handlers,
13555
+ onNotification: (type, data) => {
13556
+ const detail = data;
13557
+ if (gated && type === "extension_ui_request" && detail.method === "notify" && detail.message === readyToken) {
13558
+ resolveReady();
13559
+ return;
13560
+ }
13561
+ handlers.onNotification?.(type, data);
13562
+ },
13422
13563
  onClose: (reason) => {
13564
+ rejectReady(new Error("Pi \u5DE5\u5177\u6388\u6743\u8FDE\u63A5\u5DF2\u5173\u95ED"));
13423
13565
  handlers.onClose?.(reason);
13424
13566
  }
13425
13567
  }, "pi");
13426
13568
  this.processes.set(key, { child, rpc, projectId, sessionId });
13427
13569
  try {
13428
- await rpc.request("get_state");
13570
+ await Promise.all([rpc.request("get_state"), ready]);
13429
13571
  if (generation !== this.generation) throw new Error("Pi runtime stopped during startup");
13430
13572
  return rpc;
13431
13573
  } catch (error) {
13432
13574
  await this.kill(key);
13433
13575
  throw error;
13576
+ } finally {
13577
+ clearTimeout(readyTimeout);
13434
13578
  }
13435
13579
  }
13436
13580
  async kill(key) {
@@ -13594,7 +13738,7 @@ var ProjectRegistry = class {
13594
13738
  };
13595
13739
 
13596
13740
  // src/terminal.ts
13597
- import { randomUUID as randomUUID16 } from "node:crypto";
13741
+ import { randomUUID as randomUUID17 } from "node:crypto";
13598
13742
  import { access as access2, stat as stat9 } from "node:fs/promises";
13599
13743
  import { constants as constants5 } from "node:fs";
13600
13744
  import { platform as platform3, userInfo as userInfo3 } from "node:os";
@@ -13680,7 +13824,7 @@ var TerminalManager = class {
13680
13824
  throw new Error(`\u7EC8\u7AEF Shell \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${shell}\uFF1B\u8BF7\u4FEE\u6B63 Proxy \u670D\u52A1\u7684 SHELL \u73AF\u5883\u53D8\u91CF`);
13681
13825
  }
13682
13826
  const { helper } = await ensureTerminalSupport();
13683
- const id = randomUUID16();
13827
+ const id = randomUUID17();
13684
13828
  const path = [
13685
13829
  process.env.PATH,
13686
13830
  "/usr/bin",
@@ -13779,7 +13923,7 @@ var PlatformDesktopSession = class {
13779
13923
 
13780
13924
  // src/remote-manager.ts
13781
13925
  import { fork } from "node:child_process";
13782
- import { randomBytes as randomBytes2, randomUUID as randomUUID17 } from "node:crypto";
13926
+ import { randomBytes as randomBytes2, randomUUID as randomUUID18 } from "node:crypto";
13783
13927
  import { fileURLToPath as fileURLToPath4 } from "node:url";
13784
13928
 
13785
13929
  // src/remote-stun.ts
@@ -13913,7 +14057,7 @@ var RemoteManager = class {
13913
14057
  if (this.current) throw new Error("\u4E3B\u673A\u6B63\u5728\u88AB\u63A7\u5236");
13914
14058
  const quality = RemoteQualitySchema.parse(payload.quality ?? "hd");
13915
14059
  const budget = remoteSessionBudget(this.config, quality, payload.quality === void 0);
13916
- const session = { remoteId: randomUUID17(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
14060
+ const session = { remoteId: randomUUID18(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
13917
14061
  this.current = session;
13918
14062
  try {
13919
14063
  const [permissions, stunUrls] = await Promise.all([
@@ -13992,7 +14136,7 @@ var RemoteManager = class {
13992
14136
  };
13993
14137
 
13994
14138
  // src/tool-selection.ts
13995
- import { randomBytes as randomBytes3, randomUUID as randomUUID18 } from "node:crypto";
14139
+ import { randomBytes as randomBytes3, randomUUID as randomUUID19 } from "node:crypto";
13996
14140
  import { access as access3, constants as constants6, readFile as readFile10 } from "node:fs/promises";
13997
14141
  import { platform as platform5 } from "node:os";
13998
14142
  import { isDeepStrictEqual } from "node:util";
@@ -14136,7 +14280,7 @@ var ToolSelection = class {
14136
14280
  });
14137
14281
  }
14138
14282
  modelCommand(projectId) {
14139
- return { type: "command", id: randomUUID18(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
14283
+ return { type: "command", id: randomUUID19(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
14140
14284
  }
14141
14285
  profiles(raw) {
14142
14286
  return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  supportedPlatform,
36
36
  systemdUnitPath,
37
37
  toolSearchDirs
38
- } from "./chunk-R73E3EFB.js";
38
+ } from "./chunk-J3ZMMSW2.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-R73E3EFB.js";
5
+ } from "./chunk-J3ZMMSW2.js";
6
6
  import "./chunk-GS3Q6EU5.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.63",
3
+ "version": "0.1.64",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {