dsh-comfyui-canvas 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-comfyui-canvas contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # dsh-comfyui-canvas
2
+
3
+ > [中文](README.zh.md) · English
4
+
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+ [![Version](https://img.shields.io/badge/version-0.1.1-brightgreen.svg)](https://github.com/wbin0001/dsh-comfyui-canvas/releases)
7
+ [![GitHub Stars](https://img.shields.io/github/stars/wbin0001/dsh-comfyui-canvas.svg?style=social)](https://github.com/wbin0001/dsh-comfyui-canvas)
8
+ [![DSH](https://img.shields.io/badge/DeepSeek_Harness-compatible-blueviolet.svg)](https://github.com/DeepSeek-Harness/DSH)
9
+ [![ComfyUI](https://img.shields.io/badge/ComfyUI-0.34+-orange.svg)](https://github.com/comfyanonymous/ComfyUI)
10
+ [![Canvas](https://img.shields.io/badge/canvas-split--screen-teal.svg)](docs/architecture.html)
11
+
12
+ ![dsh-comfyui-canvas demo — agent drives a live ComfyUI workflow and fetches the output grid back into the chat](docs/screenshots/03-workflow-output.png)
13
+
14
+ Embed your **ComfyUI** (local or cloud) as a split-screen canvas tab inside [DeepSeek Harness](https://github.com/DeepSeek-Harness/DSH) Web, and merge DSH's LLM power with ComfyUI's generation into **one visual creation platform** — the agent sparks ideas, writes prompts and scripts right in the chat, applies them live to the canvas in front of you, and produces images, music, video, and 3D. From idea to finished output without ever leaving the conversation or switching front-ends:
15
+
16
+ - **Canvas ops** — compose and arrange pipelines, read/write workflows, edit nodes, wire links, run, tune parameters, and debug errors, all live and WYSIWYG on the exact canvas you are looking at
17
+ - **Production tasks** — batch parameter sweeps (`batch_run`) and automatic output-image retrieval back into the chat (`get_outputs`), powering multi-modal creative and batch generation across images, music, video, and 3D
18
+ - **Environment upkeep** — one-click launch of ComfyUI and one-click upgrade of the core plus every custom node (`upgrade`), keeping the stack healthy without interruption
19
+
20
+ This package is the DSH-side plugin, and it ships the ComfyUI-side bridge node too. For headless/scale workloads it can be paired with the official ComfyUI MCP server — see [Canvas vs MCP](#canvas-vs-mcp--two-ways-to-drive-comfyui).
21
+
22
+ ## What you get
23
+
24
+ | Surface | Description |
25
+ |---|---|
26
+ | **ComfyUI canvas tab** | A `ComfyUI` conversation view that embeds the ComfyUI frontend (local or cloud) side by side with the Chat rail. The iframe stays alive across tab switches (no reload). |
27
+ | **Visual canvas copilot** | The agent operates **the canvas you are looking at** — nodes appear, links wire, widgets change and runs trigger live on screen, so you watch every step instead of trusting an opaque JSON edit. Output images come back into the chat via `comfyui_get_outputs`. |
28
+ | **Canvas ops tools** | `comfyui_read_workflow`, `add_node`, `connect`, `set_param`, `remove_node`, `inject_text`, `load_workflow`, `run`, `debug` — build and fix workflows on the live canvas; `inject_text` writes conversation text straight into a node or a new wirable source. |
29
+ | **Production tools** | `comfyui_batch_run` sweeps a parameter matrix (seeds/prompts/strengths) in one go; `comfyui_get_outputs` pulls the resulting images back into the chat; `comfyui_attach_image` uploads a local image into ComfyUI's input/ for a LoadImage node; `comfyui_export_api` exports the live canvas as API-format workflow JSON for comfy-cli headless batch runs. |
30
+ | **Upkeep tool** | `comfyui_upgrade` one-click updates the ComfyUI core and every git-backed custom node; `comfyui_config` reports the active connection and canvas focus. |
31
+ | **Canvas focus mode** | The agent can tell (via `comfyui_config`) whether the browser is on the canvas tab for the current session, and focus on canvas work only then. Session-isolated. |
32
+ | **Settings page** | ComfyUI base URL / port / network mode / bridge token / launch command / rail width. Changes apply live. Customized nav icon with ComfyUI logo. |
33
+ | **Rail polish** | Image previews inside the input box, a `+` button to attach local images (DSH's official attachment path), approval popup over the canvas (split layout), send button pinned to the panel corner. |
34
+
35
+ ## Install
36
+
37
+ ### 1. Install the DSH plugin
38
+
39
+ ```bash
40
+ dsh plugin add github:<your-name>/dsh-comfyui-canvas
41
+ ```
42
+
43
+ or from a local checkout:
44
+
45
+ ```bash
46
+ # in your DSH profile
47
+ pnpm add <path-to-this-package>
48
+ ```
49
+
50
+ The bundled `cordis.patch.yml` mounts the plugin automatically (`dsh.bundle.patch`).
51
+
52
+ ### 2. Install the ComfyUI bridge node
53
+
54
+ The agent tools talk to the ComfyUI page through a bridge (`/dsh-bridge/*`). The bridge node ships **inside this repo** at `comfyui-bridge/ComfyUI-DSH-Canvas` — copy it into ComfyUI's `custom_nodes`:
55
+
56
+ **Windows (PowerShell / cmd):**
57
+
58
+ ```powershell
59
+ # from this repo checkout:
60
+ Copy-Item -Recurse comfyui-bridge\ComfyUI-DSH-Canvas <ComfyUI>\custom_nodes\ComfyUI-DSH-Canvas
61
+ ```
62
+
63
+ **macOS / Linux (bash):**
64
+
65
+ ```bash
66
+ # from this repo checkout:
67
+ cp -r comfyui-bridge/ComfyUI-DSH-Canvas <ComfyUI>/custom_nodes/ComfyUI-DSH-Canvas
68
+ ```
69
+
70
+ Or, after `dsh plugin add`, the installed package carries it too:
71
+
72
+ ```bash
73
+ # Windows (PowerShell)
74
+ Copy-Item -Recurse (npm root -g)\dsh-comfyui-canvas\comfyui-bridge\ComfyUI-DSH-Canvas <ComfyUI>\custom_nodes\ComfyUI-DSH-Canvas
75
+
76
+ # macOS / Linux
77
+ cp -r $(npm root -g)/dsh-comfyui-canvas/comfyui-bridge/ComfyUI-DSH-Canvas <ComfyUI>/custom_nodes/ComfyUI-DSH-Canvas
78
+ ```
79
+
80
+ Then restart ComfyUI and load the canvas page once (the injected `bridge.js` reports the graph and listens for commands).
81
+
82
+ ### 3. Configure
83
+
84
+ Open **Settings → ComfyUI 画布** and set the ComfyUI base URL (default `http://127.0.0.1:8188`), port, network mode, optional bridge token, launch command, and the right-side rail width.
85
+
86
+ The **launch command** differs by platform:
87
+
88
+ | Platform | Example |
89
+ |---|---|
90
+ | Windows | `ComfyUI启动器.bat` (or `python main.py`) |
91
+ | macOS | `python main.py` or `./start.sh` |
92
+ | Linux | `python main.py` or `./start.sh` |
93
+
94
+ ## Security
95
+
96
+ The bridge (`/dsh-bridge/*`) is the only network surface this plugin adds to ComfyUI. Read this before exposing ComfyUI beyond loopback.
97
+
98
+ - **Trust model.** By default the bridge is unauthenticated, matching ComfyUI's own `/prompt` trust model — anyone who can reach the ComfyUI port can read the canvas, report state, and dispatch commands (`load_workflow`/`run` consume GPU). Commands are whitelisted on the frontend, so no arbitrary code execution is possible, but the surface is real.
99
+ - **Bind to loopback.** Keep ComfyUI on `127.0.0.1` unless you explicitly need LAN/cloud access. `networkMode` is informational; the actual bind is whatever ComfyUI was launched with (`--listen`).
100
+ - **Optional shared token.** Set a token in **Settings → ComfyUI 画布 → 桥接 Token** AND launch ComfyUI with the same value in its own environment (`DSH_BRIDGE_TOKEN=...`). When the token is set, every agent-initiated request — reading the canvas, dispatching a command, polling its result — must present `Authorization: Bearer <token>`; the host side sends it automatically and the bridge rejects requests without it. The frontend's own status reporting (`/report`, result callbacks) stays open, since the injected page cannot hold the token; those endpoints only mutate the in-memory snapshot and never dispatch execution. Leave it empty on both sides for the default open behavior.
101
+ - **Multiple tabs are safe.** Commands are targeted at the last-reporting frontend (`clientId`), so several open ComfyUI tabs do not each execute a command.
102
+
103
+ ## Platform support
104
+
105
+ Works on **Windows**, **macOS** and **Linux**. The agent tools talk to ComfyUI over plain HTTP (`/dsh-bridge/*`), so nothing platform-specific lives in the plugin itself — only the copy command and the ComfyUI launch command differ, and both are documented above.
106
+
107
+ ## Usage
108
+
109
+ 1. Open a conversation, switch to the **ComfyUI** tab — the canvas splits on the left, chat on the right.
110
+ 2. Ask the agent to do canvas work: *"读取当前工作流"*, *"给 KSampler 设 seed 为 42"*, *"检查画布有没有报错"*, *"运行一次"*.
111
+ 3. The agent reads `comfyui_config` first, so it knows it's on the canvas and stays focused on canvas operations.
112
+
113
+ ### Conversation → canvas
114
+
115
+ Content the agent generates in the chat — images and text — can become ComfyUI workflow node inputs directly, closing the loop from conversation idea to canvas output:
116
+
117
+ - `comfyui_attach_image`: upload a local image into ComfyUI's `input/` and optionally point a LoadImage node at it. Uses ComfyUI's native `/upload/image` (not the bridge) — the host reads and uploads the file from the agent's own machine, which matters when the DSH machine and the ComfyUI machine differ (cloud deployments).
118
+ - `comfyui_inject_text`: write text to a node's widget; or create a new source node, set its value, and connect it to a target input — "conversation text as a wirable source". A one-step wrapper over `add_node + set_param + connect`; `set_param` alone suffices when only an existing widget changes, and `inject_text` is for "create a new source and wire it".
119
+ - `comfyui_export_api`: export the live canvas as API-format workflow JSON (the format `/prompt` and comfy-cli `run_workflow` consume), bridging canvas → MCP headless runs.
120
+
121
+ > Architecture boundary: file transfer (image → `input/`) goes through the host + native API; canvas node ops go through bridge commands; reading results goes through native `/history` + `/view` — three layers that never mix.
122
+
123
+ ### Canvas vs MCP — two ways to drive ComfyUI
124
+
125
+ This plugin is the **canvas driver**: it sees and edits the *live canvas* the user is looking at (add nodes, wire links, tweak widgets, run, fetch the run's output images via `comfyui_get_outputs`, sweep parameters via `comfyui_batch_run`). It never needs a saved workflow file.
126
+
127
+ For **pipeline-style / headless workloads**, ComfyUI's official **Comfy CLI** (`comfy-cli`) is a complementary tool. It is a standalone Python CLI (installed via `pip install comfy-cli`, and can also expose an MCP server) — **not** a DSH plugin, so it is used separately from this plugin rather than mounted into the DSH profile. It covers capabilities this canvas plugin deliberately does **not** re-implement:
128
+
129
+ | Capability | This plugin (canvas) | ComfyUI CLI (comfy-cli) |
130
+ |---|---|---|
131
+ | Operate the live canvas the user sees | ✅ | — |
132
+ | Run a saved / API-format workflow file | ✅ (via canvas) | ✅ (directly) |
133
+ | Batch-queue runs + fetch output images | ✅ (`batch_run` + `get_outputs`) | ✅ (`run_workflow` + `fetch_outputs`) |
134
+ | Official workflow templates | — | ✅ (`templates`) |
135
+ | Model download / management | — | ✅ (`models`) |
136
+ | Hosted/paid models (Flux, Veo, …) | — | ✅ (`partner`) |
137
+ | Pre-flight graph validation (`validate` / deps) | ✅ (`debug`, local) | ✅ (`validate`, server) |
138
+
139
+ **Recommended split**: use this plugin while you are *building/tuning* a workflow on the canvas; use the Comfy CLI once you want to *run the same graph headlessly at scale* (batch pipelines, templates, model management, hosted models). They talk to the same ComfyUI instance and can be used side by side. Install the Comfy CLI with:
140
+
141
+ ```bash
142
+ pip install comfy-cli # standalone CLI, not a DSH plugin — see https://github.com/Comfy-Org/comfy-cli
143
+ ```
144
+
145
+ ## Requirements
146
+
147
+ - DeepSeek Harness Web (DSH), Node `^22.19.0 || >=24`
148
+ - ComfyUI running (local by default at `127.0.0.1:8188`; for a cloud instance, deploy the bridge node there and make sure DSH can reach it) with the bridge node installed
149
+ - A browser tab with the ComfyUI page open (the canvas tab loads it automatically)
150
+
151
+ ## Development
152
+
153
+ ```bash
154
+ npm run check # node --check both lib files
155
+ ```
156
+
157
+ The plugin lives in the DSH profile under `node_modules/dsh-comfyui-canvas`; edit `lib/index.js` (host tools) and `lib/client.js` (web client), then restart DSH.
158
+
159
+ ## Repository layout
160
+
161
+ ```
162
+ dsh-comfyui-canvas/
163
+ ├── cordis.patch.yml # DSH bundle layer (auto-mount)
164
+ ├── comfyui-bridge/ # ComfyUI-side bridge node (self-contained)
165
+ │ └── ComfyUI-DSH-Canvas/
166
+ │ ├── __init__.py # /dsh-bridge/* HTTP routes on the ComfyUI server
167
+ │ └── entry/bridge.js # injected frontend: reports graph + runs commands
168
+ ├── lib/
169
+ │ ├── index.js # DSH host: 15 canvas tools + session-isolated mode
170
+ │ └── client.js # DSH web: canvas tab / settings / rail polish
171
+ ├── LICENSE
172
+ ├── README.md
173
+ └── package.json
174
+ ```
175
+
176
+ The **bridge** is the only ComfyUI-side dependency. It exposes `/dsh-bridge/workflow|report|command|result` and is injected into the ComfyUI page via `app.registerExtension`; without it the agent tools cannot reach the canvas.
177
+
178
+ ## Known issues
179
+
180
+ _(None pending. The former "node previews missing after a run" is fixed in v0.1.1: removed the iframe's `referrerpolicy="no-referrer"` to match the native tab environment, and `bridge.js` now listens to ComfyUI's `executed` event to force a canvas redraw.)_
181
+
182
+ ## License
183
+
184
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,210 @@
1
+ # dsh-comfyui-canvas(DSH 画布插件)
2
+
3
+ > 中文 · [English](README.md)
4
+
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+ [![Version](https://img.shields.io/badge/version-0.1.1-brightgreen.svg)](https://github.com/wbin0001/dsh-comfyui-canvas/releases)
7
+ [![GitHub Stars](https://img.shields.io/github/stars/wbin0001/dsh-comfyui-canvas.svg?style=social)](https://github.com/wbin0001/dsh-comfyui-canvas)
8
+ [![DSH](https://img.shields.io/badge/DeepSeek_Harness-compatible-blueviolet.svg)](https://github.com/DeepSeek-Harness/DSH)
9
+ [![ComfyUI](https://img.shields.io/badge/ComfyUI-0.34+-orange.svg)](https://github.com/comfyanonymous/ComfyUI)
10
+ [![Canvas](https://img.shields.io/badge/canvas-split--screen-teal.svg)](docs/architecture.html)
11
+
12
+ ![dsh-comfyui-canvas 演示 —— agent 驱动实时 ComfyUI 工作流,把出图网格直接带回对话](docs/screenshots/03-workflow-output.png)
13
+
14
+ 把 **ComfyUI**(本地或云端)以分屏画布标签嵌入 [DeepSeek Harness](https://github.com/DeepSeek-Harness/DSH) Web,把 DSH 的 LLM 能力与 ComfyUI 的生成能力合成**一个可视化创作平台**——agent 在对话里激发创意、书写提示词与脚本,实时落到你眼前的画布上,产出图像、音乐、视频、3D。从灵感到成品,全程不离开对话,不用切换任何前端工具:
15
+
16
+ - **画布操作**:搭建编排、读写工作流、修改节点、连线、运行、调整参数、工作流查错——所见即所得,实时落在你眼前的画布上
17
+ - **生产任务**:批量扫参(`batch_run`)、自动取回出图(`get_outputs`)带回对话,实现图像、音乐、视频、3D 等多任务智能创作与批量生产
18
+ - **环境维护**:一键启动 ComfyUI、一键升级核心与全部自定义节点(`upgrade`),省心维护不间断
19
+
20
+ 本仓库集成了 **DSH 侧插件(画布副驾)+ ComfyUI 侧桥接节点**。需要无人值守 / 规模化执行时,可配合官方 ComfyUI MCP 服务器使用——见[「画布驱动 vs MCP」](#画布驱动-vs-mcp两种操控-comfyui-的方式)。
21
+
22
+ ---
23
+
24
+ ## 功能特性
25
+
26
+ | 能力 | 说明 |
27
+ |---|---|
28
+ | **画布标签页** | 对话里新增 `ComfyUI` 标签,左边画布、右边对话 rail 分屏。iframe 常驻不重载,切标签秒回。 |
29
+ | **可视化画布副驾** | agent 操作**你正在看的画布**——节点出现、连线接上、参数变化、运行触发,全部实时显示在屏幕上,每一步都看得见,而不是黑盒改 JSON。出图经 `comfyui_get_outputs` 直接带回对话。 |
30
+ | **画布操作工具** | `comfyui_read_workflow` / `add_node` / `connect` / `set_param` / `remove_node` / `inject_text` / `load_workflow` / `run` / `debug`——在活画布上搭建与修复工作流;`inject_text` 把对话文本一步注入为可连线节点。 |
31
+ | **生产工具** | `comfyui_batch_run` 一次扫参数矩阵(seed / prompt / 强度);`comfyui_get_outputs` 把出图直接带回对话;`comfyui_attach_image` 把对话里的图片送进画布 LoadImage;`comfyui_export_api` 把当前画布导出为 API 格式工作流,供 comfy-cli 无人值守批量。 |
32
+ | **维护工具** | `comfyui_upgrade` 一键升级 ComfyUI 核心与全部 git 自定义节点;`comfyui_config` 报告当前连接与画布专注状态。 |
33
+ | **画布专注模式(会话隔离)** | agent 通过 `comfyui_config` 感知当前会话是否在画布标签,只在画布场景专注画布操作,且**按会话隔离**——多个会话互不干扰。 |
34
+ | **ComfyUI 报错处理** | `debug` 校验工作流并高亮报错节点(纯校验,不触发执行),agent 帮你定位/修复画布错误。 |
35
+ | **设置页** | ComfyUI 地址 / 端口 / 网络模式 / 桥接 Token / 启动命令 / 右侧面板宽度,实时生效。导航栏已自定义为 ComfyUI logo 图标。 |
36
+ | **对话栏增强** | 图片预览并入输入框、`+` 号上传本地图片(走 DSH 官方附件通道)、画布上的授权弹窗、发送按钮贴右下角。 |
37
+
38
+ ---
39
+
40
+ ## 安装
41
+
42
+ ### 1. 安装 DSH 插件
43
+
44
+ ```bash
45
+ dsh plugin add github:<你的用户名>/dsh-comfyui-canvas
46
+ ```
47
+
48
+ 或本地安装:
49
+
50
+ ```bash
51
+ # 在 DSH profile 目录下
52
+ pnpm add <本仓库路径>
53
+ ```
54
+
55
+ 插件自带 `cordis.patch.yml`(通过 `dsh.bundle.patch` 声明),装完自动挂载,无需手改配置。
56
+
57
+ ### 2. 安装 ComfyUI 桥接节点
58
+
59
+ agent 工具通过 `/dsh-bridge/*` 与 ComfyUI 页面通信。桥接节点**已内嵌在仓库** `comfyui-bridge/ComfyUI-DSH-Canvas`,把它复制进 ComfyUI 的 `custom_nodes`:
60
+
61
+ **Windows(PowerShell / cmd):**
62
+
63
+ ```powershell
64
+ # 方式一:从仓库 checkout 复制
65
+ Copy-Item -Recurse comfyui-bridge\ComfyUI-DSH-Canvas <ComfyUI>\custom_nodes\ComfyUI-DSH-Canvas
66
+ ```
67
+
68
+ **macOS / Linux(bash):**
69
+
70
+ ```bash
71
+ # 方式一:从仓库 checkout 复制
72
+ cp -r comfyui-bridge/ComfyUI-DSH-Canvas <ComfyUI>/custom_nodes/ComfyUI-DSH-Canvas
73
+ ```
74
+
75
+ `dsh plugin add` 安装后,已安装的插件里也带这份桥接节点:
76
+
77
+ ```bash
78
+ # Windows(PowerShell)
79
+ Copy-Item -Recurse (npm root -g)\dsh-comfyui-canvas\comfyui-bridge\ComfyUI-DSH-Canvas <ComfyUI>\custom_nodes\ComfyUI-DSH-Canvas
80
+
81
+ # macOS / Linux
82
+ cp -r $(npm root -g)/dsh-comfyui-canvas/comfyui-bridge/ComfyUI-DSH-Canvas <ComfyUI>/custom_nodes/ComfyUI-DSH-Canvas
83
+ ```
84
+
85
+ 然后重启 ComfyUI 并打开一次画布页面(注入的 `bridge.js` 会上报画布状态并监听命令)。
86
+
87
+ ### 3. 配置
88
+
89
+ 打开 **设置 → ComfyUI 画布**,填写 ComfyUI 地址(默认 `http://127.0.0.1:8188`)、端口、网络模式、可选桥接 Token、启动命令和右侧面板宽度。
90
+
91
+ **启动命令**按平台不同:
92
+
93
+ | 平台 | 示例 |
94
+ |---|---|
95
+ | Windows | `ComfyUI启动器.bat`(或 `python main.py`) |
96
+ | macOS | `python main.py` 或 `./start.sh` |
97
+ | Linux | `python main.py` 或 `./start.sh` |
98
+
99
+ ---
100
+
101
+ ## 安全
102
+
103
+ 桥接(`/dsh-bridge/*`)是本插件给 ComfyUI 新增的唯一网络面。把 ComfyUI 暴露到回环地址之外之前请先读这里。
104
+
105
+ - **信任模型**。默认桥接无鉴权,与 ComfyUI 自身 `/prompt` 的信任模型一致——任何能访问 ComfyUI 端口的人都能读画布、上报状态、下发命令(`load_workflow`/`run` 会消耗 GPU)。前端有命令白名单,**无法**执行任意代码,但这个面是真实存在的。
106
+ - **绑定回环**。除非确有局域网/云端需求,请让 ComfyUI 保持 `127.0.0.1`。`networkMode` 只是信息性字段;实际绑定取决于 ComfyUI 启动时的 `--listen`。
107
+ - **可选共享 Token**。在 **设置 → ComfyUI 画布 → 桥接 Token** 里填一个 Token,同时用相同值启动 ComfyUI(给它自己的环境变量 `DSH_BRIDGE_TOKEN=...`)。启用后,每个**由 agent 发起**的请求——读画布、下发命令、轮询结果——都必须携带 `Authorization: Bearer <token>`;host 端自动带上,bridge 端拒绝没有 Token 的请求。前端自身的状态上报(`/report`、结果回传)保持开放,因为注入页面无法持有 Token;这些端点只改动内存快照、从不触发执行。两端都留空则保持默认开放行为。
108
+ - **多标签页安全**。命令会定向到「最近上报的前端」(`clientId`),所以同时开着多个 ComfyUI 标签页不会各自执行一次命令。
109
+
110
+ ---
111
+
112
+ ## 平台支持
113
+
114
+ 支持 **Windows / macOS / Linux**。agent 工具通过纯 HTTP(`/dsh-bridge/*`)与 ComfyUI 通信,插件本身不依赖任何平台特性——只有**复制命令**和 **ComfyUI 启动命令**因平台而异,上面都已分别说明。
115
+
116
+ ---
117
+
118
+ ## 使用
119
+
120
+ 1. 打开一个会话,切换到 **ComfyUI** 标签——左边是画布,右边是对话。
121
+ 2. 直接让 agent 干画布活:
122
+ - *“读取当前工作流”*
123
+ - *“给 KSampler 设 seed 为 42”*
124
+ - *“检查画布有没有报错”*
125
+ - *“运行一次”*
126
+ 3. agent 会先读 `comfyui_config` 确认当前在画布模式,然后专注画布操作。
127
+
128
+ ### 对话产物 → 画布
129
+
130
+ agent 在对话里生成的图片与文本,可直接作为 ComfyUI 工作流的节点输入,形成「对话创意 → 画布产出」闭环:
131
+
132
+ - `comfyui_attach_image`:把本机一张图片上传进 ComfyUI `input/`,并可选指向某个 LoadImage 节点。走 ComfyUI 原生 `/upload/image`,不经桥接——文件读取与上传由 host 从本机发起(云端 ComfyUI 场景下 DSH 机器与 ComfyUI 机器可能不同机,必须由 host 发起)。
133
+ - `comfyui_inject_text`:把一段文本写入某节点 widget;或新建一个源节点、填值、再连到目标输入——「对话文本作为独立可连线源」。是 `add_node + set_param + connect` 的一步封装;只改已有 widget 时 `set_param` 已够,`inject_text` 用于「新建源并连线」。
134
+ - `comfyui_export_api`:把当前画布导出为 API 格式工作流 JSON(`/prompt` 与 comfy-cli `run_workflow` 所需格式),打通「画布 ↔ MCP」衔接——画布上调好图,导出后交 comfy-cli 无人值守批量跑。
135
+
136
+ > 架构边界:文件传输(图片→`input/`)走 host + 原生 API;画布节点操作走桥接 command;读结果走原生 `/history`+`/view`,三层不混。
137
+
138
+ ### 画布驱动 vs MCP——两种操控 ComfyUI 的方式
139
+
140
+ 本插件是**画布驱动**:它看到并编辑用户**正在看的那张活画布**(加节点、连线、改参数、运行,并用 `comfyui_get_outputs` 取回本次出图、用 `comfyui_batch_run` 扫参),无需保存工作流文件。
141
+
142
+ 如果要做**流水线/无人值守**类的批量任务,ComfyUI 官方的 **Comfy CLI(comfy-cli)** 是与之互补的工具。它是一个独立的 Python CLI(通过 `pip install comfy-cli` 安装,也可对外暴露 MCP 服务器)——**不是** DSH 插件,因此与本插件分开使用,而非挂进 DSH profile。它覆盖本画布插件**刻意不重复实现**的能力:
143
+
144
+ | 能力 | 本插件(画布) | ComfyUI CLI(comfy-cli) |
145
+ |---|---|---|
146
+ | 操作用户正在看的活画布 | ✅ | — |
147
+ | 直接运行已保存 / API 格式工作流文件 | ✅(经画布) | ✅(直接) |
148
+ | 批量排队 + 取回输出图 | ✅(`batch_run` + `get_outputs`) | ✅(`run_workflow` + `fetch_outputs`) |
149
+ | 官方工作流模板 | — | ✅(`templates`) |
150
+ | 模型下载 / 管理 | — | ✅(`models`) |
151
+ | 托管 / 付费模型(Flux、Veo…) | — | ✅(`partner`) |
152
+ | 图结构预检(validate / 依赖) | ✅(`debug`,本地) | ✅(`validate`,服务端) |
153
+
154
+ **推荐分工**:在画布上**构建/调优**工作流时用本插件;需要**以相同图无人值守规模化执行**(批量流水线、模板、模型管理、托管模型)时用 Comfy CLI。两者连的是同一个 ComfyUI 实例,可并存使用。安装 Comfy CLI:
155
+
156
+ ```bash
157
+ pip install comfy-cli # 独立 CLI,非 DSH 插件 —— 见 https://github.com/Comfy-Org/comfy-cli
158
+ ```
159
+
160
+ ---
161
+
162
+ ## 环境要求
163
+
164
+ - DeepSeek Harness Web(DSH),Node `^22.19.0 || >=24`
165
+ - 运行中的 ComfyUI(默认本地 `127.0.0.1:8188`,云端需自行部署桥接节点并确保 DSH 可达),且已装桥接节点
166
+ - 浏览器打开过 ComfyUI 画布页(画布标签会自动加载)
167
+
168
+ ---
169
+
170
+ ## 开发
171
+
172
+ ```bash
173
+ npm run check # node --check 校验 lib 两个文件
174
+ ```
175
+
176
+ 插件位于 DSH profile 的 `node_modules/dsh-comfyui-canvas`;`lib/index.js` 是 host 端工具、`lib/client.js` 是 web 端,改完重启 DSH 生效。
177
+
178
+ ---
179
+
180
+ ## 仓库结构
181
+
182
+ ```
183
+ dsh-comfyui-canvas/
184
+ ├── cordis.patch.yml # DSH bundle 加载层(自动挂载)
185
+ ├── comfyui-bridge/ # ComfyUI 侧桥接节点(随仓库发布)
186
+ │ └── ComfyUI-DSH-Canvas/
187
+ │ ├── __init__.py # ComfyUI 服务端 /dsh-bridge/* 路由
188
+ │ └── entry/bridge.js # 注入画布前端:上报画布 + 执行命令
189
+ ├── lib/
190
+ │ ├── index.js # DSH host:15 个画布工具 + 会话隔离模式
191
+ │ └── client.js # DSH web:画布标签 / 设置页 / 对话栏增强
192
+ ├── LICENSE
193
+ ├── README.md
194
+ ├── README.zh.md # 本文档
195
+ └── package.json
196
+ ```
197
+
198
+ **桥接节点**是唯一的 ComfyUI 侧依赖:它暴露 `/dsh-bridge/workflow | report | command | result`,并通过 `app.registerExtension` 注入画布页面;没有它 agent 工具就够不到画布。
199
+
200
+ ---
201
+
202
+ ## 已知问题
203
+
204
+ _(当前无已知未决问题。此前"画布运行后节点预览不显示"已在 v0.1.1 修复:移除 iframe 的 `referrerpolicy="no-referrer"` 消除环境差异,并在 `bridge.js` 监听 ComfyUI `executed` 事件强制重绘画布。)_
205
+
206
+ ---
207
+
208
+ ## 许可证
209
+
210
+ MIT
@@ -0,0 +1,174 @@
1
+ # ComfyUI-DSH-Canvas bridge backend
2
+ # Exposes the live ComfyUI canvas state to the DSH-ComfyUI-Canvas plugin.
3
+ #
4
+ # M0: read path — the injected frontend passively reports graph changes.
5
+ # M1: write path — /dsh-bridge/command pushes LiteGraph commands to the
6
+ # frontend over the ComfyUI WebSocket, results are stored for the caller.
7
+ # M3: optional shared-token auth (DSH_BRIDGE_TOKEN), report body size limit,
8
+ # and per-tab command targeting via the last-reported clientId.
9
+
10
+ import os
11
+ import time
12
+ import uuid
13
+
14
+ import server
15
+ from aiohttp import web
16
+
17
+ WEB_DIRECTORY = "entry"
18
+ NODE_CLASS_MAPPINGS = {}
19
+ __all__ = ["NODE_CLASS_MAPPINGS"]
20
+
21
+ # Largest accepted /dsh-bridge/report body (bytes). A hostile or broken
22
+ # reporter must not be able to balloon ComfyUI's memory with one upload.
23
+ MAX_REPORT_BYTES = 8 * 1024 * 1024
24
+
25
+ # Optional shared secret. When set, every /dsh-bridge/* request must carry
26
+ # `Authorization: Bearer <token>`. When unset (the default), the bridge stays
27
+ # open for backward compatibility — same trust model as ComfyUI's own /prompt.
28
+ _BRIDGE_TOKEN = os.environ.get("DSH_BRIDGE_TOKEN", "").strip()
29
+
30
+ # Latest graph-change report from the injected frontend (M0 in-memory only).
31
+ _latest = {"nodes": [], "workflow": None, "prompt": None, "updated_at": None}
32
+
33
+ # Completed command results, keyed by command id (M1). Short-lived: the DSH
34
+ # tool polls a result, then this dict is trimmed opportunistically.
35
+ _command_results: dict[str, dict] = {}
36
+
37
+
38
+ def _authorized(request) -> bool:
39
+ """True when no token is configured, or the request carries it."""
40
+ if not _BRIDGE_TOKEN:
41
+ return True
42
+ header = request.headers.get("Authorization", "")
43
+ return header == f"Bearer {_BRIDGE_TOKEN}"
44
+
45
+
46
+ def _reject_unauthorized(request) -> web.Response | None:
47
+ if _authorized(request):
48
+ return None
49
+ return web.json_response({"ok": False, "error": "unauthorized"}, status=401)
50
+
51
+
52
+ @server.PromptServer.instance.routes.get("/dsh-bridge/workflow")
53
+ async def get_workflow(request):
54
+ """DSH reads the last reported canvas state (nodes summary + workflow JSON)."""
55
+ denied = _reject_unauthorized(request)
56
+ if denied:
57
+ return denied
58
+ return web.json_response(_latest)
59
+
60
+
61
+ @server.PromptServer.instance.routes.post("/dsh-bridge/report")
62
+ async def report(request):
63
+ """The injected frontend posts graph changes here.
64
+
65
+ This endpoint is called by the injected bridge.js inside the ComfyUI page,
66
+ which cannot hold the optional token, so it is intentionally NOT gated by
67
+ `_reject_unauthorized`. It only mutates the in-memory `_latest` snapshot
68
+ and never triggers execution, so it adds no execution surface beyond what
69
+ ComfyUI's own unauthenticated /prompt already exposes.
70
+ """
71
+ try:
72
+ raw = await request.read()
73
+ except Exception:
74
+ return web.Response(status=400, text="read failed")
75
+ if len(raw) > MAX_REPORT_BYTES:
76
+ return web.Response(status=413, text=f"report body exceeds {MAX_REPORT_BYTES} bytes")
77
+ try:
78
+ import json
79
+
80
+ data = json.loads(raw)
81
+ except Exception:
82
+ return web.Response(status=400, text="invalid json")
83
+ if not isinstance(data, dict):
84
+ return web.Response(status=400, text="expected a json object")
85
+ _latest["nodes"] = data.get("nodes") or []
86
+ _latest["workflow"] = data.get("workflow")
87
+ _latest["prompt"] = data.get("prompt")
88
+ _latest["client_id"] = data.get("clientId")
89
+ _latest["updated_at"] = time.time()
90
+ return web.json_response({"accepted": True})
91
+
92
+
93
+ @server.PromptServer.instance.routes.post("/dsh-bridge/command")
94
+ async def command(request):
95
+ """DSH tool dispatches a LiteGraph command to the canvas frontend.
96
+
97
+ The command is forwarded to every connected ComfyUI frontend over the
98
+ native WebSocket (`send_sync`); the injected bridge.js listens for the
99
+ `dsh-bridge-command` event, executes it, and POSTs the result back to
100
+ /dsh-bridge/result. M3 targets the command at the last-reported frontend
101
+ (its `clientId`) so multiple open tabs do not each execute the command.
102
+ """
103
+ denied = _reject_unauthorized(request)
104
+ if denied:
105
+ return denied
106
+ try:
107
+ data = await request.json()
108
+ except Exception:
109
+ return web.Response(status=400, text="invalid json")
110
+ if not isinstance(data, dict) or "cmd" not in data:
111
+ return web.Response(status=400, text="expected { cmd, ... }")
112
+
113
+ cmd_id = data.get("id") or uuid.uuid4().hex
114
+ target = _latest.get("client_id")
115
+ if not target:
116
+ # No frontend has reported yet, so there is no safe single executor.
117
+ # Broadcasting would make every open tab run the command (duplicates).
118
+ # Refuse with a diagnostic instead — the caller should read the canvas
119
+ # first (comfyui_read_workflow triggers a refresh_report), which
120
+ # establishes the target clientId.
121
+ return web.json_response({
122
+ "ok": False,
123
+ "accepted": False,
124
+ "id": cmd_id,
125
+ "error": "no ComfyUI frontend has reported yet — read the canvas once (or open the ComfyUI page) so the bridge can target a single tab",
126
+ })
127
+ payload = {
128
+ "id": cmd_id,
129
+ "cmd": data.get("cmd"),
130
+ "payload": data.get("payload", {}),
131
+ # Only the frontend that last reported should act on the command.
132
+ "target": target,
133
+ }
134
+ try:
135
+ server.PromptServer.instance.send_sync("dsh-bridge-command", payload)
136
+ except Exception as exc:
137
+ return web.json_response({"ok": False, "error": f"ws send failed: {exc}"})
138
+ return web.json_response({"accepted": True, "id": cmd_id})
139
+
140
+
141
+ @server.PromptServer.instance.routes.post("/dsh-bridge/result")
142
+ async def result(request):
143
+ """The injected frontend reports a command's outcome.
144
+
145
+ Frontend-originated like /report, so not token-gated. A spoofed POST would
146
+ need to guess the random command id, and it only records an in-memory
147
+ outcome — it never dispatches execution.
148
+ """
149
+ try:
150
+ data = await request.json()
151
+ except Exception:
152
+ return web.Response(status=400, text="invalid json")
153
+ cmd_id = data.get("id")
154
+ if not cmd_id:
155
+ return web.Response(status=400, text="missing id")
156
+ _command_results[cmd_id] = data
157
+ # Opportunistic trim: keep only the newest 200 results.
158
+ if len(_command_results) > 200:
159
+ for stale in list(_command_results)[: len(_command_results) - 200]:
160
+ _command_results.pop(stale, None)
161
+ return web.json_response({"accepted": True})
162
+
163
+
164
+ @server.PromptServer.instance.routes.get("/dsh-bridge/result/{cmd_id}")
165
+ async def get_result(request):
166
+ """DSH tool polls a command result by id."""
167
+ denied = _reject_unauthorized(request)
168
+ if denied:
169
+ return denied
170
+ cmd_id = request.match_info.get("cmd_id", "")
171
+ item = _command_results.get(cmd_id)
172
+ if item is None:
173
+ return web.json_response({"ok": False, "error": "not_found"})
174
+ return web.json_response(item)