@xiaobuyu/nodesign 0.0.6 → 0.0.8

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.
Files changed (383) hide show
  1. package/README.md +266 -79
  2. package/README.zh-CN.md +254 -67
  3. package/package.json +2 -2
  4. package/server/api/admin.js +7 -0
  5. package/server/api/assets/docx-page.js +5 -4
  6. package/server/api/assets/helpers.js +26 -0
  7. package/server/api/assets/notes.js +50 -0
  8. package/server/api/assets.js +31 -38
  9. package/server/api/board.js +29 -1
  10. package/server/api/browse.js +3 -2
  11. package/server/api/chatai.js +9 -8
  12. package/server/api/exports/cards.js +7 -6
  13. package/server/api/exports/docx-pdf.js +4 -1
  14. package/server/api/instruction.js +3 -3
  15. package/server/api/local.js +10 -8
  16. package/server/api/projects.js +19 -3
  17. package/server/api/publish.js +3 -2
  18. package/server/api/sessions-rewind.js +220 -0
  19. package/server/api/sessions.js +58 -194
  20. package/server/api/stage.js +35 -0
  21. package/server/api/turn-model-switch.js +109 -0
  22. package/server/api/turn.js +52 -88
  23. package/server/auth/middleware.js +29 -6
  24. package/server/auth/tier.js +27 -7
  25. package/server/auth/users-store.js +14 -1
  26. package/server/engine/agent/actor-trail.js +110 -0
  27. package/server/engine/agent/agent-shared.js +22 -52
  28. package/server/engine/agent/beat-state.js +61 -0
  29. package/server/engine/agent/cast.js +322 -0
  30. package/server/engine/agent/events.js +18 -1
  31. package/server/engine/agent/hooks/beat-gate.js +73 -0
  32. package/server/engine/agent/hooks/failure.js +11 -1
  33. package/server/engine/agent/hooks/lifecycle.js +16 -41
  34. package/server/engine/agent/hooks/post-guidance.js +4 -38
  35. package/server/engine/agent/hooks/post-trim.js +1 -0
  36. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  37. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  38. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  39. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  40. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  41. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  42. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  43. package/server/engine/agent/hooks/slot-alias.js +30 -0
  44. package/server/engine/agent/hooks/user-prompt-submit.js +208 -47
  45. package/server/engine/agent/hooks.js +80 -14
  46. package/server/engine/agent/isolation.js +25 -2
  47. package/server/engine/agent/memory-config.js +87 -0
  48. package/server/engine/agent/model-context.js +102 -18
  49. package/server/engine/agent/model-table.js +361 -58
  50. package/server/engine/agent/prompts/nodesign-prelude.md +579 -70
  51. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  52. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +26 -9
  53. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  54. package/server/engine/agent/prompts/tools/site-reference.md +15 -4
  55. package/server/engine/agent/role-card.js +175 -0
  56. package/server/engine/agent/rp-mode.js +17 -0
  57. package/server/engine/agent/session-loop.js +55 -32
  58. package/server/engine/agent/stage-status.js +73 -0
  59. package/server/engine/agent/subagent-flight.js +73 -0
  60. package/server/engine/agent/system-prompts.js +41 -0
  61. package/server/engine/agent/tool-input-stream.js +165 -0
  62. package/server/engine/agents/explorer.md +1 -1
  63. package/server/engine/agents/index.js +1 -1
  64. package/server/engine/mcp/actor.js +59 -0
  65. package/server/engine/mcp/capability-gate.js +21 -2
  66. package/server/engine/mcp/index.js +118 -33
  67. package/server/engine/mcp/mode-profile.js +139 -0
  68. package/server/engine/mcp/server-name.js +19 -0
  69. package/server/engine/mcp/tools/artifact-session.js +2 -1
  70. package/server/engine/mcp/tools/browse-computer.js +2 -1
  71. package/server/engine/mcp/tools/browse-find-batch.js +95 -16
  72. package/server/engine/mcp/tools/browse.js +25 -3
  73. package/server/engine/mcp/tools/cast-role.js +167 -0
  74. package/server/engine/mcp/tools/crystallize-skill.js +1 -1
  75. package/server/engine/mcp/tools/draw-trend.js +104 -0
  76. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  77. package/server/engine/mcp/tools/edit-board.js +569 -0
  78. package/server/engine/mcp/tools/generate-image.js +50 -70
  79. package/server/engine/mcp/tools/helpers/codex-imagegen.js +139 -0
  80. package/server/engine/mcp/tools/helpers/rembg.js +7 -2
  81. package/server/engine/mcp/tools/helpers/shot-pipeline.js +109 -0
  82. package/server/engine/mcp/tools/list-pages.js +11 -5
  83. package/server/engine/mcp/tools/look-at-board.js +4 -3
  84. package/server/engine/mcp/tools/open-sheet.js +242 -0
  85. package/server/engine/mcp/tools/organize-board.js +45 -6
  86. package/server/engine/mcp/tools/paint-still.js +1 -1
  87. package/server/engine/mcp/tools/pin-to-board.js +103 -19
  88. package/server/engine/mcp/tools/read-board.js +123 -16
  89. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  90. package/server/engine/mcp/tools/report-issue.js +15 -7
  91. package/server/engine/mcp/tools/role-memory.js +83 -0
  92. package/server/engine/mcp/tools/roll-dice.js +82 -0
  93. package/server/engine/mcp/tools/roll-film.js +1 -1
  94. package/server/engine/mcp/tools/screenshot.js +19 -9
  95. package/server/engine/mcp/tools/set-vars.js +150 -0
  96. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  97. package/server/engine/mcp/tools/web-search.js +12 -16
  98. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  99. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  100. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  101. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  102. package/server/engine/mcp/tools/write-on-board.js +568 -99
  103. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  104. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +14 -7
  105. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +44 -5
  106. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
  107. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +24 -8
  108. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  109. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  110. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  111. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  112. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  113. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  114. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  115. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  116. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  117. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  118. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  119. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  120. package/server/engine/runs/board-seater.js +272 -0
  121. package/server/engine/runs/board-tasklist.js +90 -48
  122. package/server/engine/runs/turn-relay.js +24 -2
  123. package/server/engine/runs/user-chalk-echo.js +44 -0
  124. package/server/index.js +2 -2
  125. package/server/lib/artifact-target.js +31 -14
  126. package/server/lib/board-anchor.js +49 -0
  127. package/server/lib/board-dirty.js +93 -0
  128. package/server/lib/board-excerpt.js +80 -0
  129. package/server/lib/board-follow.js +111 -0
  130. package/server/lib/board-groups.js +1 -5
  131. package/server/lib/board-hero.js +3 -1
  132. package/server/lib/board-kind-sizes.js +38 -3
  133. package/server/lib/board-lanes.js +58 -0
  134. package/server/lib/board-obstacles.js +80 -0
  135. package/server/lib/board-relations.js +5 -1
  136. package/server/lib/board-sheets.js +467 -0
  137. package/server/lib/board-shelf.js +86 -0
  138. package/server/lib/board-transform.js +62 -0
  139. package/server/lib/canvas-id.js +46 -4
  140. package/server/lib/chalk-flow.js +88 -0
  141. package/server/lib/chalk-rewrite.js +63 -0
  142. package/server/lib/chalk-size-pref.js +48 -0
  143. package/server/lib/chalk.js +42 -3
  144. package/server/lib/export-collect.js +3 -1
  145. package/server/lib/ingress/forward-openai-chat.js +16 -4
  146. package/server/lib/ingress/openai-chat.js +23 -12
  147. package/server/lib/ingress/session-routes.js +3 -3
  148. package/server/lib/ingress/stream-watchdog.js +53 -0
  149. package/server/lib/ingress/upstream-billing.js +15 -0
  150. package/server/lib/ingress/upstream-error-hints.js +36 -0
  151. package/server/lib/issues-store.js +3 -1
  152. package/server/lib/json-preview.js +58 -0
  153. package/server/lib/kinds/docx.js +11 -4
  154. package/server/lib/kinds/index.js +6 -16
  155. package/server/lib/kinds/site.js +40 -7
  156. package/server/lib/model-ingress.js +26 -1
  157. package/server/lib/moderation.js +10 -5
  158. package/server/lib/quota.js +26 -6
  159. package/server/lib/rect.js +50 -0
  160. package/server/lib/rewrite-refs.js +110 -0
  161. package/server/lib/screen.js +36 -0
  162. package/server/lib/sheet-state.js +19 -0
  163. package/server/lib/sketch-array.js +118 -0
  164. package/server/lib/sketch-hatch.js +99 -0
  165. package/server/lib/sketch-layout.js +269 -69
  166. package/server/lib/sketch-shapes.js +152 -0
  167. package/server/lib/sketch-stencils.js +170 -0
  168. package/server/lib/ssrf-guard.js +9 -0
  169. package/server/lib/state-table.js +237 -0
  170. package/server/lib/state-trend.js +94 -0
  171. package/server/lib/state-triggers.js +199 -0
  172. package/server/lib/task-scan.js +18 -0
  173. package/server/lib/tavern-json.js +48 -0
  174. package/server/lib/user-uploads.js +88 -0
  175. package/server/projects/board-limits.js +5 -3
  176. package/server/projects/board-sanitize.js +220 -10
  177. package/server/projects/board-store.js +137 -7
  178. package/server/projects/memory-migration.js +85 -0
  179. package/server/projects/move-entry.js +11 -1
  180. package/server/projects/session-jsonl.js +110 -0
  181. package/server/projects/store.js +56 -3
  182. package/server/projects/ui-config.js +2 -1
  183. package/server/projects/viewpoint-store.js +35 -3
  184. package/server/projects/workspace-slots.js +30 -0
  185. package/server/projects/workspace-templates.js +42 -10
  186. package/server/projects/workspace.js +25 -26
  187. package/server/runtime/capabilities.js +6 -2
  188. package/server/services/rembg-launcher.js +5 -2
  189. package/server/shared/locales.js +70 -0
  190. package/server/shared/messages-en.js +69 -0
  191. package/server/shared/messages.js +65 -0
  192. package/server/ws/broker.js +3 -0
  193. package/server/ws/index.js +43 -3
  194. package/web/README.md +1 -1
  195. package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-CcaGRWz5.js} +1 -1
  196. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  197. package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B9KZmU5z.js} +2 -2
  198. package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-DrfRnz8E.js} +43 -43
  199. package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-CJOjCF7d.js} +6 -6
  200. package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  201. package/web/dist/assets/{arc-D_khH_xN.js → arc-C1-HSUny.js} +1 -1
  202. package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  203. package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  204. package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  205. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  206. package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  207. package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  208. package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  209. package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  210. package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  211. package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  212. package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  213. package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  214. package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  215. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  216. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  217. package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  218. package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  219. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  220. package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  221. package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  222. package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  223. package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  224. package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  225. package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  226. package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  227. package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  228. package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  229. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  230. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  231. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  232. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  233. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  234. package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  235. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  236. package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  237. package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  238. package/web/dist/assets/{layout-CnXiF_NB.js → layout-Dhgck-Zo.js} +1 -1
  239. package/web/dist/assets/{linear-BMW0u6G6.js → linear-DMSQ-aUw.js} +1 -1
  240. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  241. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  242. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  243. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  314. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  315. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  316. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  317. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  318. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  319. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  320. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  321. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  322. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  323. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  324. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  325. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  326. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  327. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  328. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  329. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  330. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  331. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  332. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  333. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  334. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  335. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  336. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  337. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  338. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  339. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  340. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  341. package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  342. package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  343. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  344. package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  345. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  346. package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  347. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  348. package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-62liz7LX.js} +1 -1
  349. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  350. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  351. package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  352. package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  353. package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  354. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  355. package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  356. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  357. package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  358. package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  359. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  360. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  361. package/web/dist/index.html +2 -2
  362. package/server/api/memory.js +0 -185
  363. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +0 -583
  364. package/server/engine/mcp/tools/arrange-on-board.js +0 -124
  365. package/server/engine/mcp/tools/create-on-board.js +0 -152
  366. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  367. package/server/engine/mcp/tools/record-decision.js +0 -140
  368. package/server/engine/mcp/tools/relate-on-board.js +0 -112
  369. package/server/engine/mcp/tools/sketch-on-board.js +0 -337
  370. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  371. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +0 -78
  372. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  373. package/web/dist/assets/DeckWindow-BpLv5082.js +0 -16
  374. package/web/dist/assets/channel-DGkccMO1.js +0 -1
  375. package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +0 -1
  376. package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +0 -1
  377. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  378. package/web/dist/assets/index-GAamh1p4.js +0 -2152
  379. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  380. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  381. package/web/dist/assets/pending-edit-apply-AcdOQ9Ij.js +0 -31
  382. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
  383. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
