@xiaobuyu/nodesign 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/README.md +266 -79
  2. package/README.zh-CN.md +254 -67
  3. package/package.json +2 -2
  4. package/server/api/admin.js +7 -0
  5. package/server/api/assets/docx-page.js +5 -4
  6. package/server/api/assets/helpers.js +26 -0
  7. package/server/api/assets/notes.js +50 -0
  8. package/server/api/assets.js +31 -38
  9. package/server/api/board.js +29 -1
  10. package/server/api/browse.js +3 -2
  11. package/server/api/chatai.js +9 -8
  12. package/server/api/exports/cards.js +7 -6
  13. package/server/api/exports/docx-pdf.js +4 -1
  14. package/server/api/instruction.js +3 -3
  15. package/server/api/local.js +10 -8
  16. package/server/api/projects.js +19 -3
  17. package/server/api/publish.js +3 -2
  18. package/server/api/sessions-rewind.js +220 -0
  19. package/server/api/sessions.js +58 -194
  20. package/server/api/stage.js +35 -0
  21. package/server/api/turn-model-switch.js +109 -0
  22. package/server/api/turn.js +52 -88
  23. package/server/auth/middleware.js +29 -6
  24. package/server/auth/tier.js +27 -7
  25. package/server/auth/users-store.js +14 -1
  26. package/server/engine/agent/actor-trail.js +110 -0
  27. package/server/engine/agent/agent-shared.js +22 -52
  28. package/server/engine/agent/beat-state.js +61 -0
  29. package/server/engine/agent/cast.js +322 -0
  30. package/server/engine/agent/events.js +18 -1
  31. package/server/engine/agent/hooks/beat-gate.js +73 -0
  32. package/server/engine/agent/hooks/failure.js +11 -1
  33. package/server/engine/agent/hooks/lifecycle.js +16 -41
  34. package/server/engine/agent/hooks/post-guidance.js +4 -38
  35. package/server/engine/agent/hooks/post-trim.js +1 -0
  36. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  37. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  38. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  39. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  40. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  41. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  42. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  43. package/server/engine/agent/hooks/slot-alias.js +30 -0
  44. package/server/engine/agent/hooks/user-prompt-submit.js +208 -47
  45. package/server/engine/agent/hooks.js +80 -14
  46. package/server/engine/agent/isolation.js +25 -2
  47. package/server/engine/agent/memory-config.js +87 -0
  48. package/server/engine/agent/model-context.js +102 -18
  49. package/server/engine/agent/model-table.js +361 -58
  50. package/server/engine/agent/prompts/nodesign-prelude.md +579 -70
  51. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  52. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +26 -9
  53. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  54. package/server/engine/agent/prompts/tools/site-reference.md +15 -4
  55. package/server/engine/agent/role-card.js +175 -0
  56. package/server/engine/agent/rp-mode.js +17 -0
  57. package/server/engine/agent/session-loop.js +55 -32
  58. package/server/engine/agent/stage-status.js +73 -0
  59. package/server/engine/agent/subagent-flight.js +73 -0
  60. package/server/engine/agent/system-prompts.js +41 -0
  61. package/server/engine/agent/tool-input-stream.js +165 -0
  62. package/server/engine/agents/explorer.md +1 -1
  63. package/server/engine/agents/index.js +1 -1
  64. package/server/engine/mcp/actor.js +59 -0
  65. package/server/engine/mcp/capability-gate.js +21 -2
  66. package/server/engine/mcp/index.js +118 -33
  67. package/server/engine/mcp/mode-profile.js +139 -0
  68. package/server/engine/mcp/server-name.js +19 -0
  69. package/server/engine/mcp/tools/artifact-session.js +2 -1
  70. package/server/engine/mcp/tools/browse-computer.js +2 -1
  71. package/server/engine/mcp/tools/browse-find-batch.js +95 -16
  72. package/server/engine/mcp/tools/browse.js +25 -3
  73. package/server/engine/mcp/tools/cast-role.js +167 -0
  74. package/server/engine/mcp/tools/crystallize-skill.js +1 -1
  75. package/server/engine/mcp/tools/draw-trend.js +104 -0
  76. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  77. package/server/engine/mcp/tools/edit-board.js +569 -0
  78. package/server/engine/mcp/tools/generate-image.js +50 -70
  79. package/server/engine/mcp/tools/helpers/codex-imagegen.js +139 -0
  80. package/server/engine/mcp/tools/helpers/rembg.js +7 -2
  81. package/server/engine/mcp/tools/helpers/shot-pipeline.js +109 -0
  82. package/server/engine/mcp/tools/list-pages.js +11 -5
  83. package/server/engine/mcp/tools/look-at-board.js +4 -3
  84. package/server/engine/mcp/tools/open-sheet.js +242 -0
  85. package/server/engine/mcp/tools/organize-board.js +45 -6
  86. package/server/engine/mcp/tools/paint-still.js +1 -1
  87. package/server/engine/mcp/tools/pin-to-board.js +103 -19
  88. package/server/engine/mcp/tools/read-board.js +123 -16
  89. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  90. package/server/engine/mcp/tools/report-issue.js +15 -7
  91. package/server/engine/mcp/tools/role-memory.js +83 -0
  92. package/server/engine/mcp/tools/roll-dice.js +82 -0
  93. package/server/engine/mcp/tools/roll-film.js +1 -1
  94. package/server/engine/mcp/tools/screenshot.js +19 -9
  95. package/server/engine/mcp/tools/set-vars.js +150 -0
  96. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  97. package/server/engine/mcp/tools/web-search.js +12 -16
  98. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  99. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  100. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  101. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  102. package/server/engine/mcp/tools/write-on-board.js +568 -99
  103. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  104. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +14 -7
  105. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +44 -5
  106. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
  107. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +24 -8
  108. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  109. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  110. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  111. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  112. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  113. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  114. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  115. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  116. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  117. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  118. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  119. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  120. package/server/engine/runs/board-seater.js +272 -0
  121. package/server/engine/runs/board-tasklist.js +90 -48
  122. package/server/engine/runs/turn-relay.js +24 -2
  123. package/server/engine/runs/user-chalk-echo.js +44 -0
  124. package/server/index.js +2 -2
  125. package/server/lib/artifact-target.js +31 -14
  126. package/server/lib/board-anchor.js +49 -0
  127. package/server/lib/board-dirty.js +93 -0
  128. package/server/lib/board-excerpt.js +80 -0
  129. package/server/lib/board-follow.js +111 -0
  130. package/server/lib/board-groups.js +1 -5
  131. package/server/lib/board-hero.js +3 -1
  132. package/server/lib/board-kind-sizes.js +38 -3
  133. package/server/lib/board-lanes.js +58 -0
  134. package/server/lib/board-obstacles.js +80 -0
  135. package/server/lib/board-relations.js +5 -1
  136. package/server/lib/board-sheets.js +467 -0
  137. package/server/lib/board-shelf.js +86 -0
  138. package/server/lib/board-transform.js +62 -0
  139. package/server/lib/canvas-id.js +46 -4
  140. package/server/lib/chalk-flow.js +88 -0
  141. package/server/lib/chalk-rewrite.js +63 -0
  142. package/server/lib/chalk-size-pref.js +48 -0
  143. package/server/lib/chalk.js +42 -3
  144. package/server/lib/export-collect.js +3 -1
  145. package/server/lib/ingress/forward-openai-chat.js +16 -4
  146. package/server/lib/ingress/openai-chat.js +23 -12
  147. package/server/lib/ingress/session-routes.js +3 -3
  148. package/server/lib/ingress/stream-watchdog.js +53 -0
  149. package/server/lib/ingress/upstream-billing.js +15 -0
  150. package/server/lib/ingress/upstream-error-hints.js +36 -0
  151. package/server/lib/issues-store.js +3 -1
  152. package/server/lib/json-preview.js +58 -0
  153. package/server/lib/kinds/docx.js +11 -4
  154. package/server/lib/kinds/index.js +6 -16
  155. package/server/lib/kinds/site.js +40 -7
  156. package/server/lib/model-ingress.js +26 -1
  157. package/server/lib/moderation.js +10 -5
  158. package/server/lib/quota.js +26 -6
  159. package/server/lib/rect.js +50 -0
  160. package/server/lib/rewrite-refs.js +110 -0
  161. package/server/lib/screen.js +36 -0
  162. package/server/lib/sheet-state.js +19 -0
  163. package/server/lib/sketch-array.js +118 -0
  164. package/server/lib/sketch-hatch.js +99 -0
  165. package/server/lib/sketch-layout.js +269 -69
  166. package/server/lib/sketch-shapes.js +152 -0
  167. package/server/lib/sketch-stencils.js +170 -0
  168. package/server/lib/ssrf-guard.js +9 -0
  169. package/server/lib/state-table.js +237 -0
  170. package/server/lib/state-trend.js +94 -0
  171. package/server/lib/state-triggers.js +199 -0
  172. package/server/lib/task-scan.js +18 -0
  173. package/server/lib/tavern-json.js +48 -0
  174. package/server/lib/user-uploads.js +88 -0
  175. package/server/projects/board-limits.js +5 -3
  176. package/server/projects/board-sanitize.js +220 -10
  177. package/server/projects/board-store.js +137 -7
  178. package/server/projects/memory-migration.js +85 -0
  179. package/server/projects/move-entry.js +11 -1
  180. package/server/projects/session-jsonl.js +110 -0
  181. package/server/projects/store.js +56 -3
  182. package/server/projects/ui-config.js +2 -1
  183. package/server/projects/viewpoint-store.js +35 -3
  184. package/server/projects/workspace-slots.js +30 -0
  185. package/server/projects/workspace-templates.js +42 -10
  186. package/server/projects/workspace.js +25 -26
  187. package/server/runtime/capabilities.js +6 -2
  188. package/server/services/rembg-launcher.js +5 -2
  189. package/server/shared/locales.js +70 -0
  190. package/server/shared/messages-en.js +69 -0
  191. package/server/shared/messages.js +65 -0
  192. package/server/ws/broker.js +3 -0
  193. package/server/ws/index.js +43 -3
  194. package/web/README.md +1 -1
  195. package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-CcaGRWz5.js} +1 -1
  196. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  197. package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B9KZmU5z.js} +2 -2
  198. package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-DrfRnz8E.js} +43 -43
  199. package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-CJOjCF7d.js} +6 -6
  200. package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  201. package/web/dist/assets/{arc-D_khH_xN.js → arc-C1-HSUny.js} +1 -1
  202. package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  203. package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  204. package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  205. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  206. package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  207. package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  208. package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  209. package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  210. package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  211. package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  212. package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  213. package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  214. package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  215. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  216. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  217. package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  218. package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  219. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  220. package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  221. package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  222. package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  223. package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  224. package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  225. package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  226. package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  227. package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  228. package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  229. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  230. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  231. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  232. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  233. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  234. package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  235. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  236. package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  237. package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  238. package/web/dist/assets/{layout-CnXiF_NB.js → layout-Dhgck-Zo.js} +1 -1
  239. package/web/dist/assets/{linear-BMW0u6G6.js → linear-DMSQ-aUw.js} +1 -1
  240. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  241. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  242. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  243. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  314. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  315. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  316. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  317. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  318. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  319. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  320. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  321. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  322. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  323. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  324. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  325. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  326. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  327. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  328. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  329. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  330. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  331. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  332. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  333. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  334. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  335. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  336. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  337. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  338. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  339. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  340. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  341. package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  342. package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  343. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  344. package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  345. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  346. package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  347. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  348. package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-62liz7LX.js} +1 -1
  349. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  350. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  351. package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  352. package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  353. package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  354. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  355. package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  356. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  357. package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  358. package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  359. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  360. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  361. package/web/dist/index.html +2 -2
  362. package/server/api/memory.js +0 -185
  363. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +0 -583
  364. package/server/engine/mcp/tools/arrange-on-board.js +0 -124
  365. package/server/engine/mcp/tools/create-on-board.js +0 -152
  366. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  367. package/server/engine/mcp/tools/record-decision.js +0 -140
  368. package/server/engine/mcp/tools/relate-on-board.js +0 -112
  369. package/server/engine/mcp/tools/sketch-on-board.js +0 -337
  370. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  371. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +0 -78
  372. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  373. package/web/dist/assets/DeckWindow-BpLv5082.js +0 -16
  374. package/web/dist/assets/channel-DGkccMO1.js +0 -1
  375. package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +0 -1
  376. package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +0 -1
  377. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  378. package/web/dist/assets/index-GAamh1p4.js +0 -2152
  379. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  380. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  381. package/web/dist/assets/pending-edit-apply-AcdOQ9Ij.js +0 -31
  382. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
  383. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
