@xiaobuyu/nodesign 0.0.7 → 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 (353) 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 +8 -0
  7. package/server/api/assets.js +21 -33
  8. package/server/api/board.js +29 -1
  9. package/server/api/browse.js +3 -2
  10. package/server/api/chatai.js +9 -8
  11. package/server/api/exports/cards.js +7 -6
  12. package/server/api/exports/docx-pdf.js +4 -1
  13. package/server/api/local.js +10 -8
  14. package/server/api/projects.js +19 -3
  15. package/server/api/publish.js +3 -2
  16. package/server/api/sessions-rewind.js +220 -0
  17. package/server/api/sessions.js +58 -194
  18. package/server/api/stage.js +35 -0
  19. package/server/api/turn-model-switch.js +109 -0
  20. package/server/api/turn.js +52 -88
  21. package/server/auth/middleware.js +29 -6
  22. package/server/auth/tier.js +27 -7
  23. package/server/auth/users-store.js +14 -1
  24. package/server/engine/agent/actor-trail.js +110 -0
  25. package/server/engine/agent/agent-shared.js +22 -52
  26. package/server/engine/agent/beat-state.js +61 -0
  27. package/server/engine/agent/cast.js +322 -0
  28. package/server/engine/agent/events.js +9 -2
  29. package/server/engine/agent/hooks/beat-gate.js +73 -0
  30. package/server/engine/agent/hooks/lifecycle.js +6 -2
  31. package/server/engine/agent/hooks/post-trim.js +1 -0
  32. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  33. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  34. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  35. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  36. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  37. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  38. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  39. package/server/engine/agent/hooks/slot-alias.js +30 -0
  40. package/server/engine/agent/hooks/user-prompt-submit.js +197 -7
  41. package/server/engine/agent/hooks.js +76 -6
  42. package/server/engine/agent/isolation.js +25 -2
  43. package/server/engine/agent/memory-config.js +27 -1
  44. package/server/engine/agent/model-context.js +102 -18
  45. package/server/engine/agent/model-table.js +361 -58
  46. package/server/engine/agent/prompts/nodesign-prelude.md +566 -59
  47. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  48. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +2 -2
  49. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  50. package/server/engine/agent/prompts/tools/site-reference.md +2 -1
  51. package/server/engine/agent/role-card.js +175 -0
  52. package/server/engine/agent/rp-mode.js +17 -0
  53. package/server/engine/agent/session-loop.js +26 -14
  54. package/server/engine/agent/stage-status.js +73 -0
  55. package/server/engine/agent/subagent-flight.js +73 -0
  56. package/server/engine/agent/system-prompts.js +41 -0
  57. package/server/engine/agent/tool-input-stream.js +165 -0
  58. package/server/engine/mcp/actor.js +59 -0
  59. package/server/engine/mcp/capability-gate.js +21 -2
  60. package/server/engine/mcp/index.js +116 -24
  61. package/server/engine/mcp/mode-profile.js +139 -0
  62. package/server/engine/mcp/server-name.js +19 -0
  63. package/server/engine/mcp/tools/artifact-session.js +2 -1
  64. package/server/engine/mcp/tools/browse-find-batch.js +76 -14
  65. package/server/engine/mcp/tools/browse.js +1 -1
  66. package/server/engine/mcp/tools/cast-role.js +167 -0
  67. package/server/engine/mcp/tools/draw-trend.js +104 -0
  68. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  69. package/server/engine/mcp/tools/edit-board.js +569 -0
  70. package/server/engine/mcp/tools/look-at-board.js +4 -3
  71. package/server/engine/mcp/tools/open-sheet.js +242 -0
  72. package/server/engine/mcp/tools/organize-board.js +19 -5
  73. package/server/engine/mcp/tools/paint-still.js +1 -1
  74. package/server/engine/mcp/tools/pin-to-board.js +78 -3
  75. package/server/engine/mcp/tools/read-board.js +109 -16
  76. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  77. package/server/engine/mcp/tools/report-issue.js +1 -0
  78. package/server/engine/mcp/tools/role-memory.js +83 -0
  79. package/server/engine/mcp/tools/roll-dice.js +82 -0
  80. package/server/engine/mcp/tools/roll-film.js +1 -1
  81. package/server/engine/mcp/tools/screenshot.js +2 -2
  82. package/server/engine/mcp/tools/set-vars.js +150 -0
  83. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  84. package/server/engine/mcp/tools/web-search.js +1 -1
  85. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  86. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  87. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  88. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  89. package/server/engine/mcp/tools/write-on-board.js +567 -107
  90. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  91. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +11 -3
  92. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +34 -4
  93. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +20 -4
  94. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  95. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  96. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  97. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  98. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  99. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  100. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  101. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  102. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  103. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  104. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  105. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  106. package/server/engine/runs/board-seater.js +272 -0
  107. package/server/engine/runs/board-tasklist.js +90 -48
  108. package/server/engine/runs/turn-relay.js +24 -2
  109. package/server/engine/runs/user-chalk-echo.js +44 -0
  110. package/server/index.js +2 -0
  111. package/server/lib/board-anchor.js +49 -0
  112. package/server/lib/board-dirty.js +93 -0
  113. package/server/lib/board-excerpt.js +80 -0
  114. package/server/lib/board-follow.js +111 -0
  115. package/server/lib/board-groups.js +1 -5
  116. package/server/lib/board-hero.js +3 -1
  117. package/server/lib/board-kind-sizes.js +36 -0
  118. package/server/lib/board-lanes.js +58 -0
  119. package/server/lib/board-obstacles.js +80 -0
  120. package/server/lib/board-relations.js +5 -1
  121. package/server/lib/board-sheets.js +467 -0
  122. package/server/lib/board-shelf.js +86 -0
  123. package/server/lib/board-transform.js +62 -0
  124. package/server/lib/canvas-id.js +18 -1
  125. package/server/lib/chalk-flow.js +88 -0
  126. package/server/lib/chalk-rewrite.js +63 -0
  127. package/server/lib/chalk-size-pref.js +48 -0
  128. package/server/lib/chalk.js +42 -3
  129. package/server/lib/ingress/forward-openai-chat.js +16 -4
  130. package/server/lib/ingress/openai-chat.js +23 -12
  131. package/server/lib/ingress/session-routes.js +3 -3
  132. package/server/lib/ingress/stream-watchdog.js +53 -0
  133. package/server/lib/ingress/upstream-billing.js +15 -0
  134. package/server/lib/ingress/upstream-error-hints.js +36 -0
  135. package/server/lib/json-preview.js +58 -0
  136. package/server/lib/model-ingress.js +26 -1
  137. package/server/lib/moderation.js +10 -5
  138. package/server/lib/quota.js +26 -6
  139. package/server/lib/rect.js +50 -0
  140. package/server/lib/screen.js +36 -0
  141. package/server/lib/sheet-state.js +19 -0
  142. package/server/lib/sketch-array.js +118 -0
  143. package/server/lib/sketch-hatch.js +99 -0
  144. package/server/lib/sketch-layout.js +269 -69
  145. package/server/lib/sketch-shapes.js +152 -0
  146. package/server/lib/sketch-stencils.js +170 -0
  147. package/server/lib/state-table.js +237 -0
  148. package/server/lib/state-trend.js +94 -0
  149. package/server/lib/state-triggers.js +199 -0
  150. package/server/lib/tavern-json.js +48 -0
  151. package/server/lib/user-uploads.js +88 -0
  152. package/server/projects/board-limits.js +5 -3
  153. package/server/projects/board-sanitize.js +220 -10
  154. package/server/projects/board-store.js +95 -6
  155. package/server/projects/session-jsonl.js +110 -0
  156. package/server/projects/store.js +56 -3
  157. package/server/projects/viewpoint-store.js +35 -3
  158. package/server/projects/workspace-slots.js +30 -0
  159. package/server/projects/workspace-templates.js +29 -0
  160. package/server/projects/workspace.js +16 -17
  161. package/server/runtime/capabilities.js +6 -2
  162. package/server/shared/locales.js +70 -0
  163. package/server/shared/messages-en.js +69 -0
  164. package/server/shared/messages.js +65 -0
  165. package/server/ws/broker.js +3 -0
  166. package/server/ws/index.js +43 -3
  167. package/web/README.md +1 -1
  168. package/web/dist/assets/{BrowserWindow-BoOBaIyz.js → BrowserWindow-CcaGRWz5.js} +1 -1
  169. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  170. package/web/dist/assets/{DocxWindow-B_u6aX9o.js → DocxWindow-B9KZmU5z.js} +2 -2
  171. package/web/dist/assets/{MermaidBlock-BO-46Du5.js → MermaidBlock-DrfRnz8E.js} +43 -43
  172. package/web/dist/assets/{SiteWindow-ConPmzdM.js → SiteWindow-CJOjCF7d.js} +6 -6
  173. package/web/dist/assets/{abnfDiagram-VCTEODGH-FCO9yf-n.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  174. package/web/dist/assets/{arc-BLcEOSRX.js → arc-C1-HSUny.js} +1 -1
  175. package/web/dist/assets/{architectureDiagram-5GKGNRK7-kxiX4TeN.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  176. package/web/dist/assets/{blockDiagram-NRAW4CY4-CptFksix.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  177. package/web/dist/assets/{c4Diagram-UCG6FXSJ-Dxmxj34F.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  178. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  179. package/web/dist/assets/{chunk-2Q5K7J3B-D9inx5oo.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  180. package/web/dist/assets/{chunk-5VM5RSS4-DydfHlEc.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  181. package/web/dist/assets/{chunk-F27PBJKO-BO7GxhSv.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  182. package/web/dist/assets/{chunk-G27WJ6UU-BzP6YiQ_.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  183. package/web/dist/assets/{chunk-JWPE2WC7-CHcgcd0s.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  184. package/web/dist/assets/{chunk-LCL6LL3I-D2Wg5JtX.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  185. package/web/dist/assets/{chunk-POPQ4Y6H-B2fCRfpM.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  186. package/web/dist/assets/{chunk-SVP7TREG-JSLcsRlZ.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  187. package/web/dist/assets/{chunk-XXDRQBXY-BCjvaeHm.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  188. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  189. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  190. package/web/dist/assets/{cose-bilkent-JH36ORCC-0uKv6oJ7.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  191. package/web/dist/assets/{cynefin-OW5HDTMX-Bvt-gEmA.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  192. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-C9Ht02qC.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  193. package/web/dist/assets/{dagre-3AP2YEHR-BfxZny4s.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  194. package/web/dist/assets/{diagram-S7CK7UJ4-CrE0ZETE.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  195. package/web/dist/assets/{diagram-UQ7AKVKN-BO8VQ4Fx.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  196. package/web/dist/assets/{diagram-VSXAHHWV-CHUZnR9F.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  197. package/web/dist/assets/{diagram-VX7I27RA-DYQAz1Vu.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  198. package/web/dist/assets/{diagram-Z3DM3KII-BDjivgLr.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  199. package/web/dist/assets/{ebnfDiagram-PWID7BFC-Bpe_QVbH.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  200. package/web/dist/assets/{erDiagram-SSCWMZ5O-BxhwuAu1.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  201. package/web/dist/assets/{flowDiagram-A5DVABFB-CCw2H08-.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  202. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-DtTc7MyI.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  203. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-BvORxMd2.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  204. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  205. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  206. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  207. package/web/dist/assets/{infoDiagram-RXCK75RN-DMcrkypD.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  208. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CZkTIbe-.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  209. package/web/dist/assets/{journeyDiagram-EYS64GPL-DR0XazdE.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  210. package/web/dist/assets/{kanban-definition-3QL26DDD-DN8VbwVu.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  211. package/web/dist/assets/{layout-9_paCbyt.js → layout-Dhgck-Zo.js} +1 -1
  212. package/web/dist/assets/{linear-g6Ct012e.js → linear-DMSQ-aUw.js} +1 -1
  213. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  214. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  215. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  216. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  217. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  218. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  219. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  220. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  221. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  222. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  223. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  224. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  225. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  226. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  227. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  228. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  229. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  230. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  231. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  232. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  233. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  234. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  235. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  236. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  237. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  238. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  239. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  240. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  241. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  242. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  243. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  314. package/web/dist/assets/{mindmap-definition-FBJOCRG2-BGt4omYC.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  315. package/web/dist/assets/{pegDiagram-XKGWAZYB-uSfS-PBE.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  316. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  317. package/web/dist/assets/{pieDiagram-E7YTZNPT-DpeI935H.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  318. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-B7IuNvWa.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  319. package/web/dist/assets/{railroadDiagram-O6MQD6OU-ARzYpPQI.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  320. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-Cj4upkAo.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  321. package/web/dist/assets/{rotate-cw-CXJcyHWk.js → rotate-cw-62liz7LX.js} +1 -1
  322. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-OaVwSnLP.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  323. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-1yrUbq4t.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  324. package/web/dist/assets/{sizeCapture-X5ZJPWSS-nYH5p9D6.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  325. package/web/dist/assets/{square-dashed-mouse-pointer-CTtTVKoH.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  326. package/web/dist/assets/{stateDiagram-HBIQ2CUA-Dj5HvyNq.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  327. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  328. package/web/dist/assets/{swimlanes-XN3QIQJK-umeT0HpB.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  329. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  330. package/web/dist/assets/{timeline-definition-24CTP7MA-MqnQfHKB.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  331. package/web/dist/assets/{vennDiagram-4TSXK5OY-b2liyZra.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  332. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-QMK-zNh1.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  333. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-D5Mc6tfA.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  334. package/web/dist/index.html +2 -2
  335. package/server/engine/mcp/tools/arrange-on-board.js +0 -126
  336. package/server/engine/mcp/tools/create-on-board.js +0 -152
  337. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  338. package/server/engine/mcp/tools/relate-on-board.js +0 -150
  339. package/server/engine/mcp/tools/sketch-on-board.js +0 -345
  340. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  341. 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
  342. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  343. package/web/dist/assets/DeckWindow-Bg3rgkvi.js +0 -11
  344. package/web/dist/assets/channel-DFyiuGL0.js +0 -1
  345. package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +0 -1
  346. package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +0 -1
  347. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  348. package/web/dist/assets/index-DGG5wIZs.js +0 -2118
  349. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  350. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  351. package/web/dist/assets/pending-edit-apply-uP4uvrOD.js +0 -31
  352. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +0 -1
  353. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +0 -8
@@ -39,6 +39,7 @@ import { resolveWireModel, UPSTREAMS } from '../engine/agent/model-context.js';
39
39
  import { resolveSessionWire, fallbackLogged } from './ingress/session-routes.js';
40
40
  import { forwardOpenAIChat } from './ingress/forward-openai-chat.js';
41
41
  import { failStreaks, exhaustedErrorBody } from './ingress/upstream-fail-streak.js';
42
+ import { armIdleWatchdog } from './ingress/stream-watchdog.js';
42
43
  import { noteUpstreamBilling } from './ingress/upstream-billing.js';
43
44
  import { noteUpstreamTruncation } from './ingress/upstream-truncation.js';
44
45
  import { noticeSession } from './ingress/session-notice.js';
@@ -295,6 +296,15 @@ async function handleRequest(req, res, bodyBuf) {
295
296
  noteOutcome(true);
296
297
  }
297
298
  res.writeHead(proxyRes.statusCode, proxyRes.headers);
299
+ // 断流看门狗(2026-08-29,proj_mtexu1kp 现场):上游 SSE 半路断粮不报错不 EOF,
300
+ // run 会无限挂起。只对流式响应上岗;掐掉后 CLI 收到流错误自己重试。
301
+ if (proxyRes.statusCode < 400 && String(proxyRes.headers['content-type'] || '').includes('event-stream')) {
302
+ armIdleWatchdog(proxyRes, { onIdle: (silentMs) => {
303
+ console.warn(`[model-ingress] sid=${sidShort} upstream=${wire.upstreamId} 流静默 ${Math.round(silentMs / 1000)}s —— 看门狗掐断死流`);
304
+ noteOutcome(false, 'stream idle');
305
+ try { proxyRes.destroy(new Error('ingress stream idle watchdog')); } catch { /* 已经死了就算了 */ }
306
+ } });
307
+ }
298
308
  proxyRes.pipe(res);
299
309
  });
300
310
 
@@ -327,7 +337,12 @@ export async function transformForUpstream(parsed, wire) {
327
337
 
328
338
  // thinking:'strip' = 删字段让上游自决(Gemini thinking 关不掉、参数过桥行为
329
339
  // 未知);'enabled8k' = adaptive 改写成 enabled+budget(Kimi 实测 adaptive =
330
- // 0 thinking blocks
340
+ // 0 thinking blocks);'adaptive' = 反过来,enabled+budget 改写成 adaptive。
341
+ //
342
+ // ⚠️ 本站给**每一条 API 行**发的都是 enabled+8192(model-context.pickThinkingConfig),
343
+ // 对"自己会判断该不该想"的模型等于逐轮强制想满预算。MiniMax M3 是这一类(08-25 实测
344
+ // GMI 部署 adaptive/disabled/enabled 三种都收):agent 一轮里大半是读文件调工具,
345
+ // 该不该想交给它自己。
331
346
  if (wire.thinking === 'strip') {
332
347
  if ('thinking' in parsed) { delete parsed.thinking; mutated = true; }
333
348
  } else if (wire.thinking === 'enabled8k') {
@@ -335,6 +350,12 @@ export async function transformForUpstream(parsed, wire) {
335
350
  parsed.thinking = { type: 'enabled', budget_tokens: 8192 };
336
351
  mutated = true;
337
352
  }
353
+ } else if (wire.thinking === 'adaptive') {
354
+ // 'disabled' 不动:那是调用方明确要求别想,行的默认档不该压过它
355
+ if (parsed.thinking && parsed.thinking.type !== 'adaptive' && parsed.thinking.type !== 'disabled') {
356
+ parsed.thinking = { type: 'adaptive' };
357
+ mutated = true;
358
+ }
338
359
  }
339
360
 
340
361
  // tool_result 里的图提升到 user message 顶层(丢图桥的必修 shim)
@@ -349,6 +370,10 @@ export async function transformForUpstream(parsed, wire) {
349
370
  if (await normalizeImages(parsed.messages, VISION_MAX_DIM, allowed)) mutated = true;
350
371
  }
351
372
 
373
+ // 上游要的额外顶层字段(merge 的 vendor 点名)。放在这里是 **Anthropic 透传腿的读者** ——
374
+ // 那条腿直接 JSON.stringify(parsed)。openai-chat 腿另有一个读者,见 toOpenAIChatRequest
375
+ if (wire.bodyExtra) { Object.assign(parsed, wire.bodyExtra); mutated = true; }
376
+
352
377
  return mutated;
353
378
  }
354
379
 
@@ -20,7 +20,9 @@
20
20
  * 强度三档,**per-user、per-通路可调**(站主在控制台按人设)。08-20 起按模型通路分成
21
21
  * 两个独立旋钮:users.moderation_level 管订阅模型(Sonnet/Opus,跑在站主账号上),
22
22
  * users.moderation_level_api 管本地 qwen / 中转站那些走 ingress 的 API 行。哪条通路
23
- * 由 model-context 的 resolveModelRoute 说了算,这里不判模型名。两边默认档推导相同。
23
+ * 由 model-context 的 resolveModelRoute 说了算,这里不判模型名。
24
+ * ⭐ **08-30 起两边的默认档也各算各的**(tier.js 的 moderationDefault / moderationDefaultApi):
25
+ * 订阅 strict、非订阅 off。在此之前两边推导同一个值,"订阅严、其他放开"只能靠逐人钉。
24
26
  * 同一档位值同时管 GPT 外审和 prelude 的成人句(见 agent/system-prompts.js)。
25
27
  * off 不审
26
28
  * loose 只拦"换个说法也还是违法"的硬线:未成年人色情 / 恐怖主义 / 武器毒品
@@ -29,8 +31,10 @@
29
31
  * 小说 / RP 形态天然要碰这些,误伤创作比漏审更伤产品。
30
32
  * strict loose 全部 + 露骨色情 / 美化煽动真实暴力 / 群体仇恨歧视。
31
33
  *
32
- * 默认档(users.moderation_level NULL 时):试用号 strict / 正式号 loose /
33
- * admin off。改默认档不影响已显式设过的号。`NODESIGN_MODERATION=off` 是全站总闸。
34
+ * 默认档(该旋钮为 NULL 时)走 tier.js 的能力表:订阅通路 admin off / pro·basic strict,
35
+ * API 通路一律 off。改默认档不影响已显式钉过档的号 —— 所以改默认时要顺手看一眼库里还有多少
36
+ * 显式值压着(08-30 那次把 41 个显式的 API 档清回 NULL,就是为了让这张表真的说了算)。
37
+ * `NODESIGN_MODERATION=off` 是全站总闸。
34
38
  */
35
39
 
36
40
  import db from '../engine/runs/store.js';
@@ -94,9 +98,10 @@ export function levelForKnob(user, knob) {
94
98
  if (!user) return 'off';
95
99
  const explicit = knob === 'api' ? user.moderationLevelApi : user.moderationLevel;
96
100
  if (LEVELS.includes(explicit)) return explicit;
97
- // 默认档按账号档位(auth/tier.js 能力表):admin off / pro loose / basic strict
101
+ // 默认档按账号档位**和通路**(auth/tier.js 能力表):订阅 admin off / pro·basic strict,API 一律 off
98
102
  // 08-21 前这里猜的是 lifetimeCostLimitUsd(试用码 ⇒ strict),开放注册号该字段为 null 被当成正式号拿 loose。
99
- return defaultModerationLevel(user);
103
+ // ⚠️ knob 必须往下传:08-30 之前这里不传,于是 tier 表里就算分了两栏也读不到第二栏。
104
+ return defaultModerationLevel(user, knob);
100
105
  }
101
106
 
102
107
  export function shouldModerate(user, appModel = null) {
@@ -222,12 +222,22 @@ export function usedTodayByFamily(userId, now = Date.now()) {
222
222
  //
223
223
  // 08-21 经营态转向后分两档:
224
224
  // - 订阅行(花站主的订阅):全局固定数 NODESIGN_MAX_CONCURRENT_RUNS(默认 3)
225
- // - 免费行(Ox 这类零价模型):不受全局固定数限制。它的约束只剩机器本身 ——
225
+ // - 非订阅行(走 ingress 的 API 行):不受全局固定数限制。它的约束只剩机器本身 ——
226
226
  // 每个 claude CLI 进程 350MB~1GB、机器 1 vCPU/8G 无 swap,所以改成**内存闸**:
227
227
  // MemAvailable 低于 NODESIGN_MIN_FREE_MEM_MB(默认 700)就拒,外加一个防失控的
228
228
  // 上限 NODESIGN_FREE_MAX_CONCURRENT_RUNS(默认 12)。内存闸对两档都生效(订阅行
229
229
  // 的固定数 3 本来就是按内存拍的,内存先见底时固定数也救不了)
230
230
  // - 每用户同时 1 个(admin 免)两档都一样:这是公平性不是资源
231
+ //
232
+ // ⭐⭐ **2026-08-30 判据从「免费/付费」改成「订阅/非订阅」**。起因:全员默认行换成
233
+ // glm-5.3-flash-merge(付费但极便宜,$0.015/M)。按老判据它落进"付费"那一档,站点默认
234
+ // 路径的并发天花板会从 12 掉到 3,而这台盒子实测峰值在飞 turn 是 4 —— 第 4 个人当场
235
+ // 吃「现在有点挤」。
236
+ // 这不是把闸放松,是**把闸对准它本来护的东西**:那个 3 从头到尾护的是站主的 Claude 订阅
237
+ // (同时开太多 query 会烧额度、也会被上游限流),跟"这一轮花不花钱"没有关系。一条
238
+ // $0.015/M 的网关行既不烧订阅、也不烧机器以外的任何东西,它该受的约束就是内存。
239
+ // ⚠️ 于是**付费的 API 行现在也走 12 + 内存闸**(deepseek 视觉行、merge 行)。真要给某一家
240
+ // 单独收并发,那是 per-upstream 的闸,不是把它塞回订阅那一档。
231
241
 
232
242
  export function memAvailableMb() {
233
243
  try {
@@ -236,13 +246,20 @@ export function memAvailableMb() {
236
246
  } catch { return null; } // 非 Linux / 读不到 → 不以内存拒
237
247
  }
238
248
 
239
- /** 纯决策:输入全是数,方便测 */
240
- export function decideConcurrency({ running, mine, isAdmin, free, memMb, env = process.env }) {
249
+ /**
250
+ * 纯决策:输入全是数,方便测。
251
+ * @param {boolean} offSubscription 这一轮**不花站主订阅**(走 ingress 的 API 行)。
252
+ * ⚠️ 08-30 前这个参数叫 `free`、判据是"模型免不免费",改名是因为那个名字骗过人一次:
253
+ * 默认行换成一条付费但极便宜的 API 行时,它被判进订阅那一档、天花板从 12 掉到 3。
254
+ */
255
+ export function decideConcurrency({ running, mine, isAdmin, offSubscription, free, memMb, env = process.env }) {
256
+ // 老参数名还认(旧调用方 / 测试),但新名优先
257
+ const offSub = offSubscription !== undefined ? offSubscription : !!free;
241
258
  const minMem = Number(env.NODESIGN_MIN_FREE_MEM_MB) || 700;
242
259
  if (memMb != null && memMb < minMem) {
243
260
  return { ok: false, code: 'BUSY', message: `机器内存快满了(${running} 个任务在跑),稍等一会儿再发` };
244
261
  }
245
- const globalMax = free
262
+ const globalMax = offSub
246
263
  ? (Number(env.NODESIGN_FREE_MAX_CONCURRENT_RUNS) || 12)
247
264
  : (Number(env.NODESIGN_MAX_CONCURRENT_RUNS) || 3);
248
265
  if (running >= globalMax) {
@@ -257,7 +274,7 @@ export function decideConcurrency({ running, mine, isAdmin, free, memMb, env = p
257
274
  return { ok: true };
258
275
  }
259
276
 
260
- export function checkConcurrency(user, { free = false } = {}) {
277
+ export function checkConcurrency(user, { offSubscription = false, free = false } = {}) {
261
278
  // running turn → user 归属:runId 查 runs.user_id(不给 session 注册表加
262
279
  // userId 字段 —— 正在跑的 turn 就几个,查表成本可忽略)
263
280
  let mine = 0;
@@ -266,5 +283,8 @@ export function checkConcurrency(user, { free = false } = {}) {
266
283
  if (getRun(rid)?.userId === user?.id) mine += 1;
267
284
  }
268
285
  }
269
- return decideConcurrency({ running: countRunningTurns(), mine, isAdmin: user?.role === 'admin', free, memMb: memAvailableMb() });
286
+ return decideConcurrency({
287
+ running: countRunningTurns(), mine, isAdmin: user?.role === 'admin',
288
+ offSubscription: offSubscription || free, memMb: memAvailableMb(),
289
+ });
270
290
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * server/lib/rect.js —— 矩形原语(2026-08-27 审计收敛)
3
+ *
4
+ * 收敛前的账:AABB 重叠判定在服务端手写了 4 份、包围盒 5 份、UNIT=24 两处
5
+ * export,各自维护、容差和空集语义悄悄分叉(bbox 空集有的返回 null 有的返回
6
+ * 零框)。改判定只改这一份;需要不同空集语义的调用方自己在出口包一层。
7
+ *
8
+ * 前端的对应物是 web/src/lib/board-geometry.js 的 rectsHit —— 语义必须一致
9
+ * (相邻贴边 = 不重叠)。改这边记得看那边。
10
+ */
11
+
12
+ /** 网格步长:全部世界坐标的公倍数。曾在 board-place / sketch-layout 各 export 一份。 */
13
+ export const UNIT = 24;
14
+
15
+ /** AABB 重叠(贴边不算重叠)。pad = 双方各让的身位。 */
16
+ export const overlaps = (a, b, pad = 0) => !(
17
+ a.x + a.w + pad <= b.x || b.x + b.w <= a.x - pad
18
+ || a.y + a.h + pad <= b.y || b.y + b.h <= a.y - pad
19
+ );
20
+
21
+ /** 一组矩形的包围盒;空集返回 null(要零框语义自己在出口兜)。 */
22
+ export function bboxOf(rects) {
23
+ let x0 = Infinity; let y0 = Infinity; let x1 = -Infinity; let y1 = -Infinity;
24
+ for (const r of rects) {
25
+ x0 = Math.min(x0, r.x); y0 = Math.min(y0, r.y);
26
+ x1 = Math.max(x1, r.x + (r.w || 0)); y1 = Math.max(y1, r.y + (r.h || 0));
27
+ }
28
+ return Number.isFinite(x0) ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
29
+ }
30
+
31
+ /** 点在矩形内(含边)。 */
32
+ export const pointIn = (pt, r) => pt.x >= r.x && pt.x <= r.x + r.w && pt.y >= r.y && pt.y <= r.y + r.h;
33
+
34
+ /**
35
+ * 线段 a→b 是否穿过矩形 r(Liang-Barsky 裁剪;端点擦边也算穿)。
36
+ * 落位引擎用它判「连线走廊」:新块和它锚点之间那条线会不会压在第三块身上。
37
+ */
38
+ export function segHitsRect(a, b, r) {
39
+ const dx = b.x - a.x; const dy = b.y - a.y;
40
+ let t0 = 0; let t1 = 1;
41
+ const clip = (p, q) => {
42
+ if (p === 0) return q >= 0; // 与这条边平行:q<0 = 整段在外
43
+ const t = q / p;
44
+ if (p < 0) { if (t > t1) return false; if (t > t0) t0 = t; }
45
+ else { if (t < t0) return false; if (t < t1) t1 = t; }
46
+ return true;
47
+ };
48
+ return clip(-dx, a.x - r.x) && clip(dx, r.x + r.w - a.x)
49
+ && clip(-dy, a.y - r.y) && clip(dy, r.y + r.h - a.y);
50
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * server/lib/screen.js —— 「一屏」的唯一基准(2026-08-29 纸范式刀 0)
3
+ *
4
+ * 纸范式拍板:一张纸 = **0.75 倍缩放下等于用户一屏**的矩形工作区。
5
+ * 在这之前「一屏」有三个同源近邻值各自散落(fitFor 的 /0.8、board-place 的
6
+ * ONE_SCREEN 1750×1125、sketch-layout 的 SKETCH_FIT 1700×1100),注释里互相
7
+ * 提醒「改缩放基准要几处一起看」—— 现在基准只有这一份,改这里就是改全部。
8
+ *
9
+ * DEFAULT_SCREEN 是没有视点上报时的兜底屏(普通笔记本 1400×900);真实纸尺寸
10
+ * 永远优先按上报的 device 屏幕算(见 sketch-layout.js fitFor)。
11
+ */
12
+
13
+ export const ZOOM_BASIS = 0.75;
14
+
15
+ export const DEFAULT_SCREEN = { w: 1400, h: 900 };
16
+
17
+ /** 兜底「一屏」世界像素(1400×900 ÷ 0.75 = 1867×1200) */
18
+ export const ONE_SCREEN = {
19
+ w: Math.round(DEFAULT_SCREEN.w / ZOOM_BASIS),
20
+ h: Math.round(DEFAULT_SCREEN.h / ZOOM_BASIS),
21
+ };
22
+
23
+ /**
24
+ * 一张卡的高度天花板(2026-08-29 占位契约刀 B,站主定「一张纸的 40%」)。
25
+ *
26
+ * 生产真板上最高一条板书 **2471px** —— 312 宽、2471 高,一整章小说写成一根柱子。
27
+ * 卡的高度原来完全由内容决定、没有上限,于是两件坏事同时发生:版面被柱子劈开,
28
+ * 而落位系统按估算高度排好队之后,渲染回写又把卡撑大去压邻居。
29
+ *
30
+ * 定成固定值而不是「按当前纸算」,是因为卡的高度不该随看它的设备变 —— 同一块板
31
+ * 在手机和大屏上看,卡的版面必须是同一个。384 = 16 格,也接近 973 高的纸的 40%。
32
+ *
33
+ * 超出的部分不是丢掉:卡上折叠(可展开,展开是临时的、不改占位),工具如实告诉
34
+ * agent「你这条被折叠了,实际 N px」——「越界钳住但要说」。
35
+ */
36
+ export const CARD_MAX_H = 384;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * server/lib/sheet-state.js —— 「当前纸」的会话指针(2026-08-29 纸范式)
3
+ *
4
+ * 每个会话记一张「正写在哪张纸上」。进程内存不落盘:丢了也无害 —— 读侧回落到
5
+ * 登记时间最新的一张(board-sheets.js currentSheet 的兜底),agent 重新铺纸或
6
+ * 点名纸名都能续上。放 board.json 是错的:两个会话同时开工会互相抢指针。
7
+ */
8
+
9
+ const cur = new Map(); // sessionId → sheet name
10
+
11
+ export function setCurrentSheetId(sessionId, id) {
12
+ if (sessionId && typeof id === 'string') cur.set(sessionId, id);
13
+ }
14
+
15
+ export function currentSheetIdOf(sessionId) {
16
+ return (sessionId && cur.get(sessionId)) || null;
17
+ }
18
+
19
+ export function _resetSheetState() { cur.clear(); }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * server/lib/sketch-array.js —— 阵列 / 环列 / 镜像 / 散布(2026-08-30 画图能力线刀④)
3
+ *
4
+ * 复杂图案大多是简单元素的规律重复,而**等距和对称恰是模型最不擅长的算术**
5
+ * (画一圈椅子 = 12 次三角函数)。这四个算子把规律归机器、元素归 agent:
6
+ *
7
+ * repeat {n, dx, dy} 沿直线摆 n 份(栅栏、窗、台阶)
8
+ * ring {n, cx, cy} 绕圆心转 n 份(圆桌椅子、钟面、花瓣)
9
+ * mirror {axis, at} 对称补一份(画半只蝴蝶得整只)
10
+ * scatter {n, in:{…}} 区域内播撒 n 份、带大小抖动(星空、草地、碎石)
11
+ *
12
+ * 每一份都用**自己的种子**重生成笔迹 —— 抖动各不相同,看起来是手画了 n 遍,
13
+ * 不是图章盖了 n 下。scatter 的随机也是种子确定的:同一次调用重放结果一致。
14
+ */
15
+
16
+ import { UNIT, transformD } from './sketch-layout.js';
17
+
18
+ /** 与 sketch-layout 同款确定性随机(那边的没导出,够小就地一份) */
19
+ function rng(seedStr) {
20
+ let h = 0x811c9dc5;
21
+ for (let i = 0; i < seedStr.length; i += 1) { h ^= seedStr.charCodeAt(i); h = Math.imul(h, 0x01000193) >>> 0; }
22
+ let a = h >>> 0;
23
+ return () => {
24
+ a = (a + 0x6D2B79F5) >>> 0;
25
+ let t = a;
26
+ t = Math.imul(t ^ (t >>> 15), t | 1);
27
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
28
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
29
+ };
30
+ }
31
+
32
+ /** 把 {rect,d}(d 局部于 rect)过一个**绝对坐标**变换,返回重建包围盒后的 {rect,d} */
33
+ function applyAbs(base, fn) {
34
+ let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity;
35
+ const dAbs = transformD(base.d, (p) => {
36
+ const q = fn({ x: base.rect.x + p.x, y: base.rect.y + p.y });
37
+ if (q.x < minX) minX = q.x; if (q.x > maxX) maxX = q.x;
38
+ if (q.y < minY) minY = q.y; if (q.y > maxY) maxY = q.y;
39
+ return q;
40
+ });
41
+ if (!Number.isFinite(minX)) return base;
42
+ const P = 6; // 与全族同款 pad(抖动和线宽的呼吸位;不补的话镜像副本比本尊瘦一圈)
43
+ const d = transformD(dAbs, (p) => ({ x: p.x - minX + P, y: p.y - minY + P }));
44
+ return { rect: { x: Math.round(minX - P), y: Math.round(minY - P), w: Math.max(4, Math.round(maxX - minX + P * 2)), h: Math.max(4, Math.round(maxY - minY + P * 2)) }, d };
45
+ }
46
+
47
+ /**
48
+ * 展开一个形状的算子。
49
+ * @param s 入参形状(可能带 repeat/ring/mirror/scatter;坐标全是格)
50
+ * @param makeOne (seedSuffix) => {rect, d} 重新生成一份本尊(各份各自的抖)
51
+ * @param key 形状名(副本叫 key-2, key-3…)
52
+ * @returns {Array<{key,rect,d}>} 或 { error }
53
+ */
54
+ export function expandModifiers(s, makeOne, key) {
55
+ const mods = ['repeat', 'ring', 'mirror', 'scatter'].filter((k) => s[k]);
56
+ if (!mods.length) return [{ key, ...makeOne('') }];
57
+ if (mods.length > 1) return { error: `形状 ${key}:repeat/ring/mirror/scatter 一次只能挂一个` };
58
+ const out = [];
59
+ const push = (i, piece) => out.push({ key: i === 0 ? key : `${key}-${i + 1}`, ...piece });
60
+
61
+ if (s.repeat) {
62
+ const { n, dx = 0, dy = 0 } = s.repeat;
63
+ if (!dx && !dy) return { error: `形状 ${key}:repeat 要给步长 dx/dy(格)——步长 0 是 n 份叠在原地` };
64
+ for (let i = 0; i < n; i += 1) {
65
+ const b = makeOne(`:${i}`);
66
+ push(i, { rect: { ...b.rect, x: Math.round(b.rect.x + dx * UNIT * i), y: Math.round(b.rect.y + dy * UNIT * i) }, d: b.d });
67
+ }
68
+ return out;
69
+ }
70
+ if (s.ring) {
71
+ const { n, cx, cy, upright = false } = s.ring;
72
+ const C = { x: cx * UNIT, y: cy * UNIT };
73
+ for (let i = 0; i < n; i += 1) {
74
+ const th = (Math.PI * 2 * i) / n;
75
+ const cos = Math.cos(th); const sin = Math.sin(th);
76
+ const b = makeOne(`:${i}`);
77
+ if (i === 0) { push(i, b); continue; }
78
+ if (upright) {
79
+ // 不转身,只沿圆周换座(侧视图里有重力的东西:帐篷、篝火边的人)
80
+ const bc = { x: b.rect.x + b.rect.w / 2, y: b.rect.y + b.rect.h / 2 };
81
+ const nc = {
82
+ x: C.x + (bc.x - C.x) * cos - (bc.y - C.y) * sin,
83
+ y: C.y + (bc.x - C.x) * sin + (bc.y - C.y) * cos,
84
+ };
85
+ push(i, { rect: { ...b.rect, x: Math.round(nc.x - b.rect.w / 2), y: Math.round(nc.y - b.rect.h / 2) }, d: b.d });
86
+ continue;
87
+ }
88
+ push(i, applyAbs(b, (p) => ({
89
+ x: C.x + (p.x - C.x) * cos - (p.y - C.y) * sin,
90
+ y: C.y + (p.x - C.x) * sin + (p.y - C.y) * cos,
91
+ })));
92
+ }
93
+ return out;
94
+ }
95
+ if (s.mirror) {
96
+ const { axis, at } = s.mirror;
97
+ const ax = at * UNIT;
98
+ push(0, makeOne(''));
99
+ push(1, applyAbs(makeOne(':m'), (p) => (axis === 'x' ? { x: 2 * ax - p.x, y: p.y } : { x: p.x, y: 2 * ax - p.y })));
100
+ return out;
101
+ }
102
+ if (s.scatter) {
103
+ const { n } = s.scatter;
104
+ const zone = s.scatter.in;
105
+ const R = { x: zone.x * UNIT, y: zone.y * UNIT, w: zone.w * UNIT, h: zone.h * UNIT };
106
+ const rand = rng(`scatter:${key}:${n}`);
107
+ for (let i = 0; i < n; i += 1) {
108
+ const b = makeOne(`:${i}`);
109
+ const k = 0.7 + rand() * 0.45; // 大小抖动 0.7~1.15
110
+ const scaled = applyAbs(b, (p) => ({ x: b.rect.x + (p.x - b.rect.x) * k, y: b.rect.y + (p.y - b.rect.y) * k }));
111
+ const x = R.x + rand() * Math.max(1, R.w - scaled.rect.w);
112
+ const y = R.y + rand() * Math.max(1, R.h - scaled.rect.h);
113
+ push(i, { rect: { ...scaled.rect, x: Math.round(x), y: Math.round(y) }, d: scaled.d });
114
+ }
115
+ return out;
116
+ }
117
+ return [{ key, ...makeOne('') }];
118
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * server/lib/sketch-hatch.js —— 排线填充(2026-08-30 画图能力线)
3
+ *
4
+ * 线稿世界里唯一体面的"上色":45° 手排线。闭合轮廓 → 一组扫描线段(偶奇规则),
5
+ * 端点带一点抖 —— 像人一笔一笔排出来的,不是激光刻的。
6
+ *
7
+ * 只对**闭合轮廓**生效(Z 收尾的子路径):往开放折线里灌填充没有定义良好的
8
+ * 内部,硬灌就是把问题藏起来 —— 调用方大声拒。
9
+ */
10
+
11
+ const f1 = (n) => Math.round(n * 10) / 10;
12
+
13
+ function rng(seedStr) {
14
+ let h = 0x811c9dc5;
15
+ for (let i = 0; i < seedStr.length; i += 1) { h ^= seedStr.charCodeAt(i); h = Math.imul(h, 0x01000193) >>> 0; }
16
+ let a = h >>> 0;
17
+ return () => {
18
+ a = (a + 0x6D2B79F5) >>> 0;
19
+ let t = a;
20
+ t = Math.imul(t ^ (t >>> 15), t | 1);
21
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
22
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
23
+ };
24
+ }
25
+
26
+ /** 大写绝对 M/L/Q/C/Z → 多边形组(曲线采样 8 段;只收 Z 闭合的子路径) */
27
+ export function flattenClosed(d) {
28
+ const tokens = String(d).match(/[MLQCZ]|-?\d*\.?\d+(?:[eE][-+]?\d+)?/g) || [];
29
+ const polys = [];
30
+ let pts = []; let i = 0; let closedAny = false;
31
+ const num = () => Number(tokens[i++]);
32
+ const flush = (closed) => { if (closed && pts.length >= 3) { polys.push(pts); closedAny = true; } pts = []; };
33
+ while (i < tokens.length) {
34
+ const t = tokens[i++];
35
+ if (t === 'M') { flush(false); pts = [{ x: num(), y: num() }]; continue; }
36
+ if (t === 'L') { pts.push({ x: num(), y: num() }); continue; }
37
+ if (t === 'Q' || t === 'C') {
38
+ const cps = t === 'Q' ? [{ x: num(), y: num() }] : [{ x: num(), y: num() }, { x: num(), y: num() }];
39
+ const end = { x: num(), y: num() };
40
+ const a = pts[pts.length - 1] || end;
41
+ for (let k = 1; k <= 8; k += 1) {
42
+ const u = k / 8;
43
+ let p;
44
+ if (t === 'Q') {
45
+ const v = 1 - u;
46
+ p = { x: v * v * a.x + 2 * v * u * cps[0].x + u * u * end.x, y: v * v * a.y + 2 * v * u * cps[0].y + u * u * end.y };
47
+ } else {
48
+ const v = 1 - u;
49
+ p = {
50
+ x: v ** 3 * a.x + 3 * v * v * u * cps[0].x + 3 * v * u * u * cps[1].x + u ** 3 * end.x,
51
+ y: v ** 3 * a.y + 3 * v * v * u * cps[0].y + 3 * v * u * u * cps[1].y + u ** 3 * end.y,
52
+ };
53
+ }
54
+ pts.push(p);
55
+ }
56
+ continue;
57
+ }
58
+ if (t === 'Z') { flush(true); continue; }
59
+ }
60
+ flush(false);
61
+ return { polys, closedAny };
62
+ }
63
+
64
+ /**
65
+ * 多边形组 → 45° 排线 d。偶奇规则(洞是真洞:月牙的内弧不会被排穿)。
66
+ * @returns {string} 空串 = 没有可填的面积
67
+ */
68
+ export function hatchD(polys, seed = 'hatch', { spacing = 11, angle = Math.PI / 4 } = {}) {
69
+ if (!polys?.length) return '';
70
+ const cos = Math.cos(-angle); const sin = Math.sin(-angle);
71
+ const rot = polys.map((p) => p.map(({ x, y }) => ({ x: x * cos - y * sin, y: x * sin + y * cos })));
72
+ let minY = Infinity; let maxY = -Infinity;
73
+ for (const p of rot) for (const q of p) { if (q.y < minY) minY = q.y; if (q.y > maxY) maxY = q.y; }
74
+ if (!Number.isFinite(minY)) return '';
75
+ const rand = rng(seed);
76
+ const back = (x, y) => ({ x: x * Math.cos(angle) - y * Math.sin(angle), y: x * Math.sin(angle) + y * Math.cos(angle) });
77
+ const segs = [];
78
+ for (let y = minY + spacing * 0.6; y < maxY; y += spacing) {
79
+ const xs = [];
80
+ for (const p of rot) {
81
+ for (let k = 0; k < p.length; k += 1) {
82
+ const a = p[k]; const b = p[(k + 1) % p.length];
83
+ if ((a.y <= y && b.y > y) || (b.y <= y && a.y > y)) {
84
+ xs.push(a.x + ((y - a.y) / (b.y - a.y)) * (b.x - a.x));
85
+ }
86
+ }
87
+ }
88
+ xs.sort((m, n) => m - n);
89
+ for (let k = 0; k + 1 < xs.length; k += 2) {
90
+ if (xs[k + 1] - xs[k] < 3) continue; // 太窄的缝不排
91
+ const inset = 1.5; // 别顶到轮廓线
92
+ const j = () => (rand() * 2 - 1) * 0.9;
93
+ const a = back(xs[k] + inset + j(), y + j());
94
+ const b = back(xs[k + 1] - inset + j(), y + j());
95
+ segs.push(`M ${f1(a.x)} ${f1(a.y)} L ${f1(b.x)} ${f1(b.y)}`);
96
+ }
97
+ }
98
+ return segs.join(' ');
99
+ }