@@ -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
- // codex CLI 订阅(零 API 费):spawn `codex exec` 让它调自带图像生成工具,
140
- // 图直接落到我们指定的绝对路径。实测单张 ~45-60s,参考图走 -i 附件(同样实测
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
- ALWAYS pair generation with mcp__nodesign__record_decision so the prompt + role
428
- land on the shared decision sticky (notes/decisions). Its params are title +
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
- .describe('Natural-language scene description. Describe, don\'t list keywords.'),
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(),
@@ -0,0 +1,139 @@
1
+ /**
2
+ * helpers/codex-imagegen.js — codex 生图桥的 prompt 组装 + 子进程执行
3
+ * (2026-08-24 从 generate-image.js 迁出:给棘轮腾行 + prompt 组装层归位一处)
4
+ *
5
+ * 两层各管一事:
6
+ * - buildVariationPrompt:变体模式(variationOf)的 prompt 骨架。把"要保持的
7
+ * 东西逐项列出"从 agent 手写咒语变成参数 —— 32 张换装立绘实战里,漏写一项
8
+ * (袜子)整行就坏,这套骨架把踩过的坑固化成默认禁止项。
9
+ * - buildCodexBridgePrompt / runCodexImageGen:codex CLI 桥(原样迁出,
10
+ * 仅第 3 条在变体模式下改口径:参考图第 1 张是编辑基底,不是风格参照)。
11
+ */
12
+
13
+ import fs from 'node:fs/promises';
14
+ import { spawn } from 'node:child_process';
15
+
16
+ export const CODEX_BIN = process.env.NODESIGN_CODEX_BIN || 'codex';
17
+ export const CODEX_IMAGE_TIMEOUT_MS = Number(process.env.NODESIGN_CODEX_IMAGE_TIMEOUT_MS) || 240_000;
18
+
19
+ /**
20
+ * 变体模式的 preserve 词表。键是 agent 传的枚举值,值是展开进 prompt 的英文短语。
21
+ * 短语必须把"缺席也是要保持的"写死(no socks 的参考图,结果也必须 no socks)——
22
+ * 这是实战里最容易漂的一类。
23
+ */
24
+ export const PRESERVE_PHRASES = {
25
+ pose: 'the exact same pose and body position',
26
+ framing: 'the same framing, camera distance and position of the subject in the frame',
27
+ proportions: 'the same height and body proportions',
28
+ face: 'the same face and the same expression',
29
+ hair: 'the same hairstyle and hair color',
30
+ hands: 'the same hand positions, holding the same things (empty hands stay empty)',
31
+ top: 'the same top garment — identical cut, color, sleeves, neckline and every detail',
32
+ bottom: 'the same bottom garment (skirt / trousers) — identical in every detail',
33
+ shoes: 'the same footwear, identical in every detail',
34
+ socks: 'the same socks / legwear (if the reference has none, the result has none)',
35
+ accessories: 'the same accessories — nothing added, nothing removed',
36
+ background: 'the same background',
37
+ lighting: 'the same lighting and color grading',
38
+ style: 'the same art style and rendering technique',
39
+ };
40
+ export const PRESERVE_KEYS = Object.keys(PRESERVE_PHRASES);
41
+
42
+ // 不论 preserve 传什么都追加的通用军规 —— 全部是真实翻过车的:凭空长出背带、
43
+ // 白袜整行消失、取景悄悄拉近。
44
+ const VARIATION_HARD_RULES = [
45
+ 'Do NOT invent anything that is not in the reference: no added straps, suspenders,'
46
+ + ' belts, hair ornaments, jewelry or extra garments.',
47
+ 'Absences are part of the reference: whatever the reference does not have, the result must not have.',
48
+ 'Do NOT change the subject\'s height, body proportions, or position in the frame.',
49
+ ].join('\n');
50
+
51
+ /**
52
+ * variationOf 模式:把 change + preserve 展开成"只改一处、其余照搬"的固定骨架。
53
+ * @param {object} o
54
+ * @param {string} o.change 唯一要改的那一处(自然语言)
55
+ * @param {string[]} [o.preserve] PRESERVE_KEYS 的子集;缺省 = 全部
56
+ * @param {string} [o.extra] 附注(agent 想额外说明的,放在骨架之后)
57
+ */
58
+ export function buildVariationPrompt({ change, preserve, extra }) {
59
+ const keys = (preserve && preserve.length > 0) ? preserve : PRESERVE_KEYS;
60
+ const phrases = keys.map((k) => PRESERVE_PHRASES[k]).filter(Boolean);
61
+ return [
62
+ 'Reproduce the reference illustration (the first reference image) with ONE single change.',
63
+ `THE ONLY CHANGE: ${change}`,
64
+ `Everything else must be identical to the reference: ${phrases.join('; ')}.`,
65
+ VARIATION_HARD_RULES,
66
+ ...(extra ? [extra] : []),
67
+ ].join('\n');
68
+ }
69
+
70
+ /**
71
+ * codex 桥接 prompt。必须写死"逐字传递零改写"——codex agent 默认会按自己的
72
+ * Augmentation rules 润色 prompt。变体模式下第 3 条改口径:第 1 张参考图是
73
+ * **编辑基底**,必须除指定改动外逐细节复现("风格参照"的软口径会被当成
74
+ * style reference,收敛不住)。
75
+ */
76
+ export function buildCodexBridgePrompt({ prompt, aspectRatio, absOut, refCount, variation = false }) {
77
+ let refLine;
78
+ if (refCount > 0 && variation) {
79
+ refLine = `3. 本消息附带 ${refCount} 张参考图。第 1 张是**编辑基底**:除 <image-prompt> 指定的那一处改动外,`
80
+ + '必须逐细节复现它的全部内容;其余参考图(如有)才是风格 / 一致性参照。';
81
+ } else if (refCount > 0) {
82
+ refLine = `3. 本消息附带 ${refCount} 张参考图,把它们作为图像生成的参考输入(风格 / 主体一致性参照)。`;
83
+ } else {
84
+ refLine = '3. 本次无参考图。';
85
+ }
86
+ return [
87
+ '你是图像生成管道的执行端,只做下面几件事,不做任何多余动作:',
88
+ '1. 调用你的图像生成工具生成一张图。<image-prompt> 标签内的内容必须逐字作为生成 prompt,禁止改写、增删、翻译或润色。',
89
+ `2. 输出比例:${aspectRatio}。优先用工具的比例/尺寸参数;工具没有对应参数时,作为补充说明传给工具,但不修改 <image-prompt> 原文。`,
90
+ refLine,
91
+ `4. 生成后把图片文件复制到精确路径 ${absOut}(目录已存在)。`,
92
+ '5. 最后只回复该绝对路径。',
93
+ '<image-prompt>',
94
+ prompt,
95
+ '</image-prompt>',
96
+ ].join('\n');
97
+ }
98
+
99
+ /**
100
+ * 跑一次 codex exec 生图,以目标文件落盘为成功标准(codex 的文本回复不可信),
101
+ * 失败自动重试一次。abort signal / 超时都 SIGKILL 子进程。
102
+ */
103
+ export async function runCodexImageGen({ bridgePrompt, refPaths, cwd, signal, expectFile, timeoutMs = CODEX_IMAGE_TIMEOUT_MS }) {
104
+ const args = ['exec', '--skip-git-repo-check', '-s', 'workspace-write', '-C', cwd, bridgePrompt];
105
+ for (const p of refPaths) args.push('-i', p);
106
+
107
+ const runOnce = () => new Promise((resolve, reject) => {
108
+ const child = spawn(CODEX_BIN, args, { stdio: ['ignore', 'pipe', 'pipe'], env: process.env });
109
+ let stderrTail = '';
110
+ child.stdout.on('data', () => { /* 排空防背压 */ });
111
+ child.stderr.on('data', (d) => { stderrTail = (stderrTail + d.toString()).slice(-2000); });
112
+ const killTimer = setTimeout(() => {
113
+ try { child.kill('SIGKILL'); } catch { /* */ }
114
+ reject(new Error(`codex exec timeout after ${Math.round(timeoutMs / 1000)}s`));
115
+ }, timeoutMs);
116
+ const onAbort = () => { try { child.kill('SIGKILL'); } catch { /* */ } };
117
+ signal?.addEventListener?.('abort', onAbort, { once: true });
118
+ child.on('error', (err) => { clearTimeout(killTimer); reject(err); });
119
+ child.on('close', (code) => {
120
+ clearTimeout(killTimer);
121
+ signal?.removeEventListener?.('abort', onAbort);
122
+ if (signal?.aborted) return reject(new Error('aborted'));
123
+ if (code !== 0) return reject(new Error(`codex exec exited ${code}: ${stderrTail.slice(-300) || 'no stderr'}`));
124
+ resolve();
125
+ });
126
+ });
127
+
128
+ for (let attempt = 1; attempt <= 2; attempt++) {
129
+ try {
130
+ await runOnce();
131
+ const st = await fs.stat(expectFile).catch(() => null);
132
+ if (st && st.size > 0) return;
133
+ throw new Error(`codex finished but target file missing/empty: ${expectFile}`);
134
+ } catch (err) {
135
+ if (attempt === 2 || signal?.aborted) throw err;
136
+ console.warn(`[generate-image] codex attempt ${attempt} failed (${err.message}), retrying once`);
137
+ }
138
+ }
139
+ }
@@ -39,13 +39,18 @@ export const REMBG_SETUP_HINT = IS_WIN
39
39
  : 'cd server && python3 -m venv .venv-rembg && .venv-rembg/bin/python3 -m pip install rembg onnxruntime';