@@ -19,7 +19,7 @@ import { can } from '../../../lib/kinds/index.js';
19
19
  import { screenshotDocx } from './screenshot-docx.js';
20
20
  import { SITE_DEVICE_W } from './helpers/perception-page.js';
21
21
  import { acquireArtifactPage, LIVE_PARAM_DESC } from './helpers/acquire-page.js';
22
- import { normalizeShot, detectPaintTransform, attachPageDiagnostics, runWaitFor, runBeforeShot } from './helpers/shot-pipeline.js';
22
+ import { normalizeShot, detectPaintTransform, attachPageDiagnostics, runWaitFor, runBeforeShot, shotWithFallback, longPageSheet, clipShotWithFallback } from './helpers/shot-pipeline.js';
23
23
  import { recordMotion, pickNearestFrames, composeSheet, encodeWebm, motionCaptionLines } from './helpers/motion-lab.js';
24
24
  import { wheelScroll, elementMotionReport, elementMotionLines } from './helpers/motion-scroll.js';
25
25
 
@@ -87,7 +87,7 @@ to also read your own console.log output from the page (grouped, capped).
87
87
 
88
88
  FILMSTRIP — the eye for ANIMATION. A single still cannot show easing, overshoot,
89
89
  hard cuts between tweens, or "the whole move played off-screen". Pass
