@xiaobuyu/nodesign 0.0.6 → 0.0.7
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/package.json +1 -1
- package/server/api/assets/helpers.js +18 -0
- package/server/api/assets/notes.js +50 -0
- package/server/api/assets.js +11 -6
- package/server/api/instruction.js +3 -3
- package/server/engine/agent/events.js +11 -1
- package/server/engine/agent/hooks/failure.js +11 -1
- package/server/engine/agent/hooks/lifecycle.js +10 -39
- package/server/engine/agent/hooks/post-guidance.js +4 -38
- package/server/engine/agent/hooks/user-prompt-submit.js +11 -40
- package/server/engine/agent/hooks.js +4 -8
- package/server/engine/agent/memory-config.js +61 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +14 -12
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +24 -7
- package/server/engine/agent/prompts/tools/site-reference.md +13 -3
- package/server/engine/agent/session-loop.js +29 -18
- package/server/engine/agents/explorer.md +1 -1
- package/server/engine/agents/index.js +1 -1
- package/server/engine/mcp/index.js +4 -11
- package/server/engine/mcp/tools/arrange-on-board.js +3 -1
- package/server/engine/mcp/tools/browse-computer.js +2 -1
- package/server/engine/mcp/tools/browse-find-batch.js +20 -3
- package/server/engine/mcp/tools/browse.js +24 -2
- package/server/engine/mcp/tools/crystallize-skill.js +1 -1
- package/server/engine/mcp/tools/generate-image.js +50 -70
- package/server/engine/mcp/tools/helpers/codex-imagegen.js +139 -0
- package/server/engine/mcp/tools/helpers/rembg.js +7 -2
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +109 -0
- package/server/engine/mcp/tools/list-pages.js +11 -5
- package/server/engine/mcp/tools/organize-board.js +26 -1
- package/server/engine/mcp/tools/pin-to-board.js +26 -17
- package/server/engine/mcp/tools/read-board.js +19 -5
- package/server/engine/mcp/tools/relate-on-board.js +42 -4
- package/server/engine/mcp/tools/report-issue.js +14 -7
- package/server/engine/mcp/tools/screenshot.js +17 -7
- package/server/engine/mcp/tools/sketch-on-board.js +13 -5
- package/server/engine/mcp/tools/web-search.js +11 -15
- package/server/engine/mcp/tools/write-on-board.js +15 -6
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +3 -4
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +10 -1
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +4 -4
- package/server/index.js +0 -2
- package/server/lib/artifact-target.js +31 -14
- package/server/lib/board-kind-sizes.js +2 -3
- package/server/lib/canvas-id.js +29 -4
- package/server/lib/export-collect.js +3 -1
- package/server/lib/issues-store.js +3 -1
- package/server/lib/kinds/docx.js +11 -4
- package/server/lib/kinds/index.js +6 -16
- package/server/lib/kinds/site.js +40 -7
- package/server/lib/rewrite-refs.js +110 -0
- package/server/lib/ssrf-guard.js +9 -0
- package/server/lib/task-scan.js +18 -0
- package/server/projects/board-store.js +42 -1
- package/server/projects/memory-migration.js +85 -0
- package/server/projects/move-entry.js +11 -1
- package/server/projects/ui-config.js +2 -1
- package/server/projects/workspace-templates.js +13 -10
- package/server/projects/workspace.js +15 -15
- package/server/services/rembg-launcher.js +5 -2
- package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-BoOBaIyz.js} +1 -1
- package/web/dist/assets/DeckWindow-Bg3rgkvi.js +11 -0
- package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B_u6aX9o.js} +1 -1
- package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-BO-46Du5.js} +6 -6
- package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-ConPmzdM.js} +1 -1
- package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-FCO9yf-n.js} +1 -1
- package/web/dist/assets/{arc-D_khH_xN.js → arc-BLcEOSRX.js} +1 -1
- package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-kxiX4TeN.js} +1 -1
- package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-CptFksix.js} +1 -1
- package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-Dxmxj34F.js} +1 -1
- package/web/dist/assets/channel-DFyiuGL0.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-D9inx5oo.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DydfHlEc.js} +1 -1
- package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-BO7GxhSv.js} +1 -1
- package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BzP6YiQ_.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-CHcgcd0s.js} +1 -1
- package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-D2Wg5JtX.js} +1 -1
- package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-B2fCRfpM.js} +1 -1
- package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-JSLcsRlZ.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BCjvaeHm.js} +1 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-0uKv6oJ7.js} +1 -1
- package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-Bvt-gEmA.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-C9Ht02qC.js} +1 -1
- package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BfxZny4s.js} +1 -1
- package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-CrE0ZETE.js} +1 -1
- package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-BO8VQ4Fx.js} +1 -1
- package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-CHUZnR9F.js} +1 -1
- package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-DYQAz1Vu.js} +1 -1
- package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII-BDjivgLr.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Bpe_QVbH.js} +1 -1
- package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-BxhwuAu1.js} +1 -1
- package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-CCw2H08-.js} +1 -1
- package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-DtTc7MyI.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-BvORxMd2.js} +1 -1
- package/web/dist/assets/index-DGG5wIZs.js +2118 -0
- package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-DMcrkypD.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CZkTIbe-.js} +1 -1
- package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-DR0XazdE.js} +1 -1
- package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DN8VbwVu.js} +1 -1
- package/web/dist/assets/{layout-CnXiF_NB.js → layout-9_paCbyt.js} +1 -1
- package/web/dist/assets/{linear-BMW0u6G6.js → linear-g6Ct012e.js} +1 -1
- package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-BGt4omYC.js} +1 -1
- package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-uSfS-PBE.js} +1 -1
- package/web/dist/assets/{pending-edit-apply-AcdOQ9Ij.js → pending-edit-apply-uP4uvrOD.js} +1 -1
- package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DpeI935H.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-B7IuNvWa.js} +1 -1
- package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-ARzYpPQI.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-Cj4upkAo.js} +1 -1
- package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-CXJcyHWk.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-OaVwSnLP.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-1yrUbq4t.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-nYH5p9D6.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-CTtTVKoH.js} +1 -1
- package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-Dj5HvyNq.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +1 -0
- package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-umeT0HpB.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +8 -0
- package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-MqnQfHKB.js} +1 -1
- package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-b2liyZra.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-QMK-zNh1.js} +1 -1
- package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D5Mc6tfA.js} +1 -1
- package/web/dist/index.html +1 -1
- package/server/api/memory.js +0 -185
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +0 -583
- package/server/engine/mcp/tools/record-decision.js +0 -140
- package/web/dist/assets/DeckWindow-BpLv5082.js +0 -16
- package/web/dist/assets/channel-DGkccMO1.js +0 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +0 -1
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +0 -1
- package/web/dist/assets/index-GAamh1p4.js +0 -2152
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
本文 append 在 SDK preset `claude_code` 之后,是平台事实与硬约束。设计怎么做由 skill 讲
|
|
4
4
|
(deck 看 `deskskill-engine-mini`,站点看 `site-craft`,你自己判断何时 `Skill` 加载)。
|
|
5
|
-
工具用法不在这里教:胖工具(生图 /
|
|
5
|
+
工具用法不在这里教:胖工具(生图 / DirectEdit 细则 / 技术参考)在你第一次用到时由系统注入。
|
|
6
6
|
|
|
7
7
|
## 不是每次对话都是设计任务
|
|
8
8
|
|
|
@@ -84,10 +84,10 @@ cwd = 这个项目的工作区,所有路径默认相对 cwd。仓库路径你
|
|
|
84
84
|
| `canvas.template.html` | deck 起手模板,Read 后改写(加载 skill 时自动拷进来)。站点**没有**模板,从骨架和风格名自己写 |
|
|
85
85
|
| `notes/` | 便利贴(见下一节) |
|
|
86
86
|
| `assets/` | 上传素材 + 生成图;`assets/references/` 是逛站采回来的(palette / fonts / css / skeleton / motion json + 截图,出处在同目录 `.meta/`)。**开工前先看有没有现成的,别重复去搜**;参考图挑关键的一张 Read 看一眼再下笔。每轮开头的状态块首轮列全、之后只报新增 |
|
|
87
|
-
|
|
|
87
|
+
| `记忆/` | **你的长期记忆**(系统提示里那套 memory 机制的家)。画布可见:用户看得到每一条,也可能改过 —— 他改的算数。风格定案(色号/字体/材质/艺术方向)随手记一条 `type: project` 记忆,别等收尾 |
|
|
88
|
+
| `CLAUDE.md`(根上)| **项目档案**:指引 / 风格档案 / 用户习惯三节,每次会话全文进你的上下文。放定了就不常变的东西;硬约束改动要用户点头。生长中的事实写 `记忆/` 不写这里 |
|
|
88
89
|
| `.claude/skills/` `.claude/agents/` | 项目级自定义 skill / 子代理 |
|
|
89
90
|
| `exports/` | export_handoff 的落点 |
|
|
90
|
-
| `.claude/CLAUDE.md` | 项目指引。用户点头才写,别自作主张 |
|
|
91
91
|
| `.nd/<会话id>/` | 这次对话自己的暗档案(压缩历史)。系统写,你一般不用碰 |
|
|
92
92
|
|
|
93
93
|
`curl -L -o` 只能写到 cwd 底下,写别处静默失败。
|
|
@@ -127,7 +127,6 @@ index.html 直接在工作区根上的「根站」仍被识别,但**新站一
|
|
|
127
127
|
他看得到、翻得动、也能改 —— 这不是你的私人日志,是你们俩共用的白板边角。
|
|
128
128
|
|
|
129
129
|
- 一个文件 = 一张贴;`\n---\n` 分面(翻页看);每面首行 `# 标题`。
|
|
130
|
-
- **设计决策**调 `record_decision`,它追加到 `notes/决策.md` 的新一面。
|
|
131
130
|
- **讨论结论 / 参考素材消化 / 接力棒**("下次从这继续")直接 Write `notes/<slug>.md`。
|
|
132
131
|
搜索/调研的发现里值得留的也落一张 —— 你的上下文迟早压缩,贴纸不会。
|
|
133
132
|
- 别当垃圾桶:一张贴讲一件事,过时的删掉(文件删了贴就下墙)。每轮注入的清单
|
|
@@ -290,7 +289,7 @@ Edit/Write canvas 后系统会自动跑一致性校验(anchor 唯一 / layout-
|
|
|
290
289
|
出一张按时刻拼好的胶片条,缓动/过冲/硬切一张图看完,数值级判断再上 `trace_motion`,`saveVideo: true` 出 webm 给用户过目)· `screenshot_url`
|
|
291
290
|
(外部 URL 截图,找视觉参考用眼睛看)· `list_pages` · `read_page` ·
|
|
292
291
|
`query_elements` · `get_computed_styles` · `navigate_to_page` · `highlight` ·
|
|
293
|
-
`preview_deck` · `
|
|
292
|
+
`preview_deck` · `get_pending_changes` / `clear_pending_changes`
|
|
294
293
|
|
|
295
294
|
浏览器九件(常驻):`browser_navigate` · `browser_read` · `browser_click` ·
|
|
296
295
|
`browser_screenshot` · `browser_capture` · `browser_request_help` ·
|
|
@@ -340,7 +339,7 @@ batch**,一页一个回合太慢;逛到不确定的地方(要不要进、
|
|
|
340
339
|
所以你打开的页面是"给人看的",别把它停在一张错误页上就走。
|
|
341
340
|
|
|
342
341
|
按需先 `ToolSearch("select:mcp__nodesign__<tool>")` 拉 schema:`generate_image` ·
|
|
343
|
-
`remove_background` · `web_search` · `
|
|
342
|
+
`remove_background` · `web_search` · `export_handoff` ·
|
|
344
343
|
`pin_to_board` · `relate_on_board` ·
|
|
345
344
|
`read_board` · `arrange_on_board` · `create_on_board` · `organize_board` · `deliver_files` ·
|
|
346
345
|
`write_on_board` · `sketch_on_board` · `edit_sketch` · `finish_sketch` · `look_at_board` · `read_user_view` ·
|
|
@@ -358,12 +357,15 @@ noobai / noobai-eps / pony=danbooru 标签,anima=自然语言,krea2=12B 审
|
|
|
358
357
|
限制,那是谁说了算的问题:别替他判断"好不好看",别因为自己不喜欢就重滚。
|
|
359
358
|
看的成本要克制:默认清晰度扫一眼够判崩坏,别每张都 `detail:'high'`。
|
|
360
359
|
|
|
361
|
-
harness
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
360
|
+
**这套 harness 正处 beta 测试期,你就是它的一线测试员** —— harness 里的事用
|
|
361
|
+
`report_issue` 给维护者记一笔(进问题列表,不是给用户看的),分三类:
|
|
362
|
+
bug(行为错了)、friction(能用但绕路 —— 绕过一次就值得记,别等第二次)、
|
|
363
|
+
idea(没坏,但你看出了更好的做法:该存在的工具、能合三步为一步的参数、
|
|
364
|
+
以及**你自己摸出来的方法论** —— 某个套路/判断依据值得 harness 教给每个会话
|
|
365
|
+
或做成自动化的,写下来)。每一条都会被认真读,具体的会被做出来;犹豫要不要
|
|
366
|
+
报的时候,报。**这不是交差的替代品**:照常把活儿干完,顺手记一笔而已,别因为
|
|
367
|
+
报了就少交东西。自己犯的错(路径写错、Edit 没匹配上)不算,空泛的夸奖或
|
|
368
|
+
"感觉能更好"也不算 —— 没有可实施内容的上报是噪音。
|
|
367
369
|
|
|
368
370
|
用户说"把这套风格留下来""以后还想这么做"时用 `crystallize_skill`:把这次探索的**判断
|
|
369
371
|
依据**(为什么这个字号阶梯、他否掉了什么、这套气质在什么场合会失效)写成他自己的
|
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
> 注入,每 session 只注一次。SKILL.md 已含精简版核心要点,本文是深度参考。
|
|
5
5
|
>
|
|
6
6
|
> ⚠️ Gemini gateway 时代的参数段(imageSize / thinkingLevel / model 路由 flash-pro /
|
|
7
|
-
> Image Search Grounding / PDF 文档输入 / NB2 model card
|
|
8
|
-
>
|
|
9
|
-
>
|
|
10
|
-
> `NODESIGN_IMAGE_PROVIDER=gateway` 重开时再把那份取回来。
|
|
7
|
+
> Image Search Grounding / PDF 文档输入 / NB2 model card 失败模式)**已整体移出本文**
|
|
8
|
+
> (那份存档 08-24 已删:全仓无引用的孤儿文件;要复活 gateway 从 git 历史里捞)。
|
|
9
|
+
> 那些旋钮在当前后端上传了不报错也没效果,留在这里只会让你花心思拨一个不存在的开关。
|
|
11
10
|
|
|
12
11
|
## 前置 · 后端事实 —— 先读这节,它决定下面所有写法
|
|
13
12
|
|
|
@@ -344,6 +343,24 @@ Step 3: 给角色起名锚定("Maya, character from Reference 1")
|
|
|
344
343
|
|
|
345
344
|
每次只改一处,改完再看,别一轮里改五件事。
|
|
346
345
|
|
|
346
|
+
### G1. variationOf 模式 —— 系列图一致性用参数,别手写咒语
|
|
347
|
+
|
|
348
|
+
上面那套 "Change only… Keep… unchanged" 的骨架,工具已经内置成结构化参数:
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
{ "variationOf": "assets/generated/A1.png",
|
|
352
|
+
"change": "裙子换成薄荷绿短裙,长度到膝盖以上",
|
|
353
|
+
"preserve": ["pose","framing","face","hair","top","shoes"] }
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- `variationOf` 就是编辑基底(自动成为第 1 张 reference),`change` 是唯一要动的那处,
|
|
357
|
+
`preserve` 是必须一致的方面(**省略 = 全保,最安全**)。工具会展开成完整骨架并
|
|
358
|
+
自动追加通用禁止项(不凭空加背带/发饰、缺席也要保持 —— 都是真实翻过车的)。
|
|
359
|
+
- 何时用它而不是手写 § G 模板:**做系列**(换装矩阵、多页同角色、多帧同场景)。
|
|
360
|
+
组织成一棵树:先出一张锚图,之后每次调用只沿一个轴变一处。
|
|
361
|
+
- 手写 prompt 仍然适合单次精修(改光线、删元素这类一次性编辑)。
|
|
362
|
+
- 此模式下 `prompt` 可省略;传了就当补充说明附在骨架后。
|
|
363
|
+
|
|
347
364
|
## H. 连续帧的能力边界(实测,别重新踩)
|
|
348
365
|
|
|
349
366
|
| 用法 | 结果 |
|
|
@@ -390,9 +407,9 @@ Step 3: 给角色起名锚定("Maya, character from Reference 1")
|
|
|
390
407
|
|
|
391
408
|
## J. 调完必做
|
|
392
409
|
|
|
393
|
-
1.
|
|
394
|
-
|
|
395
|
-
|
|
410
|
+
1. **记进记忆** —— 定案的 anchor(prompt 骨架 + role + 产物路径 + 用户评价)写一条
|
|
411
|
+
`type: project` 记忆(`记忆/` 主题文件 + MEMORY.md 一行)。重生时能查回,
|
|
412
|
+
用户在画布上也看得到。
|
|
396
413
|
2. **关键节点的反馈循环**:cover / 第一个 portrait / logo 嵌入这种会被当跨页种子的 anchor,
|
|
397
414
|
生完在自然回话里邀请用户看一眼方向(生图的 image block 已自动渲染在 chat,他直接看得到)。
|
|
398
415
|
种子早定早收益。
|
|
@@ -21,12 +21,22 @@
|
|
|
21
21
|
|
|
22
22
|
(旧项目里工作区根上直接放 index.html 的「根站」仍被识别和支持,但**新站一律入夹**。)
|
|
23
23
|
|
|
24
|
-
**构建型站点**(Astro / 11ty / 自写 build
|
|
25
|
-
`dist/`(或 `out/` `build/` `_site/`,有 index.html
|
|
26
|
-
|
|
24
|
+
**构建型站点**(Vite / React / Vue / Astro / 11ty / 自写 build 脚本):源随便组织,
|
|
25
|
+
构建产物落 `dist/`(或 `out/` `build/` `_site/` `public/`,有 index.html 的那个自动
|
|
26
|
+
被认作**产物根**)。现代工程源码里的 `index.html`(引 `/src/main.tsx` 那种 dev 入口)
|
|
27
|
+
**不会**被误当产物 —— 构建完系统自动优先 dist。`.nd-project.json` 的 `"root": "<目录>"`
|
|
28
|
+
可显式指定产物根:**放站点文件夹里,路径相对该文件夹**(如 `观察日志/.nd-project.json`
|
|
29
|
+
写 `{"root":"dist"}`);根站的放工作区根。预览、截图、`list_pages`、
|
|
27
30
|
导出、发布看的**全是产物根** —— 改完源必须重新构建,否则用户看到的还是旧的。
|
|
31
|
+
构建必须用绝对路径或 `(cd <站点文件夹> && npm run build)` 子 shell 跑,**build 完
|
|
32
|
+
`ls` 确认产物真的落在了站点文件夹里的 dist/**(cwd 漂了会把 dist 建到别处)。
|
|
28
33
|
`node_modules` / 构建缓存系统永远不扫,不用进 .ndignore。
|
|
29
34
|
|
|
35
|
+
WebGL/Three.js(含 R3F)的一个静默坑:渲染器属性(如剖切要的
|
|
36
|
+
`localClippingEnabled`)传给 `<Canvas gl={{…}}>` 构造参数会被**静默忽略**(无报错
|
|
37
|
+
无效果),要在 `onCreated={({gl}) => { gl.localClippingEnabled = true }}` 里设;
|
|
38
|
+
`clippingPlanes` 裁掉的是法线**负**侧,"看起来没剖"先怀疑法线方向。
|
|
39
|
+
|
|
30
40
|
手写起手:`mkdir 观察日志`,然后 Write `观察日志/index.html`(文件夹名换成真站名;
|
|
31
41
|
没有起手模板,骨架从需求推、样式从风格名推)。**唯一硬要求是产物根上有 `index.html`**;
|
|
32
42
|
样式文件怎么拆怎么叫你定,按站的体量来。
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
import { loadInstalledPlugins } from './plugin-loader.js';
|
|
55
55
|
import { createHooks } from './hooks.js';
|
|
56
56
|
import { buildIsolationOptions, prepareAgentDirs, sandboxShimEnv } from './isolation.js';
|
|
57
|
+
import { MEMORY_EXTRA_GUIDELINES, mergeAgentSettings } from './memory-config.js';
|
|
57
58
|
import { createNodesignMcpServer } from '../mcp/index.js';
|
|
58
59
|
import { assertInitContract } from './init-contract.js';
|
|
59
60
|
import { createAgents, resolveDefaultFastModel } from '../agents/index.js';
|
|
@@ -329,8 +330,21 @@ export async function runSession({
|
|
|
329
330
|
// npm 缓存 + 沙盒可写 tmp($TMPDIR / pip 缓存):细节与教训见 isolation.js
|
|
330
331
|
const agentDirs = await prepareAgentDirs({ dataRoot: PROJECTS_DATA_ROOT, projectId, sessionId });
|
|
331
332
|
|
|
333
|
+
// ⛔ 服务器自己的运行姿态不许漏进 agent 环境(08-24 案):
|
|
334
|
+
// - NODE_ENV=production(pm2 注的)会让 agent 沙盒里的 npm install 静默跳过
|
|
335
|
+
// devDependencies —— 返回 0 还报 "up to date",vite/typescript 根本没装上,
|
|
336
|
+
// 构建道当场断。npm_config_production / npm_config_omit 是同一开关的旁路。
|
|
337
|
+
// - PWD 是 pm2 进程的 cwd(仓库根);spawn({cwd}) 不更新它,bash 会自校正但
|
|
338
|
+
// python/node/构建工具读 $PWD 拿到的就是错目录 ——"cwd 被重置到父目录"的
|
|
339
|
+
// 另一半病根。下面显式钉成 cwdRoot。
|
|
340
|
+
const {
|
|
341
|
+
NODE_ENV: _dropNodeEnv, npm_config_production: _dropNpmProd,
|
|
342
|
+
npm_config_omit: _dropNpmOmit, OLDPWD: _dropOldpwd,
|
|
343
|
+
...inheritedEnv
|
|
344
|
+
} = process.env;
|
|
332
345
|
const sdkEnv = {
|
|
333
|
-
...
|
|
346
|
+
...inheritedEnv,
|
|
347
|
+
PWD: cwdRoot,
|
|
334
348
|
ANTHROPIC_BASE_URL: baseUrlForBinary,
|
|
335
349
|
// 订阅模型:apiKeyForBinary = process.env 原值(通常 undefined)——binary 见到
|
|
336
350
|
// ANTHROPIC_API_KEY 会弃用 ~/.claude 订阅 OAuth,所以订阅路绝不能注入。
|
|
@@ -341,6 +355,8 @@ export async function runSession({
|
|
|
341
355
|
// auto-memory 强制开启分支(binary gate:DISABLE 置 falsy 值 = force on,
|
|
342
356
|
// 绕过 CLAUDE_CODE_SIMPLE 等后置门;前置门 U$/zl 若拦住则此招无效 → 走自建 B 计划)
|
|
343
357
|
CLAUDE_CODE_DISABLE_AUTO_MEMORY: '0',
|
|
358
|
+
// 记忆指导追加段(保留 SDK 原合同只追加产品口径,理由见 memory-config.js)
|
|
359
|
+
CLAUDE_COWORK_MEMORY_EXTRA_GUIDELINES: MEMORY_EXTRA_GUIDELINES,
|
|
344
360
|
// 工具搜索:非 alwaysLoad 的 MCP 工具延迟加载(省 ~25-30k 常驻 schema tokens),
|
|
345
361
|
// agent 用 ToolSearch 按需取。白名单见 mcp/index.js ALWAYS_LOAD_TOOLS
|
|
346
362
|
ENABLE_TOOL_SEARCH: 'true',
|
|
@@ -530,22 +546,6 @@ export async function runSession({
|
|
|
530
546
|
persistSession: true,
|
|
531
547
|
settingSources: ['project'],
|
|
532
548
|
|
|
533
|
-
// skipWebFetchPreflight 来自 runtime/platform.js(gateway key 模式永远关)
|
|
534
|
-
// 详细因果链见 platform.js 的 skipWebFetchPreflight 注释
|
|
535
|
-
settings: {
|
|
536
|
-
skipWebFetchPreflight: platform.skipWebFetchPreflight,
|
|
537
|
-
// 项目 memory(2026-07-28 正式启用):骑 SDK 原生 auto-memory ——
|
|
538
|
-
// 二级索引(MEMORY.md 常驻 + 主题文件按需召回)、召回监督器
|
|
539
|
-
// (run.memory_recall 前端已渲染)、auto-dream 后台固化全由 SDK 包办。
|
|
540
|
-
// ⚠️ 目录必须显式指到项目共享区:默认按 cwd 派生,而 cwd 是会话级的,
|
|
541
|
-
// 不指定则每个会话一座记忆孤岛;指定后全项目所有会话共享一套。
|
|
542
|
-
...(sharedRoot ? {
|
|
543
|
-
autoMemoryEnabled: true,
|
|
544
|
-
// 落在 .claude/agent-memory/auto —— 跟前端记忆卡读的是同一棵树
|
|
545
|
-
// (2026-07-28 修:之前指到 shared/agent-memory/auto,SDK 记了前端也看不到)
|
|
546
|
-
autoMemoryDirectory: path.join(sharedRoot, '.claude', 'agent-memory', 'auto'),
|
|
547
|
-
} : {}),
|
|
548
|
-
},
|
|
549
549
|
|
|
550
550
|
includePartialMessages: STREAMING_ENABLED,
|
|
551
551
|
// 子代理时间轴(2026-07-28):转发子代理完整对话(text/thinking 也带
|
|
@@ -582,7 +582,18 @@ export async function runSession({
|
|
|
582
582
|
|
|
583
583
|
// 隔离两道闸(sandbox 管 Bash,permissions.deny 管 Read/Write 这类进程内工具)
|
|
584
584
|
// 全在 agent/isolation.js 里,改之前读那份文件头上的四条实测教训。
|
|
585
|
-
|
|
585
|
+
// ⛔ settings 必须与 isolationOptions.settings 深合并 —— 08-15 起两处 settings
|
|
586
|
+
// 静默互吞八天(autoMemory*/skipWebFetchPreflight 全丢)。合并+出口断言在
|
|
587
|
+
// memory-config.js 的 mergeAgentSettings。
|
|
588
|
+
...(() => {
|
|
589
|
+
const isolation = buildIsolationOptions({ cwdRoot, sharedRoot, ...agentDirs, dataRoot: PROJECTS_DATA_ROOT, env: sdkEnv });
|
|
590
|
+
return {
|
|
591
|
+
...isolation,
|
|
592
|
+
settings: mergeAgentSettings(isolation.settings, {
|
|
593
|
+
skipWebFetchPreflight: platform.skipWebFetchPreflight, sharedRoot,
|
|
594
|
+
}),
|
|
595
|
+
};
|
|
596
|
+
})(),
|
|
586
597
|
|
|
587
598
|
toolConfig: {
|
|
588
599
|
askUserQuestion: { previewFormat: 'html' },
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
- `Read` / `Glob` / `Grep` —— 看本地 `./assets/` 里的素材 / `./spec.json` 决策档案
|
|
45
45
|
- `TodoWrite` —— 3 步以上研究列计划
|
|
46
46
|
|
|
47
|
-
**工具集外的工具**(用了也无效,且打破子代理边界):Write / Edit / Bash / screenshot_canvas / export /
|
|
47
|
+
**工具集外的工具**(用了也无效,且打破子代理边界):Write / Edit / Bash / screenshot_canvas / export /
|
|
48
48
|
AskUserQuestion —— 你的产物是信息不是修改,跟用户对话由主 agent 负责。研究就是研究。
|
|
49
49
|
|
|
50
50
|
---
|
|
@@ -137,7 +137,7 @@ export function createAgents({ mainModel, sdkModel, fastModel } = {}) {
|
|
|
137
137
|
// - TodoWrite(多步研究列计划)
|
|
138
138
|
// 不给:Write/Edit(不写代码)/ Bash(不要 shell shenanigans)/
|
|
139
139
|
// AskUserQuestion(子代理不直接跟用户说话)/ screenshot/export/
|
|
140
|
-
//
|
|
140
|
+
// Task(不允许嵌套子代理)
|
|
141
141
|
// ⏸ explorer 研究员(2026-08-14 用户暂时停用:"有点没用")。定义与
|
|
142
142
|
// prompt(explorer.md)原样保留,恢复 = 取消注释 + prelude 补回子代理
|
|
143
143
|
// 清单那两行。停用期间搜索/读外链由主 agent 直接用 web_search / WebFetch。
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 反馈层(用户在 canvas 上的直接编辑 + 评论 buffer):
|
|
11
11
|
* get_pending_changes / clear_pending_changes
|
|
12
12
|
* 产物层(NoDesign 差异化能力):
|
|
13
|
-
* expose_tweaks
|
|
13
|
+
* export_handoff …(expose_tweaks 08-24 暂退役待升级;record_decision 08-24 拆除,记忆体系接棒)
|
|
14
14
|
* 研究层:
|
|
15
15
|
* web_search(4 provider,CJK auto baidu)
|
|
16
16
|
*
|
|
@@ -33,7 +33,6 @@ import { withCapabilityGate } from './capability-gate.js';
|
|
|
33
33
|
import { makeScreenshotCanvasTool } from './tools/screenshot.js';
|
|
34
34
|
import { makeScreenshotUrlTool } from './tools/screenshot-url.js';
|
|
35
35
|
import { makeExportHandoffTool } from './tools/export-handoff.js';
|
|
36
|
-
import { makeRecordDecisionTool } from './tools/record-decision.js';
|
|
37
36
|
import { makeWebSearchTool } from './tools/web-search.js';
|
|
38
37
|
import { withTierGate } from './tools/tier-gate.js'; // 档位闸包装器(auth/tier.js;按项目 owner)
|
|
39
38
|
import { makeReadPageTool } from './tools/read-page.js';
|
|
@@ -55,7 +54,7 @@ import { makeNavigateToPageTool } from './tools/navigate-to-page.js';
|
|
|
55
54
|
import { makeHighlightTool } from './tools/highlight.js';
|
|
56
55
|
import { makePreviewDeckTool } from './tools/preview-deck.js';
|
|
57
56
|
import { makeBuildDocxTool } from './tools/build-docx.js';
|
|
58
|
-
|
|
57
|
+
// expose-tweaks 08-24 暂退役(能力不足待升级再加回):本体与前端 TweaksPanel 留档休眠,不注册
|
|
59
58
|
import { makeGetPendingChangesTool } from './tools/get-pending-changes.js';
|
|
60
59
|
import { makeClearPendingChangesTool } from './tools/clear-pending-changes.js';
|
|
61
60
|
import { makeGenerateImageTool } from './tools/generate-image.js';
|
|
@@ -103,7 +102,7 @@ import { makeLookupTagsTool } from './tools/lookup-tags.js';
|
|
|
103
102
|
const ALWAYS_LOAD_TOOLS = new Set([
|
|
104
103
|
'screenshot_canvas', 'read_page', 'list_pages', 'query_elements',
|
|
105
104
|
'get_computed_styles', 'navigate_to_page', 'highlight', 'preview_deck',
|
|
106
|
-
'
|
|
105
|
+
'get_pending_changes', 'clear_pending_changes',
|
|
107
106
|
// screenshot_url 常驻:explorer 显式 tools 列表没有 ToolSearch,defer 了它就
|
|
108
107
|
// 永远拉不到 schema;schema 本身很小(4 字段),常驻成本可忽略
|
|
109
108
|
'screenshot_url',
|
|
@@ -182,9 +181,6 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
|
|
|
182
181
|
// C10 export_handoff — 复用 exports.js 的 buildHandoffZip,写到 workspace/exports/
|
|
183
182
|
makeExportHandoffTool({ workspaceRoot, sharedRoot, projectId, sessionId, ctx }),
|
|
184
183
|
|
|
185
|
-
// C11 record_decision — 写入 spec.json decisions[] 设计意图档案
|
|
186
|
-
makeRecordDecisionTool({ workspaceRoot, sessionId, ctx }),
|
|
187
|
-
|
|
188
184
|
// crystallize_skill — 把探索出来的方法论固化成用户自己的 skill + 作品进橱窗
|
|
189
185
|
// (2026-07-30)。用户明确要求才调;写的是判断依据不是成品 HTML。
|
|
190
186
|
makeCrystallizeSkillTool({ projectId, sessionId, ctx }),
|
|
@@ -284,9 +280,6 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
|
|
|
284
280
|
makeGetPendingChangesTool({ workspaceRoot, ctx }),
|
|
285
281
|
makeClearPendingChangesTool({ workspaceRoot, ctx }),
|
|
286
282
|
|
|
287
|
-
// Tweaks 协议:agent 暴露 deck 专属可调参数 schema → 前端按 schema 渲染控件
|
|
288
|
-
makeExposeTweaksTool({ workspaceRoot, ctx }),
|
|
289
|
-
|
|
290
283
|
// 图片生成(gemini-3.1-flash-image-preview / Nano Banana 2,via NoDesk passthrough)
|
|
291
284
|
// 落档优先 sharedRoot/assets/generated/,fallback workspaceRoot/assets/generated/。
|
|
292
285
|
// 跨 session 共享靠 sessions/<sid>/assets softlink → shared/assets。
|
|
@@ -304,7 +297,7 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
|
|
|
304
297
|
// 关系线(2026-08-07):agent 把「这版改自那版」「这两个是对照」这类
|
|
305
298
|
// **只有它知道**的关系画到画布上。画布知道每个产物是什么,但不知道它们
|
|
306
299
|
// 之间是什么关系 —— 那是北极星(排出有版面感的布局)真正缺的那一块。
|
|
307
|
-
makeRelateOnBoardTool({ projectId, ctx }),
|
|
300
|
+
makeRelateOnBoardTool({ sharedRoot, projectId, ctx }),
|
|
308
301
|
// agent 摆位四件(2026-08-14):看版面 / 语义摆位·立主角 / 落手写便签 / 归纳收纳
|
|
309
302
|
makeReadBoardTool({ projectId }),
|
|
310
303
|
makeArrangeOnBoardTool({ projectId, ctx }),
|
|
@@ -90,7 +90,9 @@ drag things afterwards — their move wins, do not fight it.`,
|
|
|
90
90
|
if (subjectEntry) {
|
|
91
91
|
const subjectLayer = layerOf(subject, subjectEntry, known);
|
|
92
92
|
if (subjectLayer !== anchorLayer) {
|
|
93
|
-
return err(`不同层(${subjectLayer || '根'} vs ${anchorLayer || '根'}
|
|
93
|
+
return err(`不同层(${subjectLayer || '根'} vs ${anchorLayer || '根'}):摆位只在同一层内有意义。`
|
|
94
|
+
+ '要摆到一起:先把文件放进同一个文件夹(assets/ 里的素材用 cp 复制过去 —— 母版别挪走);'
|
|
95
|
+
+ '只是想表达"相关",用 relate_on_board 连一条线更合适。');
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -179,7 +179,8 @@ async function zoomShot(page, region, frame, lead) {
|
|
|
179
179
|
}
|
|
180
180
|
const [x0, y0, x1, y1] = region.map(Math.round);
|
|
181
181
|
if (x0 < 0 || y0 < 0 || x1 > frame.w || y1 > frame.h || x1 - x0 < 8 || y1 - y0 < 8) {
|
|
182
|
-
return asText(`Error: zoom region [${region.join(', ')}] must lie inside the ${frame.w}×${frame.h}
|
|
182
|
+
return asText(`Error: zoom region [${region.join(', ')}] must lie inside the ${frame.w}×${frame.h} LIVE VIEWPORT and be at least 8×8. `
|
|
183
|
+
+ 'zoom takes viewport pixels — coordinates from a fullPage screenshot are in page space and do NOT work here; scroll the target into view first, or subtract the scroll offset.', true);
|
|
183
184
|
}
|
|
184
185
|
const clip = { x: toPage(x0, frame), y: toPage(y0, frame), width: toPage(x1 - x0, frame), height: toPage(y1 - y0, frame) };
|
|
185
186
|
const buf = await page.screenshot({ type: 'png', clip });
|
|
@@ -104,6 +104,7 @@ export function makeBatchTool({ name, description, tools, batchable, finalShot }
|
|
|
104
104
|
const out = [];
|
|
105
105
|
const n = actions.length;
|
|
106
106
|
let failedAt = -1;
|
|
107
|
+
let failText = '';
|
|
107
108
|
let lastHadImage = false;
|
|
108
109
|
for (let i = 0; i < n; i += 1) {
|
|
109
110
|
const { name: toolName, input } = actions[i];
|
|
@@ -112,14 +113,16 @@ export function makeBatchTool({ name, description, tools, batchable, finalShot }
|
|
|
112
113
|
const def = byName.get(toolName);
|
|
113
114
|
if (!def) {
|
|
114
115
|
failedAt = i;
|
|
115
|
-
|
|
116
|
+
failText = `"${toolName}" is not batchable here (use one of ${names.join(', ')}).`;
|
|
117
|
+
out.push({ type: 'text', text: `${label}: Error: ${failText}` });
|
|
116
118
|
continue;
|
|
117
119
|
}
|
|
118
120
|
const parsed = z.object(def.inputSchema).safeParse(input || {});
|
|
119
121
|
if (!parsed.success) {
|
|
120
122
|
failedAt = i;
|
|
121
123
|
const why = parsed.error.issues.map(is => `${is.path.join('.') || '(root)'}: ${is.message}`).join('; ');
|
|
122
|
-
|
|
124
|
+
failText = `invalid input — ${why}`;
|
|
125
|
+
out.push({ type: 'text', text: `${label}: Error: ${failText}` });
|
|
123
126
|
continue;
|
|
124
127
|
}
|
|
125
128
|
let r;
|
|
@@ -135,7 +138,21 @@ export function makeBatchTool({ name, description, tools, batchable, finalShot }
|
|
|
135
138
|
if (b.type === 'text' && !prefixed) { out.push({ type: 'text', text: `${label}: ${b.text}` }); prefixed = true; }
|
|
136
139
|
else out.push(b);
|
|
137
140
|
}
|
|
138
|
-
if (r?.isError)
|
|
141
|
+
if (r?.isError) {
|
|
142
|
+
failedAt = i;
|
|
143
|
+
failText = blocks.find(b => b.type === 'text')?.text || '(no error text)';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// 失败时把错误行提到最前面:下游(记账层截 120/500 字符、模型扫返回)都先看到
|
|
147
|
+
// 真正的报错,而不是前面成功步骤的输出。同时钉死"别整批重跑"——前面的步骤
|
|
148
|
+
// (click/type 这类非幂等动作)已经执行过了。
|
|
149
|
+
if (failedAt >= 0) {
|
|
150
|
+
const step = actions[failedAt];
|
|
151
|
+
out.unshift({
|
|
152
|
+
type: 'text',
|
|
153
|
+
text: `FAILED at step ${failedAt + 1}/${n} (${step.name}${step.input?.action ? ` ${step.input.action}` : ''}): ${String(failText).split('\n')[0]}\n`
|
|
154
|
+
+ `Steps 1-${failedAt} already ran — do NOT re-run the whole batch; continue from the failed step.`,
|
|
155
|
+
});
|
|
139
156
|
}
|
|
140
157
|
if (finalShot && screenshotAfter !== false && !lastHadImage) {
|
|
141
158
|
const shotDef = byName.get(finalShot.name);
|
|
@@ -121,7 +121,7 @@ and reopens on the next call — the profile survives, so you stay logged in.
|
|
|
121
121
|
The user has a browser card on their desktop and can watch, or take over.`,
|
|
122
122
|
{
|
|
123
123
|
url: z.string().min(4).describe('Absolute http(s) URL.'),
|
|
124
|
-
waitUntil: z.enum(['domcontentloaded', 'load', 'networkidle']).optional()
|
|
124
|
+
waitUntil: z.enum(['commit', 'domcontentloaded', 'load', 'networkidle']).optional()
|
|
125
125
|
.describe("How long to wait. Default 'domcontentloaded' — fastest and enough to read; measured on this machine, 'networkidle' costs an extra 1.6-4s per page, so do not reach for it by reflex. Use it only when a JS-built page comes back empty."),
|
|
126
126
|
digest: z.boolean().optional()
|
|
127
127
|
.describe('Include the page digest (default true). Set false only when you are navigating purely to screenshot and do not want the text.'),
|
|
@@ -175,9 +175,31 @@ The user has a browser card on their desktop and can watch, or take over.`,
|
|
|
175
175
|
].filter(Boolean).join('\n'), true);
|
|
176
176
|
}
|
|
177
177
|
if (navErr) {
|
|
178
|
+
// 超时 ≠ 全失败:commit 之后页面往往已有内容。半张页面也交回去 ——
|
|
179
|
+
// agent 拿到骨架就能决定继续还是换站(08-19 上报:原建议「试
|
|
180
|
+
// waitUntil:"load"」是往错方向推 —— load 比 domcontentloaded 严,
|
|
181
|
+
// 连 DOMContentLoaded 都没到换 load 只会更慢地再失败一次)
|
|
182
|
+
const timedOut = /Timeout/i.test(navErr);
|
|
183
|
+
let landedUrl = null;
|
|
184
|
+
try { const u = page.url(); if (timedOut && u !== before && /^https?:/.test(u)) landedUrl = u; } catch { /* */ }
|
|
185
|
+
if (landedUrl) {
|
|
186
|
+
let lines = [];
|
|
187
|
+
try {
|
|
188
|
+
const data = await collectPage(page);
|
|
189
|
+
if (!data.missing) lines = formatPage(data, { compact: true });
|
|
190
|
+
} catch { /* 半张页面读不出摘要也照样交回 */ }
|
|
191
|
+
try { ctx?.emit?.({ type: 'run.browser_opened', url: landedUrl, ts: new Date().toISOString() }); } catch { /* */ }
|
|
192
|
+
return asText([
|
|
193
|
+
`⚠️ 等待超时(${navErr}),但页面已部分加载 —— 当前状态如下,够用就继续(read/screenshot 都可),不够就换站:`,
|
|
194
|
+
...lines,
|
|
195
|
+
gate,
|
|
196
|
+
].filter(Boolean).join('\n'));
|
|
197
|
+
}
|
|
178
198
|
return asText([
|
|
179
199
|
`导航失败:${navErr}`,
|
|
180
|
-
|
|
200
|
+
timedOut
|
|
201
|
+
? '超时多半是广告/追踪脚本卡死或站点反自动化,不是"等得不够久"。别换 waitUntil:"load"(更严只会更慢地失败);可试 waitUntil:"commit"(一有响应就接手,配 read/screenshot 看拿到多少),或者直接换一个参考站。'
|
|
202
|
+
: '站点可能在挡自动化访问;换一个参考站。',
|
|
181
203
|
gate,
|
|
182
204
|
].filter(Boolean).join('\n'), true);
|
|
183
205
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (~/.nodesign/plugins/<userId>/<name>/),下次开新会话在**这个用户的所有项目**里
|
|
10
10
|
* 都能用(plugin 发现是 startup-time,当前会话不生效)。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* 跟长期记忆(记忆/ + 根 CLAUDE.md)的分工:记忆记的是**这个项目/这位用户**的持续状态,随项目走;
|
|
13
13
|
* 这里是跨项目复用的方法论,随用户走。一个是日志,一个是沉淀。
|
|
14
14
|
*
|
|
15
15
|
* 归属:写到**项目 owner** 的目录,不是"当前请求者"——同一个项目谁跑都该产出同一
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
|
|
42
42
|
import path from 'node:path';
|
|
43
43
|
import fs from 'node:fs/promises';
|
|
44
|
-
import { spawn } from 'node:child_process';
|
|
45
44
|
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
46
45
|
import { Events } from '../../agent/events.js';
|
|
47
46
|
import { z } from 'zod';
|
|
@@ -49,6 +48,9 @@ import sharp from 'sharp';
|
|
|
49
48
|
import {
|
|
50
49
|
THUMBNAIL_MAX_DIM, THUMBNAIL_QUALITY, writeWebpSibling, // 预热由 writeWebpSibling 顺带做
|
|
51
50
|
} from '../../../lib/image-variant.js';
|
|
51
|
+
import {
|
|
52
|
+
buildCodexBridgePrompt, runCodexImageGen, buildVariationPrompt, PRESERVE_KEYS,
|
|
53
|
+
} from './helpers/codex-imagegen.js';
|
|
52
54
|
|
|
53
55
|
// Thumbnail 配置(env 可调)。**原图不动**——保留 Gemini 输出的全分辨率(通常
|
|
54
56
|
// 1080×1920+ PNG,6-8MB)让用户最终交付不损失质量。仅生成低清 thumbnail 给
|
|
@@ -136,71 +138,9 @@ const DEFAULT_DMXAPI_BASE = 'https://www.dmxapi.cn';
|
|
|
136
138
|
const DEFAULT_CHANNEL = 'DMX';
|
|
137
139
|
|
|
138
140
|
// ── codex 生图桥(2026-07-27:NoDesk 网关退役,codex 成为默认 provider)──
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// 风格参照有效)。桥接 prompt 必须写死"逐字传递零改写"——codex agent 默认会
|
|
142
|
-
// 按自己的 Augmentation rules 润色 prompt。
|
|
141
|
+
// 桥的 prompt 组装 + 子进程执行 + 变体模式骨架都在 helpers/codex-imagegen.js
|
|
142
|
+
// (2026-08-24 迁出)。这里只留 provider 分流开关。
|
|
143
143
|
const IMAGE_PROVIDER = () => (process.env.NODESIGN_IMAGE_PROVIDER || 'codex').toLowerCase();
|
|
144
|
-
const CODEX_BIN = process.env.NODESIGN_CODEX_BIN || 'codex';
|
|
145
|
-
const CODEX_IMAGE_TIMEOUT_MS = Number(process.env.NODESIGN_CODEX_IMAGE_TIMEOUT_MS) || 240_000;
|
|
146
|
-
|
|
147
|
-
function buildCodexBridgePrompt({ prompt, aspectRatio, absOut, refCount }) {
|
|
148
|
-
return [
|
|
149
|
-
'你是图像生成管道的执行端,只做下面几件事,不做任何多余动作:',
|
|
150
|
-
'1. 调用你的图像生成工具生成一张图。<image-prompt> 标签内的内容必须逐字作为生成 prompt,禁止改写、增删、翻译或润色。',
|
|
151
|
-
`2. 输出比例:${aspectRatio}。优先用工具的比例/尺寸参数;工具没有对应参数时,作为补充说明传给工具,但不修改 <image-prompt> 原文。`,
|
|
152
|
-
refCount > 0
|
|
153
|
-
? `3. 本消息附带 ${refCount} 张参考图,把它们作为图像生成的参考输入(风格 / 主体一致性参照)。`
|
|
154
|
-
: '3. 本次无参考图。',
|
|
155
|
-
`4. 生成后把图片文件复制到精确路径 ${absOut}(目录已存在)。`,
|
|
156
|
-
'5. 最后只回复该绝对路径。',
|
|
157
|
-
'<image-prompt>',
|
|
158
|
-
prompt,
|
|
159
|
-
'</image-prompt>',
|
|
160
|
-
].join('\n');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 跑一次 codex exec 生图,以目标文件落盘为成功标准(codex 的文本回复不可信),
|
|
165
|
-
* 失败自动重试一次。abort signal / 超时都 SIGKILL 子进程。
|
|
166
|
-
*/
|
|
167
|
-
async function runCodexImageGen({ bridgePrompt, refPaths, cwd, signal, expectFile, timeoutMs = CODEX_IMAGE_TIMEOUT_MS }) {
|
|
168
|
-
const args = ['exec', '--skip-git-repo-check', '-s', 'workspace-write', '-C', cwd, bridgePrompt];
|
|
169
|
-
for (const p of refPaths) args.push('-i', p);
|
|
170
|
-
|
|
171
|
-
const runOnce = () => new Promise((resolve, reject) => {
|
|
172
|
-
const child = spawn(CODEX_BIN, args, { stdio: ['ignore', 'pipe', 'pipe'], env: process.env });
|
|
173
|
-
let stderrTail = '';
|
|
174
|
-
child.stdout.on('data', () => { /* 排空防背压 */ });
|
|
175
|
-
child.stderr.on('data', (d) => { stderrTail = (stderrTail + d.toString()).slice(-2000); });
|
|
176
|
-
const killTimer = setTimeout(() => {
|
|
177
|
-
try { child.kill('SIGKILL'); } catch { /* */ }
|
|
178
|
-
reject(new Error(`codex exec timeout after ${Math.round(timeoutMs / 1000)}s`));
|
|
179
|
-
}, timeoutMs);
|
|
180
|
-
const onAbort = () => { try { child.kill('SIGKILL'); } catch { /* */ } };
|
|
181
|
-
signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
182
|
-
child.on('error', (err) => { clearTimeout(killTimer); reject(err); });
|
|
183
|
-
child.on('close', (code) => {
|
|
184
|
-
clearTimeout(killTimer);
|
|
185
|
-
signal?.removeEventListener?.('abort', onAbort);
|
|
186
|
-
if (signal?.aborted) return reject(new Error('aborted'));
|
|
187
|
-
if (code !== 0) return reject(new Error(`codex exec exited ${code}: ${stderrTail.slice(-300) || 'no stderr'}`));
|
|
188
|
-
resolve();
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
193
|
-
try {
|
|
194
|
-
await runOnce();
|
|
195
|
-
const st = await fs.stat(expectFile).catch(() => null);
|
|
196
|
-
if (st && st.size > 0) return;
|
|
197
|
-
throw new Error(`codex finished but target file missing/empty: ${expectFile}`);
|
|
198
|
-
} catch (err) {
|
|
199
|
-
if (attempt === 2 || signal?.aborted) throw err;
|
|
200
|
-
console.warn(`[generate-image] codex attempt ${attempt} failed (${err.message}), retrying once`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
144
|
|
|
205
145
|
const PASSTHROUGH_PATH = '/default/passthrough';
|
|
206
146
|
// model id 在 callGateway 时动态拼,因为支持 flash / pro 路由
|
|
@@ -413,6 +353,15 @@ REFERENCES:
|
|
|
413
353
|
conversation. To iterate on a previous image, pass it back in referenceImages
|
|
414
354
|
and restate the invariants every round.
|
|
415
355
|
|
|
356
|
+
VARIATION MODE (series consistency — outfit swaps, one-detail edits):
|
|
357
|
+
Instead of hand-writing "change only X, keep Y" prompts, pass
|
|
358
|
+
variationOf (path of the base image) + change (the ONE thing to alter)
|
|
359
|
+
+ optional preserve (which aspects must stay identical; default all).
|
|
360
|
+
The tool expands these into a battle-tested skeleton incl. standard
|
|
361
|
+
prohibitions (no invented straps/ornaments, absences preserved). Build
|
|
362
|
+
series as a tree: one anchor, then vary one axis per call. In this mode
|
|
363
|
+
prompt is optional (used as extra notes).
|
|
364
|
+
|
|
416
365
|
WHEN TO USE:
|
|
417
366
|
- You're building a deck / landing / report and want real imagery
|
|
418
367
|
- You need a backdrop that pure CSS gradient can't achieve
|
|
@@ -424,15 +373,29 @@ WHEN NOT TO USE:
|
|
|
424
373
|
- Data charts — use Recharts/ECharts/Mermaid via React mount
|
|
425
374
|
- Simple inline icons (≤5 per page) — use lucide-react inline SVG
|
|
426
375
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
rationale (both required), scope, alternatives — there is no "topic" param.`,
|
|
376
|
+
After a generation that settles style direction, record the settled anchor as a
|
|
377
|
+
memory (记忆/, type: project) so later sessions inherit it.`,
|
|
430
378
|
{
|
|
431
379
|
prompt: z
|
|
432
380
|
.string()
|
|
433
381
|
.min(4)
|
|
434
382
|
.max(3500)
|
|
435
|
-
.
|
|
383
|
+
.optional()
|
|
384
|
+
.describe('Natural-language scene description. Describe, don\'t list keywords. Required unless variationOf is set (then it is appended as extra notes).'),
|
|
385
|
+
variationOf: z
|
|
386
|
+
.string()
|
|
387
|
+
.optional()
|
|
388
|
+
.describe('VARIATION MODE: workspace-relative path of the base image to reproduce with ONE change. Becomes the first reference image (edit target).'),
|
|
389
|
+
change: z
|
|
390
|
+
.string()
|
|
391
|
+
.min(4)
|
|
392
|
+
.max(500)
|
|
393
|
+
.optional()
|
|
394
|
+
.describe('VARIATION MODE: the single thing to alter, e.g. "裙子换成薄荷绿短裙,长度到膝盖以上". Required with variationOf.'),
|
|
395
|
+
preserve: z
|
|
396
|
+
.array(z.enum(PRESERVE_KEYS))
|
|
397
|
+
.optional()
|
|
398
|
+
.describe('VARIATION MODE: aspects that must stay identical. Omit = all of them (safest).'),
|
|
436
399
|
aspectRatio: z
|
|
437
400
|
.enum(ASPECT_RATIOS)
|
|
438
401
|
.optional()
|
|
@@ -476,6 +439,9 @@ rationale (both required), scope, alternatives — there is no "topic" param.`,
|
|
|
476
439
|
},
|
|
477
440
|
async ({
|
|
478
441
|
prompt,
|
|
442
|
+
variationOf,
|
|
443
|
+
change,
|
|
444
|
+
preserve,
|
|
479
445
|
aspectRatio = '16:9',
|
|
480
446
|
imageSize = '1K',
|
|
481
447
|
referenceImages,
|
|
@@ -496,6 +462,19 @@ rationale (both required), scope, alternatives — there is no "topic" param.`,
|
|
|
496
462
|
isError: true,
|
|
497
463
|
};
|
|
498
464
|
}
|
|
465
|
+
// 变体模式归一化:variationOf 只是 referenceImages[0] 的语义化别名,
|
|
466
|
+
// prompt 换成固定骨架 —— 下游(ref 解析 / -i 附件 / gateway inline)零改动。
|
|
467
|
+
const isVariation = Boolean(variationOf);
|
|
468
|
+
if (isVariation && !change) {
|
|
469
|
+
return { content: [{ type: 'text', text: 'generate_image failed: variationOf requires `change` (the ONE thing to alter).' }], isError: true };
|
|
470
|
+
}
|
|
471
|
+
if (!isVariation && !prompt) {
|
|
472
|
+
return { content: [{ type: 'text', text: 'generate_image failed: `prompt` is required (or use variationOf + change).' }], isError: true };
|
|
473
|
+
}
|
|
474
|
+
if (isVariation) {
|
|
475
|
+
prompt = buildVariationPrompt({ change, preserve, extra: prompt });
|
|
476
|
+
referenceImages = [variationOf, ...(referenceImages || [])];
|
|
477
|
+
}
|
|
499
478
|
// 1. provider 分流(2026-07-27:NoDesk 网关退役,默认 codex 订阅生图;
|
|
500
479
|
// gateway 分支保留给显式 NODESIGN_IMAGE_PROVIDER=gateway 的场景)
|
|
501
480
|
const provider = IMAGE_PROVIDER();
|
|
@@ -550,7 +529,7 @@ rationale (both required), scope, alternatives — there is no "topic" param.`,
|
|
|
550
529
|
fileName = `${finalName}.png`;
|
|
551
530
|
absOut = path.join(outDir, fileName);
|
|
552
531
|
const bridgePrompt = buildCodexBridgePrompt({
|
|
553
|
-
prompt, aspectRatio, absOut, refCount: resolvedRefs.length,
|
|
532
|
+
prompt, aspectRatio, absOut, refCount: resolvedRefs.length, variation: isVariation,
|
|
554
533
|
});
|
|
555
534
|
try {
|
|
556
535
|
await runCodexImageGen({
|
|
@@ -706,6 +685,7 @@ rationale (both required), scope, alternatives — there is no "topic" param.`,
|
|
|
706
685
|
provider,
|
|
707
686
|
model: provider === 'codex' ? 'codex' : model,
|
|
708
687
|
referenceImageCount: resolvedRefs.length,
|
|
688
|
+
variationOf: variationOf || null, // 变体血缘:产物墙 / 引用层能顺出系列树
|
|
709
689
|
sessionId: ctx?.sessionId || null,
|
|
710
690
|
runId: ctx?.runId || null,
|
|
711
691
|
ts: new Date().toISOString(),
|