@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
@@ -0,0 +1,272 @@
1
+ /**
2
+ * server/engine/runs/board-seater.js —— 服务端入座(2026-08-25 范式重做④)
3
+ *
4
+ * 病根(08-25 信箱实证):入座算法原来只住前端 board-seating.js,只在浏览器渲染
5
+ * 那一层时发生。agent 写完 `小说/第一章.md`,26 秒后 `write_on_board {near:它}`
6
+ * 失败「还没有座位」,6 小时后依然没有 —— 用户没打开过那个文件夹。而工具描述
7
+ * 正教 agent「Use it right after you finish something」。read_board 同病:刚写的
8
+ * 文件看不见 → agent 误判"没上画布" → 去 pin → 重影。
9
+ *
10
+ * 这里把入座下沉:挂在 project bus 上收本轮的 run.file_changed,**回合末一批**
11
+ * 排座(站主拍板的时机)。判据不新写黑名单 —— 复用 task-scan 的同一套排除件
12
+ * (隐藏目录 / node_modules / _drafts / 保留文件),层隔离照 layerOf。
13
+ *
14
+ * 板书领养:prelude 说「你写的每张 .md 在桌面上渲成贴纸」,但 Write 直接落
15
+ * notes/板书/ 的文件从来不上墙(10-05 friction)。这里把它领养成正式板书:
16
+ * 认 frontmatter(nd: chalk 的 anchor/reply_to/tag),照 write_on_board 同款
17
+ * 画线落座 —— 从此那句 prelude 是真话。
18
+ *
19
+ * 座位一律 seat:'auto'(可被前端重排);已有座位的绝不动。
20
+ *
21
+ * ## 2026-08-30 刀 G:入座**不再自己铺纸**
22
+ *
23
+ * 站主拍板:「产物也需要 agent 提前规划放置位置落在纸上,甚至包括文件夹」。
24
+ * 在此之前这里排不下就 allocateSheetRect 翻一张新纸 —— 那张纸没标题、没版位、
25
+ * 署名 by:'agent',而 agent 根本不知道它存在。真板实证(proj_mtfix5rv):agent
26
+ * Write 了 CLAUDE.md,入座顺手铺了 p1,agent 随后自己 open_sheet 开了 p2,板上
27
+ * 从此永久留着一张空白无规划的纸。这正是「机器是兜底不是版面」要禁的事。
28
+ *
29
+ * 现在的三档:
30
+ * ① 这一页规划了 `for:'artifacts'` 的地 → 落进那块地(agent 事前说了放哪儿)
31
+ * ② 没规划但当前纸还排得下 → 纸内顺排(兜底,跟以前一样)
32
+ * ③ 排不下 → **不铺纸**,进 board.pending 待摆队列,每回合状态块点名,
33
+ * 等 agent 规划出地方(open_sheet{plan} / pin_to_board 点名)再落座。
34
+ * 唯一的例外是**一张纸都还没有**:那不是翻页,是开工,照旧铺第一张。
35
+ *
36
+ * ## 2026-08-30 暂存架:机器的手只够得到架
37
+ *
38
+ * 上面那个「开工例外」当天就翻了车(proj_mtfz7n8p):web_search 采回的参考图
39
+ * 先于 agent 的第一笔到场,入座器铺了 p1 —— 第一张纸又成了机器铺的,agent
40
+ * 不知道它存在,自己的板书全在缝里流。站主拍板:**机器从此完全不产纸、
41
+ * 也不往纸面顺排**。三档收成两档:
42
+ * ① 这一页规划了 `for:'artifacts'` 的地 → 落进那块地(agent 事前说了放哪儿)
43
+ * ② 其余(没规划 / 排不下 / 一张纸都没有)→ 一律上暂存架(lib/board-shelf.js,
44
+ * seat:'shelf'),每回合状态块点名,agent 用 pin_to_board / edit_board move
45
+ * 安置。旧 board.pending 队列并进架上(它们从「看不见」变「看得见」)。
46
+ */
47
+
48
+ import path from 'node:path';
49
+ import { promises as fs } from 'node:fs';
50
+ import { readBoard, patchBoard } from '../../projects/board-store.js';
51
+ import { getSharedDir } from '../../projects/workspace.js';
52
+ import { estimateSizeOn } from '../../lib/board-kind-sizes.js';
53
+ import { obstaclesIn } from '../../lib/board-obstacles.js';
54
+ import { layerOf, normalizeCanvasId } from '../../lib/canvas-id.js';
55
+ import {
56
+ currentSheet, placeThread, placeBeside, slotRectOf, nextSpotInSlot,
57
+ } from '../../lib/board-sheets.js';
58
+ import { resolveShelfOrigin, nextShelfSpot, FOLDER_BOX } from '../../lib/board-shelf.js';
59
+ import { textBox } from '../../lib/sketch-layout.js';
60
+ import { getViewpoint } from '../../projects/viewpoint-store.js';
61
+ import { parseChalk, CHALK_DIR } from '../../lib/chalk.js';
62
+ import { isReservedFile, HARD_IGNORE_DIRS, RESERVED_DIRS, DRAFTS_DIR } from '../../lib/task-scan.js';
63
+ import { applyFollows } from '../../lib/board-follow.js';
64
+ import { canvasIdForRel } from './board-tasklist.js';
65
+
66
+ const MAX_SEATS_PER_RUN = 24; // 一轮生成几百个文件的(构建产物漏网)也别刷爆板
67
+
68
+ /** 会渲染成卡的才配座位:跟 /artifacts 扫描同一套排除精神 */
69
+ export function seatable(rel) {
70
+ if (typeof rel !== 'string' || !rel || rel.length > 300) return false;
71
+ if (rel.startsWith('/') || rel.includes('\\') || rel.includes('\0')) return false;
72
+ const segs = rel.split('/');
73
+ if (segs.some(s => s === '..' || s === DRAFTS_DIR || HARD_IGNORE_DIRS.has(s))) return false;
74
+ // 点开头的段(.nd/.claude/.thumbnails…)整条不渲染
75
+ if (segs.some(s => s.startsWith('.'))) return false;
76
+ if (isReservedFile(segs[segs.length - 1])) return false;
77
+ return true;
78
+ }
79
+
80
+ let seq = 0;
81
+ const stamp = () => `${Date.now().toString(36)}${(seq++ % 1000).toString(36)}`;
82
+
83
+ /**
84
+ * 给一批相对路径排座(幂等:已有座位的跳过)。导出给测试与手动对账用。
85
+ * @returns {Promise<{seated: number, lines: number}>}
86
+ */
87
+ export async function seatArtifacts(projectId, rels) {
88
+ const sharedRoot = getSharedDir(projectId);
89
+ const board = await readBoard(projectId);
90
+ // 待摆队列先并进来(刀 G):上一批排不下的,这一批 agent 可能已经规划出地方了。
91
+ // 排在新来的前面 —— 等得久的先落。
92
+ const queued = Array.isArray(board.pending) ? board.pending : [];
93
+ const uniq = [...new Set([...queued, ...rels])].filter(seatable).slice(0, MAX_SEATS_PER_RUN * 2);
94
+ if (!uniq.length) return { seated: 0, lines: 0, pending: 0 };
95
+ const known = new Set(Object.keys(board.zones || {}));
96
+ const vp = getViewpoint(projectId);
97
+ const objects = {}; const bindings = {};
98
+ // 本批内后来者要避开先来者:live 副本随排随更新
99
+ const live = { ...board.objects };
100
+ // 暂存架(2026-08-30):批内原点算一次(架立了就不挪);本批内后来者靠
101
+ // live 障碍矩形自然码在先来者下面。
102
+ const rootCam = (vp?.camera && !vp.layer) ? vp.camera : null;
103
+ const shelfOrigin = resolveShelfOrigin(board, rootCam);
104
+ const stillPending = []; // 本批没轮到的(只剩封顶截流一种情况)
105
+ let seated = 0; let lines = 0; let shelved = 0;
106
+
107
+ // 文件夹卡也上架(2026-08-30,站主拍板「文件夹和单个文件一律落暂存区」):
108
+ // 这批文件揭示的顶层目录,还没有文件夹卡坐标的,先码在架上 —— 前端的
109
+ // newStackedZoneRect 只给没坐标的排位,这里写了它就不再排。判据与 seatable
110
+ // 同一套精神:保留目录(assets/notes/…)和隐藏目录不是用户的文件夹。
111
+ const zonesPatch = {};
112
+ const zoneRects = Object.entries(board.zones || {})
113
+ .filter(([, z]) => Number.isFinite(z?.x) && Number.isFinite(z?.y))
114
+ .map(([, z]) => ({ x: z.x, y: z.y, w: FOLDER_BOX.w, h: FOLDER_BOX.h }));
115
+ for (const rel of uniq) {
116
+ const segs = rel.split('/');
117
+ if (segs.length < 2 || !seatable(rel)) continue;
118
+ const top = segs[0];
119
+ if (RESERVED_DIRS.has(top) || zonesPatch[top]) continue;
120
+ if (board.zones?.[top] && Number.isFinite(board.zones[top].x)) continue;
121
+ const rootRects = Object.entries(live)
122
+ .filter(([id, e]) => Number.isFinite(e?.x) && layerOf(id, e, known) === '')
123
+ .map(([id, e]) => ({ x: e.x, y: e.y, ...estimateSizeOn(board, id, e) }));
124
+ const spot = nextShelfSpot(shelfOrigin, [...rootRects, ...zoneRects]);
125
+ zonesPatch[top] = { x: spot.x, y: spot.y };
126
+ zoneRects.push({ x: spot.x, y: spot.y, w: FOLDER_BOX.w, h: FOLDER_BOX.h });
127
+ known.add(top); // 这批的文件按新文件夹归层(跟前端 homeOf 同判)
128
+ }
129
+
130
+ for (const rel of uniq) {
131
+ const id = canvasIdForRel({ objects: live, zones: board.zones }, rel);
132
+ if (!id) continue;
133
+ if (live[id] && Number.isFinite(live[id].x)) continue; // 已有座位
134
+ // 封顶截流:没轮到的留在队列里下批再来(暂存架永远有地方,这是唯一的排队原因)
135
+ if (seated >= MAX_SEATS_PER_RUN) { stillPending.push(rel); continue; }
136
+ // 文件还在才入座(本轮建又删的别复活)
137
+ try { await fs.access(path.join(sharedRoot, rel)); } catch { continue; }
138
+
139
+ let box = estimateSizeOn(board, id, null);
140
+ let inSlot = null; // 落进了哪块规划好的产物地
141
+ let anchorRect = null; let replyRect = null;
142
+ let anchorId = null; let parentId = null; let tag = null; let by = null;
143
+
144
+ // 板书领养:Write 直接落盘的 notes/板书/*.md 按 frontmatter 接线
145
+ if (rel.startsWith(`${CHALK_DIR}/`) && rel.endsWith('.md')) {
146
+ try {
147
+ const raw = await fs.readFile(path.join(sharedRoot, rel), 'utf8');
148
+ const { body, chalk } = parseChalk(raw);
149
+ if (chalk) {
150
+ by = chalk.by;
151
+ tag = chalk.tag || null;
152
+ box = textBox(body, 'md', { md: true });
153
+ if (chalk.anchor) {
154
+ const aid = normalizeCanvasId(chalk.anchor);
155
+ const e = aid && live[aid];
156
+ if (e && Number.isFinite(e.x)) { anchorId = aid; anchorRect = { x: e.x, y: e.y, ...estimateSizeOn(board, aid, e) }; }
157
+ }
158
+ if (chalk.replyTo) {
159
+ const pid2 = normalizeCanvasId(chalk.replyTo);
160
+ const e = pid2 && live[pid2];
161
+ if (e && Number.isFinite(e.x)) { parentId = pid2; replyRect = { x: e.x, y: e.y, ...estimateSizeOn(board, pid2, e) }; }
162
+ }
163
+ }
164
+ } catch { /* 读不到就按普通文件排 */ }
165
+ }
166
+
167
+ const zone = layerOf(id, live[id], known);
168
+ const obstacles = obstaclesIn(board, zone, { objects: live, exclude: [id] });
169
+ // 落位(2026-08-30 暂存架):线程接楼 > 锚点贴放 > 产物地(根层)> 暂存架(根层)
170
+ // / 内容底下(文件夹层)。机器不产纸、不往纸面顺排。
171
+ const liveBoard = { ...board, objects: live };
172
+ let placed = null; let onShelf = false;
173
+ if (replyRect) {
174
+ const p = placeThread(liveBoard, replyRect, box, { obstacles });
175
+ placed = p.sheetFull ? null : p; // 线程纸满:接不了楼就上暂存架(线还在,找得回)
176
+ }
177
+ if (!placed && anchorRect) placed = placeBeside(anchorRect, box, 'below');
178
+ if (!placed && !zone) {
179
+ const cur = currentSheet(liveBoard, null);
180
+ // ① agent 事前规划的产物地(slot.for === 'artifacts')
181
+ if (cur) {
182
+ const named = Object.entries(cur.slots || {}).find(([, sl]) => sl.for === 'artifacts');
183
+ if (named) {
184
+ const r = slotRectOf(cur, named[0]);
185
+ const spot = r ? nextSpotInSlot(liveBoard, r, box) : { full: true };
186
+ if (!spot.full) { placed = spot; inSlot = named[0]; }
187
+ }
188
+ }
189
+ // ② 其余一律上暂存架:agent 没说放哪儿的,机器不替它定版面。
190
+ // 文件夹卡不在 objects 里,避让要把 zoneRects 一并算上
191
+ if (!placed) {
192
+ placed = nextShelfSpot(shelfOrigin, [...obstacles, ...zoneRects]);
193
+ onShelf = true;
194
+ }
195
+ }
196
+ if (!placed) {
197
+ // 文件夹层:这一层内容底下接着排(没有纸也没有启发式 —— 单条规则)
198
+ const left = obstacles.length ? Math.min(...obstacles.map(o => o.x)) : 10;
199
+ const bottom = obstacles.reduce((m, o) => Math.max(m, o.y + o.h), 0);
200
+ placed = { x: Math.round(left), y: Math.round(bottom) + 40 };
201
+ }
202
+ const entry = {
203
+ x: Math.round(placed.x), y: Math.round(placed.y), z: 1,
204
+ w: Math.round(box.w), h: Math.round(box.h),
205
+ zone, seat: onShelf ? 'shelf' : 'auto',
206
+ ...(by ? { by } : {}), ...(tag ? { tag } : {}),
207
+ };
208
+ if (onShelf) shelved += 1;
209
+ objects[id] = entry; live[id] = entry;
210
+ seated += 1;
211
+ if (anchorId) { bindings[`b:a${stamp()}`] = { type: 'annotates', from: id, to: anchorId, by: by || 'agent', ...(tag ? { tag } : {}) }; lines += 1; }
212
+ if (parentId) { bindings[`b:a${stamp()}`] = { type: 'flow', from: parentId, to: id, by: by || 'agent', material: 'pencil', ...(tag ? { tag } : {}) }; lines += 1; }
213
+ }
214
+
215
+ // 队列整表写回:**必须无条件写**,哪怕这一批一件都没坐下 ——
216
+ // 队列清空也是一次状态变化(旧 pending 这一轮上了架,队列该空)。
217
+ const pendingChanged = JSON.stringify(queued) !== JSON.stringify(stillPending);
218
+ const zoned = Object.keys(zonesPatch).length;
219
+ if (seated || pendingChanged || zoned) {
220
+ await patchBoard(projectId, {
221
+ ...(seated ? { objects, bindings } : {}),
222
+ ...(zoned ? { zones: zonesPatch } : {}),
223
+ ...(pendingChanged ? { pending: stillPending } : {}),
224
+ // 架的原点:第一次用到(或被纸压住重立)才落盘
225
+ ...((shelved || zoned) && shelfOrigin.changed ? { shelf: { x: shelfOrigin.x, y: shelfOrigin.y } } : {}),
226
+ });
227
+ }
228
+ // 领养的板书带 tag:有人跟着这个 tag(状态板)就自动重锚(fail-soft)
229
+ for (const [id, e] of Object.entries(objects)) {
230
+ if (e?.tag) { try { await applyFollows(projectId, { tag: e.tag, newId: id }); } catch { /* */ } }
231
+ }
232
+ return { seated, lines, shelved, pending: stillPending.length };
233
+ }
234
+
235
+ /**
236
+ * 挂 project bus(与 board-tasklist 同栖)。时机 = **即时**:file_changed 攒 1.5s
237
+ * 就排一批(回合中写完马上 near 它/read_board 都要看得见 —— 惰性到回合末只修
238
+ * 一半病);run 收尾再冲一次兜底(防抖窗口里挂掉的尾巴)。
239
+ */
240
+ const FLUSH_MS = 1500;
241
+
242
+ export function attachBoardSeater(bus, projectId) {
243
+ const pending = new Set();
244
+ let timer = null;
245
+ const flush = async () => {
246
+ if (timer) { clearTimeout(timer); timer = null; }
247
+ if (!pending.size) return;
248
+ const batch = [...pending]; pending.clear();
249
+ try {
250
+ const { seated, shelved } = await seatArtifacts(projectId, batch);
251
+ // 上架的要出声:架不是版面,agent 得给它们找地方(状态块每回合也点名)
252
+ if (seated) {
253
+ bus.publish({ type: 'board.updated', sessionId: null,
254
+ summary: `${seated} 件新产物入了座${shelved ? `(${shelved} 件在暂存架等安置)` : ''}` });
255
+ }
256
+ } catch (err) {
257
+ console.warn('[board-seater]', projectId, err?.message || err);
258
+ }
259
+ };
260
+ bus.subscribe('*', (evt) => {
261
+ if (!evt?.runId) return;
262
+ if (evt.type === 'run.file_changed') {
263
+ // 两种历史载荷形状都认(Events.fileChanged 的 filePath / 旧内联对象的 path)
264
+ const rel = typeof evt.filePath === 'string' ? evt.filePath : (typeof evt.path === 'string' ? evt.path : null);
265
+ if (!rel || !seatable(rel)) return;
266
+ pending.add(rel);
267
+ if (!timer) timer = setTimeout(flush, FLUSH_MS);
268
+ return;
269
+ }
270
+ if (evt.type === 'run.done' || evt.type === 'run.cancelled' || evt.type === 'run.error') void flush();
271
+ });
272
+ }
@@ -3,34 +3,55 @@
3
3
  *