40
40
  const DEFAULT_PYTHON = REMBG_VENV_PYTHON;
41
41
  const DEFAULT_HELPER = path.join(__dirname, 'rembg-bridge.py');
42
- const DEFAULT_SOCKET = '/tmp/nodesign-rembg.sock';
43
42
  // fallback spawn 的 timeout——首次冷启 + 模型 load 留余量
44
43
  const DEFAULT_FALLBACK_TIMEOUT_MS = 60_000;
45
44
 
46
45
  function resolvePython() { return process.env.NODESIGN_REMBG_PYTHON || DEFAULT_PYTHON; }
47
46
  function resolveHelper() { return process.env.NODESIGN_REMBG_HELPER || DEFAULT_HELPER; }
48
- function resolveSocket() { return process.env.NODESIGN_REMBG_SOCKET || DEFAULT_SOCKET; }
47
+ // socket 默认按**端口**派生(08-24 案:prod/exp 共用一个默认路径,exp 启动把生产的
48
+ // 抠图服务当 stale 杀掉再自己占坑)。端口一实例一个,是现成的实例身份。
49
+ // 这里是全仓唯一一份真相源,rembg-launcher.js import 它。
50
+ export function resolveRembgSocket() {
51
+ return process.env.NODESIGN_REMBG_SOCKET || `/tmp/nodesign-rembg-${process.env.PORT || '4001'}.sock`;
52
+ }
53
+ function resolveSocket() { return resolveRembgSocket(); }
49
54
 