90
- frames (2-30 ms offsets, e.g. [0,120,240,400,700]) + trigger:"window.game.reload()" (JS that
90
+ frames (2-30 ms offsets within a 30s window, e.g. [0,120,240,400,700]) + trigger:"window.game.reload()" (JS that
91
91
  starts the move) and you get ONE contact sheet: the same viewport at each of those
92
92
  moments, timestamped. One image = one motion curve — judge attack, overshoot,
93
93
  settle and cuts directly. click:"#start" performs a REAL trusted click instead
@@ -168,7 +168,7 @@ Do NOT use this tool when:
168
168
  .optional()
169
169
  .describe("Console capture level for the caption. Default 'warn' returns only warnings/errors (the count of filtered log lines is reported). Pass 'all' to also get console.log/info/debug output — the only way to read your own debug logging from the page."),
170
170
  frames: z
171
- .array(z.number().min(0).max(15000))
171
+ .array(z.number().min(0).max(30000))
172
172
  .min(1)
173
173
  .max(30)
174
174
  .optional()
@@ -428,6 +428,8 @@ Do NOT use this tool when:
428
428
  // (系统 fit script 包 frame + scroll-snap),locator.screenshot 自动
429
429
  // 拿目标 section 的 bbox,不需要再操作 DOM。
430
430
  let buf;
431
+ let shotDegraded = false;
432
+ let longPageNote = null;
431
433
  let captureMode;
432
434
  const targetSelector = selector
433
435
  || (pageIndex ? `section[data-page="${pageIndex}"]` : null);
@@ -476,13 +478,19 @@ Do NOT use this tool when:
476
478
  };