4
4
  * 用户定的文化:板书是基础设施。agent 开工时用 TodoWrite 列的步骤不该只活在侧栏的计划
5
5
  * 面板里 —— 它是这一轮的骨架,该写在画布上、每一步产出的东西连到对应步骤。
6
- * 这件事**由 harness 做,不靠 agent 记得**:
7
- * - run.todo.updated → 一条板书 `notes/板书/<stamp>-步骤.md`(一轮一条、原地更新),
8
- * Markdown 任务列表:✓ 已做 / → 正在(加粗)/ 待做;落定不走 staging
9
- * - run.file_changed(有"正在"的步骤时)→ 这条板书到那件产物的 annotates 线,label「第 N 步」,
10
- * by:'auto'(不算进主角焦点分;同一轮同一件只连一次)
11
- * - run.done/cancelled/error 忘掉这一轮的状态(板书留着,它是过程记录)
12
- * 挂在 project bus 上(broker.getProjectBus 建 bus 时调一次),与 live-turn 折叠器同级。
13
- * 失败一律吞掉只 warn:镜像挂了不能影响 run。
6
+ * 这件事**由 harness 做,不靠 agent 记得**。
7
+ *
8
+ * ## 2026-08-30 改版:一个项目一张,可更新的版面组件
9
+ *
10
+ * 原来一轮(runId)一张便签,真板实证(proj_mtfz7n8p):同一场会话两轮就两张
11
+ * 几乎一样的「步骤」,服务端一重启在内存的台账也丢,下一轮又新建 —— 板上全是
12
+ * 步骤便签的尸体。站主拍板改成**项目单例**:
13
+ * - run.todo.updated 全项目共用一张 `notes/板书/<stamp>-步骤.md`(tag=步骤),
14
+ * 内容原地重写;内存台账丢了就按 tag 从板上**认领旧的**,不再新建
15
+ * - 首次落位上暂存架(seat:'shelf',机器的手只够得到架);agent/用户挪过之后
16
+ * 位置跨轮保留 —— 它就是一块可以被 edit_board move 经营的版面组件
17
+ * - run.file_changed(有"正在"的步骤时)→ 便签到那件产物的 annotates 线,
18
+ * label「第 N 步」,by:'auto'
19
+ * - run.done/cancelled/error → **拆掉这一轮拉出去的 auto 步骤线**(线说的是
20
+ * "正在其上工作",工完线收;便签本身留着)
21
+ * 并发的轮各自记账、渲染时拼在同一张上(峰值并发 4,罕见且下一次更新就纠正)。
22
+ * 挂在 project bus 上;失败一律吞掉只 warn:镜像挂了不能影响 run。
14
23
  */
