@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
@@ -0,0 +1,108 @@
1
+ /**
2
+ * mcp/tools/write-on-board-flow.js —— write_on_board 的 flow 档(2026-08-30 刀⑦,
3
+ * 行数棘轮拆件:主文件只留一个入口)。
4
+ *
5
+ * 「剩 15 行 vs 我这段几行」这道算术模型做不准也不该做(proj_mtfpehm3 真会话
6
+ * 14 发 board_batch 挂 6 发全是容量拒收)。flow 把它拿走:整段内容按段落边界
7
+ * 拆成一串 ≤ 一张卡高的小板书,链着往版位/纸里排,装到哪儿是哪儿 —— 装不下的
8
+ * **原样退回**(不挤 / 不丢 / 不替它翻页,刀 F 的拒收教义一条不破)。
9
+ */
10
+
11
+ import { flowChunks } from '../../../lib/chalk-flow.js';
12
+ import { textBox, capacityOf } from '../../../lib/sketch-layout.js';
13
+ import { CARD_MAX_H } from '../../../lib/screen.js';
14
+ import { UNIT } from '../../../lib/rect.js';
15
+ import { renderChalk, chalkFileName, writeChalkFile } from '../../../lib/chalk.js';
16
+ import { patchBoard } from '../../../projects/board-store.js';
17
+ import { applyFollows } from '../../../lib/board-follow.js';
18
+ import { Events } from '../../agent/events.js';
19
+
20
+ /**
21
+ * flow 写入。返回工具结果;**返回 null = 用不上 flow**(守卫不过 / 一块就装下),
22
+ * 调用方接着走正常单条路径。
23
+ */
24
+ export async function maybeFlowWrite({
25
+ projectId, sharedRoot, sessionId, by, ctx, args, body, wUnits, zone,
26
+ slotInfo, parentId, replyRect, anchorId, b2, obstaclesOf,
27
+ placeInSlot, placeOnSheets, describeSheetFull, stamp,
28
+ }) {
29
+ const err = (t) => ({ content: [{ type: 'text', text: t }], isError: true });
30
+ if (args.ink === 'hand') return err("flow 拆的是板书链,ink:'hand' 没有文件本体接不了链 —— 去掉其中一个。");
31
+ if (args.open_lane) return err('flow 跟 open_lane 分两步:先 open_lane 落线头,再 {tag, chain:true, flow:true} 续长文。');
32
+ if (zone) return err('flow 排的是纸,文件夹层没有纸 —— 在文件夹里逐条写。');
33
+ const chunks = flowChunks(body, { wUnits, size: args.size || 'md', maxH: CARD_MAX_H });
34
+ if (chunks.length <= 1) return null; // 体积本来就小,正常单条路径
35
+
36
+ let live = b2;
37
+ const objects = {}; const bindings = {};
38
+ const written = [];
39
+ let prevRel = parentId; let prevRect = replyRect;
40
+ let leftover = 0; let fullMsg = null;
41
+ for (let i = 0; i < chunks.length; i += 1) {
42
+ const cBox = textBox(chunks[i], args.size === 'sm' ? 'md' : (args.size || 'md'), { md: true, wUnits });
43
+ const obs = obstaclesOf(live, '');
44
+ let p;
45
+ if (slotInfo) {
46
+ p = placeInSlot(live, { rect: slotInfo.rect, sheet: slotInfo.sheet, slotName: args.slot, box: cBox, obstacles: obs });
47
+ if (p.full) { leftover = chunks.length - i; fullMsg = p.message; break; }
48
+ } else {
49
+ p = await placeOnSheets(live, {
50
+ box: cBox, at: (i === 0 && args.at) ? args.at : null, sheetName: args.sheet || null,
51
+ replyRect: prevRect, anchorRect: null, side: null, obstacles: obs,
52
+ });
53
+ if (p.sheetFull) { leftover = chunks.length - i; fullMsg = describeSheetFull(live, p.sheetFull); break; }
54
+ if (p.opened) live = { ...live, sheets: { ...(live.sheets || {}), [p.opened.id]: { x: p.opened.x, y: p.opened.y, w: p.opened.w, h: p.opened.h, at: p.opened.at, by } } };
55
+ }
56
+ const content = renderChalk({
57
+ body: chunks[i], by, anchor: i === 0 ? anchorId : null,
58
+ replyTo: prevRel, tag: args.tag || null, sessionId: sessionId || null,
59
+ });
60
+ const rel = await writeChalkFile(sharedRoot, chalkFileName(chunks[i]), content);
61
+ const entry = {
62
+ x: Math.round(p.x), y: Math.round(p.y), z: 1, w: cBox.w, h: cBox.h,
63
+ zone: '', by, seat: 'agent', ...(args.tag ? { tag: args.tag } : {}),
64
+ };
65
+ objects[rel] = entry;
66
+ if (i === 0 && anchorId) {
67
+ const type = args.relation || 'annotates';
68
+ const [from, to] = type === 'flow' ? [anchorId, rel] : [rel, anchorId];
69
+ bindings[`b:a${stamp()}`] = { type, from, to, by, ...(args.tag ? { tag: args.tag } : {}) };
70
+ }
71
+ if (prevRel) bindings[`b:a${stamp()}`] = { type: 'flow', from: prevRel, to: rel, by, material: 'pencil', ...(args.tag ? { tag: args.tag } : {}) };
72
+ live = { ...live, objects: { ...(live.objects || {}), [rel]: entry } };
73
+ written.push({ rel, rect: { x: entry.x, y: entry.y, w: entry.w, h: entry.h } });
74
+ prevRel = rel; prevRect = written[written.length - 1].rect;
75
+ }
76
+ if (!written.length) return err(fullMsg || '⛔ 一条都没放下。');
77
+ await patchBoard(projectId, { objects, bindings });
78
+ if (args.tag) { try { await applyFollows(projectId, { tag: args.tag, newId: written[written.length - 1].rel }); } catch { /* */ } }
79
+ const xs = written.map((w) => w.rect.x); const ys = written.map((w) => w.rect.y);
80
+ const bbox = {
81
+ x: Math.min(...xs), y: Math.min(...ys),
82
+ w: Math.max(...written.map((w) => w.rect.x + w.rect.w)) - Math.min(...xs),
83
+ h: Math.max(...written.map((w) => w.rect.y + w.rect.h)) - Math.min(...ys),
84
+ };
85
+ try {
86
+ ctx?.emit?.({ type: 'board.updated', sessionId: null, summary: `写了一串板书(${written.length} 条)` });
87
+ ctx?.emit?.(Events.boardFocus(bbox, { tag: args.tag || null, layer: '', soft: true, chalk: written[0].rel, actor: by !== 'agent' ? by : null }));
88
+ } catch { /* fail-soft */ }
89
+ const lines = [
90
+ `Flowed into ${written.length} chained notes${args.slot ? ` in slot "${args.slot}"` : ''}:`,
91
+ ...written.map((w) => ` ${w.rel} at (${w.rect.x},${w.rect.y}) ${w.rect.w}x${w.rect.h}`),
92
+ ];
93
+ if (slotInfo) {
94
+ const last = written[written.length - 1].rect;
95
+ const freeH = Math.max(0, Math.round(slotInfo.rect.y + slotInfo.rect.h - (last.y + last.h) - UNIT));
96
+ const c = capacityOf(slotInfo.rect.w, freeH);
97
+ lines.push(`Slot "${args.slot}" now has ~${c.lines} lines (~${c.cjk} CJK chars, ${freeH}px) left.`);
98
+ }
99
+ if (leftover) {
100
+ const rest = chunks.slice(chunks.length - leftover);
101
+ const restH = rest.reduce((n, t) => n + textBox(t, 'md', { md: true, wUnits }).h, 0);
102
+ lines.push(`⚠ ${leftover} paragraph(s) did NOT fit (from 「${[...rest[0]].slice(0, 16).join('')}…」, ~${Math.ceil(restH / 26)} lines). They were returned untouched — nothing was squeezed or dropped.`);
103
+ if (fullMsg) lines.push(fullMsg.split('\n')[0]);
104
+ lines.push(`Continue them yourself: open the next page (open_sheet) or pick another slot, then write the REST again with {flow:true, chain:true${args.tag ? `, tag:"${args.tag}"` : ''}} — chain threads it onto ${written[written.length - 1].rel}.`);
105
+ }
106
+ lines.push('The user can annotate any of them to reply; answer with reply_to.');
107
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
108
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * mcp/tools/write-on-board-place.js —— write_on_board 的纸上落位(2026-08-29 纸范式刀 2,
3
+ * 行数棘轮拆件:text 与 sketch 两条路共用同一份落位与返回文案)。
4
+ *
5
+ * 决策树(启发式引擎退役后仅存的分支):
6
+ * near+side 显式 → 精确贴放(语义要求;压上如实报)
7
+ * slot → 规划好的那块地里往下堆(装不下**拒收**,一个字不落盘)
8
+ * reply_to → 接楼正下方(纸满拒收)
9
+ * at → 纸内定点(钳进版心,钳了如实报)
10
+ * 什么都没有 → 纸内顺排(先往下、这一列到底往右;真的排不下才拒收)
11
+ * 文件夹层没有纸:线程/贴放照常,否则排在这一层内容底下。
12
+ */
13
+
14
+ import {
15
+ currentSheet, toLocal, placeAtOnSheet, placeThread, placeBeside,
16
+ nextSpotInSheet, overlapIds, sheetOfPoint, slotRectOf, nextSpotInSlot,
17
+ } from '../../../lib/board-sheets.js';
18
+ import { capacityOf } from '../../../lib/sketch-layout.js';
19
+ import { currentSheetIdOf, setCurrentSheetId } from '../../../lib/sheet-state.js';
20
+ import { UNIT } from '../../../lib/rect.js';
21
+ import { openSheetFor } from './open-sheet.js';
22
+
23
+ export function makeSheetPlacer({ projectId, sessionId, by }) {
24
+ /**
25
+ * 版位解析(2026-08-29 刀 E)。**要在算板书宽度之前调** —— 一块地多宽,
26
+ * 写进去的东西就多宽,不再各自按内容估。
27
+ * @returns {{rect,sheet}|{error:string,message:string}}
28
+ */
29
+ const resolveSlot = (b, { slotName, sheetName }) => {
30
+ const sheets = b.sheets || {};
31
+ const sheet = (sheetName && sheets[sheetName])
32
+ ? { id: sheetName, ...sheets[sheetName] }
33
+ : currentSheet({ sheets }, currentSheetIdOf(sessionId));
34
+ if (!sheet) {
35
+ return { error: 'no-sheet', message: 'No sheet yet — open_sheet first (plan the page, then write into its slots).' };
36
+ }
37
+ const rect = slotRectOf(sheet, slotName);
38
+ if (!rect) {
39
+ const names = Object.keys(sheet.slots || {});
40
+ return {
41
+ error: 'no-slot',
42
+ message: names.length
43
+ ? `Sheet ${sheet.id} has no slot "${slotName}". It has: ${names.join(', ')}.`
44
+ : `Sheet ${sheet.id} has no slots planned. Plan the page first: open_sheet{plan:[{slot,at,w,h,about}…]}.`,
45
+ };
46
+ }
47
+ return { rect, sheet };
48
+ };
49
+
50
+ /**
51
+ * 版位内落位。装不下**拒收**(站主拍板:提示 agent 分块内容、重新布置)——
52
+ * 折叠/裁切/挤进去都是替它把问题藏起来,而它下一条还会照写不误。
53
+ */
54
+ const placeInSlot = (b, { rect, sheet, slotName, box, obstacles }) => {
55
+ const spot = nextSpotInSlot(b, rect, box);
56
+ if (spot.full) {
57
+ const left = capacityOf(rect.w, spot.freeH);
58
+ const whole = capacityOf(rect.w, rect.h);
59
+ // 量纲对齐(刀⑥ 2026-08-30):两边都报 px + 行,还差多少直接说 —— 此前
60
+ // 「剩 ~15 行 / 要 ~15 行」被拒,在模型眼里就是量具坏了(真差 21px)。
61
+ const short = spot.needH - spot.freeH;
62
+ return {
63
+ full: true,
64
+ message: [
65
+ `⛔ Slot "${slotName}" on sheet ${sheet.id} cannot take this — short by ${short}px (~${Math.max(1, Math.ceil(short / 26))} line${short > 26 ? 's' : ''}).`,
66
+ ` Free: ${spot.freeH}px (~${left.lines} lines / ~${left.cjk} CJK chars); this note needs ${spot.needH}px.`
67
+ + `${spot.taken ? ` The ${rect.w}x${rect.h} slot (~${whole.cjk} CJK chars) already holds ${spot.taken} item(s).` : ''}`,
68
+ ' Nothing was written. Split it YOUR way: make this slot taller (replan it by name,',
69
+ ' omit at — it resizes in place) or carve fresh blocks (omit at to stack below) and fill them one',
70
+ ' note each. Or trim it. Lazy fallback: flow:true lets the machine split at paragraph breaks.',
71
+ ].join('\n'),
72
+ };
73
+ }
74
+ setCurrentSheetId(sessionId, sheet.id);
75
+ return {
76
+ x: spot.x, y: spot.y, resolution: 'slot', slot: slotName, sheetId: sheet.id,
77
+ pressed: overlapIds({ x: spot.x, y: spot.y, w: box.w, h: box.h }, obstacles),
78
+ };
79
+ };
80
+
81
+ /** 根层:纸上落位。返回 {x,y,resolution,sheetId,opened,clamped,pressed} */
82
+ const placeOnSheets = async (b, { box, at, sheetName, replyRect, anchorRect, side, obstacles }) => {
83
+ let sheets = b.sheets || {};
84
+ let opened = null;
85
+ const pick = () => {
86
+ if (sheetName && sheets[sheetName]) return { id: sheetName, ...sheets[sheetName] };
87
+ return currentSheet({ sheets }, currentSheetIdOf(sessionId));
88
+ };
89
+ /** 铺第一张纸(还一张都没有时)。**不用于翻页** —— 见下方 sheetFull。 */
90
+ const openFirst = async () => {
91
+ opened = await openSheetFor(projectId, { sessionId, by, where: null });
92
+ sheets = { ...sheets, [opened.id]: { x: opened.x, y: opened.y, w: opened.w, h: opened.h, at: opened.at, by: opened.by } };
93
+ return { id: opened.id, ...sheets[opened.id] };
94
+ };
95
+ const bWith = () => ({ ...b, sheets });
96
+ const done = (p, resolution, sheetId, clamped = false) => {
97
+ if (sheetId) setCurrentSheetId(sessionId, sheetId);
98
+ const pressed = overlapIds({ x: p.x, y: p.y, w: box.w, h: box.h }, obstacles);
99
+ return {
100
+ x: Math.round(p.x), y: Math.round(p.y), resolution, sheetId, opened, clamped, pressed,
101
+ overflowY: p.overflowY || 0, // 纸从那个 y 往下不够高还差多少(换纸判据)
102
+ moved: !!p.moved, // 这一列到底了、往右挪了一块空地
103
+ };
104
+ };
105
+ const sheetOf = (p) => {
106
+ const hit = sheetOfPoint(bWith(), { x: p.x + box.w / 2, y: p.y + box.h / 2 });
107
+ return hit ? hit.id : null;
108
+ };
109
+
110
+ // 显式贴放(near + side):语义要求(题注在上方)的精确几何,不搜索
111
+ if (anchorRect && side) {
112
+ const p = placeBeside(anchorRect, box, side, UNIT);
113
+ return done(p, `beside-${side}`, sheetOf(p));
114
+ }
115
+ // 线程:正下方;接不下去就在同一张纸上顺排(真满了才翻页)
116
+ if (replyRect) {
117
+ const p = placeThread(bWith(), replyRect, box, { obstacles });
118
+ if (!p.sheetFull) return done(p, 'thread', p.sheetId);
119
+ /**
120
+ * ⚠️ placeThread 只认**正下方一个方向**,被挡住就往那件底下跳,一跳出纸底
121
+ * 就报 sheetFull —— 但这只说明"这条线接不下去",不说明"这张纸满了"。
122
+ *
123
+ * 真案 proj_mtg61or1 19:13:p1 上母板书正下方杵着 960x628 的试作站点卡,
124
+ * 接楼跳过它就出了版心底(2437),于是报了
125
+ * 「⛔ Sheet p1 is full (all columns used) — nothing was written」。
126
+ * 同一张纸同一刻,nextSpotInSheet 返回 (1392,1512) —— 第 4 栏整栏空着。
127
+ * agent 信了这句话去 open_sheet,一场会话开出四张纸;每开一张,暂存架和
128
+ * 纸互相顶的那个棘轮就再转一格(见 lib/board-shelf.js bandHitsSheet)。
129
+ *
130
+ * 所以先在同一张纸上顺排。回复关系靠 reply_to 落的那条边保着,本来就
131
+ * 不依赖几何相邻 —— 挪一栏丢的只是"紧贴在下面"这个视觉暗示。
132
+ */
133
+ const flow = nextSpotInSheet(bWith(), p.sheetFull, box);
134
+ if (flow) return done(flow, 'thread-flow', p.sheetFull);
135
+ return { sheetFull: p.sheetFull };
136
+ }
137
+ // 定点 / 顺排:都要有一张纸
138
+ let s = pick();
139
+ if (!s) s = await openFirst();
140
+ if (at) {
141
+ const p = placeAtOnSheet(s, at, box);
142
+ return done(p, 'at', s.id, p.clamped);
143
+ }
144
+ const flow = nextSpotInSheet(bWith(), s.id, box);
145
+ if (flow) return done(flow, 'flow', s.id);
146
+ // 这张纸排满了。**不替它翻页**(2026-08-29 刀 F,站主拍板"每张纸规划一次"):
147
+ // 机器悄悄翻页的话,agent 根本不知道自己换了页,新纸自然也没有版面 —— 真会话
148
+ // proj_mtfhey1x 里 p2 规划得好好的,写满翻到 p3 就散回顺排了。纸是它开的,
149
+ // 满了该由它决定下一页什么样。
150
+ return { sheetFull: s.id };
151
+ };
152
+
153
+ /** 文件夹层落位(没有纸):线程/贴放照常,否则排在这一层内容底下 */
154
+ const placeInZone = ({ box, replyRect, anchorRect, side, obstacles }) => {
155
+ if (replyRect) {
156
+ const p = placeThread({ sheets: {} }, replyRect, box, { obstacles });
157
+ return { x: p.x, y: p.y, resolution: 'thread', pressed: [] };
158
+ }
159
+ if (anchorRect) {
160
+ const p = placeBeside(anchorRect, box, side || 'below', UNIT);
161
+ return { x: p.x, y: p.y, resolution: `beside-${side || 'below'}`, pressed: overlapIds({ x: p.x, y: p.y, w: box.w, h: box.h }, obstacles) };
162
+ }
163
+ const left = obstacles.length ? Math.min(...obstacles.map(o => o.x)) : 10;
164
+ const bottom = obstacles.reduce((m, o) => Math.max(m, o.y + o.h), 0);
165
+ return { x: Math.round(left), y: Math.round(bottom) + 40, resolution: 'below-content', pressed: [] };
166
+ };
167
+
168
+ /** 纸排满了的报文:不替它翻页,告诉它该规划下一页了 */
169
+ const describeSheetFull = (b, sheetId) => {
170
+ const sh = b.sheets?.[sheetId];
171
+ const inner = sh ? { w: sh.w - 48, h: sh.h - 48 } : { w: 0, h: 0 };
172
+ const landscape = inner.w > inner.h;
173
+ return [
174
+ `⛔ Sheet ${sheetId} is full${landscape ? ' (all columns used)' : ''} — nothing was written.`,
175
+ ` Open the next page yourself and plan it: open_sheet{title:"…", plan:[{slot,at,w,h,about}…]}.`,
176
+ ` Each sheet gets its own layout — decide what this next page is for before filling it.`,
177
+ ].join('\n');
178
+ };
179
+
180
+ /** 返回文案:从真实落点生成("工具返回不许撒谎"—— 08-25 陷阱③ 的纪律不变) */
181
+ const describeSpot = (b, placed) => {
182
+ const bits = [];
183
+ if (placed.sheetId && b.sheets?.[placed.sheetId]) {
184
+ const s = { id: placed.sheetId, ...b.sheets[placed.sheetId] };
185
+ const l = toLocal(s, placed);
186
+ bits.push(`on sheet ${s.id}${s.title ? `(${s.title})` : ''} at local (${Math.round(l.x)},${Math.round(l.y)})`);
187
+ }
188
+ if (placed.resolution === 'thread') bits.push('under the note it replies to (thread)');
189
+ else if (placed.resolution === 'flow') {
190
+ bits.push(placed.moved
191
+ ? 'the column you were in ran out — flowed into free space further right on the same sheet'
192
+ : 'flowed below the last item');
193
+ }
194
+ else if (placed.resolution === 'slot') bits.push(`in slot "${placed.slot}" (planned block)`);
195
+ else if (placed.resolution === 'at') {
196
+ // 换纸判据(08-29 刀 C):光说"钳住了"不够 —— 钳住的结果是这条被压到贴着
197
+ // 纸底、跟上一条挤在一起,而 agent 不知道该翻页了。
198
+ bits.push(placed.overflowY
199
+ ? `at your spot but this sheet RAN OUT below that y (short by ${placed.overflowY}px) — it was pushed up to fit. Turn the page (open_sheet) or write it shorter`
200
+ : (placed.clamped ? 'at your spot, CLAMPED into the sheet (it stuck out)' : 'exactly where you asked'));
201
+ }
202
+ else if (placed.resolution?.startsWith('beside-')) bits.push(`${placed.resolution.slice(7)} of the anchor (exact — no auto-nudging)`);
203
+ else if (placed.resolution === 'lane-open') bits.push('at the head of its fresh sheet');
204
+ else if (placed.resolution === 'below-content') bits.push('below current content (folder layer has no sheets)');
205
+ if (placed.opened) bits.push(`opened sheet ${placed.opened.id} (${placed.opened.innerW}x${placed.opened.innerH} writable)`);
206
+ if (placed.pressed?.length) bits.push(`⚠ overlaps ${placed.pressed.slice(0, 4).join(', ')} — move yours (edit_board) if unintended`);
207
+ return bits.join('; ');
208
+ };
209
+
210
+ return { placeOnSheets, placeInZone, describeSpot, resolveSlot, placeInSlot, describeSheetFull };
211
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * mcp/tools/write-on-board-role-anchor.js —— 角色板书的缺省锚(2026-08-28;08-29 泛化成「这一拍」)
3
+ *
4
+ * 角色不带任何落位线索(reply_to/near/at/open_lane)时,机器替它选锚。两级:
5
+ *
6
+ * 1. **属于它的那条线**(预制摆位,用户拍板):主持人用 open_lane 以角色名(slug 或
7
+ * 展示名)开过线 → 自动接在自己那条线上(reply_to 线内最新一条 + 着线的 tag)。
8
+ * 摆位和延伸方向是主持人预制的,角色的工作退化成「补一段话」。
9
+ * 2. **这一拍**(08-29):角色没有专属线时,它这一段就是在回应**主持人最新写的那一条**
10
+ * —— reply_to 它,再经侧挂直觉落到它身侧。「章节竖着走、同一拍的几个人横着排」
11
+ * 是从「在回应谁」这个语义里自己长出来的,不硬编版式。
12
+ *
13
+ * 08-28 时这一级只在 rounds 模式下开;模式概念 08-29 整个退役(谁接这一拍由主持人
14
+ * 每拍决定,不再是一个服务端状态),所以这一级对所有角色一律成立。
15
+ *
16
+ * 角色明确给了落位(比如回用户那条)时它的手优先,这里不抢。
17
+ */
18
+ import { CHALK_DIR } from '../../../lib/chalk.js';
19
+ import { listRoleNames } from '../../agent/role-card.js';
20
+
21
+ /**
22
+ * @returns {Promise<{replyTo: string|null, tag: string|null}>}
23
+ * replyTo 非空 = 用它当缺省锚;tag 非空 = 这条板书顺带着线的 tag(仅专属线路径给)。
24
+ */
25
+ export async function roleDefaultAnchor({ board, by, sharedRoot }) {
26
+ // 属于它的那条线优先
27
+ if (board.lanes) {
28
+ let laneTag = board.lanes[by] ? by : null;
29
+ if (!laneTag) {
30
+ try {
31
+ const nm = (await listRoleNames(sharedRoot)).get(by);
32
+ if (nm && board.lanes[nm]) laneTag = nm;
33
+ } catch { /* 展示名查不到就只认 slug 线 */ }
34
+ }
35
+ if (laneTag) {
36
+ const inLane = Object.entries(board.objects)
37
+ .filter(([id, e]) => id.startsWith(`${CHALK_DIR}/`) && Number.isFinite(e?.x) && e.tag === laneTag)
38
+ .map(([id]) => id).sort();
39
+ if (inLane.length) return { replyTo: inLane[inLane.length - 1], tag: laneTag };
40
+ }
41
+ }
42
+ // 这一拍 = 主持人最新写的那一条
43
+ const beats = Object.entries(board.objects)
44
+ .filter(([id, e]) => id.startsWith(`${CHALK_DIR}/`) && Number.isFinite(e?.x) && (e.by || 'agent') === 'agent')
45
+ .map(([id]) => id).sort(); // 板书文件名以时间戳开头,路径序即时间序
46
+ if (beats.length) return { replyTo: beats[beats.length - 1], tag: null };
47
+ return { replyTo: null, tag: null };
48
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * write_on_board 的图形入参 schema(2026-08-28 从 write-on-board.js 拆出 —— 行数棘轮;
3
+ * 纯数据,跟落板逻辑零耦合)。上限是失控兜底不是风格闸:zod 硬拒(-32602 整调用作废)
4
+ * 是最贵的失败模式,可读性走返回值里的软提醒(08-25 用户拍板「移除画板上限」)。
5
+ */
6
+
7
+ import { z } from 'zod';
8
+ import { BINDING_TYPE_IDS, BINDING_MATERIALS } from '../../../lib/binding-types.js';
9
+ import { TAG_RE } from '../../../projects/board-sanitize.js';
10
+ import { CHALK_DIR } from '../../../lib/chalk.js';
11
+ import { STENCIL_NAMES } from '../../../lib/sketch-stencils.js';
12
+
13
+ const MAX_NODES = 200;
14
+ const MAX_SHAPES = 120;
15
+ const MAX_EDGES = 400;
16
+
17
+ const LOCAL_ID = z.string().regex(/^[A-Za-z0-9_-]{1,48}$/, 'local id: letters/digits/_/-');
18
+ const GRID_PT = z.object({ x: z.number().min(-2000).max(2000), y: z.number().min(-2000).max(2000) });
19
+ export const WORLD_PT = z.object({ x: z.number().min(-1e6).max(1e6), y: z.number().min(-1e6).max(1e6) });
20
+ /**
21
+ * 纸内坐标(2026-08-29 纸范式):以当前纸版心左上角为原点的像素。越界**钳住不拒收**
22
+ * (schema 整单拒是最贵的失败模式;钳过在返回里如实报)。preprocess 在给模型看的
23
+ * JSON schema 里隐形 —— 文档照旧严格,垫片只当安全网。
24
+ */
25
+ const clampN = (lo, hi) => (v) => (typeof v === 'number' && Number.isFinite(v) ? Math.max(lo, Math.min(hi, v)) : v);
26
+ export const SHEET_PT = z.object({
27
+ x: z.preprocess(clampN(0, 12000), z.number().min(0).max(12000)),
28
+ y: z.preprocess(clampN(0, 12000), z.number().min(0).max(12000)),
29
+ });
30
+
31
+ export const NODES = z.array(z.object({
32
+ id: LOCAL_ID.describe('Local id to reference from edges/shapes'),
33
+ text: z.string().min(1).max(8000),
34
+ format: z.enum(['plain', 'md']).optional().describe('Default: md when the text carries markdown marks, else plain'),
35
+ size: z.enum(['sm', 'md', 'lg', 'xl']).optional(),
36
+ font: z.enum(['pen', 'kai', 'sans', 'serif', 'mono']).optional(),
37
+ color: z.enum(['ink', 'red', 'pencil', 'brass']).optional(),
38
+ at: GRID_PT.optional().describe('Grid position (layout free); top-left of the node'),
39
+ w: z.number().min(3).max(120).optional().describe('Width in grid units (prefer ≤22 = 528px: paragraphs read better growing down than wide)'),
40
+ })).max(MAX_NODES);
41
+
42
+ export const SHAPES = z.array(z.object({
43
+ id: LOCAL_ID.optional(),
44
+ kind: z.enum(['rect', 'ellipse', 'circle', 'line', 'arrow', 'underline', 'path', 'stencil']),
45
+ at: GRID_PT.optional(),
46
+ name: z.enum(STENCIL_NAMES).optional()
47
+ .describe('stencil kind: which pictogram — a hand-tuned little drawing placed at at, scaled to w (h optional, defaults to its natural aspect). Use these instead of drawing common things stroke by stroke'),
48
+ flip: z.boolean().optional().describe('stencil: mirror horizontally (facing the other way)'),
49
+ around: LOCAL_ID.optional().describe('rect/ellipse/circle/underline: wrap this node instead of at/w/h'),
50
+ w: z.number().min(0).max(200).optional(),
51
+ h: z.number().min(0).max(200).optional(),
52
+ to: GRID_PT.optional(),
53
+ toNode: LOCAL_ID.optional(),
54
+ d: z.string().max(8000).optional().describe('path kind: SVG path, UPPERCASE absolute M/L/Q/C/Z only. Coordinates are in the SAME GRID UNITS as at/w/h (1 unit = 24px; decimals fine) — one coordinate space for the whole sketch. Hand-drawn wobble is applied for you; Q/C curves are how you draw anything smooth (a crescent moon, a wave, a sail)'),
55
+ color: z.enum(['ink', 'red', 'pencil', 'brass']).optional(),
56
+ width: z.number().min(1).max(12).optional(),
57
+ fill: z.enum(['hatch']).optional()
58
+ .describe('Shade the inside with hand-drawn 45° hatching — the line-art way to darken an area (a shadow side, water, a filled banner). Closed shapes only (rect/ellipse/circle, a path whose subpaths end with Z, closable stencils)'),
59
+ // ── 算子(一次挂一个;等距/对称/播撒这类算术归机器,每份笔迹各自抖)──
60
+ repeat: z.object({ n: z.number().int().min(2).max(24), dx: z.number().min(-200).max(200).optional(), dy: z.number().min(-200).max(200).optional() }).optional()
61
+ .describe('LINEAR ARRAY: n copies stepped by (dx,dy) grid units — a fence, windows, steps. Copies are re-inked, not stamped'),
62
+ ring: z.object({ n: z.number().int().min(2).max(24), cx: z.number().min(-2000).max(2000), cy: z.number().min(-2000).max(2000), upright: z.boolean().optional() }).optional()
63
+ .describe('CIRCULAR ARRAY: n copies around grid point (cx,cy). Draw ONE at its 12-o-clock spot. Default ROTATES each copy (petals, clock marks, top-down chairs facing a table); upright:true keeps them unrotated and just seats them round the circle (side-view things with gravity — tents or people round a fire)'),
64
+ mirror: z.object({ axis: z.enum(['x', 'y']), at: z.number().min(-2000).max(2000) }).optional()
65
+ .describe("SYMMETRY: adds a mirrored copy across the axis at grid position `at` (axis:'x' = vertical mirror line). Draw half a butterfly, get the whole one"),
66
+ scatter: z.object({ n: z.number().int().min(2).max(40), in: z.object({ x: z.number(), y: z.number(), w: z.number().min(1), h: z.number().min(1) }) }).optional()
67
+ .describe('SPRINKLE: n copies at seeded-random spots inside the grid rect, with slight size jitter — a starry sky, grass, pebbles. Same call replays identically'),
68
+ })).max(MAX_SHAPES);
69
+
70
+ export const EDGES = z.array(z.object({
71
+ from: z.string().min(1).max(300).describe('local node id or canvas id'),
72
+ to: z.string().min(1).max(300).describe('local node id or canvas id'),
73
+ type: z.enum(BINDING_TYPE_IDS).optional().describe('default link'),
74
+ material: z.enum(BINDING_MATERIALS).optional().describe('default pencil'),
75
+ label: z.string().max(60).optional(),
76
+ })).max(MAX_EDGES);
77
+
78
+ /**
79
+ * ⚠️ 字段顺序有意义(2026-08-29 占位契约刀 C):模型是按 schema 声明顺序生成
80
+ * JSON 的,而入参是**流式**到达前端的 —— 位置字段排在 text 前面,画布才能在
81
+ * 第一个字到达时就把框立在真位置上,让正文流进去(排在后面的话,字已经流完了
82
+ * 位置才到,只能先画在一块空地上再跳过去 —— 那正是这一刀要治的)。
83
+ * 抽取规则在 agent-shared.js 的 TOOL_INPUT_STREAM_FIELDS.spot。
84
+ */
85
+ export const WRITE_SCHEMA = {
86
+ slot: z.string().regex(TAG_RE).optional()
87
+ .describe('Drop this into a PLANNED BLOCK of the current sheet (names come from open_sheet{plan}). The block decides the width and where it lands; notes stack downward inside it. If it does not fit, the write is REFUSED with how much room is left — split the content or re-plan. Prefer this over at: plan the page first, then fill it.'),
88
+ at: SHEET_PT.optional()
89
+ .describe("Where on the CURRENT SHEET, in pixels from its top-left writable corner (x→right, y→down). Clamped into the sheet — the return says if it was. Omit it to flow top-to-bottom"),
90
+ sheet: z.string().regex(TAG_RE).optional()
91
+ .describe('Write on this sheet instead of the current one (names from open_sheet / read_board)'),
92
+ width: z.number().min(8).max(60).optional().describe('Single note width in grid units (24px). Default: the width the user last dragged chalk blocks to, else by content. Omit it unless this one block needs a different measure - the default already follows the user.'),
93
+ near: z.string().max(300).optional()
94
+ .describe('Canvas id or #tag this is ABOUT — draws an annotates line to it. Placement itself is by sheet (at / flow), not by near'),
95
+ side: z.enum(['right', 'left', 'above', 'below']).optional()
96
+ .describe('ONLY with near, when the SEMANTICS demand a side (e.g. a caption must sit above): exact placement beside the anchor. Normally omit — sheets flow downward'),
97
+ reply_to: z.string().max(300).optional().describe(`Thread: path of a board note (${CHALK_DIR}/…md) to answer under (lands right below it; a full sheet turns the page)`),
98
+ text: z.string().min(1).max(8000).optional()
99
+ .describe('The one-note shorthand: a short Markdown note (= a 1-piece board write). Give text OR nodes/shapes, not both'),
100
+ flow: z.boolean().optional()
101
+ .describe('Lazy FALLBACK for a ready-made long text: the machine splits it at paragraph breaks into a chain of card-sized notes, packs them as far as they fit, and returns the rest untouched (never squeezed, dropped, or auto-paged). PREFER carving slots yourself (open_sheet{plan}/replan, omit at to stack) and filling them one note each — you keep control of where each part breaks.'),
102
+ h: z.number().min(24).max(2000).optional()
103
+ .describe('Reserve this HEIGHT in pixels for the note box, placed before the text settles (plan the box, then fill it). Shorter content keeps the box; longer content overrides it. Ignored with flow.'),
104
+ relation: z.enum(BINDING_TYPE_IDS).optional()
105
+ .describe('Line type for the near line of a single note (default annotates; flow reads anchor→note)'),
106
+ chain: z.boolean().optional()
107
+ .describe('Single note: auto reply_to the latest board note of the same tag WRITTEN BY YOU (threads never cross authors — continuation rights)'),
108
+ open_lane: z.string().max(300).optional()
109
+ .describe("Open a NEW thread line named by tag: lays a fresh sheet for it and lands this note at its head. Value: a canvas id/#tag to BRANCH from (draws a flow line from it), or 'fresh' for a brand-new topic. Requires tag; continue with {tag, chain:true}."),
110
+ tag: z.string().regex(TAG_RE).optional()
111
+ .describe('Group tag. A 1-piece write stays untagged unless you pass one; ≥2 pieces auto-tag sk-<stamp>'),
112
+ ink: z.enum(['chalk', 'hand']).optional()
113
+ .describe("Single note body: 'chalk' (default) = a real file under notes/板书 (Read/Edit later; chain/reply threads live on these); 'hand' = canvas-native handwritten text — a light remark like the user's own handwriting, no file, no threading"),
114
+ font: z.enum(['pen', 'kai', 'sans', 'serif', 'mono']).optional().describe("Single note font (ink:'hand'; default kai)"),
115
+ color: z.enum(['ink', 'red', 'pencil', 'brass']).optional().describe("Single note color (ink:'hand')"),
116
+ size: z.enum(['sm', 'md', 'lg', 'xl']).optional().describe("Single note text size. Real for ink:'hand'; for chalk notes it only sizes the placement box (chalk renders at a fixed size)"),
117
+ title: z.string().max(60).optional().describe('Sketch: optional heading written at the top'),
118
+ layout: z.enum(['auto', 'free', 'column', 'row', 'grid', 'mindmap', 'flow']).optional()
119
+ .describe('Sketch layout. auto FOLLOWS YOUR EDGES: with edges it lays out in flow layers (roots on top, children below — give edges and placement is structure); mindmap picks the hub by degree. free needs at on EVERY node'),
120
+ cols: z.number().int().min(1).max(8).optional().describe('grid columns'),
121
+ staging: z.boolean().optional().describe('Sketch only: default true (translucent until commit/turn end). Ignored for single notes — they always land solid'),
122
+ nodes: NODES.optional(),
123
+ shapes: SHAPES.optional(),
124
+ edges: EDGES.optional(),
125
+ };