50
55
  // service health check 缓存(避免每次 request 都探一遍 /health)
51
56
  let serviceHealthCache = { ok: false, checkedAt: 0 };
@@ -278,3 +278,112 @@ export async function runBeforeShot(page, beforeShot) {
278
278
  return `beforeShot error: ${err?.message || err}`;
279
279
  }
280
280
  }
281
+
282
+ /**
283
+ * 带兜底的整页/裁剪截图(iss_mt6tru7p:WebGL 持续动画页 + device:'mobile' 下
284
+ * page.screenshot 等稳定帧/字体 30s 必超时——rAF 每帧都在重绘,"稳定"永远
285
+ * 等不到)。先给 playwright 一次机会(15s,够正常页),超时改走 CDP
286
+ * Page.captureScreenshot:**抓当前帧不等任何东西**。持续动画页抓哪一帧都是
287
+ * 对的——它本来就没有"稳定帧"这回事。
288
+ *
289
+ * @returns {Promise<{buf: Buffer, degraded: boolean}>} degraded=true 走了 CDP 兜底
290
+ */
291
+ export async function shotWithFallback(page, opts = {}) {
292
+ try {
293
+ return { buf: await page.screenshot({ ...opts, timeout: 15_000 }), degraded: false };
294
+ } catch (err) {
295
+ if (!/Timeout/i.test(String(err?.message))) throw err;
296
+ const cdp = await page.context().newCDPSession(page);
297
+ try {
298
+ const params = { format: 'png', captureBeyondViewport: !!opts.fullPage };
299
+ // CDP 的 clip:captureBeyondViewport 下是文档坐标,与我们 fullPage+clip 的语义一致
300
+ if (opts.clip) params.clip = { x: opts.clip.x, y: opts.clip.y, width: opts.clip.width, height: opts.clip.height, scale: 1 };
301
+ const { data } = await cdp.send('Page.captureScreenshot', params);
302
+ return { buf: Buffer.from(data, 'base64'), degraded: true };
303
+ } finally {
304
+ await cdp.detach().catch(() => {});
305
+ }
306
+ }
307
+ }
308
+
309
+ /**
310
+ * 超长页 fullPage → 滚动位置联络表(iss_mt0365b8):48000px 的整页压进 API 长边
311
+ * 上限后只剩 47px 宽,什么都看不见——工具自己承认不可读还是把废图塞给了模型。
312
+ * 超过阈值改成:按滚动位置取 5 帧视口,拼一张带位置标注的联络表(跟胶片条同一
313
+ * 套版式,横轴从时间换成滚动位置)。一次调用看清整页节奏;要某段的原尺寸用
314
+ * scrollTo 参数去截那一段。
315
+ */
316
+ export const LONG_PAGE_LIMIT = 12_000;
317
+
318
+ export async function longPageSheet(page, { positions = [0, 0.22, 0.45, 0.68, 0.9] } = {}) {
319
+ const dims = await page.evaluate(() => ({
320
+ docH: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight || 0),
321
+ vpH: window.innerHeight, vpW: window.innerWidth, y0: window.scrollY,
322
+ }));
323
+ if (!(dims.docH > LONG_PAGE_LIMIT)) return null;
324
+ const shots = [];
325
+ for (const p of positions) {
326
+ const y = Math.round(p * (dims.docH - dims.vpH));
327
+ await page.evaluate((yy) => window.scrollTo(0, yy), y).catch(() => {});
328
+ await page.waitForTimeout(300); // 给懒加载/reveal 一点点时间,不求全到
329
+ const { buf } = await shotWithFallback(page, { type: 'png' });
330
+ shots.push({ buf, label: `${Math.round(p * 100)}% y=${y}` });
331
+ }
332
+ await page.evaluate((yy) => window.scrollTo(0, yy), dims.y0).catch(() => {});
333
+
334
+ const { default: sharp } = await import('sharp');
335
+ const { sheetLayout } = await import('./motion-lab.js');
336
+ const meta = await sharp(shots[0].buf).metadata();
337
+ const L = sheetLayout(shots.length, meta.width, meta.height);
338
+ const composites = []; const labels = [];
339
+ for (let i = 0; i < shots.length; i += 1) {
340
+ const col = i % L.cols; const row = Math.floor(i / L.cols);
341
+ const left = L.gap + col * (L.cellW + L.gap);
342
+ const top = L.gap + row * (L.cellH + L.gap);
343
+ composites.push({ input: await sharp(shots[i].buf).resize(L.cellW, L.cellH, { fit: 'fill' }).png().toBuffer(), left, top });
344
+ labels.push({ left, top, label: shots[i].label });
345
+ }
346
+ const fontH = Math.max(13, Math.round(L.cellH * 0.055));
347
+ const escXml = (t) => String(t).replace(/[<>&]/g, (c) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[c]));
348
+ const svg = [`<svg xmlns="http://www.w3.org/2000/svg" width="${L.sheetW}" height="${L.sheetH}">`,
349
+ ...labels.map((l) => `<rect x="${l.left}" y="${l.top}" width="${(l.label.length * fontH * 0.62 + 12).toFixed(0)}" height="${fontH + 10}" fill="#000" fill-opacity="0.72"/>`
350
+ + `<text x="${l.left + 6}" y="${l.top + fontH + 2}" font-size="${fontH}" fill="#fff" font-family="monospace">${escXml(l.label)}</text>`),
351
+ '</svg>'].join('');
352
+ const buf = await sharp({ create: { width: L.sheetW, height: L.sheetH, channels: 3, background: '#15150f' } })
353
+ .composite([...composites, { input: Buffer.from(svg), left: 0, top: 0 }]).png().toBuffer();
354
+ return {
355
+ buf,
356
+ mode: `long-page contact sheet (doc ${dims.docH}px, ${shots.length} viewports)`,
357
+ note: `整页高 ${dims.docH}px,fullPage 会压成不可读的细条 —— 已改为 ${shots.length} 帧滚动位置联络表(标注=滚动百分比与 y)。要某一段的原尺寸,用 scrollTo 参数(像素/百分比/选择器)单独截那一屏。`,
358
+ };
359
+ }
360
+
361
+ /**
362
+ * fullPage+clip 的"Clipped area is either empty or outside the resulting image"
363
+ * 兜底(iss_mszxp0zy):Chromium 整页光栅有高度上限(~16384px 纹理),元素落在
364
+ * 超高页更深处时文档坐标 clip 会落到成图之外。兜底=滚到元素处,改用**视口坐标**
365
+ * clip 截当前屏(clip 语义两套:fullPage 下文档坐标 / 非 fullPage 视口坐标,
366
+ * 本机探针 08-19 定过案)。
367
+ */
368
+ export async function clipShotWithFallback(page, { clip, selector }) {
369
+ try {
370
+ return await shotWithFallback(page, { fullPage: true, clip, type: 'png' });
371
+ } catch (err) {
372
+ if (!/Clipped area/i.test(String(err?.message)) || !selector) throw err;
373
+ await page.locator(selector).first().scrollIntoViewIfNeeded({ timeout: 5000 }).catch(() => {});
374
+ const r = await page.evaluate((sel) => {
375
+ const el = document.querySelector(sel);
376
+ if (!el) return null;
377
+ const b = el.getBoundingClientRect();
378
+ return { x: b.x, y: b.y, width: b.width, height: b.height, vw: window.innerWidth, vh: window.innerHeight };
379
+ }, selector);
380
+ if (!r) throw err;
381
+ const vClip = {
382
+ x: Math.max(0, r.x), y: Math.max(0, r.y),
383
+ width: Math.max(1, Math.min(r.width, r.vw - Math.max(0, r.x))),
384
+ height: Math.max(1, Math.min(r.height, r.vh - Math.max(0, r.y))),
385
+ };
386
+ const out = await shotWithFallback(page, { clip: vClip, type: 'png' });
387
+ return { ...out, degraded: true };
388
+ }
389
+ }
@@ -152,8 +152,8 @@ async function listSiteStructure(target) {
152
152
  return {
153
153
  content: [{
154
154
  type: 'text',
155
- text: `${target.task} 里这个站点(${art?.root ? `产物根 ${art.root}/` : '任务根'})还没有 html 页面。`
156
- + (art?.root ? '(源写完了的话,先跑构建让产物落进去。)' : ''),
155
+ text: `这个站点(${art?.root ? `产物根 ${art.root}/` : '任务根'})还没有 html 页面。`
156
+ + (art?.root ? '(源写完了的话,先跑构建让产物落进去;构建后确认产物真的落在这个目录。)' : ''),
157
157
  }],
158
158
  };
159
159
  }