15
24
  import path from 'node:path';
16
25
  import { readBoard, patchBoard } from '../../projects/board-store.js';
17
26
  import { getSharedDir } from '../../projects/workspace.js';
18
27
  import { estimateSizeOn } from '../../lib/board-kind-sizes.js';
19
- import { layerOf, normalizeCanvasId } from '../../lib/canvas-id.js';
20
- import { textBox, findSpot } from '../../lib/sketch-layout.js';
21
28
  import { getViewpoint } from '../../projects/viewpoint-store.js';
29
+ import { layerOf, normalizeCanvasId } from '../../lib/canvas-id.js';
30
+ import { textBox } from '../../lib/sketch-layout.js';
31
+ import { resolveShelfOrigin, nextShelfSpot } from '../../lib/board-shelf.js';
22
32
  import { renderChalk, writeChalkFile, CHALK_DIR } from '../../lib/chalk.js';
23
33
 
24
- const byRun = new Map(); // runId → { projectId, rel, fileName, items, linked:Set, session }
34
+ const TAG = '步骤';
35
+ const byProject = new Map(); // projectId → { rel, fileName, runs: Map(runId → {items, linked:Set, session}) }
25
36
  const MAX_LINKS_PER_RUN = 24;
26
37
 
27
- function renderList(items) {
28
- const lines = ['### 这一轮的步骤', ''];
38
+ function renderItems(items) {
39
+ const out = [];
29
40
  items.forEach((it, i) => {
30
41
  const text = String(it.content || it.activeForm || '').trim() || `步骤 ${i + 1}`;
31
- if (it.status === 'completed') lines.push(`- [x] ${text}`);
32
- else if (it.status === 'in_progress') lines.push(`- [ ] **→ ${it.activeForm || text}**`);
33
- else lines.push(`- [ ] ${text}`);
42
+ if (it.status === 'completed') out.push(`- [x] ${text}`);
43
+ else if (it.status === 'in_progress') out.push(`- [ ] **→ ${it.activeForm || text}**`);
44
+ else out.push(`- [ ] ${text}`);
45
+ });
46
+ return out;
47
+ }
48
+
49
+ function renderList(runs) {
50
+ const lines = ['### 这一轮的步骤', ''];
51
+ const active = [...runs.values()].filter(r => r.items.length);
52
+ active.forEach((r, idx) => {
53
+ if (idx > 0) lines.push(''); // 并发的轮拼在同一张上,段间空行分隔
54
+ lines.push(...renderItems(r.items));
34
55
  });
35
56
  return lines.join('\n');
36
57
  }
@@ -53,73 +74,94 @@ export function canvasIdForRel(board, rel) {
53
74
  return norm || r;
54
75
  }
55
76
 
56
- async function upsert(st, items) {
57
- st.items = items;
58
- const body = renderList(items);
59
- const content = renderChalk({ body, by: 'agent', tag: st.tag, sessionId: st.session || null });
60
- const sharedRoot = getSharedDir(st.projectId);
77
+ /** 内存台账丢了(重启)就按 tag 从板上认领旧便签 —— 这是"不再新建"的另一半 */
78
+ function adoptExisting(st, board) {
79
+ if (st.rel) return;
80
+ const hits = Object.entries(board.objects || {})
81
+ .filter(([id, e]) => id.startsWith(`${CHALK_DIR}/`) && e?.by === 'agent'
82
+ && typeof e?.tag === 'string' && (e.tag === TAG || e.tag.startsWith(`${TAG}-`)))
83
+ .map(([id]) => id)
84
+ .sort();
85
+ if (hits.length) { st.rel = hits[hits.length - 1]; st.fileName = st.rel.split('/').pop(); }
86
+ }
87
+
88
+ async function upsert(st, projectId, session) {
89
+ const body = renderList(st.runs);
90
+ const content = renderChalk({ body, by: 'agent', tag: TAG, sessionId: session || null });
91
+ const sharedRoot = getSharedDir(projectId);
92
+ const board = await readBoard(projectId);
93
+ adoptExisting(st, board);
61
94
  if (!st.fileName) st.fileName = `${new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, '').replace('T', '-')}-步骤.md`;
62
95
  st.rel = await writeChalkFile(sharedRoot, st.fileName, content, { overwrite: !!st.rel });
63
96
  st.fileName = st.rel.split('/').pop(); // 首写可能被加了防撞序号,之后原地覆盖同一个文件
64
- const board = await readBoard(st.projectId);
65
97
  const box = textBox(body, 'md', { md: true, wUnits: 14 });
66
98
  const prev = board.objects?.[st.rel];
67
99
  if (prev && Number.isFinite(prev.x)) {
68
- // 已有座位:只改高(行数变了)不碰宽 —— 宽由前端量过真值,回写会跟它来回抖(fable P2)
69
- await patchBoard(st.projectId, { objects: { [st.rel]: { ...prev, h: box.h } } });
100
+ // 已有座位:只改高(行数变了)不碰宽/位置 —— 宽由前端量过真值,位置是
101
+ // agent/用户经营出来的,跨轮保留(可更新的版面组件正是这个意思)
102
+ await patchBoard(projectId, { objects: { [st.rel]: { ...prev, h: box.h, tag: TAG } } });
70
103
  return;
71
104
  }
72
- // 首次落位:用户视口的空地 > 内容底下(跟 write_on_board 同口径)
105
+ // 首次落位(2026-08-30 暂存架):机器的手只够得到架
73
106
  const known = new Set(Object.keys(board.zones || {}));
74
107
  const obstacles = Object.entries(board.objects || {})
75
108
  .filter(([id, e]) => Number.isFinite(e?.x) && layerOf(id, e, known) === '')
76
109
  .map(([id, e]) => ({ x: e.x, y: e.y, ...estimateSizeOn(board, id, e) }));
77
- let bottom = 0; for (const o of obstacles) bottom = Math.max(bottom, o.y + o.h);
78
- for (const zz of Object.values(board.zones || {})) if (Number.isFinite(zz?.y)) bottom = Math.max(bottom, zz.y + 240);
79
- const vp = getViewpoint(st.projectId);
80
- const vpRect = (vp && !vp.layer && vp.camera) ? vp.camera : null;
81
- const spot = findSpot({ w: box.w + 24, h: box.h + 24, obstacles, contentBottom: bottom, viewport: vpRect });
82
- await patchBoard(st.projectId, { objects: { [st.rel]: { x: spot.x + 12, y: spot.y + 12, z: 1, w: box.w, h: box.h, by: 'agent', tag: st.tag } } });
110
+ const vp = getViewpoint(projectId);
111
+ const origin = resolveShelfOrigin(board, (vp?.camera && !vp.layer) ? vp.camera : null);
112
+ const spot = nextShelfSpot(origin, obstacles);
113
+ await patchBoard(projectId, {
114
+ objects: { [st.rel]: { x: spot.x, y: spot.y, z: 1, w: box.w, h: box.h, by: 'agent', seat: 'shelf', tag: TAG } },
115
+ ...(origin.changed ? { shelf: { x: origin.x, y: origin.y } } : {}),
116
+ });
83
117
  }
84
118
 
85
119
  export function attachBoardTasklist(bus, projectId) {
86
120
  const handle = async (evt) => {
87
121
  if (!evt?.runId) return;
122
+ let st = byProject.get(projectId);
88
123
  if (evt.type === 'run.todo.updated') {
89
124
  const items = Array.isArray(evt.todos) ? evt.todos.filter(t => t && typeof t === 'object') : [];
90
125
  if (!items.length) return;
91
- let st = byRun.get(evt.runId);
92
- if (!st) {
93
- st = { projectId, rel: null, fileName: null, items: [], linked: new Set(), session: evt.sessionId || null, tag: `步骤-${String(evt.runId).slice(-6)}` };
94
- byRun.set(evt.runId, st);
95
- }
96
- await upsert(st, items);
126
+ if (!st) { st = { rel: null, fileName: null, runs: new Map() }; byProject.set(projectId, st); }
127
+ let run = st.runs.get(evt.runId);
128
+ if (!run) { run = { items: [], linked: new Set(), session: evt.sessionId || null }; st.runs.set(evt.runId, run); }
129
+ run.items = items;
130
+ await upsert(st, projectId, run.session);
97
131
  bus.publish({ type: 'board.updated', sessionId: null, runId: evt.runId, summary: '步骤清单上板' });
98
132
  return;
99
133
  }
100
134
  if (evt.type === 'run.file_changed') {
101
- const st = byRun.get(evt.runId);
102
- if (!st?.rel || !evt.filePath) return;
103
- const idx = st.items.findIndex(t => t.status === 'in_progress');
135
+ const run = st?.runs?.get(evt.runId);
136
+ if (!st?.rel || !run || !evt.filePath) return;
137
+ const idx = run.items.findIndex(t => t.status === 'in_progress');
104
138
  if (idx < 0) return;
105
139
  // 只连工作区内、非隐藏目录的真产物;每轮封顶(否则 /tmp、.nd、.claude 里的写入会灌一堆
106
140
  // 指向虚空的 auto 线,MAX_BINDINGS 一到用户自己的线都不收 —— fable 08-23 P2)
107
141
  const rel = String(evt.filePath).replace(/\\/g, '/');
108
142
  if (rel.startsWith('/') || rel.split('/').some(seg => seg === '..' || seg.startsWith('.') || seg === 'node_modules')) return;
109
143
  if (rel.startsWith(`${CHALK_DIR}/`) || rel.startsWith('notes/')) return;
110
- if (st.linked.size >= MAX_LINKS_PER_RUN) return;
111
- const board = await readBoard(st.projectId);
144
+ if (run.linked.size >= MAX_LINKS_PER_RUN) return;
145
+ const board = await readBoard(projectId);
112
146
  const target = canvasIdForRel(board, evt.filePath);
113
147
  if (!target || target === st.rel || target.startsWith(`${CHALK_DIR}/`)) return;
114
- if (st.linked.has(target)) return;
115
- st.linked.add(target);
116
- const id = `b:auto:step:${String(evt.runId).slice(-6)}:${st.linked.size}`;
117
- await patchBoard(st.projectId, { bindings: { [id]: { type: 'annotates', from: st.rel, to: target, by: 'auto', label: `第 ${idx + 1} 步`, tag: st.tag } } });
148
+ if (run.linked.has(target)) return;
149
+ run.linked.add(target);
150
+ const id = `b:auto:step:${String(evt.runId).slice(-6)}:${run.linked.size}`;
151
+ await patchBoard(projectId, { bindings: { [id]: { type: 'annotates', from: st.rel, to: target, by: 'auto', label: `第 ${idx + 1} 步`, tag: TAG } } });
118
152
  bus.publish({ type: 'board.updated', sessionId: null, runId: evt.runId, summary: `第 ${idx + 1} 步 → ${path.basename(target)}` });
119
153
  return;
120
154
  }
121
155
  if (evt.type === 'run.done' || evt.type === 'run.cancelled' || evt.type === 'run.error') {
122
- byRun.delete(evt.runId);
156
+ const run = st?.runs?.get(evt.runId);
157
+ if (!run) return;
158
+ st.runs.delete(evt.runId);
159
+ // 工完线收:这一轮拉出去的 auto 步骤线说的是"正在其上工作",轮结束就拆
160
+ if (run.linked.size) {
161
+ const dead = {};
162
+ for (let n = 1; n <= run.linked.size; n += 1) dead[`b:auto:step:${String(evt.runId).slice(-6)}:${n}`] = null;
163
+ await patchBoard(projectId, { bindings: dead });
164
+ }
123
165
  }
124
166
  };
125
167
  bus.subscribe('*', (evt) => {
@@ -127,4 +169,4 @@ export function attachBoardTasklist(bus, projectId) {
127
169
  });
128
170
  }
129
171
 
130
- export function _resetBoardTasklist() { byRun.clear(); }
172
+ export function _resetBoardTasklist() { byProject.clear(); }
@@ -193,8 +193,8 @@ export function getQueueDepth(sessionId) {
193
193
  * 回显到了一条排队消息、而 turn 正在跑 = CLI 把它并进了当前轮。它的 run 行不能
194
194
  * 停在 pending(那就是老病:runs 表里"排队 N 分钟"的假象),也不能等一个永远不来
195
195
  * 的 result。就地 running→succeeded,metadata 记它并进了谁;token/费用全记在承载它
196
- * 的那一轮(SDK 的 result 本来就只给一份)。run.merged 事件给前端/审计看,前端
197
- * 现在不消费(追加那条从没被认领过 runId,不需要清状态)。
196
+ * 的那一轮(SDK 的 result 本来就只给一份)。run.merged 事件给前端/审计看 ——
197
+ * 前端 08-28 起消费(aux-events.js 弹一条「并进了正在跑的回合」)。
198
198
  */
199
199
  export function closeMergedRun({ runId, intoRunId, sessionId, sdkSessionId, eventBus }) {
200
200
  try { markRunStarted(runId); } catch { /* 不该已 running;兜底继续关 */ }
@@ -215,3 +215,25 @@ export function closeMergedRun({ runId, intoRunId, sessionId, sdkSessionId, even
215
215
  export function publishQueueDepth(eventBus, sessionId) {
216
216
  eventBus.publish({ type: 'run.queue.depth', sessionId, depth: getQueueDepth(sessionId), ts: new Date().toISOString() });
217
217
  }
218
+
219
+ /**
220
+ * 这条 SDK 消息算不算「自发了一个没主的回合」(session-loop 据此 mintBackgroundTurn)。
221
+ *
222
+ * ⛔ **子代理说话不算**(2026-08-26 用户真机复现):判据原来只看 type,于是常驻角色
223
+ * 被用户的直达消息唤醒、开口第一句时就铸出一个主回合并 emit run.start。而那个 run
224
+ * **永远收不了** —— 角色跑的是子代理回合,不会有主循环的 result,finishTurn /
225
+ * releaseCurrentTurnRunId 都轮不到。连锁四个症状:
226
+ * 1. 侧栏按钮变「停止」,用户以为对话被占用
227
+ * 2. currentTurnRunId 挂死 → 每次重连 ws 的 activeRunId 又把「停止」确认一遍,回不去
228
+ * 3. 点「停止」= cancel 那个铸出来的 run → SDK abort 把**在飞的其他角色一起杀了**
229
+ * 4. 时间轴上多一条永远转的圈
230
+ *
231
+ * 后台自发回合这条路本身是对的(task_notification 唤起主代理时确实没主),
232
+ * 它只是分不出「主代理醒了」和「子代理说话了」—— 用 parent_tool_use_id 分。
233
+ */
234
+ export function isBackgroundTurnOpener(message) {
235
+ if (message?.parent_tool_use_id) return false;
236
+ return message?.type === 'assistant'
237
+ || message?.type === 'stream_event'
238
+ || message?.type === 'user';
239
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * server/engine/runs/user-chalk-echo.js —— 用户说话的落痕(2026-08-27 solo 画布对话)
3
+ *
4
+ * 用户从画布标注「说给某个角色」时,话直投收件箱(role-direct 那条 08-26 的路),
5
+ * 但板上什么都不留 —— 这条对话线于是只有角色那半声道,事后回看像一出只印了
6
+ * 一半台词的剧本。这里把同一句话落成 by:'user' 的板书接进线:
7
+ *
8
+ * - 标注的目标是板书 → frontmatter reply_to 它(线程接上,flow 线照领养同款画)
9
+ * - 目标是别的画布物件 → frontmatter anchor 它(annotates 线)
10
+ * - tag 继承目标的(同一条戏线)
11
+ *
12
+ * 落座直接复用板书领养(board-seater.seatArtifacts):say 端点不是 run,没有
13
+ * run.file_changed 事件可搭,得当场调。
14
+ *
15
+ * ⚠️ 调用方约定:落痕失败**不拦投递** —— 话必须送到,痕是锦上添花。这里抛出的
16
+ * 错由调用方吞并降级(照常 deliver,不带 echo 指针)。
17
+ */
18
+
19
+ import { readBoard } from '../../projects/board-store.js';
20
+ import { getSharedDir } from '../../projects/workspace.js';
21
+ import { normalizeCanvasId } from '../../lib/canvas-id.js';
22
+ import { renderChalk, chalkFileName, writeChalkFile, CHALK_DIR } from '../../lib/chalk.js';
23
+ import { seatArtifacts } from './board-seater.js';
24
+
25
+ /**
26
+ * @param {string} projectId
27
+ * @param {{ text: string, anchor?: string|null }} p anchor = 标注目标的 canvas id
28
+ * @returns {Promise<{ rel: string, seated: boolean }>} rel = 落下的板书相对路径
29
+ */
30
+ export async function echoUserChalk(projectId, { text, anchor = null }) {
31
+ const board = await readBoard(projectId);
32
+ const aid = anchor ? normalizeCanvasId(String(anchor).slice(0, 300)) : null;
33
+ const ae = aid ? board.objects?.[aid] : null;
34
+ const isChalk = !!(ae && aid.startsWith(`${CHALK_DIR}/`));
35
+ const content = renderChalk({
36
+ body: text, by: 'user',
37
+ // 目标不在板上就落成无链板书(还是比丢掉这半段对话强)
38
+ ...(ae ? (isChalk ? { replyTo: aid } : { anchor: aid }) : {}),
39
+ ...(ae?.tag ? { tag: ae.tag } : {}),
40
+ });
41
+ const rel = await writeChalkFile(getSharedDir(projectId), chalkFileName(text), content);
42
+ const { seated } = await seatArtifacts(projectId, [rel]);
43
+ return { rel, seated: seated > 0 };
44
+ }
package/server/index.js CHANGED
@@ -33,8 +33,8 @@ import turnRouter from './api/turn.js';
33
33
  import exportsRouter from './api/exports.js';
34
34
  import sessionsRouter from './api/sessions.js';
35
35
  import instructionRouter from './api/instruction.js';
36
- import memoryRouter from './api/memory.js';
37
36
  import boardRouter from './api/board.js';
37
+ import stageRouter from './api/stage.js';
38
38
  import pendingChangesRouter from './api/pending-changes.js';
39
39
  import browseRouter from './api/browse.js';
40
40
  import publishRouter from './api/publish.js';
@@ -118,8 +118,8 @@ app.use('/api/projects', turnRouter);
118
118
  app.use('/api/projects', exportsRouter);
119
119
  app.use('/api/projects', sessionsRouter);
120
120
  app.use('/api/projects', instructionRouter);
121
- app.use('/api/projects', memoryRouter);
122
121
  app.use('/api/projects', boardRouter);
122
+ app.use('/api/projects', stageRouter); // 用户从画布对角色说的话落在画布上
123
123
  app.use('/api/projects', pendingChangesRouter); // C4: 用户直接编辑 + 评论 buffer
124
124
  app.use('/api/projects', browseRouter); // 刷新后拿回浏览器窗/求助状态
125
125
  app.use('/api/projects', publishRouter); // 站点一键上线 Cloudflare Pages