477
479
  clip.width = Math.max(1, Math.min(rect.width, rect.docW - clip.x));
478
480
  clip.height = Math.max(1, Math.min(rect.height, rect.docH - clip.y));
479
- buf = await page.screenshot({ fullPage: true, clip, type: 'png' });
481
+ ({ buf, degraded: shotDegraded } = await clipShotWithFallback(page, { clip, selector: targetSelector }));
480
482
  captureMode = `selector="${targetSelector}"`;
481
483
  } else {
482
- buf = await page.screenshot({ fullPage: fp, type: 'png' });
483
- captureMode = isSite
484
- ? `site ${device || 'desktop'} ${vp.width}px, fullPage=${fp}`
485
- : `fullPage=${fp}`;
484
+ // 超长页 fullPage 滚动联络表(阈值与理由见 shot-pipeline.longPageSheet)
485
+ const sheet = fp ? await longPageSheet(page).catch(() => null) : null;
486
+ if (sheet) {
487
+ buf = sheet.buf; captureMode = sheet.mode; longPageNote = sheet.note;
488
+ } else {
489
+ ({ buf, degraded: shotDegraded } = await shotWithFallback(page, { fullPage: fp, type: 'png' }));
490
+ captureMode = isSite
491
+ ? `site ${device || 'desktop'} ${vp.width}px, fullPage=${fp}`
492
+ : `fullPage=${fp}`;
493
+ }
486
494
  }
