@skylandnpm/octopus-proxy 0.1.63 → 0.1.65
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 +25 -1
- package/dist/{chunk-R73E3EFB.js → chunk-6RJKEA3I.js} +231 -59
- package/dist/cli.js +21 -13
- package/dist/main.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ octopus-proxy restart
|
|
|
60
60
|
|
|
61
61
|
Claw 完成主机信任和账号认证后,通过端到端会话向 Proxy 发送只读 `runtime.snapshot` 命令。响应包含当前运行状态、Hub 和工具设置、授权项目路径,以及工具可提供的模型信息;机器人 Secret、工具密码和提供商凭据不会返回。工作区的“Proxy 信息”按钮可查看这些内容。
|
|
62
62
|
|
|
63
|
-
Codex 和 ZCode 由 Proxy 按授权项目启动各自的 app-server,默认随 Proxy 自动启动;服务未就绪时也可在 Claw
|
|
63
|
+
Codex 和 ZCode 由 Proxy 按授权项目启动各自的 app-server,默认随 Proxy 自动启动;服务未就绪时也可在 Claw 点击“启动服务”。若命令不可执行或某个项目启动失败,工作区会显示失败项目及原因。通过 `onboard` 或配置文件修改授权项目后,Claw 打开、切换或刷新工作区项目时会读取最新项目配置;目录变化会释放该项目的旧运行资源和终端,并刷新文件面板。Proxy 不轮询配置文件,也不因主机配置变化主动推送刷新通知。Claw 也可在授权项目菜单中点击“修改项目”,修改“Proxy 主机上的目录”并保存;Proxy 校验目录存在且未重复授权后写入主机配置,Claw 随即刷新工作区,无需重启 Proxy。修改工具命令、机器人凭据等其他设置后,仍需运行 `octopus-proxy restart`。
|
|
64
64
|
|
|
65
65
|
## Claw 设备配对与撤销
|
|
66
66
|
|
|
@@ -123,6 +123,30 @@ octopus-proxy update
|
|
|
123
123
|
|
|
124
124
|
安装或版本校验失败时不会触发服务重启;包安装失败不保证旧版本文件完整保留。已安装新版本但重启失败时会单独提示,可运行 `octopus-proxy doctor` 和 `octopus-proxy logs` 排查。此命令用于 npm 全局安装,覆盖安装前会检查 npm 安装目录是否与当前命令一致;源码、本地安装或 Node.js 环境不一致时会中止并给出提示。
|
|
125
125
|
|
|
126
|
+
## Linux 服务在线,但终端显示“未运行、未启用”
|
|
127
|
+
|
|
128
|
+
后台服务创建的终端可能没有登录会话中的 `XDG_RUNTIME_DIR`。此时 `systemctl --user` 会报 `Failed to connect to bus: No medium found`,不能据此判断服务已停止。CLI 会在未显式配置用户会话环境时,检查 `/run/user/<当前 UID>` 是否存在且属于当前用户,并用于服务查询和控制;查询失败会报告错误。此检查也支持只有 `systemd/private`、没有会话 `bus` socket 的环境。
|
|
129
|
+
|
|
130
|
+
旧版可先验证:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
XDG_RUNTIME_DIR=/run/user/$(id -u) octopus-proxy status
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
如果显示正常,可在该服务账号的 `~/.bashrc` 中补上以下设置,使 Claw 新建的 Bash 终端也能查询用户服务:
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
if [ -z "${XDG_RUNTIME_DIR:-}" ] && [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
|
|
140
|
+
octopus_runtime_dir="/run/user/$(id -u)"
|
|
141
|
+
if [ -d "$octopus_runtime_dir" ] && [ -O "$octopus_runtime_dir" ]; then
|
|
142
|
+
export XDG_RUNTIME_DIR="$octopus_runtime_dir"
|
|
143
|
+
fi
|
|
144
|
+
unset octopus_runtime_dir
|
|
145
|
+
fi
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
重新打开终端或运行 `source ~/.bashrc` 后验证 `octopus-proxy status`。如果对应用户运行目录或用户服务管理器不存在,仍需检查主机的登录会话和 linger 配置。
|
|
149
|
+
|
|
126
150
|
## 服务启动时报 `Unexpected token '??='`
|
|
127
151
|
|
|
128
152
|
这通常表示服务实际使用了旧版 Node.js。错误栈中 `.nvm/versions/node/v22.../lib/node_modules` 是包的安装位置,不能说明运行它的 Node.js 版本。安装器只会选择 Node.js 22 或更高版本;旧版安装器可能优先选中能执行 `--version` 的旧系统 Node。
|
|
@@ -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;
|
|
@@ -11604,7 +11616,13 @@ function standaloneZcodeSource(source) {
|
|
|
11604
11616
|
throw new Error("Unsupported ZCode account bootstrap; native compatibility check failed");
|
|
11605
11617
|
}
|
|
11606
11618
|
body = body.replace(registryCall, `${registry}($1,{standalone:{}})`).replace(nativePorts, "$&providerRuntimeHeadersPort:$1.providerRuntimeHeadersPort,");
|
|
11607
|
-
|
|
11619
|
+
source = source.slice(0, start) + body + source.slice(end);
|
|
11620
|
+
const close = /\br\(([$\w]+),"closeSession"\)/.exec(source)?.[1];
|
|
11621
|
+
if (!close) throw new Error("Unsupported ZCode bundle: session close is unavailable");
|
|
11622
|
+
const escapedClose = close.replace(/[$]/g, "\\$&");
|
|
11623
|
+
const closeBody = new RegExp(`async function ${escapedClose}\\(([$\\w]+),([$\\w]+)\\)\\{[^}]*?await ([$\\w]+)\\.eventStore\\.deleteSession\\(([$\\w]+)\\.sessionId\\),\\{closed:!0\\}`, "g");
|
|
11624
|
+
if ([...source.matchAll(closeBody)].length !== 1) throw new Error("Unsupported ZCode session close; native compatibility check failed");
|
|
11625
|
+
return source.replace(closeBody, (match, context, _params, record2, parsed) => match.replace(",{closed:!0}", `,${record2}.persistence!=="deferred"&&await ${context}.deps.sessionStore.updateSession({id:${parsed}.sessionId,timeArchived:Date.now()}),{closed:!0}`));
|
|
11608
11626
|
}
|
|
11609
11627
|
function zcodeServerLaunch(command, args) {
|
|
11610
11628
|
if (basename4(command) !== "zcode.cjs") return { command, args: [...args] };
|
|
@@ -12129,6 +12147,7 @@ var OPERATIONS = [
|
|
|
12129
12147
|
"sessions.abort",
|
|
12130
12148
|
"sessions.delete",
|
|
12131
12149
|
"configuration.models",
|
|
12150
|
+
"configuration.permissionModes",
|
|
12132
12151
|
"permissions.list",
|
|
12133
12152
|
"permissions.reply",
|
|
12134
12153
|
"questions.list",
|
|
@@ -12403,6 +12422,31 @@ var CodexAdapter = class {
|
|
|
12403
12422
|
}
|
|
12404
12423
|
return { decision: accept ? reply === "always" ? "acceptForSession" : "accept" : "decline" };
|
|
12405
12424
|
}
|
|
12425
|
+
async permissionModes(project, rpc) {
|
|
12426
|
+
const result = await rpc.request("config/read", { cwd: await this.workspace.root(project), includeLayers: false }).catch(() => void 0);
|
|
12427
|
+
const autoReview = !!result?.config && Object.hasOwn(result.config, "approvals_reviewer");
|
|
12428
|
+
return {
|
|
12429
|
+
defaultMode: "ask",
|
|
12430
|
+
modes: [
|
|
12431
|
+
{ 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" },
|
|
12432
|
+
...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" }] : [],
|
|
12433
|
+
{ 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" }
|
|
12434
|
+
],
|
|
12435
|
+
autoReview
|
|
12436
|
+
};
|
|
12437
|
+
}
|
|
12438
|
+
async permissionOverrides(project, rpc, mode) {
|
|
12439
|
+
if (mode === void 0) return {};
|
|
12440
|
+
const options = await this.permissionModes(project, rpc);
|
|
12441
|
+
if (!options.modes.some((entry) => entry.id === mode)) throw new Error("Unsupported permission mode");
|
|
12442
|
+
const reviewer = options.autoReview ? { approvalsReviewer: mode === "auto" ? "guardian_subagent" : "user" } : {};
|
|
12443
|
+
if (mode === "full") return { approvalPolicy: "never", sandboxPolicy: { type: "dangerFullAccess" }, ...reviewer };
|
|
12444
|
+
return {
|
|
12445
|
+
approvalPolicy: "on-request",
|
|
12446
|
+
sandboxPolicy: { type: "workspaceWrite", writableRoots: [await this.workspace.root(project)], networkAccess: false },
|
|
12447
|
+
...reviewer
|
|
12448
|
+
};
|
|
12449
|
+
}
|
|
12406
12450
|
async session(project, id) {
|
|
12407
12451
|
if (!describeTool("codex").sessionPattern.test(id)) throw new Error("Invalid session id");
|
|
12408
12452
|
const rpc = await this.rpc(project);
|
|
@@ -12431,6 +12475,10 @@ var CodexAdapter = class {
|
|
|
12431
12475
|
if (op === "auth.poll") return this.auth.poll(project, p);
|
|
12432
12476
|
if (op === "auth.cancel") return this.auth.cancel(project, p.flowId);
|
|
12433
12477
|
const rpc = await this.rpc(project);
|
|
12478
|
+
if (op === "configuration.permissionModes") {
|
|
12479
|
+
const { autoReview: _, ...options } = await this.permissionModes(project, rpc);
|
|
12480
|
+
return options;
|
|
12481
|
+
}
|
|
12434
12482
|
if (op === "auth.status") {
|
|
12435
12483
|
const result = await rpc.request("account/read", { refreshToken: false });
|
|
12436
12484
|
return {
|
|
@@ -12530,6 +12578,7 @@ var CodexAdapter = class {
|
|
|
12530
12578
|
if (op === "sessions.prompt") {
|
|
12531
12579
|
if (!id) throw new Error("Session required");
|
|
12532
12580
|
await this.session(project, id);
|
|
12581
|
+
const permissions = await this.permissionOverrides(project, rpc, p.permissionMode);
|
|
12533
12582
|
await this.resume(project, id, rpc);
|
|
12534
12583
|
const input = [];
|
|
12535
12584
|
const lines = [];
|
|
@@ -12544,7 +12593,7 @@ var CodexAdapter = class {
|
|
|
12544
12593
|
const selectedModel = p.model;
|
|
12545
12594
|
const model = selectedModel?.providerID === "codex" && typeof selectedModel.modelID === "string" ? selectedModel.modelID : void 0;
|
|
12546
12595
|
const effort = selectedReasoningLevel(p, "effort");
|
|
12547
|
-
const result = await rpc.request("turn/start", { threadId: id, input, ...model ? { model } : {}, ...effort ? { effort } : {} });
|
|
12596
|
+
const result = await rpc.request("turn/start", { threadId: id, input, ...model ? { model } : {}, ...effort ? { effort } : {}, ...permissions });
|
|
12548
12597
|
this.activeTurns.set(id, result.turn.id);
|
|
12549
12598
|
return result;
|
|
12550
12599
|
}
|
|
@@ -12619,6 +12668,7 @@ var OPERATIONS2 = [
|
|
|
12619
12668
|
...DAEMON_OPERATIONS,
|
|
12620
12669
|
...AUTH_OPERATIONS,
|
|
12621
12670
|
"configuration.models",
|
|
12671
|
+
"configuration.permissionModes",
|
|
12622
12672
|
"sessions.list",
|
|
12623
12673
|
"sessions.create",
|
|
12624
12674
|
"sessions.get",
|
|
@@ -12811,6 +12861,12 @@ var ZcodeAdapter = class {
|
|
|
12811
12861
|
if (op === "auth.poll") return this.auth.poll(project, p);
|
|
12812
12862
|
if (op === "auth.cancel") return this.auth.cancel(project, p.flowId);
|
|
12813
12863
|
const rpc = await this.rpc(project);
|
|
12864
|
+
if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
|
|
12865
|
+
{ id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u4F7F\u7528 ZCode build \u6A21\u5F0F\uFF0C\u4FEE\u6539\u6587\u4EF6\u524D\u8BF7\u6C42\u6279\u51C6" },
|
|
12866
|
+
{ 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" },
|
|
12867
|
+
{ id: "plan", label: "\u53EA\u8BFB\u89C4\u5212", description: "\u4F7F\u7528 ZCode plan \u6A21\u5F0F\uFF0C\u68C0\u67E5\u9879\u76EE\u5E76\u5236\u5B9A\u8BA1\u5212" },
|
|
12868
|
+
{ id: "full", label: "\u5B8C\u5168\u8BBF\u95EE", description: "\u4F7F\u7528 ZCode yolo \u6A21\u5F0F\uFF0C\u51CF\u5C11\u5DE5\u5177\u64CD\u4F5C\u786E\u8BA4" }
|
|
12869
|
+
] };
|
|
12814
12870
|
if (op === "configuration.models" || op === "auth.status") {
|
|
12815
12871
|
const snapshot = await rpc.request("session/create", { workspace: await this.workspaceRef(project), persistence: "deferred", titleGenerationEnabled: false, toolAllowlist: [] });
|
|
12816
12872
|
try {
|
|
@@ -12886,7 +12942,8 @@ var ZcodeAdapter = class {
|
|
|
12886
12942
|
}
|
|
12887
12943
|
if (op === "sessions.prompt") {
|
|
12888
12944
|
if (!id) throw new Error("Session required");
|
|
12889
|
-
await this.session(project, id);
|
|
12945
|
+
const session = await this.session(project, id);
|
|
12946
|
+
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
12947
|
await this.watch(project, rpc, id);
|
|
12891
12948
|
const lines = [];
|
|
12892
12949
|
if (typeof p.text === "string" && p.text.trim()) lines.push(screenshotPrompt(p.text));
|
|
@@ -12895,6 +12952,12 @@ var ZcodeAdapter = class {
|
|
|
12895
12952
|
lines.push(`Attached file: ${upload.destination} (${upload.name}, ${upload.mime}). Read this file if relevant to the task.`);
|
|
12896
12953
|
}
|
|
12897
12954
|
if (!lines.length) throw new Error("Message or attachments required");
|
|
12955
|
+
if (p.permissionMode !== void 0) {
|
|
12956
|
+
const modes = { ask: "build", edit: "edit", plan: "plan", full: "yolo" };
|
|
12957
|
+
const mode = typeof p.permissionMode === "string" && Object.hasOwn(modes, p.permissionMode) ? modes[p.permissionMode] : void 0;
|
|
12958
|
+
if (!mode) throw new Error("Unsupported permission mode");
|
|
12959
|
+
await rpc.request("session/setMode", { sessionId: id, mode });
|
|
12960
|
+
}
|
|
12898
12961
|
const selected = p.model;
|
|
12899
12962
|
if (selected?.providerID && selected.modelID) {
|
|
12900
12963
|
let level = selectedReasoningLevel(p, "model.options.reasoningLevel");
|
|
@@ -12986,6 +13049,22 @@ var ZcodeAdapter = class {
|
|
|
12986
13049
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
12987
13050
|
import { readFile as readFile8, readdir as readdir3, rm as rm2 } from "node:fs/promises";
|
|
12988
13051
|
import { dirname as dirname5 } from "node:path";
|
|
13052
|
+
|
|
13053
|
+
// src/tools/pi-permissions.ts
|
|
13054
|
+
var PI_PERMISSION_TITLE = "Octopus \u5DE5\u5177\u6388\u6743";
|
|
13055
|
+
var PI_PERMISSION_EXTENSION = `
|
|
13056
|
+
export default function (pi) {
|
|
13057
|
+
const ready = process.env.OCTOPUS_PI_PERMISSION_READY;
|
|
13058
|
+
pi.on('session_start', (_event, ctx) => ctx.ui.notify(ready, 'info'));
|
|
13059
|
+
pi.on('tool_call', async (event, ctx) => {
|
|
13060
|
+
if (!ctx.hasUI) return { block: true, reason: '\u5DE5\u5177\u64CD\u4F5C\u9700\u8981\u7528\u6237\u6279\u51C6' };
|
|
13061
|
+
const allowed = await ctx.ui.confirm(${JSON.stringify(PI_PERMISSION_TITLE)}, JSON.stringify({ toolName: event.toolName, input: event.input }));
|
|
13062
|
+
if (allowed !== true) return { block: true, reason: '\u7528\u6237\u62D2\u7EDD\u4E86\u5DE5\u5177\u64CD\u4F5C' };
|
|
13063
|
+
});
|
|
13064
|
+
}
|
|
13065
|
+
`;
|
|
13066
|
+
|
|
13067
|
+
// src/tools/pi.ts
|
|
12989
13068
|
var OPERATIONS3 = [
|
|
12990
13069
|
...DAEMON_OPERATIONS,
|
|
12991
13070
|
"sessions.list",
|
|
@@ -12998,7 +13077,9 @@ var OPERATIONS3 = [
|
|
|
12998
13077
|
"sessions.delete",
|
|
12999
13078
|
"auth.status",
|
|
13000
13079
|
"configuration.models",
|
|
13080
|
+
"configuration.permissionModes",
|
|
13001
13081
|
"permissions.list",
|
|
13082
|
+
"permissions.reply",
|
|
13002
13083
|
"questions.list",
|
|
13003
13084
|
"questions.reply",
|
|
13004
13085
|
"questions.reject"
|
|
@@ -13018,6 +13099,7 @@ var PiAdapter = class {
|
|
|
13018
13099
|
questions = /* @__PURE__ */ new Map();
|
|
13019
13100
|
mutations = /* @__PURE__ */ new Map();
|
|
13020
13101
|
deleting = /* @__PURE__ */ new Set();
|
|
13102
|
+
reconfiguring = /* @__PURE__ */ new Set();
|
|
13021
13103
|
supportedOperations() {
|
|
13022
13104
|
return OPERATIONS3;
|
|
13023
13105
|
}
|
|
@@ -13072,7 +13154,7 @@ var PiAdapter = class {
|
|
|
13072
13154
|
this.partials.delete(this.key(project, id));
|
|
13073
13155
|
this.active.delete(this.key(project, id));
|
|
13074
13156
|
this.clearQuestions(project, id);
|
|
13075
|
-
this.emit(project, "session.error", { sessionID: id, message: "Pi connection closed; reconnect to resume the saved session" });
|
|
13157
|
+
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
13158
|
}
|
|
13077
13159
|
});
|
|
13078
13160
|
}
|
|
@@ -13090,16 +13172,20 @@ var PiAdapter = class {
|
|
|
13090
13172
|
question.timer.unref();
|
|
13091
13173
|
}
|
|
13092
13174
|
this.questions.set(id, question);
|
|
13093
|
-
this.emit(project, "question.request", { sessionID: sessionId, requestId: id });
|
|
13175
|
+
this.emit(project, this.isPermission(question) ? "permission.request" : "question.request", { sessionID: sessionId, requestId: id });
|
|
13094
13176
|
}).catch(() => {
|
|
13095
13177
|
});
|
|
13096
13178
|
}
|
|
13097
13179
|
clearQuestions(project, id) {
|
|
13098
13180
|
for (const [key, question] of this.questions) if (question.project === project && question.sessionId === id) {
|
|
13099
13181
|
clearTimeout(question.timer);
|
|
13182
|
+
if (!question.rpc.exited) question.rpc.notify("extension_ui_response", { id: question.detail.id, cancelled: true });
|
|
13100
13183
|
this.questions.delete(key);
|
|
13101
13184
|
}
|
|
13102
13185
|
}
|
|
13186
|
+
isPermission(question) {
|
|
13187
|
+
return question.detail.method === "confirm" && question.detail.title === PI_PERMISSION_TITLE;
|
|
13188
|
+
}
|
|
13103
13189
|
close() {
|
|
13104
13190
|
for (const question of this.questions.values()) {
|
|
13105
13191
|
clearTimeout(question.timer);
|
|
@@ -13157,6 +13243,10 @@ var PiAdapter = class {
|
|
|
13157
13243
|
async execute(command, project) {
|
|
13158
13244
|
const op = command.operation, p = command.payload, id = command.sessionId;
|
|
13159
13245
|
if (!OPERATIONS3.includes(op)) unsupportedOperation("pi", op);
|
|
13246
|
+
if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
|
|
13247
|
+
{ 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" },
|
|
13248
|
+
{ 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" }
|
|
13249
|
+
] };
|
|
13160
13250
|
if (op === "auth.status") {
|
|
13161
13251
|
const rpc2 = await this.runtime.connect(project);
|
|
13162
13252
|
const result = await rpc2.request("get_available_models");
|
|
@@ -13198,11 +13288,26 @@ var PiAdapter = class {
|
|
|
13198
13288
|
}
|
|
13199
13289
|
if (op === "permissions.list") {
|
|
13200
13290
|
if (id) await this.session(project, id);
|
|
13201
|
-
return []
|
|
13291
|
+
return [...this.questions.values()].filter((q) => q.project === project && (!id || q.sessionId === id) && this.isPermission(q)).map((q) => ({
|
|
13292
|
+
id: q.id,
|
|
13293
|
+
sessionID: q.sessionId,
|
|
13294
|
+
permission: "Pi \u5DE5\u5177\u64CD\u4F5C",
|
|
13295
|
+
patterns: [q.detail.message ?? ""],
|
|
13296
|
+
replies: ["once", "reject"]
|
|
13297
|
+
}));
|
|
13298
|
+
}
|
|
13299
|
+
if (op === "permissions.reply") {
|
|
13300
|
+
const question = this.questions.get(String(p.requestId));
|
|
13301
|
+
if (!question || !this.isPermission(question) || question.project !== project || id && id !== question.sessionId) throw new Error("Request is no longer pending in this session");
|
|
13302
|
+
if (p.reply !== "once" && p.reply !== "reject") throw new Error("Invalid permission reply");
|
|
13303
|
+
question.rpc.notify("extension_ui_response", { id: question.detail.id, confirmed: p.reply === "once" });
|
|
13304
|
+
clearTimeout(question.timer);
|
|
13305
|
+
this.questions.delete(question.id);
|
|
13306
|
+
return { ok: true };
|
|
13202
13307
|
}
|
|
13203
13308
|
if (op === "questions.list") {
|
|
13204
13309
|
if (id) await this.session(project, id);
|
|
13205
|
-
return [...this.questions.values()].filter((q) => q.project === project && (!id || q.sessionId === id)).map((q) => ({
|
|
13310
|
+
return [...this.questions.values()].filter((q) => q.project === project && (!id || q.sessionId === id) && !this.isPermission(q)).map((q) => ({
|
|
13206
13311
|
id: q.id,
|
|
13207
13312
|
sessionID: q.sessionId,
|
|
13208
13313
|
questions: [{
|
|
@@ -13216,7 +13321,7 @@ var PiAdapter = class {
|
|
|
13216
13321
|
}
|
|
13217
13322
|
if (op === "questions.reply" || op === "questions.reject") {
|
|
13218
13323
|
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");
|
|
13324
|
+
if (!question || this.isPermission(question) || question.project !== project || id && id !== question.sessionId) throw new Error("Request is no longer pending in this session");
|
|
13220
13325
|
const answers = Array.isArray(p.answers) ? p.answers : [];
|
|
13221
13326
|
const first = Array.isArray(answers[0]) ? answers[0][0] : answers[0];
|
|
13222
13327
|
const value = typeof first === "string" ? first : "";
|
|
@@ -13234,13 +13339,14 @@ var PiAdapter = class {
|
|
|
13234
13339
|
await this.runtime.closeSession(project, id);
|
|
13235
13340
|
await rm2(this.runtime.sessionFile(project, id), { force: true });
|
|
13236
13341
|
await rm2(this.metadataPath(project, id));
|
|
13342
|
+
await rm2(this.runtime.sessionFile(project, id).replace(/\.jsonl$/, ".permission.ts"), { force: true });
|
|
13237
13343
|
this.clearQuestions(project, id);
|
|
13238
13344
|
} finally {
|
|
13239
13345
|
this.deleting.delete(this.key(project, id));
|
|
13240
13346
|
}
|
|
13241
13347
|
return { ok: true };
|
|
13242
13348
|
}
|
|
13243
|
-
|
|
13349
|
+
let rpc = await this.rpc(project, id);
|
|
13244
13350
|
if (op === "sessions.status") {
|
|
13245
13351
|
const state = await rpc.request("get_state");
|
|
13246
13352
|
return { [id]: { type: this.active.has(this.key(project, id)) || state.isStreaming || state.isCompacting || (state.pendingMessageCount ?? 0) > 0 ? "busy" : "idle" } };
|
|
@@ -13256,6 +13362,7 @@ var PiAdapter = class {
|
|
|
13256
13362
|
return this.media.messages(project, id, this.messages(messages, this.key(project, id)));
|
|
13257
13363
|
}
|
|
13258
13364
|
if (op === "sessions.abort") {
|
|
13365
|
+
this.clearQuestions(project, id);
|
|
13259
13366
|
await rpc.request("clear_queue");
|
|
13260
13367
|
await rpc.request("abort");
|
|
13261
13368
|
return { ok: true };
|
|
@@ -13270,6 +13377,22 @@ var PiAdapter = class {
|
|
|
13270
13377
|
} else lines.push(`Attached file: ${upload.destination} (${upload.name}, ${upload.mime}). Read this file if relevant to the task.`);
|
|
13271
13378
|
}
|
|
13272
13379
|
if (!lines.length && !images.length) throw new Error("Message or attachments required");
|
|
13380
|
+
if (p.permissionMode !== void 0) {
|
|
13381
|
+
if (p.permissionMode !== "ask" && p.permissionMode !== "full") throw new Error("Unsupported permission mode");
|
|
13382
|
+
if (p.permissionMode !== (session.permissionMode ?? "full")) {
|
|
13383
|
+
const state = await rpc.request("get_state");
|
|
13384
|
+
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");
|
|
13385
|
+
session.permissionMode = p.permissionMode;
|
|
13386
|
+
await atomicJson(this.metadataPath(project, id), session);
|
|
13387
|
+
this.reconfiguring.add(this.key(project, id));
|
|
13388
|
+
try {
|
|
13389
|
+
await this.runtime.closeSession(project, id);
|
|
13390
|
+
rpc = await this.rpc(project, id);
|
|
13391
|
+
} finally {
|
|
13392
|
+
this.reconfiguring.delete(this.key(project, id));
|
|
13393
|
+
}
|
|
13394
|
+
}
|
|
13395
|
+
}
|
|
13273
13396
|
const model = p.model;
|
|
13274
13397
|
const level = selectedReasoningLevel(p, "level");
|
|
13275
13398
|
if (model && typeof model.providerID === "string" && typeof model.modelID === "string") await rpc.request("set_model", { provider: model.providerID, modelId: model.modelID });
|
|
@@ -13286,7 +13409,8 @@ var PiAdapter = class {
|
|
|
13286
13409
|
|
|
13287
13410
|
// src/tools/pi-runtime.ts
|
|
13288
13411
|
import { execFile as execFile5, spawn as spawn5 } from "node:child_process";
|
|
13289
|
-
import { mkdir as mkdir8, open as open8 } from "node:fs/promises";
|
|
13412
|
+
import { mkdir as mkdir8, open as open8, writeFile as writeFile6 } from "node:fs/promises";
|
|
13413
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
13290
13414
|
import { join as join13 } from "node:path";
|
|
13291
13415
|
import { promisify as promisify5 } from "node:util";
|
|
13292
13416
|
var run3 = promisify5(execFile5);
|
|
@@ -13405,32 +13529,58 @@ ${bytes.toString("utf8")}`);
|
|
|
13405
13529
|
await this.kill(key);
|
|
13406
13530
|
const directory = this.project(projectId).directory;
|
|
13407
13531
|
await mkdir8(join13(this.config.stateDirectory, "pi-sessions", projectId), { recursive: true, mode: 448 });
|
|
13532
|
+
const sessionFile = sessionId ? this.sessionFile(projectId, sessionId) : void 0;
|
|
13533
|
+
const policy = sessionFile ? await readJson(sessionFile.replace(/\.jsonl$/, ".json")) : void 0;
|
|
13534
|
+
const gated = policy?.permissionMode === "ask";
|
|
13535
|
+
const extension = sessionFile?.replace(/\.jsonl$/, ".permission.ts");
|
|
13536
|
+
if (gated) await writeFile6(extension, PI_PERMISSION_EXTENSION, { mode: 384 });
|
|
13537
|
+
const readyToken = gated ? `octopus-permissions-ready:${randomUUID16()}` : "";
|
|
13408
13538
|
const logPath = join13(this.config.stateDirectory, `pi-${key}.log`);
|
|
13409
13539
|
const log = await open8(logPath, "a", 384);
|
|
13410
13540
|
if (generation !== this.generation) {
|
|
13411
13541
|
await log.close();
|
|
13412
13542
|
throw new Error("Pi runtime stopped during startup");
|
|
13413
13543
|
}
|
|
13414
|
-
const args = [...describeTool("pi").serverArgs, ...
|
|
13415
|
-
const child = spawn5(this.config.tool.command, args, { cwd: directory, env: { ...process.env }, stdio: ["pipe", "pipe", log.fd] });
|
|
13544
|
+
const args = [...describeTool("pi").serverArgs, ...sessionFile ? ["--session", sessionFile] : ["--no-session"], ...gated ? ["--extension", extension] : []];
|
|
13545
|
+
const child = spawn5(this.config.tool.command, args, { cwd: directory, env: { ...process.env, OCTOPUS_PI_PERMISSION_READY: readyToken }, stdio: ["pipe", "pipe", log.fd] });
|
|
13416
13546
|
child.once("close", () => {
|
|
13417
13547
|
void log.close();
|
|
13418
13548
|
});
|
|
13419
13549
|
this.logPaths.add(logPath);
|
|
13550
|
+
let resolveReady = () => {
|
|
13551
|
+
}, rejectReady = (_error) => {
|
|
13552
|
+
};
|
|
13553
|
+
let readyTimeout;
|
|
13554
|
+
const ready = gated ? new Promise((resolve5, reject) => {
|
|
13555
|
+
resolveReady = resolve5;
|
|
13556
|
+
rejectReady = reject;
|
|
13557
|
+
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);
|
|
13558
|
+
}) : Promise.resolve();
|
|
13420
13559
|
const rpc = new LineRpc(child, {
|
|
13421
13560
|
...handlers,
|
|
13561
|
+
onNotification: (type, data) => {
|
|
13562
|
+
const detail = data;
|
|
13563
|
+
if (gated && type === "extension_ui_request" && detail.method === "notify" && detail.message === readyToken) {
|
|
13564
|
+
resolveReady();
|
|
13565
|
+
return;
|
|
13566
|
+
}
|
|
13567
|
+
handlers.onNotification?.(type, data);
|
|
13568
|
+
},
|
|
13422
13569
|
onClose: (reason) => {
|
|
13570
|
+
rejectReady(new Error("Pi \u5DE5\u5177\u6388\u6743\u8FDE\u63A5\u5DF2\u5173\u95ED"));
|
|
13423
13571
|
handlers.onClose?.(reason);
|
|
13424
13572
|
}
|
|
13425
13573
|
}, "pi");
|
|
13426
13574
|
this.processes.set(key, { child, rpc, projectId, sessionId });
|
|
13427
13575
|
try {
|
|
13428
|
-
await rpc.request("get_state");
|
|
13576
|
+
await Promise.all([rpc.request("get_state"), ready]);
|
|
13429
13577
|
if (generation !== this.generation) throw new Error("Pi runtime stopped during startup");
|
|
13430
13578
|
return rpc;
|
|
13431
13579
|
} catch (error) {
|
|
13432
13580
|
await this.kill(key);
|
|
13433
13581
|
throw error;
|
|
13582
|
+
} finally {
|
|
13583
|
+
clearTimeout(readyTimeout);
|
|
13434
13584
|
}
|
|
13435
13585
|
}
|
|
13436
13586
|
async kill(key) {
|
|
@@ -13469,6 +13619,7 @@ function createAdapter(runtime, workspace, uploads, emit) {
|
|
|
13469
13619
|
// src/project-registry.ts
|
|
13470
13620
|
import { realpath as realpath6, readFile as readFile9, stat as stat7 } from "node:fs/promises";
|
|
13471
13621
|
import { isAbsolute as isAbsolute4 } from "node:path";
|
|
13622
|
+
import { isDeepStrictEqual } from "node:util";
|
|
13472
13623
|
var ProjectMutationSchema = ProjectSchema.extend({
|
|
13473
13624
|
name: ProjectSchema.shape.name.trim().max(120),
|
|
13474
13625
|
specDirectories: ProjectSchema.shape.specDirectories.removeDefault().element.min(1).array().max(20).default(["specs", "docs"])
|
|
@@ -13484,13 +13635,21 @@ function rawProjectId(value) {
|
|
|
13484
13635
|
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.id === "string" ? value.id : void 0;
|
|
13485
13636
|
}
|
|
13486
13637
|
var ProjectRegistry = class {
|
|
13487
|
-
constructor(configPath, config, runtime, adapter) {
|
|
13638
|
+
constructor(configPath, config, runtime, adapter, onChanged = () => {
|
|
13639
|
+
}) {
|
|
13488
13640
|
this.configPath = configPath;
|
|
13489
13641
|
this.config = config;
|
|
13490
13642
|
this.runtime = runtime;
|
|
13491
13643
|
this.adapter = adapter;
|
|
13644
|
+
this.onChanged = onChanged;
|
|
13492
13645
|
}
|
|
13493
13646
|
pending = Promise.resolve();
|
|
13647
|
+
// The daemon runs this under the same lock as tool selection and project mutations.
|
|
13648
|
+
reload() {
|
|
13649
|
+
return this.serial(async () => {
|
|
13650
|
+
await this.readCurrent();
|
|
13651
|
+
});
|
|
13652
|
+
}
|
|
13494
13653
|
add(payload) {
|
|
13495
13654
|
return this.serial(() => this.addUnlocked(payload));
|
|
13496
13655
|
}
|
|
@@ -13514,17 +13673,36 @@ var ProjectRegistry = class {
|
|
|
13514
13673
|
}
|
|
13515
13674
|
async readCurrent() {
|
|
13516
13675
|
const current = rawConfig(JSON.parse(await readFile9(this.configPath, "utf8")));
|
|
13517
|
-
const diskProjects = current.projects.map((
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
if (JSON.stringify(diskProjects) !== JSON.stringify(this.config.projects)) {
|
|
13522
|
-
throw new Error("Proxy \u914D\u7F6E\u5DF2\u5728\u4E3B\u673A\u4E0A\u88AB\u4FEE\u6539\uFF1B\u8BF7\u91CD\u542F Proxy \u540E\u518D\u7BA1\u7406\u9879\u76EE");
|
|
13523
|
-
}
|
|
13676
|
+
const diskProjects = ConfigSchema.shape.projects.parse(current.projects).map((project) => ({ ...project, directory: expandHome(project.directory) }));
|
|
13677
|
+
if (new Set(diskProjects.map((project) => project.id)).size !== diskProjects.length) throw new Error("\u9879\u76EE ID \u91CD\u590D");
|
|
13678
|
+
if (diskProjects.some((project) => !isAbsolute4(project.directory))) throw new Error("\u9879\u76EE\u76EE\u5F55\u5FC5\u987B\u662F Proxy \u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u8DEF\u5F84");
|
|
13679
|
+
await this.applyProjects(diskProjects);
|
|
13524
13680
|
return current;
|
|
13525
13681
|
}
|
|
13682
|
+
async persist(expected, projects) {
|
|
13683
|
+
const latest = rawConfig(JSON.parse(await readFile9(this.configPath, "utf8")));
|
|
13684
|
+
if (!isDeepStrictEqual(latest.projects, expected.projects)) {
|
|
13685
|
+
throw new Error("\u4E3B\u673A\u4E0A\u7684\u9879\u76EE\u914D\u7F6E\u5728\u4FDD\u5B58\u671F\u95F4\u53D1\u751F\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5");
|
|
13686
|
+
}
|
|
13687
|
+
await atomicJson(this.configPath, { ...latest, projects });
|
|
13688
|
+
}
|
|
13689
|
+
async applyProjects(projects) {
|
|
13690
|
+
if (isDeepStrictEqual(projects, this.config.projects)) return;
|
|
13691
|
+
const reset = this.config.projects.filter((previous) => !projects.some((project) => project.id === previous.id && project.directory === previous.directory));
|
|
13692
|
+
const added = projects.filter((project) => !this.config.projects.some((previous) => previous.id === project.id && previous.directory === project.directory));
|
|
13693
|
+
for (const project of reset) {
|
|
13694
|
+
await Promise.all([
|
|
13695
|
+
this.bestEffort(() => this.adapter.removeProject?.(project.id)),
|
|
13696
|
+
this.bestEffort(() => this.runtime.removeProject?.(project.id))
|
|
13697
|
+
]);
|
|
13698
|
+
}
|
|
13699
|
+
this.config.projects.splice(0, this.config.projects.length, ...projects);
|
|
13700
|
+
for (const project of added) await this.bestEffort(() => this.runtime.addProject?.(project.id));
|
|
13701
|
+
this.onChanged(reset.map((project) => project.id));
|
|
13702
|
+
}
|
|
13526
13703
|
async addUnlocked(payload) {
|
|
13527
13704
|
const parsed = ProjectMutationSchema.parse(payload);
|
|
13705
|
+
const raw = await this.readCurrent();
|
|
13528
13706
|
const directory = expandHome(parsed.directory);
|
|
13529
13707
|
if (!isAbsolute4(directory)) throw new Error("\u9879\u76EE\u76EE\u5F55\u5FC5\u987B\u662F Proxy \u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u8DEF\u5F84");
|
|
13530
13708
|
const info = await stat7(directory).catch(() => void 0);
|
|
@@ -13535,14 +13713,13 @@ var ProjectRegistry = class {
|
|
|
13535
13713
|
if (await realpath6(project2.directory).catch(() => project2.directory) === canonical) throw new Error("\u8BE5\u76EE\u5F55\u5DF2\u7ECF\u662F\u6388\u6743\u9879\u76EE");
|
|
13536
13714
|
}
|
|
13537
13715
|
const project = { ...parsed, directory: canonical };
|
|
13538
|
-
|
|
13539
|
-
await
|
|
13540
|
-
this.config.projects.push(project);
|
|
13541
|
-
await this.bestEffort(() => this.runtime.addProject?.(project.id));
|
|
13716
|
+
await this.persist(raw, [...raw.projects, project]);
|
|
13717
|
+
await this.applyProjects([...this.config.projects, project]);
|
|
13542
13718
|
return publicProjects(this.config);
|
|
13543
13719
|
}
|
|
13544
13720
|
async updateUnlocked(payload) {
|
|
13545
13721
|
const parsed = ProjectUpdateSchema.parse(payload);
|
|
13722
|
+
const raw = await this.readCurrent();
|
|
13546
13723
|
const index = this.config.projects.findIndex((project) => project.id === parsed.id);
|
|
13547
13724
|
if (index < 0) throw new Error(`\u672A\u77E5\u6388\u6743\u9879\u76EE\uFF1A${parsed.id}`);
|
|
13548
13725
|
const directory = expandHome(parsed.directory);
|
|
@@ -13556,21 +13733,8 @@ var ProjectRegistry = class {
|
|
|
13556
13733
|
}
|
|
13557
13734
|
}
|
|
13558
13735
|
const current = this.config.projects[index];
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
const raw = await this.readCurrent();
|
|
13562
|
-
await atomicJson(this.configPath, {
|
|
13563
|
-
...raw,
|
|
13564
|
-
projects: raw.projects.map((project) => rawProjectId(project) === parsed.id ? { ...project, name: parsed.name, directory: canonical } : project)
|
|
13565
|
-
});
|
|
13566
|
-
if (directoryChanged) {
|
|
13567
|
-
await Promise.all([
|
|
13568
|
-
this.bestEffort(() => this.adapter.removeProject?.(parsed.id)),
|
|
13569
|
-
this.bestEffort(() => this.runtime.removeProject?.(parsed.id))
|
|
13570
|
-
]);
|
|
13571
|
-
}
|
|
13572
|
-
this.config.projects[index] = { ...current, name: parsed.name, directory: canonical };
|
|
13573
|
-
if (directoryChanged) await this.bestEffort(() => this.runtime.addProject?.(parsed.id));
|
|
13736
|
+
await this.persist(raw, raw.projects.map((project) => rawProjectId(project) === parsed.id ? { ...project, name: parsed.name, directory: canonical } : project));
|
|
13737
|
+
await this.applyProjects(this.config.projects.map((project) => project.id === parsed.id ? { ...current, name: parsed.name, directory: canonical } : project));
|
|
13574
13738
|
return publicProjects(this.config);
|
|
13575
13739
|
}
|
|
13576
13740
|
async removeUnlocked(payload) {
|
|
@@ -13579,22 +13743,18 @@ var ProjectRegistry = class {
|
|
|
13579
13743
|
throw new Error("\u5220\u9664\u9879\u76EE\u9700\u8981\u5408\u6CD5\u7684\u9879\u76EE ID");
|
|
13580
13744
|
}
|
|
13581
13745
|
const id = payload.id;
|
|
13746
|
+
const raw = await this.readCurrent();
|
|
13582
13747
|
if (this.config.projects.length === 1) throw new Error("\u81F3\u5C11\u9700\u8981\u4FDD\u7559\u4E00\u4E2A\u6388\u6743\u9879\u76EE");
|
|
13583
13748
|
const index = this.config.projects.findIndex((project) => project.id === id);
|
|
13584
13749
|
if (index < 0) throw new Error(`\u672A\u77E5\u6388\u6743\u9879\u76EE\uFF1A${id}`);
|
|
13585
|
-
|
|
13586
|
-
await
|
|
13587
|
-
await Promise.all([
|
|
13588
|
-
this.bestEffort(() => this.adapter.removeProject?.(id)),
|
|
13589
|
-
this.bestEffort(() => this.runtime.removeProject?.(id))
|
|
13590
|
-
]);
|
|
13591
|
-
this.config.projects.splice(index, 1);
|
|
13750
|
+
await this.persist(raw, raw.projects.filter((project) => rawProjectId(project) !== id));
|
|
13751
|
+
await this.applyProjects(this.config.projects.filter((project) => project.id !== id));
|
|
13592
13752
|
return publicProjects(this.config);
|
|
13593
13753
|
}
|
|
13594
13754
|
};
|
|
13595
13755
|
|
|
13596
13756
|
// src/terminal.ts
|
|
13597
|
-
import { randomUUID as
|
|
13757
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
13598
13758
|
import { access as access2, stat as stat9 } from "node:fs/promises";
|
|
13599
13759
|
import { constants as constants5 } from "node:fs";
|
|
13600
13760
|
import { platform as platform3, userInfo as userInfo3 } from "node:os";
|
|
@@ -13680,7 +13840,7 @@ var TerminalManager = class {
|
|
|
13680
13840
|
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
13841
|
}
|
|
13682
13842
|
const { helper } = await ensureTerminalSupport();
|
|
13683
|
-
const id =
|
|
13843
|
+
const id = randomUUID17();
|
|
13684
13844
|
const path = [
|
|
13685
13845
|
process.env.PATH,
|
|
13686
13846
|
"/usr/bin",
|
|
@@ -13745,6 +13905,13 @@ var TerminalManager = class {
|
|
|
13745
13905
|
terminal.process.kill();
|
|
13746
13906
|
}
|
|
13747
13907
|
}
|
|
13908
|
+
closeProject(project) {
|
|
13909
|
+
for (const terminal of this.terminals.values()) if (terminal.project === project) {
|
|
13910
|
+
this.terminals.delete(terminal.id);
|
|
13911
|
+
terminal.process.kill();
|
|
13912
|
+
this.emit(terminal.owner, project, "terminal.closed", { id: terminal.id });
|
|
13913
|
+
}
|
|
13914
|
+
}
|
|
13748
13915
|
close() {
|
|
13749
13916
|
for (const owner of new Set([...this.terminals.values()].map((t) => t.owner))) this.closeOwned(owner);
|
|
13750
13917
|
}
|
|
@@ -13779,7 +13946,7 @@ var PlatformDesktopSession = class {
|
|
|
13779
13946
|
|
|
13780
13947
|
// src/remote-manager.ts
|
|
13781
13948
|
import { fork } from "node:child_process";
|
|
13782
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
13949
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID18 } from "node:crypto";
|
|
13783
13950
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13784
13951
|
|
|
13785
13952
|
// src/remote-stun.ts
|
|
@@ -13913,7 +14080,7 @@ var RemoteManager = class {
|
|
|
13913
14080
|
if (this.current) throw new Error("\u4E3B\u673A\u6B63\u5728\u88AB\u63A7\u5236");
|
|
13914
14081
|
const quality = RemoteQualitySchema.parse(payload.quality ?? "hd");
|
|
13915
14082
|
const budget = remoteSessionBudget(this.config, quality, payload.quality === void 0);
|
|
13916
|
-
const session = { remoteId:
|
|
14083
|
+
const session = { remoteId: randomUUID18(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
|
|
13917
14084
|
this.current = session;
|
|
13918
14085
|
try {
|
|
13919
14086
|
const [permissions, stunUrls] = await Promise.all([
|
|
@@ -13992,10 +14159,10 @@ var RemoteManager = class {
|
|
|
13992
14159
|
};
|
|
13993
14160
|
|
|
13994
14161
|
// src/tool-selection.ts
|
|
13995
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
14162
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID19 } from "node:crypto";
|
|
13996
14163
|
import { access as access3, constants as constants6, readFile as readFile10 } from "node:fs/promises";
|
|
13997
14164
|
import { platform as platform5 } from "node:os";
|
|
13998
|
-
import { isDeepStrictEqual } from "node:util";
|
|
14165
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
13999
14166
|
|
|
14000
14167
|
// src/node-resolution.ts
|
|
14001
14168
|
import { realpath as realpath7, stat as stat10 } from "node:fs/promises";
|
|
@@ -14136,7 +14303,7 @@ var ToolSelection = class {
|
|
|
14136
14303
|
});
|
|
14137
14304
|
}
|
|
14138
14305
|
modelCommand(projectId) {
|
|
14139
|
-
return { type: "command", id:
|
|
14306
|
+
return { type: "command", id: randomUUID19(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
|
|
14140
14307
|
}
|
|
14141
14308
|
profiles(raw) {
|
|
14142
14309
|
return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
|
|
@@ -14175,7 +14342,7 @@ var ToolSelection = class {
|
|
|
14175
14342
|
}
|
|
14176
14343
|
async readCurrent() {
|
|
14177
14344
|
const disk = await loadConfig(this.configPath);
|
|
14178
|
-
if (!
|
|
14345
|
+
if (!isDeepStrictEqual2(JSON.parse(JSON.stringify(disk.tool)), JSON.parse(JSON.stringify(this.config.tool))) || !isDeepStrictEqual2(disk.projects, this.config.projects) || disk.botId !== this.config.botId || disk.secretKey !== this.config.secretKey) {
|
|
14179
14346
|
throw new Error("Proxy \u914D\u7F6E\u5DF2\u5728\u4E3B\u673A\u4E0A\u88AB\u4FEE\u6539\uFF1B\u8BF7\u91CD\u542F Proxy \u540E\u518D\u5207\u6362\u667A\u80FD\u4F53");
|
|
14180
14347
|
}
|
|
14181
14348
|
return JSON.parse(await readFile10(this.configPath, "utf8"));
|
|
@@ -14219,7 +14386,12 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
14219
14386
|
return { runtime, adapter };
|
|
14220
14387
|
});
|
|
14221
14388
|
const artifacts = new Artifacts(workspace, join15(config.stateDirectory, "artifacts"), emit);
|
|
14389
|
+
const projectRegistry = () => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter, (resetProjects) => {
|
|
14390
|
+
for (const id of resetProjects) terminals.closeProject(id);
|
|
14391
|
+
});
|
|
14392
|
+
const reloadProjects = () => tools.exclusive(() => projectRegistry().reload());
|
|
14222
14393
|
async function dispatch(c, secureSessionId) {
|
|
14394
|
+
if (["runtime.snapshot", "workspace.projects", "runtime.models", "runtime.switchTool"].includes(c.operation)) await reloadProjects();
|
|
14223
14395
|
if (c.operation.startsWith("desktop.remote.")) return remote.dispatch(c, secureSessionId);
|
|
14224
14396
|
if (c.operation.startsWith("desktop.")) return desktop.dispatch(c, secureSessionId);
|
|
14225
14397
|
if (c.operation.startsWith("terminal.")) return terminals.dispatch(c, secureSessionId);
|
|
@@ -14239,17 +14411,17 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
14239
14411
|
if (c.operation === "runtime.restart") return runtime.restart();
|
|
14240
14412
|
if (c.operation === "workspace.projects") return publicProjects(config);
|
|
14241
14413
|
if (c.operation === "workspace.projects.add") {
|
|
14242
|
-
const result2 = await tools.exclusive(() =>
|
|
14414
|
+
const result2 = await tools.exclusive(() => projectRegistry().add(p));
|
|
14243
14415
|
emit(String(p.id), "octopus.resync", {});
|
|
14244
14416
|
return result2;
|
|
14245
14417
|
}
|
|
14246
14418
|
if (c.operation === "workspace.projects.update") {
|
|
14247
|
-
const result2 = await tools.exclusive(() =>
|
|
14419
|
+
const result2 = await tools.exclusive(() => projectRegistry().update(p));
|
|
14248
14420
|
emit(String(p.id), "octopus.resync", {});
|
|
14249
14421
|
return result2;
|
|
14250
14422
|
}
|
|
14251
14423
|
if (c.operation === "workspace.projects.remove") {
|
|
14252
|
-
const result2 = await tools.exclusive(() =>
|
|
14424
|
+
const result2 = await tools.exclusive(() => projectRegistry().remove(p));
|
|
14253
14425
|
emit(String(p.id), "octopus.resync", {});
|
|
14254
14426
|
return result2;
|
|
14255
14427
|
}
|
package/dist/cli.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
supportedPlatform,
|
|
36
36
|
systemdUnitPath,
|
|
37
37
|
toolSearchDirs
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-6RJKEA3I.js";
|
|
39
39
|
import {
|
|
40
40
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
41
41
|
PlatformDesktop,
|
|
@@ -48,7 +48,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
48
48
|
import { access as access4 } from "node:fs/promises";
|
|
49
49
|
|
|
50
50
|
// src/doctor.ts
|
|
51
|
-
import { access as access2, chmod, constants, mkdir as mkdir3, stat } from "node:fs/promises";
|
|
51
|
+
import { access as access2, chmod, constants, mkdir as mkdir3, stat as stat2 } from "node:fs/promises";
|
|
52
52
|
import { isAbsolute as isAbsolute3, join } from "node:path";
|
|
53
53
|
import { execFile as execFile3 } from "node:child_process";
|
|
54
54
|
import { promisify as promisify3 } from "node:util";
|
|
@@ -130,8 +130,9 @@ async function launchdStatus() {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// src/systemd.ts
|
|
133
|
-
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
133
|
+
import { mkdir as mkdir2, rm as rm2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
134
134
|
import { dirname as dirname2 } from "node:path";
|
|
135
|
+
import { userInfo as userInfo2 } from "node:os";
|
|
135
136
|
import { execFile as execFile2 } from "node:child_process";
|
|
136
137
|
import { promisify as promisify2 } from "node:util";
|
|
137
138
|
var run2 = promisify2(execFile2);
|
|
@@ -170,8 +171,15 @@ async function writeUnitFile(text) {
|
|
|
170
171
|
return path;
|
|
171
172
|
}
|
|
172
173
|
async function systemctl(args) {
|
|
174
|
+
const env = { ...process.env };
|
|
175
|
+
if (!env.XDG_RUNTIME_DIR && !env.DBUS_SESSION_BUS_ADDRESS) {
|
|
176
|
+
const uid = userInfo2().uid;
|
|
177
|
+
const runtimeDirectory = `/run/user/${uid}`;
|
|
178
|
+
const info = await stat(runtimeDirectory).catch(() => void 0);
|
|
179
|
+
if (info?.isDirectory() && info.uid === uid) env.XDG_RUNTIME_DIR = runtimeDirectory;
|
|
180
|
+
}
|
|
173
181
|
try {
|
|
174
|
-
const { stdout: stdout3 } = await run2("systemctl", ["--user", ...args]);
|
|
182
|
+
const { stdout: stdout3 } = await run2("systemctl", ["--user", ...args], { env });
|
|
175
183
|
return stdout3;
|
|
176
184
|
} catch (error) {
|
|
177
185
|
const stderr = error instanceof Error ? error.stderr ?? error.message : String(error);
|
|
@@ -214,9 +222,9 @@ async function systemdUninstall() {
|
|
|
214
222
|
await systemdReload();
|
|
215
223
|
}
|
|
216
224
|
async function systemdStatus() {
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
return { active:
|
|
225
|
+
const output = await systemctl(["show", SYSTEMD_UNIT, "--property=ActiveState", "--property=UnitFileState"]);
|
|
226
|
+
const properties = Object.fromEntries(output.trim().split("\n").map((line) => line.split("=")));
|
|
227
|
+
return { active: properties.ActiveState === "active", enabled: properties.UnitFileState === "enabled" };
|
|
220
228
|
}
|
|
221
229
|
|
|
222
230
|
// src/service.ts
|
|
@@ -339,7 +347,7 @@ async function runDoctor(opts) {
|
|
|
339
347
|
results.push(await checkTerminalSupport(opts.fix));
|
|
340
348
|
let config;
|
|
341
349
|
try {
|
|
342
|
-
const info = await
|
|
350
|
+
const info = await stat2(opts.configPath).catch((error) => {
|
|
343
351
|
if (error.code === "ENOENT") throw new Error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${opts.configPath}\uFF1B\u5148\u8FD0\u884C octopus-proxy onboard`);
|
|
344
352
|
throw error;
|
|
345
353
|
});
|
|
@@ -457,7 +465,7 @@ async function runDoctor(opts) {
|
|
|
457
465
|
import { createInterface } from "node:readline/promises";
|
|
458
466
|
import { randomBytes } from "node:crypto";
|
|
459
467
|
import { stdin, stdout } from "node:process";
|
|
460
|
-
import { access as access3, constants as constants2, mkdir as mkdir4, stat as
|
|
468
|
+
import { access as access3, constants as constants2, mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
|
|
461
469
|
import { execFile as execFile4 } from "node:child_process";
|
|
462
470
|
import { platform } from "node:os";
|
|
463
471
|
import { isAbsolute as isAbsolute4, join as join2 } from "node:path";
|
|
@@ -501,7 +509,7 @@ async function validateToolCommand(value, kind) {
|
|
|
501
509
|
const command2 = expandHome(value);
|
|
502
510
|
if (!isAbsolute4(command2)) return "\u670D\u52A1\u73AF\u5883\u9700\u8981\u7EDD\u5BF9\u8DEF\u5F84";
|
|
503
511
|
try {
|
|
504
|
-
if (!(await
|
|
512
|
+
if (!(await stat3(command2)).isFile()) return "\u547D\u4EE4\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u53EF\u6267\u884C\u6587\u4EF6";
|
|
505
513
|
await access3(command2, constants2.X_OK);
|
|
506
514
|
} catch {
|
|
507
515
|
return `\u547D\u4EE4\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${command2}`;
|
|
@@ -514,7 +522,7 @@ async function validateToolCommand(value, kind) {
|
|
|
514
522
|
return void 0;
|
|
515
523
|
}
|
|
516
524
|
async function runOnboard(opts) {
|
|
517
|
-
const configExists = await
|
|
525
|
+
const configExists = await stat3(opts.configPath).then(() => true, (error) => {
|
|
518
526
|
if (error.code === "ENOENT") return false;
|
|
519
527
|
throw error;
|
|
520
528
|
});
|
|
@@ -564,7 +572,7 @@ async function runOnboard(opts) {
|
|
|
564
572
|
console.log(" \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
|
|
565
573
|
continue;
|
|
566
574
|
}
|
|
567
|
-
if (!await
|
|
575
|
+
if (!await stat3(project.directory).then(() => true, () => false)) {
|
|
568
576
|
console.log(" \u76EE\u5F55\u4E0D\u5B58\u5728");
|
|
569
577
|
continue;
|
|
570
578
|
}
|
|
@@ -593,7 +601,7 @@ async function runOnboard(opts) {
|
|
|
593
601
|
console.log(" \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
|
|
594
602
|
continue;
|
|
595
603
|
}
|
|
596
|
-
if (!await
|
|
604
|
+
if (!await stat3(directory).then(() => true, () => false)) {
|
|
597
605
|
console.log(" \u76EE\u5F55\u4E0D\u5B58\u5728");
|
|
598
606
|
continue;
|
|
599
607
|
}
|
package/dist/main.js
CHANGED