@@ -186,6 +186,9 @@ async function listSiteStructure(target) {
186
186
  });
187
187
  }
188
188
 
189
+ // 输出统一成**工作区相对**路径:screenshot_canvas / artifact_open 收的就是这种。
190
+ // 以前 file 是产物根相对(index.html),agent 原样回喂必然 not found(08-24 案)
191
+ const prefix = art?.root ? `${art.root}/` : '';
189
192
  const known = new Set(pages);
190
193
  const broken = [];
191
194
  for (const p of out) {
@@ -199,7 +202,7 @@ async function listSiteStructure(target) {
199
202
  if (seg === '..') stack.pop();
200
203
  else stack.push(seg);
201
204
  }
202
- if (!known.has(stack.join('/'))) broken.push(`${p.file} → ${href}`);
205
+ if (!known.has(stack.join('/'))) broken.push(`${prefix}${p.file} → ${href}`);
203
206
  }
204
207
  }
205
208
  const brokenNote = broken.length
@@ -212,11 +215,14 @@ async function listSiteStructure(target) {
212
215
  ? `\n\n同任务的其他产物(平等,各自寻址):${siblings.map(a => a.entryRel).join(' / ')}`
213
216
  : '';
214
217
 
218
+ const outWs = prefix ? out.map(p => ({ ...p, file: prefix + p.file })) : out;
219
+
215
220
  return {
216
221
  content: [{
217
222
  type: 'text',
218
- text: `站点 ${target.task} · ${out.length} 个页面(入口 ${pages[0]})${rootNote}:\n\n`
219
- + `${JSON.stringify(out, null, 2)}${brokenNote}${siblingNote}`,
223
+ text: `站点${art?.title ? ` ${art.title}` : ''} · ${out.length} 个页面(入口 ${prefix}${pages[0]})${rootNote}`
224
+ + ',file 是工作区相对路径,可直接传给 screenshot_canvas / artifact_open:\n\n'
225
+ + `${JSON.stringify(outWs, null, 2)}${brokenNote}${siblingNote}`,
220
226
  }],
221
227
  };
222
228
  }