487
495
  // live 页的 DPR 是会话的(1),detail:'normal' 的 0.6 光栅在这里事后缩
488
496
  if (acq.live && rasterScale < 1) {
@@ -537,7 +545,9 @@ Do NOT use this tool when:
537
545
  const shot = await normalizeShot(buf);
538
546
 
539
547
  const captionParts = [
540
- `Screenshot of ${target.relPath} (layout ${vp.width}x${vp.height} @${rasterScale}x raster, ${captureMode})`,
548
+ `Screenshot of ${target.relPath} (layout ${vp.width}x${vp.height} @${rasterScale}x raster, ${captureMode})`
549
+ + (shotDegraded ? '(常规截图等稳定帧超时——页面在持续动画(WebGL/rAF),已改抓当前帧:画面是真实的某一瞬间,动画中间态属正常)' : '')
550
+ + (longPageNote ? `(${longPageNote})` : ''),
541
551
  ];
542
552
  // 加载通道写进 caption:agent 不用再靠"把 location.protocol 写进 DOM 再截一张"
543
553
  // 去反推自己被什么方式打开了(问题库 iss_msxk2oci_0v0v 就是这么查了四轮)
@@ -0,0 +1,150 @@
1
+ /**
2
+ * mcp/tools/set-vars.js —— `set_vars`:改状态表里的几格(2026-08-30)
3
+ *
4
+ * ## 它跟 `edit_board set_text` 的分工
5
+ *
6
+ * set_text 是「重写这条板书的整篇正文」。状态板每拍都要动几个数字,用 set_text 就得
7
+ * 把整张卡连叙述带表格重写一遍 —— 那正是它老写漏、老写错的地方(改一个数字要重述
8
+ * 十几行,模型每次都在重新生成不该变的部分)。
9
+ *
10
+ * set_vars 只动表里那一格,别的一个字不碰。落盘走的是**跟 set_text 同一个**
11
+ * 重写函数(lib/chalk-rewrite.js),所以 frontmatter 保留、卡高重算、
12
+ * 用户拖出来的留白留得住这三条不会两边不一致。
13
+ *
14
+ * ## 为什么写口要大声失败,不 fail-soft
15
+ *
16
+ * 状态表的真相是一份**三方可写**的自由文本:set_vars、agent 的 set_text/Edit、
17
+ * 以及用户自己(chalk_edit 就是为让他随手改板书造的)。写口只守得住自己那一路。
18
+ * 所以这一路必须**看不懂就当场停**:表被改坏时若还 fail-soft 地"尽力写",
19
+ * 结果是表被写成第二种坏格式,而且没有任何人知道。
20
+ * 读侧另有一层报警(每轮状态块的 vars 节,见 hooks/user-prompt-submit.js)。
21
+ *
22
+ * ⚠️ 笔权(只有作者能改自己的板书)在这里**不判**:状态表是场务件,不是谁的台词。
23
+ * 谁在管这一场,谁就该能改数字。这跟 edit_board 对叙事板书的规矩是两回事。
24
+ */
25
+
26
+ import { tool } from '@anthropic-ai/claude-agent-sdk';
27
+ import { z } from 'zod';
28
+ import { readBoard, patchBoard } from '../../../projects/board-store.js';
29
+ import { rewriteChalkBody } from '../../../lib/chalk-rewrite.js';
30
+ import {
31
+ findStateTable, applyVars, parseStateTable, renderRows,
32
+ STATE_TABLE_TAG, VALUE_MAX, KEY_MAX,
33
+ } from '../../../lib/state-table.js';
34
+
35
+ /** 一次最多改几个键:再多就不是"改状态"是"重建状态表",那该用 set_text */
36
+ const MAX_KEYS = 20;
37
+
38
+ export function makeSetVarsTool({ projectId, sharedRoot }) {
39
+ return tool(
40
+ 'set_vars',
41
+ `Update values in the board's state table — the one chalk note tagged "${STATE_TABLE_TAG}".
42
+
43
+ Use this every beat for anything that changed and must be remembered: HP, 好感度, 时间,
44
+ 线索, 持有物, 进度. It edits ONLY those cells; the rest of the note (your prose, the
45
+ layout, other tables) is left byte-for-byte alone, and the card is re-measured for you.
46
+
47
+ Why not edit_board set_text: that rewrites the whole note, so changing one number means
48
+ regenerating a dozen lines that should not change. That is where numbers get silently lost.
49
+
50
+ The table is plain markdown living in a note the user can see and edit:
51
+
52
+ | 键 | 值 |
53
+ | --- | --- |
54
+ | 好感度_苏绵 | 3 |
55
+
56
+ Values you set here show up at the top of your NEXT turn, so you do not have to carry
57
+ them in your head. Keys: letters/digits/CJK/_/-/·, max ${KEY_MAX} chars. Values are
58
+ single-line, max ${VALUE_MAX} chars — long text belongs in the note body, not a cell.
59
+
60
+ If there is no state table yet, this tool creates nothing: write one first with
61
+ write_on_board (tag: "${STATE_TABLE_TAG}"), then set values here.`,
62
+ {
63
+ vars: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
64
+ .describe('Keys to set, e.g. {"好感度_苏绵": 5, "时间": "戌时"}. Existing keys are updated, new ones appended.'),
65
+ },
66
+ async (args) => {
67
+ const fail = (msg) => ({ content: [{ type: 'text', text: msg }], isError: true });
68
+ if (!projectId || !sharedRoot) return fail('没有项目工作区,set_vars 不可用。');
69
+
70
+ const vars = args?.vars && typeof args.vars === 'object' ? args.vars : null;
71
+ const keys = vars ? Object.keys(vars) : [];
72
+ if (!keys.length) return fail('vars 是空的 —— 给至少一个键值对。');
73
+ if (keys.length > MAX_KEYS) {
74
+ return fail(`一次改了 ${keys.length} 个键,超过 ${MAX_KEYS} 上限。`
75
+ + `改这么多说明你在重建整张表 —— 那用 write_on_board / edit_board 的 set_text 重写更合适。`);
76
+ }
77
+
78
+ const found = await findStateTable(sharedRoot);
79
+ if (!found.found) {
80
+ if (found.reason === 'multiple') {
81
+ return fail(`板上有 ${found.rels.length} 条 tag 是「${STATE_TABLE_TAG}」的板书:${found.rels.join('、')}。`
82
+ + `状态表只能有一条 —— 先把多余的那条改掉 tag 或撤下来(edit_board remove),我不猜该改哪一条。`);
83
+ }
84
+ return fail(`板上还没有状态表。先用 write_on_board 落一条(\`tag: "${STATE_TABLE_TAG}"\`),`
85
+ + `正文里放一张两列的表:\n\n| 键 | 值 |\n| --- | --- |\n| 好感度_苏绵 | 3 |\n\n然后再来 set_vars。`);
86
+ }
87
+
88
+ let applied;
89
+ try {
90
+ applied = applyVars(found.body, vars);
91
+ } catch (err) {
92
+ // ⚠️ 两种失败要说成两句话(真工具探针当场抓到的:原来一律说"表看不懂",
93
+ // 于是模型传了个坏键、却被指去修一张根本没坏的表)。
94
+ // 错误归因说错方向,比不报错好不了多少 —— 它会让人去改对的东西。
95
+ if (err.code === 'BAD_KEY') {
96
+ return fail(`这个键不能用,一个格都没改:${err.message}\n`
97
+ + `(表本身没问题,换个键名重来。)`);
98
+ }
99
+ // 大声失败:读不懂就停,绝不"尽力写" —— 写坏的表下一次会被写得更坏,
100
+ // 而这一路是三个写入方里唯一守得住的那个
101
+ return fail(`${found.rel} 的状态表看不懂,这次一个键都没改:${err.message}\n`
102
+ + `(先把表修好 —— 它是普通 markdown,Read 那个文件就能看见。)`);
103
+ }
104
+
105
+ if (!applied.changed.length && !applied.added.length) {
106
+ return { content: [{ type: 'text', text: `表里这几个键已经是这个值了,没动:${keys.join('、')}(${found.rel})` }] };
107
+ }
108
+
109
+ // 落盘 + 板上重算:走跟 set_text 同一个函数,三条语义(frontmatter 保留 /
110
+ // 卡高重算 / 用户拖出来的留白留得住)不会两边不一致
111
+ const board = await readBoard(projectId);
112
+ const entry = board.objects?.[found.rel] || {};
113
+ let box;
114
+ try {
115
+ box = await rewriteChalkBody(found.abs, applied.body, entry);
116
+ } catch (err) {
117
+ return fail(`${found.rel} 写不进去(${err.message})—— 表没改成。`);
118
+ }
119
+ if (board.objects?.[found.rel]) {
120
+ // patchBoard 收的是 diff 对象不是回调(board-store.js:85)
121
+ try {
122
+ await patchBoard(projectId, { objects: { [found.rel]: { ...entry, w: box.w, h: box.h } } });
123
+ } catch { /* 板上尺寸没跟上不影响真相(文件已经改了),下次 reflow 会对 */ }
124
+ }
125
+
126
+ const lines = [];
127
+ if (applied.changed.length) {
128
+ lines.push(`改了 ${applied.changed.length} 格:`
129
+ + applied.changed.map((c) => `${c.key} ${c.from || '(空)'} → ${c.to}`).join(';'));
130
+ }
131
+ if (applied.added.length) {
132
+ lines.push(`新增 ${applied.added.length} 格:`
133
+ + applied.added.map((c) => `${c.key} = ${c.value}`).join(';'));
134
+ }
135
+ if (applied.clamped.length) {
136
+ // 钳住但要说(入参不许静默改口径)
137
+ lines.push(`⚠️ 这几个值被清洗过(换行压成空格、竖线换成斜杠、超 ${VALUE_MAX} 字截断):${applied.clamped.join('、')}`);
138
+ }
139
+ lines.push(`表在 ${found.rel},现在共 ${applied.rows.length} 格。下一轮开头你会看到它。`);
140
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
141
+ },
142
+ );
143
+ }
144
+
145
+ /** 给测试与 SKILL 用的样板表(保持和工具描述里那段一致) */
146
+ export function sampleStateTable(rows = [{ key: '好感度_苏绵', value: '3' }]) {
147
+ return renderRows(rows).join('\n');
148
+ }
149
+
150
+ export { parseStateTable };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * mcp/tools/sheet-replan.js —— edit_board 的 replan op(2026-08-30 刀⑧,
3
+ * 行数棘轮拆件)。
4
+ *
5
+ * 版位原来只能在 open_sheet 那一刻定,写到一半发现规划错了只能整张重来 ——
6
+ * 「先画格子」得是能修正的动作。按名合并:点名的版位增改,没点名的原样保留;
7
+ * 新声明 for:'artifacts' 时旧的产物位摘牌(一张纸只有一个产物位)。
8
+ */
9
+
10
+ import { innerRect, currentSheet } from '../../../lib/board-sheets.js';
11
+ import { currentSheetIdOf } from '../../../lib/sheet-state.js';
12
+ import { capacityOf } from '../../../lib/sketch-layout.js';
13
+ import { clampPlan } from './open-sheet.js';
14
+
15
+ /**
16
+ * @returns {{ error:string } | { sheetId:string, entry:object, report:string }}
17
+ * entry = 该纸更新后的完整登记项(调用方放进 sheets patch)
18
+ */
19
+ export function applyReplan({ board, sheetsPatch, sessionId, op }) {
20
+ const base = { ...board, sheets: { ...(board.sheets || {}), ...sheetsPatch } };
21
+ const sh = op.sheet && base.sheets?.[op.sheet]
22
+ ? { id: op.sheet, ...base.sheets[op.sheet] }
23
+ : currentSheet(base, currentSheetIdOf(sessionId));
24
+ if (!sh) return { error: '还没有纸 —— 先 open_sheet' };
25
+ const inn = innerRect(sh);
26
+ // prevSlots:below 可以引用纸上已有的版位(「在 main 底下补一块」是最常见的补法)
27
+ const { slots: addSlots, clampedSlots } = clampPlan(op.plan, { w: inn.w, h: inn.h }, base.sheets[sh.id].slots || {});
28
+ const merged = { ...(base.sheets[sh.id].slots || {}) };
29
+ if (Object.values(addSlots).some((sl) => sl.for === 'artifacts')) {
30
+ for (const nm of Object.keys(merged)) {
31
+ if (merged[nm].for === 'artifacts') { merged[nm] = { ...merged[nm] }; delete merged[nm].for; }
32
+ }
33
+ }
34
+ // 属性级合并(2026-08-30):replan 只想改尺寸时不该把旧的 about / for 一并抹掉
35
+ for (const [nm, sl] of Object.entries(addSlots)) merged[nm] = { ...(merged[nm] || {}), ...sl };
36
+ const names = Object.entries(addSlots).map(([nm, sl]) => {
37
+ const c = capacityOf(sl.w, sl.h);
38
+ return `${nm} at (${sl.x},${sl.y}) ${sl.w}x${sl.h}(~${c.lines} 行 / ~${c.cjk} 字)`;
39
+ });
40
+ return {
41
+ sheetId: sh.id,
42
+ entry: { ...base.sheets[sh.id], slots: merged },
43
+ report: `· replan:纸 ${sh.id} 增改版位 ${names.join(';')}`
44
+ + `${clampedSlots.length ? `;⚠ 钳进版心:${clampedSlots.join(', ')}` : ''}。没点名的版位原样保留。`,
45
+ };
46
+ }
@@ -393,6 +393,7 @@ Suggested flow for image-led pages:
393
393
  // - 普通模式:tavily/exa/baidu/zhipu auto-route by language(CJK→baidu, EN→tavily)
394
394
  // - include_images:zhipu 不支持图,从候选中剔除;其余按语言 auto-route
395
395
  let providerId;
396
+ let providerNote = '';
396
397
  if (include_images) {
397
398
  if (provider === 'zhipu') {
398
399
  return {
@@ -418,15 +419,13 @@ Suggested flow for image-led pages:
418
419
  };
419
420
  }
420
421
  } else {
422
+ // 点名的 provider 没配 key:换配了的顶上别硬失败(结果>供应商身份,iss_mt5z4s8j)
421
423
  providerId = provider;
422
424
  if (!getKey(providerId)) {
423
- return {
424
- content: [{
425
- type: 'text',
426
- text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set in .env.`,
427
- }],
428
- isError: true,
429
- };
425
+ const alt = ((looksChinese(query) ? PRIORITY_CJK : PRIORITY_NON_CJK).filter(id => id !== 'zhipu')).find(id => getKey(id)) || null;
426
+ if (!alt) return { content: [{ type: 'text', text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set, and no other image-capable provider configured.` }], isError: true };
427
+ providerNote = `(provider "${provider}" 没配 key,已换用 ${alt})`;
428
+ providerId = alt;
430
429
  }
431
430
  }
432
431
  } else {
@@ -443,13 +442,10 @@ Suggested flow for image-led pages:
443
442
  };
