@xiaobuyu/nodesign 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/README.md +266 -79
  2. package/README.zh-CN.md +254 -67
  3. package/package.json +2 -2
  4. package/server/api/admin.js +7 -0
  5. package/server/api/assets/docx-page.js +5 -4
  6. package/server/api/assets/helpers.js +8 -0
  7. package/server/api/assets.js +21 -33
  8. package/server/api/board.js +29 -1
  9. package/server/api/browse.js +3 -2
  10. package/server/api/chatai.js +9 -8
  11. package/server/api/exports/cards.js +7 -6
  12. package/server/api/exports/docx-pdf.js +4 -1
  13. package/server/api/local.js +10 -8
  14. package/server/api/projects.js +19 -3
  15. package/server/api/publish.js +3 -2
  16. package/server/api/sessions-rewind.js +220 -0
  17. package/server/api/sessions.js +58 -194
  18. package/server/api/stage.js +35 -0
  19. package/server/api/turn-model-switch.js +109 -0
  20. package/server/api/turn.js +52 -88
  21. package/server/auth/middleware.js +29 -6
  22. package/server/auth/tier.js +27 -7
  23. package/server/auth/users-store.js +14 -1
  24. package/server/engine/agent/actor-trail.js +110 -0
  25. package/server/engine/agent/agent-shared.js +22 -52
  26. package/server/engine/agent/beat-state.js +61 -0
  27. package/server/engine/agent/cast.js +322 -0
  28. package/server/engine/agent/events.js +9 -2
  29. package/server/engine/agent/hooks/beat-gate.js +73 -0
  30. package/server/engine/agent/hooks/lifecycle.js +6 -2
  31. package/server/engine/agent/hooks/post-trim.js +1 -0
  32. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  33. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  34. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  35. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  36. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  37. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  38. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  39. package/server/engine/agent/hooks/slot-alias.js +30 -0
  40. package/server/engine/agent/hooks/user-prompt-submit.js +197 -7
  41. package/server/engine/agent/hooks.js +76 -6
  42. package/server/engine/agent/isolation.js +25 -2
  43. package/server/engine/agent/memory-config.js +27 -1
  44. package/server/engine/agent/model-context.js +102 -18
  45. package/server/engine/agent/model-table.js +361 -58
  46. package/server/engine/agent/prompts/nodesign-prelude.md +566 -59
  47. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  48. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +2 -2
  49. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  50. package/server/engine/agent/prompts/tools/site-reference.md +2 -1
  51. package/server/engine/agent/role-card.js +175 -0
  52. package/server/engine/agent/rp-mode.js +17 -0
  53. package/server/engine/agent/session-loop.js +26 -14
  54. package/server/engine/agent/stage-status.js +73 -0
  55. package/server/engine/agent/subagent-flight.js +73 -0
  56. package/server/engine/agent/system-prompts.js +41 -0
  57. package/server/engine/agent/tool-input-stream.js +165 -0
  58. package/server/engine/mcp/actor.js +59 -0
  59. package/server/engine/mcp/capability-gate.js +21 -2
  60. package/server/engine/mcp/index.js +116 -24
  61. package/server/engine/mcp/mode-profile.js +139 -0
  62. package/server/engine/mcp/server-name.js +19 -0
  63. package/server/engine/mcp/tools/artifact-session.js +2 -1
  64. package/server/engine/mcp/tools/browse-find-batch.js +76 -14
  65. package/server/engine/mcp/tools/browse.js +1 -1
  66. package/server/engine/mcp/tools/cast-role.js +167 -0
  67. package/server/engine/mcp/tools/draw-trend.js +104 -0
  68. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  69. package/server/engine/mcp/tools/edit-board.js +569 -0
  70. package/server/engine/mcp/tools/look-at-board.js +4 -3
  71. package/server/engine/mcp/tools/open-sheet.js +242 -0
  72. package/server/engine/mcp/tools/organize-board.js +19 -5
  73. package/server/engine/mcp/tools/paint-still.js +1 -1
  74. package/server/engine/mcp/tools/pin-to-board.js +78 -3
  75. package/server/engine/mcp/tools/read-board.js +109 -16
  76. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  77. package/server/engine/mcp/tools/report-issue.js +1 -0
  78. package/server/engine/mcp/tools/role-memory.js +83 -0
  79. package/server/engine/mcp/tools/roll-dice.js +82 -0
  80. package/server/engine/mcp/tools/roll-film.js +1 -1
  81. package/server/engine/mcp/tools/screenshot.js +2 -2
  82. package/server/engine/mcp/tools/set-vars.js +150 -0
  83. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  84. package/server/engine/mcp/tools/web-search.js +1 -1
  85. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  86. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  87. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  88. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  89. package/server/engine/mcp/tools/write-on-board.js +567 -107
  90. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  91. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +11 -3
  92. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +34 -4
  93. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +20 -4
  94. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  95. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  96. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  97. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  98. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  99. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  100. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  101. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  102. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  103. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  104. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  105. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  106. package/server/engine/runs/board-seater.js +272 -0
  107. package/server/engine/runs/board-tasklist.js +90 -48
  108. package/server/engine/runs/turn-relay.js +24 -2
  109. package/server/engine/runs/user-chalk-echo.js +44 -0
  110. package/server/index.js +2 -0
  111. package/server/lib/board-anchor.js +49 -0
  112. package/server/lib/board-dirty.js +93 -0
  113. package/server/lib/board-excerpt.js +80 -0
  114. package/server/lib/board-follow.js +111 -0
  115. package/server/lib/board-groups.js +1 -5
  116. package/server/lib/board-hero.js +3 -1
  117. package/server/lib/board-kind-sizes.js +36 -0
  118. package/server/lib/board-lanes.js +58 -0
  119. package/server/lib/board-obstacles.js +80 -0
  120. package/server/lib/board-relations.js +5 -1
  121. package/server/lib/board-sheets.js +467 -0
  122. package/server/lib/board-shelf.js +86 -0
  123. package/server/lib/board-transform.js +62 -0
  124. package/server/lib/canvas-id.js +18 -1
  125. package/server/lib/chalk-flow.js +88 -0
  126. package/server/lib/chalk-rewrite.js +63 -0
  127. package/server/lib/chalk-size-pref.js +48 -0
  128. package/server/lib/chalk.js +42 -3
  129. package/server/lib/ingress/forward-openai-chat.js +16 -4
  130. package/server/lib/ingress/openai-chat.js +23 -12
  131. package/server/lib/ingress/session-routes.js +3 -3
  132. package/server/lib/ingress/stream-watchdog.js +53 -0
  133. package/server/lib/ingress/upstream-billing.js +15 -0
  134. package/server/lib/ingress/upstream-error-hints.js +36 -0
  135. package/server/lib/json-preview.js +58 -0
  136. package/server/lib/model-ingress.js +26 -1
  137. package/server/lib/moderation.js +10 -5
  138. package/server/lib/quota.js +26 -6
  139. package/server/lib/rect.js +50 -0
  140. package/server/lib/screen.js +36 -0
  141. package/server/lib/sheet-state.js +19 -0
  142. package/server/lib/sketch-array.js +118 -0
  143. package/server/lib/sketch-hatch.js +99 -0
  144. package/server/lib/sketch-layout.js +269 -69
  145. package/server/lib/sketch-shapes.js +152 -0
  146. package/server/lib/sketch-stencils.js +170 -0
  147. package/server/lib/state-table.js +237 -0
  148. package/server/lib/state-trend.js +94 -0
  149. package/server/lib/state-triggers.js +199 -0
  150. package/server/lib/tavern-json.js +48 -0
  151. package/server/lib/user-uploads.js +88 -0
  152. package/server/projects/board-limits.js +5 -3
  153. package/server/projects/board-sanitize.js +220 -10
  154. package/server/projects/board-store.js +95 -6
  155. package/server/projects/session-jsonl.js +110 -0
  156. package/server/projects/store.js +56 -3
  157. package/server/projects/viewpoint-store.js +35 -3
  158. package/server/projects/workspace-slots.js +30 -0
  159. package/server/projects/workspace-templates.js +29 -0
  160. package/server/projects/workspace.js +16 -17
  161. package/server/runtime/capabilities.js +6 -2
  162. package/server/shared/locales.js +70 -0
  163. package/server/shared/messages-en.js +69 -0
  164. package/server/shared/messages.js +65 -0
  165. package/server/ws/broker.js +3 -0
  166. package/server/ws/index.js +43 -3
  167. package/web/README.md +1 -1
  168. package/web/dist/assets/{BrowserWindow-BoOBaIyz.js → BrowserWindow-CcaGRWz5.js} +1 -1
  169. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  170. package/web/dist/assets/{DocxWindow-B_u6aX9o.js → DocxWindow-B9KZmU5z.js} +2 -2
  171. package/web/dist/assets/{MermaidBlock-BO-46Du5.js → MermaidBlock-DrfRnz8E.js} +43 -43
  172. package/web/dist/assets/{SiteWindow-ConPmzdM.js → SiteWindow-CJOjCF7d.js} +6 -6
  173. package/web/dist/assets/{abnfDiagram-VCTEODGH-FCO9yf-n.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  174. package/web/dist/assets/{arc-BLcEOSRX.js → arc-C1-HSUny.js} +1 -1
  175. package/web/dist/assets/{architectureDiagram-5GKGNRK7-kxiX4TeN.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  176. package/web/dist/assets/{blockDiagram-NRAW4CY4-CptFksix.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  177. package/web/dist/assets/{c4Diagram-UCG6FXSJ-Dxmxj34F.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  178. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  179. package/web/dist/assets/{chunk-2Q5K7J3B-D9inx5oo.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  180. package/web/dist/assets/{chunk-5VM5RSS4-DydfHlEc.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  181. package/web/dist/assets/{chunk-F27PBJKO-BO7GxhSv.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  182. package/web/dist/assets/{chunk-G27WJ6UU-BzP6YiQ_.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  183. package/web/dist/assets/{chunk-JWPE2WC7-CHcgcd0s.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  184. package/web/dist/assets/{chunk-LCL6LL3I-D2Wg5JtX.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  185. package/web/dist/assets/{chunk-POPQ4Y6H-B2fCRfpM.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  186. package/web/dist/assets/{chunk-SVP7TREG-JSLcsRlZ.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  187. package/web/dist/assets/{chunk-XXDRQBXY-BCjvaeHm.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  188. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  189. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  190. package/web/dist/assets/{cose-bilkent-JH36ORCC-0uKv6oJ7.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  191. package/web/dist/assets/{cynefin-OW5HDTMX-Bvt-gEmA.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  192. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-C9Ht02qC.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  193. package/web/dist/assets/{dagre-3AP2YEHR-BfxZny4s.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  194. package/web/dist/assets/{diagram-S7CK7UJ4-CrE0ZETE.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  195. package/web/dist/assets/{diagram-UQ7AKVKN-BO8VQ4Fx.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  196. package/web/dist/assets/{diagram-VSXAHHWV-CHUZnR9F.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  197. package/web/dist/assets/{diagram-VX7I27RA-DYQAz1Vu.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  198. package/web/dist/assets/{diagram-Z3DM3KII-BDjivgLr.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  199. package/web/dist/assets/{ebnfDiagram-PWID7BFC-Bpe_QVbH.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  200. package/web/dist/assets/{erDiagram-SSCWMZ5O-BxhwuAu1.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  201. package/web/dist/assets/{flowDiagram-A5DVABFB-CCw2H08-.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  202. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-DtTc7MyI.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  203. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-BvORxMd2.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  204. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  205. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  206. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  207. package/web/dist/assets/{infoDiagram-RXCK75RN-DMcrkypD.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  208. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CZkTIbe-.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  209. package/web/dist/assets/{journeyDiagram-EYS64GPL-DR0XazdE.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  210. package/web/dist/assets/{kanban-definition-3QL26DDD-DN8VbwVu.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  211. package/web/dist/assets/{layout-9_paCbyt.js → layout-Dhgck-Zo.js} +1 -1
  212. package/web/dist/assets/{linear-g6Ct012e.js → linear-DMSQ-aUw.js} +1 -1
  213. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  214. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  215. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  216. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  217. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  218. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  219. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  220. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  221. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  222. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  223. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  224. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  225. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  226. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  227. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  228. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  229. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  230. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  231. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  232. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  233. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  234. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  235. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  236. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  237. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  238. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  239. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  240. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  241. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  242. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  243. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  314. package/web/dist/assets/{mindmap-definition-FBJOCRG2-BGt4omYC.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  315. package/web/dist/assets/{pegDiagram-XKGWAZYB-uSfS-PBE.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  316. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  317. package/web/dist/assets/{pieDiagram-E7YTZNPT-DpeI935H.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  318. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-B7IuNvWa.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  319. package/web/dist/assets/{railroadDiagram-O6MQD6OU-ARzYpPQI.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  320. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-Cj4upkAo.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  321. package/web/dist/assets/{rotate-cw-CXJcyHWk.js → rotate-cw-62liz7LX.js} +1 -1
  322. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-OaVwSnLP.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  323. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-1yrUbq4t.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  324. package/web/dist/assets/{sizeCapture-X5ZJPWSS-nYH5p9D6.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  325. package/web/dist/assets/{square-dashed-mouse-pointer-CTtTVKoH.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  326. package/web/dist/assets/{stateDiagram-HBIQ2CUA-Dj5HvyNq.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  327. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  328. package/web/dist/assets/{swimlanes-XN3QIQJK-umeT0HpB.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  329. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  330. package/web/dist/assets/{timeline-definition-24CTP7MA-MqnQfHKB.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  331. package/web/dist/assets/{vennDiagram-4TSXK5OY-b2liyZra.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  332. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-QMK-zNh1.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  333. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-D5Mc6tfA.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  334. package/web/dist/index.html +2 -2
  335. package/server/engine/mcp/tools/arrange-on-board.js +0 -126
  336. package/server/engine/mcp/tools/create-on-board.js +0 -152
  337. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  338. package/server/engine/mcp/tools/relate-on-board.js +0 -150
  339. package/server/engine/mcp/tools/sketch-on-board.js +0 -345
  340. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  341. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +0 -78
  342. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  343. package/web/dist/assets/DeckWindow-Bg3rgkvi.js +0 -11
  344. package/web/dist/assets/channel-DFyiuGL0.js +0 -1
  345. package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +0 -1
  346. package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +0 -1
  347. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  348. package/web/dist/assets/index-DGG5wIZs.js +0 -2118
  349. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  350. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  351. package/web/dist/assets/pending-edit-apply-uP4uvrOD.js +0 -31
  352. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +0 -1
  353. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +0 -8
@@ -13,10 +13,38 @@ import path from 'node:path';
13
13
  import fs from 'node:fs/promises';
14
14
  import { tool } from '@anthropic-ai/claude-agent-sdk';
15
15
  import { z } from 'zod';
16
- import { detectKind, digest, fetchEntries } from '../../../lib/tavern-json.js';
16
+ import { detectKind, digest, fetchEntries, extractCardFromPng, listBookEntries } from '../../../lib/tavern-json.js';
17
17
 
18
18
  const MAX_FETCH_CHARS = 24_000;
19
19
 
20
+ /**
21
+ * 世界书条目表。四方世界卡这种 570 条的怪物逐条带引子会刷出上千行 ——
22
+ * 超过 80 条就转紧凑档:常驻的全列(那是要人工挑进 CLAUDE.md 的),
23
+ * 触发的只报名字+触发词不带引子,并指路 export_book 一步落盘。
24
+ */
25
+ function 渲染世界书(条目, 标题) {
26
+ const L = [标题 + ':'];
27
+ const compact = 条目.length > 80;
28
+ const 常驻 = 条目.filter(e => e.常驻 && !e.停用);
29
+ const 触发 = 条目.filter(e => !e.常驻 && !e.停用);
30
+ const 停用数 = 条目.filter(e => e.停用).length;
31
+ if (compact) {
32
+ L.push(`启用 ${常驻.length + 触发.length}(常驻 ${常驻.length} / 触发 ${触发.length})· 停用 ${停用数}`);
33
+ L.push('');
34
+ L.push(`常驻条目(每轮在场的候选 —— 逐条人工过:内容进 CLAUDE.md,引擎件不搬):`);
35
+ for (const e of 常驻) L.push(`- ${e.名字} ${e.字数}字`);
36
+ L.push('');
37
+ L.push(`触发条目 ${触发.length} 条(名字|触发词)——别逐条 fetch,用 mode="export_book" 一步落成 世界书/ 文件:`);
38
+ for (const e of 触发) L.push(`- ${e.名字} | ${e.触发.slice(0, 4).join(' ')}`);
39
+ } else {
40
+ for (const e of 条目) {
41
+ L.push(`- ${e.名字} ${e.常驻 ? '常驻' : `触发[${e.触发.join(' ')}]`} ${e.字数}字${e.停用 ? ' (停用)' : ''}`);
42
+ if (e.引子) L.push(` ${e.引子}…`);
43
+ }
44
+ }
45
+ return L.join('\n');
46
+ }
47
+
20
48
  function 渲染摘要(d, 文件名) {
21
49
  const L = [];
22
50
  if (d.形态 === 'preset') {
@@ -46,17 +74,11 @@ function 渲染摘要(d, 文件名) {
46
74
  if (d.开场白备选) L.push(`- alternate_greetings ${d.开场白备选} 条备选开场白`);
47
75
  if (d.世界书.length) {
48
76
  L.push('');
49
- L.push(`内嵌世界书 ${d.世界书.length} 条:`);
50
- for (const e of d.世界书) {
51
- L.push(`- ${e.名字} ${e.常驻 ? '常驻' : `触发[${e.触发.join(' ')}]`} ${e.字数}字${e.停用 ? ' (停用)' : ''}`);
52
- }
77
+ L.push(渲染世界书(d.世界书, `内嵌世界书 ${d.世界书.length} 条`));
53
78
  }
54
79
  } else {
55
- L.push(`酒馆世界书:${文件名},${d.条目.length} 条`);
56
- for (const e of d.条目) {
57
- L.push(`- ${e.名字} ${e.常驻 ? '常驻' : `触发[${e.触发.join(' ')}]`} ${e.字数}字${e.停用 ? ' (停用)' : ''}`);
58
- if (e.引子) L.push(` ${e.引子}…`);
59
- }
80
+ L.push(`酒馆世界书:${文件名}`);
81
+ L.push(渲染世界书(d.条目, `${d.条目.length} 条`));
60
82
  }
61
83
  L.push('');
62
84
  L.push('要正文就再调一次本工具:mode="fetch",entries=["名字或名字的一部分", …]。');
@@ -80,10 +102,18 @@ Two steps:
80
102
  2. mode "fetch" with entries[] — full text of just the ones you picked (name,
81
103
  partial name, or id). Capped at ${MAX_FETCH_CHARS} chars per call.
82
104
 
83
- This tool only reads. Turning a preset into 编排.yaml + 设定 files is your job
84
- you decide the grouping, and you drop what does not belong (markers are filled by
85
- 酒馆 at runtime and have no place here; platform-specific jailbreak sections are
86
- pointless on this platform).`,
105
+ Also reads **PNG character cards** directly (V2 chara / V3 ccv3 embedded data)
106
+ point path at the .png, no conversion needed.
107
+
108
+ mode "export_book" is the one writing mode: dumps every enabled lorebook entry to
109
+ workspace files in one call (triggered entries one file each with trigger keys in
110
+ frontmatter → grep them before writing a chapter; constant entries under 常驻/ for
111
+ you to curate into CLAUDE.md). Use it for big world cards — hundreds of entries
112
+ must not flow through your context. Judgment stays yours: what goes to CLAUDE.md,
113
+ and dropping 酒馆 engine machinery (MVU variables / HTML status bars / CoT
114
+ frameworks / regex_scripts — this platform has its own 状态板/明骰/记忆). Markers
115
+ are filled by 酒馆 at runtime and have no place here; jailbreak sections are
116
+ pointless on this platform.`,
87
117
  {
88
118
  path: z.string().describe(
89
119
  'Path to the .json. Relative paths resolve against the session workspace, '
@@ -91,12 +121,14 @@ pointless on this platform).`,
91
121
  ),
92
122
  // ⚠️ 参数名和枚举值一律 ASCII —— 工具 schema 是模型要照着填的东西,
93
123
  // 中文键名在这条路上不可靠(全仓其他工具也都是 ASCII,别开这个头)
94
- mode: z.enum(['digest', 'fetch']).optional()
95
- .describe('digest = structure only (default); fetch = full text of the picked entries'),
124
+ mode: z.enum(['digest', 'fetch', 'export_book']).optional()
125
+ .describe('digest = structure only (default); fetch = full text of picked entries; export_book = write every enabled lorebook entry to workspace files (triggered ones one file each with keys in frontmatter, constant ones under 常驻/) — the mechanical half of an import, judgment stays yours'),
96
126
  entries: z.array(z.string()).optional()
97
127
  .describe('For mode="fetch": entry names (partial match ok) or ids'),
128
+ out: z.string().max(120).optional()
129
+ .describe('For mode="export_book": target folder, workspace-relative (default 世界书)'),
98
130
  },
99
- async ({ path: rel, mode = 'digest', entries = [] }) => {
131
+ async ({ path: rel, mode = 'digest', entries = [], out }) => {
100
132
  const fail = (text) => ({ content: [{ type: 'text', text }], isError: true });
101
133
  try {
102
134
  const raw = String(rel || '').trim();
@@ -112,8 +144,15 @@ pointless on this platform).`,
112
144
  if (!abs) return fail(`File not found: ${raw}\nLooked in:\n${candidates.map(c => ` ${c}`).join('\n')}`);
113
145
 
114
146
  let doc;
115
- try { doc = JSON.parse(await fs.readFile(abs, 'utf8')); } catch (e) {
116
- return fail(`这个文件不是合法 JSON:${e.message}`);
147
+ const buf = await fs.readFile(abs);
148
+ if (buf.length > 3 && buf.readUInt32BE(0) === 0x89504e47) {
149
+ // 酒馆的卡就是一张 PNG(V3 ccv3 / V2 chara 藏在 tEXt 块里)
150
+ doc = extractCardFromPng(buf);
151
+ if (!doc) return fail('这张 PNG 里没有角色卡数据(tEXt 块无 ccv3/chara)——它可能只是普通图片。');
152
+ } else {
153
+ try { doc = JSON.parse(buf.toString('utf8')); } catch (e) {
154
+ return fail(`这个文件不是合法 JSON:${e.message}`);
155
+ }
117
156
  }
118
157
  const kind = detectKind(doc);
119
158
  if (!kind) {
@@ -134,6 +173,35 @@ pointless on this platform).`,
134
173
  return { content: [{ type: 'text', text: 块.join('\n\n') }] };
135
174
  }
136
175
 
176
+ if (mode === 'export_book') {
177
+ const all = listBookEntries(doc);
178
+ const live = all.filter(e => !e.停用 && e.正文.trim());
179
+ if (!live.length) return fail('这份文件里没有启用且有正文的世界书条目。');
180
+ const root = sharedRoot || workspaceRoot;
181
+ const dirRel = String(out || '世界书').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
182
+ if (!dirRel || dirRel.split('/').some(s2 => s2 === '..' || s2.startsWith('.'))) return fail('out 目录不合法。');
183
+ const slug = (t) => String(t).replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '').slice(0, 32) || 'entry';
184
+ const used = new Set();
185
+ let 触发数 = 0; let 常驻数 = 0;
186
+ for (const e of live) {
187
+ const sub = e.常驻 ? `${dirRel}/常驻` : dirRel;
188
+ await fs.mkdir(path.join(root, sub), { recursive: true });
189
+ let name = slug(e.名字);
190
+ for (let i = 2; used.has(`${sub}/${name}`); i += 1) name = `${slug(e.名字)}-${i}`;
191
+ used.add(`${sub}/${name}`);
192
+ const fm = ['---', 'nd: lore',
193
+ `keys: ${JSON.stringify(e.触发)}`,
194
+ `constant: ${e.常驻}`, `source: ${path.basename(abs)}`, '---', '', e.正文.trim(), ''];
195
+ await fs.writeFile(path.join(root, sub, `${name}.md`), fm.join('\n'), 'utf8');
196
+ if (e.常驻) 常驻数 += 1; else 触发数 += 1;
197
+ }
198
+ return { content: [{ type: 'text', text:
199
+ `Exported ${live.length} entries → ${dirRel}/:触发 ${触发数} 条(一条一文件,frontmatter 带 keys —— `
200
+ + `每章动笔前拿本章的人名/地名/物件 grep 这个目录,命中就 Read);常驻 ${常驻数} 条在 ${dirRel}/常驻/`
201
+ + `(逐条人工过:世界观内容挑十条以内进 CLAUDE.md,MVU/状态栏/CoT/回复格式这类酒馆引擎件不搬 —— `
202
+ + `平台有自己的状态板/明骰/记忆)。停用条目已跳过。` }] };
203
+ }
204
+
137
205
  return { content: [{ type: 'text', text: 渲染摘要(digest(doc), path.basename(abs)) }] };
138
206
  } catch (err) {
139
207
  return fail(`read_tavern_json failed: ${err?.message || String(err)}`);
@@ -101,6 +101,7 @@ every time; a viewportOnly flag would remove the whole detour" is actionable.`,
101
101
  };
102
102
  }
103
103
  try {
104
+ // ℹ️ 遥测事件:前端刻意不消费 —— 摩擦上报的读者是站主(信箱/审计),不是当场的用户
104
105
  ctx?.emit?.({ type: 'run.friction_reported', kind, summary, toolName: toolName || null, count: rec.count });
105
106
  } catch { /* emit fail-safe */ }
106
107
  return {
@@ -0,0 +1,83 @@
1
+ /**
2
+ * mcp/tools/role-memory.js —— jot_memory:角色往自己家里记一笔(2026-08-28)
3
+ *
4
+ * 角色文件夹范式(用户拍板):`角色/<名>/` 是一个角色的家 —— 角色卡.md 住这,
5
+ * 记忆.md 也住这,**用户随时能改**。角色没有 Write/Edit(白名单如此,不放开),
6
+ * 所以「角色自己记记忆」只能是一件 MCP 工具:身份从 byOf 盖章来(不是自称),
7
+ * 落点由 harness 拼(角色写不了别人家)。
8
+ *
9
+ * 记忆是**追加式日志**:一次一条、带时间戳小节。改写/整理归用户和 GM(他们有真编辑
10
+ * 权),角色只增不删 —— 「角色能抹掉自己说过的话」不是我们要的性质。
11
+ */
12
+
13
+ import path from 'node:path';
14
+ import fs from 'node:fs/promises';
15
+ import { tool } from '@anthropic-ai/claude-agent-sdk';
16
+ import { z } from 'zod';
17
+ import { byOf } from '../actor.js';
18
+ import { isResidentRole, isSlotType } from '../../agent/cast.js';
19
+ import { readCastRegistry } from '../../agent/role-card.js';
20
+
21
+ const ROLES_DIR = '角色';
22
+ const MEMORY_FILE = '记忆.md';
23
+ const MAX_NOTE = 2000;
24
+
25
+ /** 这个角色的家在哪:登记表里卡的目录优先,没登记就按 slug 开家 */
26
+ export async function roleHomeDir(workspaceRoot, slug) {
27
+ const reg = await readCastRegistry(workspaceRoot);
28
+ const card = reg.roles?.[slug]?.card;
29
+ if (typeof card === 'string' && card.startsWith(`${ROLES_DIR}/`)) {
30
+ const dir = path.dirname(card);
31
+ // 登记表模型可写 —— 家必须还在 角色/ 里,逃出去就按 slug 兜底
32
+ if (!dir.includes('..')) return dir;
33
+ }
34
+ return `${ROLES_DIR}/${slug}`;
35
+ }
36
+
37
+ export function makeJotMemoryTool({ workspaceRoot }) {
38
+ return tool(
39
+ 'jot_memory',
40
+ `Jot one note into YOUR OWN memory file (角色/<你>/${MEMORY_FILE}).
41
+
42
+ For resident roles only. Use it for things future-you must remember across scenes:
43
+ promises made, secrets learned, how you feel about someone now, where an item went.
44
+ One event per call, a few lines. Append-only — the user and the GM curate the file,
45
+ you never rewrite history. When you wake up lost, Read your card and this file.`,
46
+ {
47
+ text: z.string().min(1).max(MAX_NOTE).describe('The note. A few lines, first person, concrete.'),
48
+ },
49
+ async (args, extra) => {
50
+ const fail = (msg) => ({ content: [{ type: 'text', text: msg }], isError: true });
51
+ if (!workspaceRoot) return fail('没有工作区。');
52
+ const me = byOf(extra);
53
+ // ⛔ 演员位(rp-actor/rp-narrator)单独说:它**是**角色,只是实例名还没解析出来
54
+ // (别名桥要到派发结果或第一次 SendMessage 才学到)。08-28 真会话实录:角色刚上场
55
+ // 就 jot,连吃两次「你是主控」,于是它以为自己不是角色 —— 而它没有 Write 权限,
56
+ // 那句指引把它引向一条走不通的路。话术必须说实话,并给一条走得通的出路。
57
+ if (isSlotType(me)) {
58
+ return fail('你的实例名这会儿还没解析出来(刚上场、还没被点过名),记不进你的记忆文件。'
59
+ + '先照常演,下一拍再记;要是一直记不上,让主控用 SendMessage 点你一次。');
60
+ }
61
+ if (!isResidentRole(me)) {
62
+ return fail('只有常驻角色有记忆文件。你是主控 —— 项目记忆走 记忆/ 目录(Write/Edit)。');
63
+ }
64
+ const text = String(args.text || '').trim();
65
+ if (!text) return fail('空的记不了。');
66
+
67
+ const homeRel = await roleHomeDir(workspaceRoot, me);
68
+ const dir = path.join(workspaceRoot, homeRel);
69
+ const file = path.join(dir, MEMORY_FILE);
70
+ if (!path.resolve(file).startsWith(path.resolve(workspaceRoot, ROLES_DIR) + path.sep)) {
71
+ return fail('记忆文件路径异常,拒绝写入。');
72
+ }
73
+ await fs.mkdir(dir, { recursive: true });
74
+ let head = '';
75
+ try { await fs.access(file); } catch {
76
+ head = `# 记忆\n\n<!-- ${me} 的记忆:角色自己 jot_memory 追加,用户和 GM 可整理改写。 -->\n`;
77
+ }
78
+ const stamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
79
+ await fs.appendFile(file, `${head}\n## ${stamp}\n\n${text}\n`, 'utf8');
80
+ return { content: [{ type: 'text', text: `记下了 → ${homeRel}/${MEMORY_FILE}` }] };
81
+ },
82
+ );
83
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * mcp/tools/roll-dice.js —— roll_dice:服务端真随机骰(2026-08-28,沉浸感机制刀①)
3
+ *
4
+ * ## 为什么必须是工具
5
+ *
6
+ * 「明骰」此前是教义(GM 手写骰表),骰子本身却只能由模型**编一个数** ——
7
+ * 玩家一旦意识到骰运是编的,跑团的信任根基当场塌掉。这里用 crypto 真随机投,
8
+ * 结果同时走两条路:工具返回给 GM(写进正文与骰表),`run.dice` 事件直达前端
9
+ * (用户看到与 GM 笔无关的第一手骰面 —— 明骰才真的"明")。
10
+ *
11
+ * 只注册给 GM(不进角色工具白名单):判定归场务的笔,角色要检定就在戏里喊。
12
+ * 参数 ASCII(feedback-ascii-tool-params)。
13
+ */
14
+
15
+ import { tool } from '@anthropic-ai/claude-agent-sdk';
16
+ import { z } from 'zod';
17
+ import crypto from 'node:crypto';
18
+
19
+ /** 判定结果一句话(纯函数可断言)。nd:rp-prompt */
20
+ export function describeRoll({ label, n, sides, modifier, advantage, rolls, kept, total, dc }) {
21
+ const adv = advantage === 'adv' ? '(优势取高)' : advantage === 'dis' ? '(劣势取低)' : '';
22
+ const mod = modifier ? (modifier > 0 ? `+${modifier}` : `${modifier}`) : '';
23
+ const faces = rolls.length > 1 ? `[${rolls.join(', ')}]` : `[${rolls[0]}]`;
24
+ const crit = sides === 20 && n === 1
25
+ ? (kept === 20 ? ' —— **自然 20**,给它一个镜头' : kept === 1 ? ' —— **自然 1**,给它一个镜头' : '')
26
+ : '';
27
+ const vs = dc != null ? ` vs DC ${dc} —— ${total >= dc ? '**成功**' : '**失败**'}` : '';
28
+ return `🎲 ${label}:${n}d${sides}${mod}${adv} → ${faces}${mod ? ` ${mod}` : ''} = **${total}**${vs}${crit}`;
29
+ }
30
+
31
+ export function makeRollDiceTool({ projectId, ctx = null, rngInt = crypto.randomInt } = {}) {
32
+ return tool(
33
+ 'roll_dice',
34
+ `Roll real server-side dice (cryptographic RNG) for a check. The result also fires a
35
+ run.dice event straight to the user's screen — open dice the GM's pen cannot fake, which
36
+ is the whole point: never make up a roll yourself. After rolling: write the outcome into
37
+ the beat (a TRPG-style hint beats reporting raw numbers) and log it in the 明骰表.
38
+ Failure buys information or a cost; success buys a new problem — never a flat nothing.`,
39
+ {
40
+ label: z.string().min(1).max(60)
41
+ .describe('What this check is, shown to the user (e.g. "斥候·侦查" or "哥布林·突袭")'),
42
+ sides: z.number().int().min(2).max(1000).default(20).optional().describe('Die faces (default 20)'),
43
+ n: z.number().int().min(1).max(20).default(1).optional().describe('How many dice (default 1)'),
44
+ modifier: z.number().int().min(-100).max(100).default(0).optional().describe('Flat bonus/malus added to the total'),
45
+ advantage: z.enum(['none', 'adv', 'dis']).default('none').optional()
46
+ .describe('adv = roll twice keep high, dis = keep low (n must be 1)'),
47
+ dc: z.number().int().min(1).max(1000).optional().describe('Difficulty class; result line says success/failure'),
48
+ },
49
+ async (args, _extra) => {
50
+ if (!projectId) return { content: [{ type: 'text', text: 'No project bound.' }], isError: true };
51
+ const sides = args.sides ?? 20;
52
+ const n = args.n ?? 1;
53
+ const modifier = args.modifier ?? 0;
54
+ const advantage = args.advantage ?? 'none';
55
+ if (advantage !== 'none' && n !== 1) {
56
+ return { content: [{ type: 'text', text: 'advantage/disadvantage 只对单骰(n=1)有意义。' }], isError: true };
57
+ }
58
+ const rolls = [];
59
+ const times = advantage === 'none' ? n : 2;
60
+ for (let i = 0; i < times; i += 1) rolls.push(rngInt(1, sides + 1));
61
+ const kept = advantage === 'adv' ? Math.max(...rolls)
62
+ : advantage === 'dis' ? Math.min(...rolls)
63
+ : null;
64
+ const total = (advantage === 'none' ? rolls.reduce((a, b) => a + b, 0) : kept) + modifier;
65
+ const outcome = args.dc != null ? (total >= args.dc ? 'success' : 'failure') : null;
66
+ const line = describeRoll({
67
+ label: args.label, n, sides, modifier, advantage,
68
+ rolls, kept: kept ?? rolls[0], total, dc: args.dc ?? null,
69
+ });
70
+ // 明骰直达用户(fail-soft:事件坏了不拦骰)—— 这条是"骰子可信"的另一半
71
+ try {
72
+ ctx?.emit?.({
73
+ type: 'run.dice', sessionId: null,
74
+ label: args.label, sides, n, modifier, advantage,
75
+ rolls, total, ...(args.dc != null ? { dc: args.dc, outcome } : {}),
76
+ });
77
+ } catch { /* fail-soft */ }
78
+ return { content: [{ type: 'text', text:
79
+ `${line}\n(用户已在屏幕上看到这一骰 —— 正文按结果写后果,骰表记一行。)` }] };
80
+ },
81
+ );
82
+ }
@@ -254,7 +254,7 @@ and move on.`,
254
254
  shots: z.array(z.object({
255
255
  prompt: z.string().describe('H3 three-field English prompt'),
256
256
  duration: z.number().min(5.2).max(12.25).default(8),
257
- name: z.string().regex(/^[\w-]{1,40}$/).describe('shot slug, unique in batch'),
257
+ name: z.string().regex(/^[\w一-鿿぀-ヿ-]{1,40}$/).describe('shot slug, unique in batch'),
258
258
  first_frame: z.string().optional(),
259
259
  last_frame: z.string().optional(),
260
260
  steps: z.number().int().min(4).max(8).optional().describe('default 8; 4 = fast draft'),
@@ -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()
@@ -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
+ }
@@ -494,7 +494,7 @@ Suggested flow for image-led pages:
494
494
  // 落盘了就发 file_changed —— generate_image 一直在发,这里漏了,
495
495
  // 于是参考图落进工作区之后前端毫无感知(素材抽屉要刷新才看得见)
496
496
  for (const img of downloadedImages) {
497
- 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 { /* */ }
498
498
  }
499
499
  }
500
500