@@ -26,7 +26,7 @@ import { platform } from '../../../runtime/platform.js';
26
26
  import { launchPerceptionBrowser, PERCEPTION_ORIGIN } from './helpers/perception-page.js';
27
27
  import { readBoard } from '../../../projects/board-store.js';
28
28
  import { estimateSizeOn } from '../../../lib/board-kind-sizes.js';
29
- import { normalizeCanvasId } from '../../../lib/canvas-id.js';
29
+ import { normalizeCanvasId, bareTag } from '../../../lib/canvas-id.js';
30
30
 
31
31
  const VIEW = { width: 1400, height: 900 };
32
32
  const READY_TIMEOUT_MS = 25_000;
@@ -46,7 +46,7 @@ export function makeLookAtBoardTool({ projectId }) {
46
46
  return tool(
47
47
  'look_at_board',
48
48
  `LOOK at the canvas (a real screenshot of the board as the user sees it). Use it after
49
- sketch_on_board / create_on_board / relate_on_board to check what you drew, or when the
49
+ write_on_board / edit_board to check what you drew, or when the
50
50
  user talks about how things look on the board. Pick a region — the whole board shrunk
51
51
  into one image is unreadable:
52
52
  - tag: frame the group carrying this #tag (a sketch you made)
@@ -60,7 +60,8 @@ Costs a few seconds and ~1.7k tokens; don't call it in a loop.`,
60
60
  margin: z.number().min(0).max(2000).optional().describe('World px of margin around `around` (default 160)'),
61
61
  view: z.object({ x: z.number(), y: z.number(), w: z.number().min(50), h: z.number().min(50) }).optional(),
62
62
  },
63
- async ({ tag, around, margin, view }) => {
63
+ async ({ tag: rawTag, around, margin, view }) => {
64
+ const tag = rawTag ? bareTag(rawTag) : rawTag; // 同上:查询侧统一剥 #
64
65
  const err = (t) => ({ content: [{ type: 'text', text: t }], isError: true });
65
66
  if (!projectId) return err('No project bound.');
66
67
  const origin = webOrigin();