444
443
  }
445
444
  if (!getKey(providerId)) {
446
- return {
447
- content: [{
448
- type: 'text',
449
- text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set in .env.`,
450
- }],
451
- isError: true,
452
- };
445
+ const alt = autoSelectProvider(query); // 只挑配了 key 的
446
+ if (!alt || !getKey(alt)) return { content: [{ type: 'text', text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set, and no other provider configured.` }], isError: true };
447
+ providerNote = `(provider "${providerId}" 没配 key,已换用 ${alt})`;
448
+ providerId = alt;
453
449
  }
454
450
  }
455
451
 
@@ -498,7 +494,7 @@ Suggested flow for image-led pages:
498
494
  // 落盘了就发 file_changed —— generate_image 一直在发,这里漏了,
499
495
  // 于是参考图落进工作区之后前端毫无感知(素材抽屉要刷新才看得见)
500
496
  for (const img of downloadedImages) {
501
- try { ctx?.emit?.({ type: 'run.file_changed', path: img.relPath, change: 'add' }); } catch { /* */ }
497
+ try { ctx?.emit?.({ type: 'run.file_changed', filePath: img.relPath, event: 'add' }); } catch { /* */ }
502
498
  }
503
499
  }
504
500
 
@@ -518,7 +514,7 @@ Suggested flow for image-led pages:
518
514
  // count 张 + base64 体积。已在上一步通过 count 截断控量。
519
515
  const out = [{
520
516
  type: 'text',
521
- text: formatMarkdown(query, providerId, hits, { images: downloadedImages }),
517
+ text: `${providerNote ? `${providerNote}\n` : ''}${formatMarkdown(query, providerId, hits, { images: downloadedImages })}`,
522
518
  }];
523
519
  for (const img of downloadedImages) {
524
520
  if (img.base64) {
@@ -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
+ }