@skylandnpm/octopus-proxy 0.1.71 → 0.1.73
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 +8 -6
- package/dist/{chunk-GOPKVIVH.js → chunk-SSY3TLHA.js} +93 -18
- package/dist/cli.js +9 -9
- package/dist/main.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Octopus Proxy
|
|
2
2
|
|
|
3
|
+
**Embodied Dots** 意为**具身智能体节点**,是连接远程主机与智能体工作空间的节点。Taskhub 和 Claw 的菜单和列表使用复数 Dots,单个节点及其设置使用单数 Dot;接口字段 `botId`、CLI 参数 `--bot-id` 和数据库表 `hub_bots` 保持不变,已有节点和凭据可继续使用。
|
|
4
|
+
|
|
3
5
|
Octopus Proxy 是运行在 macOS 或 Linux 主机上的 Octopus 代理,提供 `octopus-proxy` 命令来初始化配置、安装系统服务和检查运行状态。需要 Node.js 22 或更高版本。
|
|
4
6
|
|
|
5
7
|
## 安装
|
|
@@ -14,9 +16,9 @@ octopus-proxy identity
|
|
|
14
16
|
|
|
15
17
|
向导默认安装服务;使用 `--no-install-service` 时,之后运行 `octopus-proxy install`。安装和重装会保留原主机密钥。`--prefer-online` 会让 npm 在安装时重新校验 registry 中的包元数据,避免本地缓存使 `@latest` 仍指向旧版本,不再需要先运行 `npm view @skylandnpm/octopus-proxy versions`。如果升级后 `octopus-proxy version` 仍显示旧版本,运行 `command -v npm`、`npm prefix -g` 和 `command -v octopus-proxy`,确认 npm 的全局安装目录与实际执行的命令属于同一套 Node.js 环境;使用 NVM 与 Homebrew 时尤其要检查这一点。
|
|
16
18
|
|
|
17
|
-
向导默认使用 `https://octopus.wuyehao.com` 作为 Hub 地址;按需可输入其他地址,或用 `--hub-url`
|
|
19
|
+
向导默认使用 `https://octopus.wuyehao.com` 作为 Hub 地址;按需可输入其他地址,或用 `--hub-url` 指定。再次运行向导时会读取现有配置,直接回车即可保留各项设置;Dot Secret 只提示已配置,不在终端显示原值。macOS 上选择编码工具时,向导优先显示已配置或探测到的命令路径,否则给出该工具的 macOS 路径示例;回车后会检查文件可执行性并运行版本命令,失败时要求重新输入。无交互环境可使用 `octopus-proxy onboard --non-interactive`,未提供 `--hub-url` 时也使用该默认地址;完整选项见 `octopus-proxy help`。安装服务后可运行 `octopus-proxy status` 和 `octopus-proxy doctor` 检查状态。
|
|
18
20
|
|
|
19
|
-
Proxy 主动连接 Hub 的 `/ws/proxy-chat`,无需开放入站端口或安装 sshd。Claw 经 `/ws/claw-chat` 建立到 Proxy 的内层 TLS 1.3 会话;Hub 只转发加密字节。首次打开工作区显示主机公钥指纹;核对后点击 Trust,未授权设备还需输入主机终端生成的配对码。Proxy 校验设备签名、配对码及主机密码并持久化设备授权,Claw 在认证成功后按 Hub
|
|
21
|
+
Proxy 主动连接 Hub 的 `/ws/proxy-chat`,无需开放入站端口或安装 sshd。Claw 经 `/ws/claw-chat` 建立到 Proxy 的内层 TLS 1.3 会话;Hub 只转发加密字节。首次打开工作区显示主机公钥指纹;核对后点击 Trust,未授权设备还需输入主机终端生成的配对码。Proxy 校验设备签名、配对码及主机密码并持久化设备授权,Claw 在认证成功后按 Hub 地址和 Dot ID 固定指纹;之后指纹变化会拒绝连接。每次连接都要输入运行 Proxy 服务账号的主机密码,由主机上的 PAM(Linux)或 Open Directory(macOS)验证。断线或连续 60 分钟没有用户操作后需要重新认证。服务安装时生成主机身份,`octopus-proxy identity` 显示指纹并生成 3 天内有效、最多可成功配对 10 次的配对码。安装主机需提供 OpenSSL 和 C 编译器;Linux 还需 PAM 开发头文件。Dot Secret 仅用于 Hub 路由,不能单独授权工作区。
|
|
20
22
|
|
|
21
23
|
macOS 12.3 及以上运行 `octopus-proxy onboard` 时,配置保存后、服务启动前会准备截屏辅助程序并申请系统屏幕录制权限;已授权时不会重复弹窗。交互模式和 `--non-interactive` 均执行此步骤,`--no-install-service` 也会申请。请在系统弹窗中允许访问;拒绝、超时或辅助程序编译失败时,向导提示原因与授权路径,并继续初始化。在“系统设置 → 隐私与安全性 → 屏幕录制(或屏幕与系统音频录制)”中应授权弹窗显示的实际程序,可能是 Terminal、iTerm、编辑器或 node,不保证出现 `octopus-desktop-capture`。onboard 和 doctor 会显示辅助程序路径,文件名不等于系统授权名称。终端授权不代表后台服务已授权,需在 Claw 点击截屏,让实际服务申请权限,按实际名称授权后重启 Proxy。Linux 没有通用的截屏授权弹窗,需使用桌面用户的图形会话环境;平台依赖与设置见[桌面快照说明](../../docs/claw-desktop-snapshots.md)。
|
|
22
24
|
|
|
@@ -58,9 +60,9 @@ NODE
|
|
|
58
60
|
octopus-proxy restart
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
Claw 完成主机信任和账号认证后,通过端到端会话向 Proxy 发送只读 `runtime.snapshot` 命令。响应包含当前运行状态、Hub
|
|
63
|
+
Claw 完成主机信任和账号认证后,通过端到端会话向 Proxy 发送只读 `runtime.snapshot` 命令。响应包含当前运行状态、Hub 和工具设置、授权项目路径,以及工具可提供的模型信息;Dot Secret、工具密码和提供商凭据不会返回。工作区的“Proxy 信息”按钮可查看这些内容。
|
|
62
64
|
|
|
63
|
-
Codex 和 ZCode 由 Proxy 按授权项目启动各自的 app-server,默认随 Proxy 自动启动;服务未就绪时也可在 Claw 点击“启动服务”。若命令不可执行或某个项目启动失败,工作区会显示失败项目及原因。通过 `onboard` 或配置文件修改授权项目后,Claw 打开、切换或刷新工作区项目时会读取最新项目配置;目录变化会释放该项目的旧运行资源和终端,并刷新文件面板。Proxy 不轮询配置文件,也不因主机配置变化主动推送刷新通知。Claw 也可在授权项目菜单中点击“修改项目”,修改“Proxy 主机上的目录”并保存;Proxy 校验目录存在且未重复授权后写入主机配置,Claw 随即刷新工作区,无需重启 Proxy
|
|
65
|
+
Codex 和 ZCode 由 Proxy 按授权项目启动各自的 app-server,默认随 Proxy 自动启动;服务未就绪时也可在 Claw 点击“启动服务”。若命令不可执行或某个项目启动失败,工作区会显示失败项目及原因。通过 `onboard` 或配置文件修改授权项目后,Claw 打开、切换或刷新工作区项目时会读取最新项目配置;目录变化会释放该项目的旧运行资源和终端,并刷新文件面板。Proxy 不轮询配置文件,也不因主机配置变化主动推送刷新通知。Claw 也可在授权项目菜单中点击“修改项目”,修改“Proxy 主机上的目录”并保存;Proxy 校验目录存在且未重复授权后写入主机配置,Claw 随即刷新工作区,无需重启 Proxy。修改工具命令、Dot 凭据等其他设置后,仍需运行 `octopus-proxy restart`。
|
|
64
66
|
|
|
65
67
|
## Claw 设备配对与撤销
|
|
66
68
|
|
|
@@ -72,7 +74,7 @@ octopus-proxy identity
|
|
|
72
74
|
|
|
73
75
|
命令显示主机完整公钥指纹、6 位数字配对码及到期时间。请核对 Claw 显示的主机指纹,再输入配对码及服务账号密码。配对码 3 天内有效,最多可成功配对 10 次,次数用尽后失效。已授权设备重连不消耗配对次数,失败的配对不消耗成功次数;最多允许 5 次失败配对提交,跨设备和连接累计,达到上限会提前关闭配对窗口。每次运行 `identity` 都废止旧码并重置次数和有效期。`octopus-proxy identity --cancel` 可立即关闭窗口。Proxy 服务未运行时,命令显示指纹并报错,不生成不可使用的配对码。
|
|
74
76
|
|
|
75
|
-
一个 Proxy 支持多台 Claw 同时在线,最多 32 条并发隧道。添加下一台设备时可复用有效且仍有剩余次数的配对码,或重新运行 `identity` 生成新码;已有授权和连接不受影响。已授权设备重连、Claw 重开、Proxy 重启或更新均无需再次配对,但每条新连接仍需主机密码认证。配对码只保存在服务内存中,重启即失效;设备授权单独保存在状态目录的 `authorized-devices.json`(权限 `0600`),按 Hub origin
|
|
77
|
+
一个 Proxy 支持多台 Claw 同时在线,最多 32 条并发隧道。添加下一台设备时可复用有效且仍有剩余次数的配对码,或重新运行 `identity` 生成新码;已有授权和连接不受影响。已授权设备重连、Claw 重开、Proxy 重启或更新均无需再次配对,但每条新连接仍需主机密码认证。配对码只保存在服务内存中,重启即失效;设备授权单独保存在状态目录的 `authorized-devices.json`(权限 `0600`),按 Hub origin、Dot ID 和设备公钥隔离。
|
|
76
78
|
|
|
77
79
|
```bash
|
|
78
80
|
octopus-proxy devices
|
|
@@ -188,7 +190,7 @@ npm run publish
|
|
|
188
190
|
|
|
189
191
|
## 编码工具账号与 OpenCode 服务
|
|
190
192
|
|
|
191
|
-
Claw
|
|
193
|
+
Claw Dot 设置可通过已解锁的端到端会话查询和切换当前 Dot 连接的 Proxy 智能体。`runtime.models` 接收 `{ "kind": "codex" }`(也支持 `opencode`、`zcode`、`pi`),临时查询所选工具原生模型目录,不修改当前 `tool`。`runtime.switchTool` 使用相同参数,先启动新工具并验证原生连接,成功后以 0600 权限原子保存配置,再关闭旧工具的 Proxy 管理进程;外部 OpenCode 服务继续由主机管理。
|
|
192
194
|
|
|
193
195
|
各工具的命令、OpenCode 地址和服务密码保存在配置文件的 `toolProfiles` 中,切回时恢复。首次选择工具会在此 Proxy 主机探测命令;自定义安装位置可预先设置 `toolProfiles.<kind>`,格式与 `tool` 相同,且其中 `kind` 必须匹配。智能体未安装、启动或连接失败时保留当前工具和配置。已打开工作区会通过 `octopus.resync` 更新能力、会话和模型。
|
|
194
196
|
|
|
@@ -7763,7 +7763,7 @@ async function startDeviceControl(directory, devices, connections) {
|
|
|
7763
7763
|
void (async () => {
|
|
7764
7764
|
try {
|
|
7765
7765
|
const message = JSON.parse(input.split("\n")[0]);
|
|
7766
|
-
if (message.scope && !devices.matchesScope(message.scope.hubOrigin, message.scope.botId)) throw new Error("\u914D\u7F6E\u7684 Hub \u6216\
|
|
7766
|
+
if (message.scope && !devices.matchesScope(message.scope.hubOrigin, message.scope.botId)) throw new Error("\u914D\u7F6E\u7684 Hub \u6216 Dot \u4E0E\u8FD0\u884C\u4E2D\u7684 Proxy \u4E0D\u4E00\u81F4\uFF0C\u8BF7\u786E\u8BA4 --config");
|
|
7767
7767
|
let data;
|
|
7768
7768
|
switch (message.action) {
|
|
7769
7769
|
case "pair":
|
|
@@ -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,6 +11068,7 @@ 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();
|
|
11054
11073
|
config;
|
|
11055
11074
|
media;
|
|
@@ -11072,7 +11091,7 @@ var OpenCodeAdapter = class {
|
|
|
11072
11091
|
}
|
|
11073
11092
|
throw error;
|
|
11074
11093
|
});
|
|
11075
|
-
if (!response.ok) throw new
|
|
11094
|
+
if (!response.ok) throw new OpenCodeHttpError(response.status, (await response.text()).slice(0, 1500));
|
|
11076
11095
|
if (response.status === 204) return null;
|
|
11077
11096
|
const text = await response.text();
|
|
11078
11097
|
return text ? JSON.parse(text) : null;
|
|
@@ -11081,7 +11100,7 @@ var OpenCodeAdapter = class {
|
|
|
11081
11100
|
return unwrap2(await this.request(project, method, path, body, signal));
|
|
11082
11101
|
}
|
|
11083
11102
|
async session(project, id) {
|
|
11084
|
-
if (
|
|
11103
|
+
if (!SESSION_ID.test(id)) throw new Error("Invalid session id");
|
|
11085
11104
|
const session = await this.data(project, "GET", `/api/session/${encodeURIComponent(id)}`);
|
|
11086
11105
|
const directory = session?.location?.directory ?? session?.directory;
|
|
11087
11106
|
if (typeof directory !== "string" || await realpath4(directory) !== await this.workspace.root(project)) throw new Error("Session belongs to another project");
|
|
@@ -11099,22 +11118,26 @@ var OpenCodeAdapter = class {
|
|
|
11099
11118
|
const op = command.operation, p = command.payload, id = command.sessionId;
|
|
11100
11119
|
if (op.startsWith("auth.")) return this.authentication(project, op, p);
|
|
11101
11120
|
const needsSession = op.startsWith("sessions.") && !["sessions.list", "sessions.create"].includes(op);
|
|
11121
|
+
let session;
|
|
11102
11122
|
if (needsSession) {
|
|
11103
11123
|
if (!id) throw new Error("Session required");
|
|
11104
|
-
await this.session(project, id);
|
|
11124
|
+
session = await this.session(project, id);
|
|
11105
11125
|
}
|
|
11106
11126
|
const base = `/api/session/${encodeURIComponent(id ?? "")}`;
|
|
11107
11127
|
if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
|
|
11108
11128
|
{ id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u6BCF\u6B21\u4F7F\u7528\u5DE5\u5177\u524D\u8BF7\u6C42\u6279\u51C6" },
|
|
11109
11129
|
{ 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
11130
|
] };
|
|
11111
|
-
if (op === "sessions.prompt") return this.prompt(project, id, p, base);
|
|
11131
|
+
if (op === "sessions.prompt") return this.prompt(project, id, p, base, session?.model);
|
|
11112
11132
|
if (op === "sessions.command" || op === "sessions.shell") {
|
|
11113
11133
|
await this.switchSelection(project, base, p);
|
|
11114
11134
|
if (op === "sessions.shell") return this.data(project, "POST", `${base}/shell`, { ...p.id ? { id: p.id } : {}, command: p.command });
|
|
11115
11135
|
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
11136
|
}
|
|
11117
|
-
if (op === "permissions.list")
|
|
11137
|
+
if (op === "permissions.list") {
|
|
11138
|
+
if (id) await this.session(project, id);
|
|
11139
|
+
return this.permissions(project, id);
|
|
11140
|
+
}
|
|
11118
11141
|
if (op === "questions.list") return this.forms(project, id);
|
|
11119
11142
|
if (op === "permissions.reply" || op === "questions.reply" || op === "questions.reject") return this.resolveInteraction(project, id, op, p);
|
|
11120
11143
|
if (op === "configuration.models") return this.models(project);
|
|
@@ -11163,18 +11186,32 @@ var OpenCodeAdapter = class {
|
|
|
11163
11186
|
if (op.startsWith("sessions.")) return this.media.normalize(project, normalizeSession(result));
|
|
11164
11187
|
return result;
|
|
11165
11188
|
}
|
|
11166
|
-
async prompt(project, session, p, base) {
|
|
11189
|
+
async prompt(project, session, p, base, sessionModel) {
|
|
11167
11190
|
const text = typeof p.text === "string" ? screenshotPrompt(p.text) : "";
|
|
11168
11191
|
const files = [];
|
|
11192
|
+
let hasImage = false;
|
|
11169
11193
|
for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
|
|
11170
11194
|
const upload = await this.uploads.attachment(project, String(uploadId), session);
|
|
11171
11195
|
files.push({ uri: pathToFileURL(await this.workspace.path(project, upload.destination)).href, name: upload.name });
|
|
11196
|
+
hasImage ||= upload.mime.startsWith("image/");
|
|
11172
11197
|
}
|
|
11173
11198
|
if (!text.trim() && !files.length) throw new Error("Message or attachments required");
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11199
|
+
const model = this.modelSelection(p);
|
|
11200
|
+
if (hasImage) await this.checkImageInput(project, model ?? sessionModel);
|
|
11201
|
+
await this.switchSelection(project, base, p, model);
|
|
11202
|
+
const admitted = await this.data(project, "POST", `${base}/prompt`, { text, files, delivery: "queue", ...p.messageID ? { id: p.messageID } : {} });
|
|
11203
|
+
return typeof admitted?.id === "string" ? { id: admitted.id } : null;
|
|
11204
|
+
}
|
|
11205
|
+
async checkImageInput(project, model) {
|
|
11206
|
+
if (typeof model?.providerID !== "string" || typeof model.id !== "string") return;
|
|
11207
|
+
const catalog = await this.data(project, "GET", "/api/model", void 0, AbortSignal.timeout(2e3)).catch(() => null);
|
|
11208
|
+
const entry = Array.isArray(catalog) ? catalog.find((item) => item.providerID === model.providerID && (item.id ?? item.modelID) === model.id) : void 0;
|
|
11209
|
+
const input = entry?.capabilities?.input;
|
|
11210
|
+
if (Array.isArray(input) && !input.includes("image")) {
|
|
11211
|
+
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`);
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
async switchSelection(project, base, payload, model = this.modelSelection(payload)) {
|
|
11178
11215
|
if (payload.permissionMode !== void 0) {
|
|
11179
11216
|
if (!["ask", "full"].includes(String(payload.permissionMode))) throw new Error("Unsupported permission mode");
|
|
11180
11217
|
const states = await this.data(project, "GET", "/api/session/active");
|
|
@@ -11183,6 +11220,9 @@ var OpenCodeAdapter = class {
|
|
|
11183
11220
|
await this.data(project, "PATCH", base, { permissions: [{ action: "*", resource: "*", effect: payload.permissionMode === "full" ? "allow" : "ask" }] });
|
|
11184
11221
|
}
|
|
11185
11222
|
if (payload.agent) await this.data(project, "POST", `${base}/agent`, { agent: payload.agent });
|
|
11223
|
+
if (model) await this.data(project, "POST", `${base}/model`, { model });
|
|
11224
|
+
}
|
|
11225
|
+
modelSelection(payload) {
|
|
11186
11226
|
if (!payload.model) return;
|
|
11187
11227
|
let model;
|
|
11188
11228
|
if (typeof payload.model === "string") {
|
|
@@ -11195,7 +11235,7 @@ var OpenCodeAdapter = class {
|
|
|
11195
11235
|
} else throw new Error("Invalid model identifier");
|
|
11196
11236
|
const level = selectedReasoningLevel(payload, "model.variant");
|
|
11197
11237
|
if (level) model.variant = level;
|
|
11198
|
-
|
|
11238
|
+
return model;
|
|
11199
11239
|
}
|
|
11200
11240
|
async models(project) {
|
|
11201
11241
|
const models = await this.data(project, "GET", "/api/model");
|
|
@@ -11214,9 +11254,29 @@ var OpenCodeAdapter = class {
|
|
|
11214
11254
|
return redact({ providers: [...providers.values()] });
|
|
11215
11255
|
}
|
|
11216
11256
|
async permissions(project, session) {
|
|
11217
|
-
|
|
11257
|
+
let entries;
|
|
11258
|
+
try {
|
|
11259
|
+
entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/permission` : "/api/permission/request");
|
|
11260
|
+
} catch (error) {
|
|
11261
|
+
if (!isPermissionMetadataError(error)) throw error;
|
|
11262
|
+
entries = [...this.permissionEvents.get(project)?.values() ?? []].filter((entry) => !session || entry.sessionID === session);
|
|
11263
|
+
if (!entries.length) throw error;
|
|
11264
|
+
}
|
|
11218
11265
|
return (Array.isArray(entries) ? entries : []).map((entry) => ({ ...entry, permission: entry.action, patterns: entry.resources ?? [], replies: ["once", "always", "reject"] }));
|
|
11219
11266
|
}
|
|
11267
|
+
trackPermission(project, type, data) {
|
|
11268
|
+
if (typeof data?.sessionID !== "string" || !SESSION_ID.test(data.sessionID)) return;
|
|
11269
|
+
if (type === "permission.asked") {
|
|
11270
|
+
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;
|
|
11271
|
+
let pending = this.permissionEvents.get(project);
|
|
11272
|
+
if (!pending) this.permissionEvents.set(project, pending = /* @__PURE__ */ new Map());
|
|
11273
|
+
pending.set(data.id, data);
|
|
11274
|
+
if (pending.size > 256) pending.delete(pending.keys().next().value);
|
|
11275
|
+
} else if (type === "permission.replied") {
|
|
11276
|
+
const pending = this.permissionEvents.get(project);
|
|
11277
|
+
if (pending?.get(data.requestID)?.sessionID === data.sessionID) pending.delete(data.requestID);
|
|
11278
|
+
}
|
|
11279
|
+
}
|
|
11220
11280
|
async forms(project, session) {
|
|
11221
11281
|
const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/form` : "/api/form");
|
|
11222
11282
|
return (Array.isArray(entries) ? entries : []).map(formToQuestion);
|
|
@@ -11228,15 +11288,18 @@ var OpenCodeAdapter = class {
|
|
|
11228
11288
|
if (this.resolving.has(key)) throw new Error("Request is already being resolved by another client");
|
|
11229
11289
|
this.resolving.add(key);
|
|
11230
11290
|
try {
|
|
11231
|
-
|
|
11291
|
+
if (kind === "permission" && session) await this.session(project, session);
|
|
11292
|
+
const pending = kind === "permission" ? await this.permissions(project, session) : await this.forms(project);
|
|
11232
11293
|
const item = pending.find((entry) => entry.id === payload.requestId);
|
|
11233
11294
|
if (!item) throw new Error("Request is no longer pending");
|
|
11234
11295
|
if (session && session !== item.sessionID) throw new Error("Request belongs to another session");
|
|
11235
|
-
await this.session(project, item.sessionID);
|
|
11296
|
+
if (kind !== "permission" || !session) await this.session(project, item.sessionID);
|
|
11236
11297
|
const base = `/api/session/${encodeURIComponent(item.sessionID)}/${kind}/${requestId}`;
|
|
11237
11298
|
if (kind === "permission") {
|
|
11238
11299
|
const decision = payload.reply === "deny" ? "reject" : payload.reply;
|
|
11239
|
-
|
|
11300
|
+
const result = await this.data(project, "POST", `${base}/reply`, { decision, ...payload.message ? { message: payload.message } : {} });
|
|
11301
|
+
this.permissionEvents.get(project)?.delete(String(payload.requestId));
|
|
11302
|
+
return result;
|
|
11240
11303
|
}
|
|
11241
11304
|
if (operation === "questions.reject") return this.data(project, "DELETE", base);
|
|
11242
11305
|
const fields = item.fields;
|
|
@@ -11336,7 +11399,10 @@ var OpenCodeAdapter = class {
|
|
|
11336
11399
|
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
11337
11400
|
if (data) {
|
|
11338
11401
|
const event = JSON.parse(data);
|
|
11339
|
-
if (!event.location?.directory || event.location.directory === root)
|
|
11402
|
+
if (!event.location?.directory || event.location.directory === root) {
|
|
11403
|
+
if (event.location?.directory === root) this.trackPermission(project, event.type, event.data);
|
|
11404
|
+
this.emit(project, event.type === "server.connected" ? "octopus.resync" : event.type, await this.media.normalize(project, event.data ?? {}));
|
|
11405
|
+
}
|
|
11340
11406
|
}
|
|
11341
11407
|
}
|
|
11342
11408
|
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 +11410,7 @@ var OpenCodeAdapter = class {
|
|
|
11344
11410
|
} catch {
|
|
11345
11411
|
if (!controller.signal.aborted) this.emit(project, "octopus.disconnected", {});
|
|
11346
11412
|
}
|
|
11413
|
+
if (!controller.signal.aborted) this.permissionEvents.delete(project);
|
|
11347
11414
|
if (!controller.signal.aborted) await new Promise((resolve5) => setTimeout(resolve5, 2e3));
|
|
11348
11415
|
}
|
|
11349
11416
|
};
|
|
@@ -11352,6 +11419,7 @@ var OpenCodeAdapter = class {
|
|
|
11352
11419
|
removeProject(project) {
|
|
11353
11420
|
this.streams.get(project)?.abort();
|
|
11354
11421
|
this.streams.delete(project);
|
|
11422
|
+
this.permissionEvents.delete(project);
|
|
11355
11423
|
for (const [key, terminal] of this.terminals) {
|
|
11356
11424
|
if (!key.startsWith(`${project}:`)) continue;
|
|
11357
11425
|
terminal.close();
|
|
@@ -11412,6 +11480,7 @@ var OpenCodeAdapter = class {
|
|
|
11412
11480
|
}
|
|
11413
11481
|
close() {
|
|
11414
11482
|
this.auth.close();
|
|
11483
|
+
this.permissionEvents.clear();
|
|
11415
11484
|
for (const controller of this.streams.values()) controller.abort();
|
|
11416
11485
|
for (const terminal of this.terminals.values()) terminal.close();
|
|
11417
11486
|
}
|
|
@@ -11434,7 +11503,13 @@ function normalizeMessage(message) {
|
|
|
11434
11503
|
if (message?.model) info.model = { ...message.model, modelID: message.model.id };
|
|
11435
11504
|
const parts = [];
|
|
11436
11505
|
if (typeof message?.text === "string" && message.text) parts.push({ type: "text", text: message.text });
|
|
11437
|
-
for (const file of message?.files ?? [])
|
|
11506
|
+
for (const file of message?.files ?? []) {
|
|
11507
|
+
if (typeof file.data === "string" && /^image\/(?:png|jpeg|gif|webp)$/.test(file.mime)) {
|
|
11508
|
+
parts.push({ type: "image", filename: file.name, mime: file.mime, base64: file.data });
|
|
11509
|
+
} else {
|
|
11510
|
+
parts.push({ type: "file", filename: file.name, mime: file.mime, url: file.uri });
|
|
11511
|
+
}
|
|
11512
|
+
}
|
|
11438
11513
|
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
11514
|
if (message?.type === "compaction") {
|
|
11440
11515
|
info.summary = true;
|
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-SSY3TLHA.js";
|
|
39
39
|
import {
|
|
40
40
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
41
41
|
PlatformDesktop,
|
|
@@ -535,7 +535,7 @@ var validUrl = (value) => {
|
|
|
535
535
|
};
|
|
536
536
|
var validId = (value) => !value ? "\u9879\u76EE ID \u4E0D\u80FD\u4E3A\u7A7A" : /^[a-zA-Z0-9_-]+$/.test(value) ? void 0 : "\u4EC5\u9650\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26";
|
|
537
537
|
var validateProjectId = (value, previousProjects) => validId(value) ?? (previousProjects.some((project) => project.id === value) ? `\u9879\u76EE ID\u300C${value}\u300D\u5DF2\u5B58\u5728\uFF0C\u8BF7\u8F93\u5165\u5176\u4ED6 ID` : void 0);
|
|
538
|
-
var validBotId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ? void 0 : "\u8BF7\u8F93\u5165
|
|
538
|
+
var validBotId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ? void 0 : "\u8BF7\u8F93\u5165 Dot UUID";
|
|
539
539
|
var run4 = promisify4(execFile4);
|
|
540
540
|
var MACOS_TOOL_EXAMPLES = {
|
|
541
541
|
opencode: "/Users/luhuili/.opencode/bin/opencode",
|
|
@@ -573,11 +573,11 @@ async function runOnboard(opts) {
|
|
|
573
573
|
if (opts.interactive) {
|
|
574
574
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
575
575
|
console.log("Octopus Proxy \u521D\u59CB\u5316\u5411\u5BFC");
|
|
576
|
-
console.log(`\u914D\u7F6E\u5C06\u5199\u5165 ${opts.configPath}\uFF1B\u6587\u4EF6\u5305\u542B
|
|
576
|
+
console.log(`\u914D\u7F6E\u5C06\u5199\u5165 ${opts.configPath}\uFF1B\u6587\u4EF6\u5305\u542B Dot Secret\uFF0C\u4F1A\u4EE5 0600 \u6743\u9650\u4FDD\u5B58\u3002
|
|
577
577
|
`);
|
|
578
578
|
hubUrl = await ask(rl, `Hub \u5730\u5740\uFF08${hubUrl} , \u56DE\u8F66\u76F4\u63A5\u786E\u8BA4\uFF09`, { default: hubUrl, showDefault: false, validate: validUrl });
|
|
579
|
-
botId = await ask(rl, "
|
|
580
|
-
secretKey = await ask(rl, "
|
|
579
|
+
botId = await ask(rl, "Dot ID\uFF08\u5728 Claw \u6216 Taskhub \u4E2D\u521B\u5EFA\u5E76\u590D\u5236\uFF09", { default: botId || void 0, validate: validBotId });
|
|
580
|
+
secretKey = await ask(rl, "Dot Secret\uFF08\u5728 Claw \u6216 Taskhub \u521B\u5EFA\uFF1B\u8F93\u5165\u4F1A\u56DE\u663E\u5728\u7EC8\u7AEF\uFF09", { default: secretKey || void 0, defaultDisplay: existing?.secretKey ? "\u5DF2\u914D\u7F6E\uFF0C\u56DE\u8F66\u4FDD\u7559" : "\u5DF2\u63D0\u4F9B\uFF0C\u56DE\u8F66\u4F7F\u7528", validate: (value) => value.length >= 20 ? void 0 : "\u8BF7\u8F93\u5165\u5B8C\u6574\u7684 Dot Secret" });
|
|
581
581
|
stateDirectory = expandHome(await ask(rl, "\u72B6\u6001\u76EE\u5F55", { default: stateDirectory, validate: (value) => isAbsolute4(expandHome(value)) ? void 0 : "\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" }));
|
|
582
582
|
const previousToolKind = toolKind;
|
|
583
583
|
toolKind = await ask(rl, `\u7F16\u7801\u5DE5\u5177\uFF08${TOOL_KINDS.join("/")}\uFF09`, { default: toolKind, validate: (value) => TOOL_KINDS.includes(value) ? void 0 : `\u8F93\u5165 ${TOOL_KINDS.join(" \u6216 ")}` });
|
|
@@ -635,7 +635,7 @@ async function runOnboard(opts) {
|
|
|
635
635
|
if (missing.length) throw new Error(`\u975E\u4EA4\u4E92\u6A21\u5F0F\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570\uFF1A${missing.join("\u3001")}`);
|
|
636
636
|
const urlProblem = validUrl(hubUrl);
|
|
637
637
|
if (urlProblem) throw new Error(`${urlProblem}\uFF08--hub-url\uFF09`);
|
|
638
|
-
if (validBotId(botId) || secretKey.length < 20) throw new Error("
|
|
638
|
+
if (validBotId(botId) || secretKey.length < 20) throw new Error("Dot ID \u6216 Secret \u683C\u5F0F\u65E0\u6548");
|
|
639
639
|
projects = projects.map((project) => ({ ...project, id: project.id ?? slug(project.name) }));
|
|
640
640
|
const invalid = projects.find((project) => validId(project.id ?? "") !== void 0);
|
|
641
641
|
if (invalid) throw new Error(`\u9879\u76EE\u300C${invalid.name}\u300D\u65E0\u6CD5\u63A8\u5BFC\u51FA\u5408\u6CD5 ID\uFF08\u4EC5\u9650\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26\uFF09\uFF0C\u8BF7\u8C03\u6574\u540D\u79F0\u6216\u624B\u5DE5\u6307\u5B9A ID`);
|
|
@@ -893,10 +893,10 @@ var USAGE = `octopus-proxy \u2014 Octopus \u4E3B\u673A\u4EE3\u7406
|
|
|
893
893
|
--config <path> \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 $OCTOPUS_PROXY_CONFIG \u6216 ~/.config/octopus/proxy.json\uFF09
|
|
894
894
|
|
|
895
895
|
onboard \u9009\u9879:
|
|
896
|
-
--non-interactive \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF1B\u9700\u63D0\u4F9B
|
|
896
|
+
--non-interactive \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF1B\u9700\u63D0\u4F9B Dot ID\u3001Secret \u548C\u9879\u76EE
|
|
897
897
|
--hub-url <url> Hub \u5730\u5740\uFF08\u9ED8\u8BA4 https://octopus.wuyehao.com\uFF09
|
|
898
|
-
--bot-id <uuid> Claw \u6216 Taskhub \u4E2D\u521B\u5EFA\u7684
|
|
899
|
-
--secret-key <key>
|
|
898
|
+
--bot-id <uuid> Claw \u6216 Taskhub \u4E2D\u521B\u5EFA\u7684 Dot ID
|
|
899
|
+
--secret-key <key> Dot Secret\uFF08\u5728 Claw \u6216 Taskhub \u521B\u5EFA\uFF09
|
|
900
900
|
--state-dir <path> \u72B6\u6001\u76EE\u5F55\uFF08\u9ED8\u8BA4 ~/.local/state/octopus\uFF09
|
|
901
901
|
--project <name=path> \u6388\u6743\u9879\u76EE\uFF0C\u53EF\u91CD\u590D\u591A\u6B21
|
|
902
902
|
--tool <kind> \u7F16\u7801\u5DE5\u5177\uFF1Aopencode\uFF08\u9ED8\u8BA4\uFF09\u3001codex\u3001zcode \u6216 pi
|
package/dist/main.js
CHANGED