@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
@@ -16,43 +16,57 @@ import { tool } from '@anthropic-ai/claude-agent-sdk';
16
16
  import { z } from 'zod';
17
17
  import { readBoard } from '../../../projects/board-store.js';
18
18
  import { estimateSizeOn } from '../../../lib/board-kind-sizes.js';
19
- import { layerOf } from '../../../lib/canvas-id.js';
19
+ import { layerOf, bareTag } from '../../../lib/canvas-id.js';
20
20
  import { relationsDigest, bindingLine } from '../../../lib/board-relations.js';
21
21
  import { groupObjects, asciiMinimap, bboxOfRects, relationOf, columnsOf, viewportRelation } from '../../../lib/board-groups.js';
22
+ import { laneSummaries } from '../../../lib/board-lanes.js';
23
+ import { capacityOf, DEFAULT_CHALK_W } from '../../../lib/sketch-layout.js';
24
+ import { sheetSummaries, rollCardRect, slotRectOf, nextSpotInSlot, freeColumnsInSheet, latestSheetId } from '../../../lib/board-sheets.js';
22
25
  import { getViewpoint } from '../../../projects/viewpoint-store.js';
23
26
  import { chalkExcerpts, CHALK_DIR } from '../../../lib/chalk.js';
24
27
  import { getSharedDir } from '../../../projects/workspace.js';
28
+ import { promises as fs } from 'node:fs';
29
+ import { byOf, describeBy } from '../actor.js';
30
+ import { listRoleNames } from '../../agent/role-card.js';
31
+ import path from 'node:path';
25
32
 
26
33
  /** 同一"行"的 y 容差:入座算法一行内顶对齐,40 世界像素内视作同行 */
27
34
  const ROW_TOLERANCE = 40;
28
35
 
29
- function describeEntry(board, id, entry, glyph = null, excerpts = null) {
36
+ function describeEntry(board, id, entry, glyph = null, excerpts = null, staleIds = null, view = null) {
37
+ // view = { by: 当前读者, names: slug→展示名 }。读 read_board 的可能是主 agent,
38
+ // 也可能是常驻角色 —— 同一句「你写的」对两个读者含义相反,所以称呼要带视角。
39
+ const who = (b) => describeBy(b || 'agent', view?.by || 'agent', view?.names);
40
+ const mine = (b) => (b || 'agent') === (view?.by || 'agent');
30
41
  const sz = estimateSizeOn(board, id, entry);
31
42
  const at = `@(${Math.round(entry.x)},${Math.round(entry.y)}) ${Math.round(sz.w)}x${Math.round(sz.h)}`;
32
43
  const g = glyph ? `[${glyph}] ` : '';
33
44
  const flags = `${entry.staging ? ' 〔草稿〕' : ''}${entry.tag ? ` #${entry.tag}` : ''}`;
34
45
  const ch = excerpts?.get(id);
35
- if (ch) return `- ${g}[板书·${ch.by === 'user' ? '用户' : '你'}写的] 「${ch.first}」 ${at} (path: ${id})${ch.anchor ? ` 关于 ${ch.anchor}` : ''}${ch.replyTo ? ` 回应 ${ch.replyTo}` : ''}${flags}`;
46
+ if (ch) return `- ${g}[板书·${who(ch.by)}写的] 「${ch.first}」 ${at} (path: ${id})${ch.anchor ? ` 关于 ${ch.anchor}` : ''}${ch.replyTo ? ` 回应 ${ch.replyTo}` : ''}${flags}`;
36
47
  if (entry.kind === 'text') {
37
48
  const t = String(entry.data?.t || '').replace(/\s+/g, ' ').slice(0, entry.data?.format === 'md' ? 60 : 24);
38
49
  const md = entry.data?.format === 'md' ? 'md' : '手写';
39
- return `- ${g}[${md}] 「${t}」 ${at} (id: ${id})${entry.by === 'agent' ? ' ·你写的' : ''}${flags}`;
50
+ return `- ${g}[${md}] 「${t}」 ${at} (id: ${id})${entry.by ? ` ·${who(entry.by)}写的` : ''}${flags}`;
40
51
  }
41
- if (entry.kind === 'scribble') return `- ${g}[涂鸦] ${at} (id: ${id})${entry.by === 'agent' ? ' ·你画的' : ''}${flags}`;
42
- return `- ${g}${id} ${at}${entry.by === 'agent' ? ' ·你摆的' : ''}${flags}`;
52
+ if (entry.kind === 'scribble') return `- ${g}[涂鸦] ${at} (id: ${id})${entry.by ? ` ·${who(entry.by)}画的` : ''}${flags}`;
53
+ // 过期座位要明说(iss_mt38ucyq:旧路径条目被 agent 当"失效卡"差点建议删素材母版)
54
+ const stale = staleIds?.has(id) ? ' 〔⚠️磁盘上已无此路径 —— 多半被移动/改名了,以磁盘为准,别据此判失效或建议删除〕' : '';
55
+ return `- ${g}${id} ${at}${entry.by ? ` ·${who(entry.by)}摆的` : ''}${flags}${stale}`;
56
+ // eslint-disable-next-line no-unused-vars -- mine 留给后续按视角过滤用
43
57
  }
44
58
 
45
- export function makeReadBoardTool({ projectId }) {
59
+ export function makeReadBoardTool({ projectId, sharedRoot = null }) {
46
60
  return tool(
47
61
  'read_board',
48
62
  `Read the workbench canvas: an ASCII minimap, then GROUPS (things linked by lines or
49
63
  sharing a #tag), then loose items row by row, then relation lines.
50
64
 
51
- Use this BEFORE arranging (arrange_on_board), writing notes (create_on_board) or
52
- sketching (sketch_on_board) — placement without looking is guessing. Coordinates are
53
- world pixels. Only seated items appear (fresh artifacts get a seat within a second).
65
+ Use this BEFORE moving things (edit_board) or writing/sketching (write_on_board)
66
+ placement without looking is guessing. Coordinates are world pixels. Only seated items
67
+ appear (files you just wrote are seated automatically within a couple of seconds).
54
68
  Items marked 〔草稿〕 are still staging (yours from this turn, half-transparent until
55
- finish_sketch / end of turn). The user's current viewport (if known) is drawn as a box
69
+ edit_board commit / end of turn). The user's current viewport (if known) is drawn as a box
56
70
  on the minimap and listed with what is inside it.`,
57
71
  {
58
72
  layer: z.string().max(300).optional()
@@ -61,7 +75,11 @@ on the minimap and listed with what is inside it.`,
61
75
  .describe('Only list items/lines carrying this #tag (one group, e.g. a sketch you made)'),
62
76
  minimap: z.boolean().optional().describe('Also print an ASCII minimap (off by default — the relative-position summary is usually enough)'),
63
77
  },
64
- async ({ layer, tag, minimap }) => {
78
+ async ({ layer, tag: rawTag, minimap }, extra) => {
79
+ const tag = rawTag ? bareTag(rawTag) : rawTag; // #状态板 也认(查询侧统一剥 #)
80
+ // 视角:谁在读这块板。常驻角色读到自己写的板书才该显示「你写的」,
81
+ // 读到别人的显示那个人的名字(展示名只是渲染,判断一律用 slug)。
82
+ const view = { by: byOf(extra), names: await listRoleNames(sharedRoot) };
65
83
  if (!projectId) {
66
84
  return { content: [{ type: 'text', text: 'No project bound.' }], isError: true };
67
85
  }
@@ -82,10 +100,23 @@ on the minimap and listed with what is inside it.`,
82
100
  const excerpts = await chalkExcerpts(getSharedDir(projectId), (byLayer.get(want) || []).map(it => it.id));
83
101
  const items = (byLayer.get(want) || [])
84
102
  .filter(({ entry }) => !tag || entry.tag === tag)
103
+ // 收卷(2026-08-27 收纳器):收着的组不逐件列 —— 版图里压成一行,这是 agent
104
+ // 上下文的收纳(跟画布收纳同一刀)。显式 tag= 点名看某组时照常展开列。
105
+ .filter(({ entry }) => tag || !entry.tag || !board.rolls?.[entry.tag])
85
106
  // 板书条目但文件已经没了 = 幽灵座位,别列给 agent(删文件那条路会清座位,这是兜底)
86
107
  .filter(({ id }) => !id.startsWith(`${CHALK_DIR}/`) || excerpts.has(id))
87
108
  .sort((a, b) => (a.entry.y - b.entry.y) || (a.entry.x - b.entry.x));
88
109
  const entryOf = new Map(items.map(it => [it.id, it.entry]));
110
+ // 座位 vs 磁盘对账(iss_mt38ucyq):文件挪走后旧座位可能还挂几十秒
111
+ // (改名对账/前端回写有时差)。查一遍真身,过期的在条目上点名。
112
+ const root = getSharedDir(projectId);
113
+ const staleIds = new Set();
114
+ await Promise.all(items.map(async ({ id, entry }) => {
115
+ if (entry.kind === 'text' || entry.kind === 'scribble' || id === 'browse') return;
116
+ const bare = String(id).replace(/^(deck|site|docx):/, '');
117
+ if (!bare || bare.includes('..') || /^(text|scribble|b):/.test(bare)) return;
118
+ try { await fs.access(path.resolve(root, bare)); } catch { staleIds.add(id); }
119
+ }));
89
120
 
90
121
  // 小地图(用户视口画框)
91
122
  const vp = getViewpoint(projectId);
@@ -129,7 +160,7 @@ on the minimap and listed with what is inside it.`,
129
160
  lines.push('', `组 ${i + 1}${tags ? ` ${tags}` : ''}(${g.members.length} 件 ${g.edges.length} 线${staging ? ',草稿' : ''}):`);
130
161
  const sorted = g.members.map(id => ({ id, entry: entryOf.get(id) }))
131
162
  .sort((a, b) => (a.entry.y - b.entry.y) || (a.entry.x - b.entry.x));
132
- for (const { id, entry } of sorted) lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts));
163
+ for (const { id, entry } of sorted) lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts, staleIds, view));
133
164
  for (const bid of g.edges.slice(0, 12)) lines.push(` ${bindingLine(board.bindings[bid], board)} (line id: ${bid})`);
134
165
  if (g.edges.length > 12) lines.push(` …还有 ${g.edges.length - 12} 条线`);
135
166
  });
@@ -147,7 +178,7 @@ on the minimap and listed with what is inside it.`,
147
178
  rowY = entry.y;
148
179
  lines.push(`— 行 y≈${Math.round(rowY)} —`);
149
180
  }
150
- lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts));
181
+ lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts, staleIds, view));
151
182
  }
152
183
  }
153
184
  }
@@ -160,7 +191,82 @@ on the minimap and listed with what is inside it.`,
160
191
  }).join('、')}`);
161
192
  }
162
193
  }
163
- if (board.hero && !tag) lines.push('', `★ 显式主角:${board.hero}(arrange_on_board 的 feature/unfeature 管它)`);
194
+ if (board.hero && !tag) lines.push('', `★ 显式主角:${board.hero}(edit_board 的 feature/unfeature 管它)`);
195
+
196
+ // 纸的清单(2026-08-29 纸范式):agent 的空间账本 —— 每张纸的名字/位置/件数/
197
+ // 剩余空地。at 坐标以当前纸版心左上为原点,这一节就是坐标系的地图。
198
+ if (!want && !tag) {
199
+ try {
200
+ const ss = sheetSummaries(board);
201
+ if (ss.length) {
202
+ const latest = latestSheetId(board);
203
+ lines.push('', '纸(sheet):at:{x,y} 写的是纸内像素(版心左上为原点)。'
204
+ + '**写满不会自动翻页** —— 排不下时写入被拒收,下一页由你自己 open_sheet 规划:');
205
+ for (const s of ss) {
206
+ // 剩多少地方按**字**报(08-29 刀 D):agent 手里的东西是字,
207
+ // 只给像素等于让它每次落笔前做一道做不准的算术
208
+ const cap = capacityOf(DEFAULT_CHALK_W, s.freeH);
209
+ lines.push(` ${s.id}${s.id === latest ? '(当前)' : ''}${s.title ? `(${s.title})` : ''}:世界 (${s.x},${s.y}) ${s.w}x${s.h},${s.count} 件,剩 ~${s.freeH}px 高(≈${cap.lines} 行 / ${cap.cjk} 字)${s.lastId ? `,最新 ${s.lastId}` : ''}`);
210
+ // 规划过的块各剩多少(08-29 刀 E):填到一半要知道还有哪块地是空的,
211
+ // 不然只能靠猜 —— 猜错的下场是写入被拒收,白跑一趟
212
+ // 这张纸还剩哪些空地(08-29 刀 F):只报"最后一件下面剩多少"是不够的 ——
213
+ // 那一列到底了不等于这张纸满了,右边可能整片空着,而 agent 要靠这个
214
+ // 判断"还放不放得下、要不要开新一页"
215
+ const free = freeColumnsInSheet(board, s.id);
216
+ if (free.length > 1) {
217
+ const cells = free.map((f) => `x=${f.x} 剩 ${capacityOf(DEFAULT_CHALK_W, f.freeH).lines} 行`);
218
+ lines.push(` 空地:${cells.join(' / ')}`);
219
+ }
220
+ const sheet = board.sheets?.[s.id];
221
+ for (const [nm, sl] of Object.entries(sheet?.slots || {})) {
222
+ const r = slotRectOf({ ...sheet, id: s.id }, nm);
223
+ const spot = nextSpotInSlot(board, r, { w: 1, h: 1 });
224
+ const freeH = spot.full ? 0 : Math.max(0, r.y + r.h - spot.y);
225
+ const c = capacityOf(r.w, freeH);
226
+ lines.push(` · ${nm}${sl.about ? `(${sl.about})` : ''}${sl.for === 'artifacts' ? '【收产物】' : ''}:块内 (${sl.x},${sl.y}) ${sl.w}x${sl.h},剩 ≈${c.lines} 行 / ${c.cjk} 字`);
227
+ }
228
+ }
229
+ }
230
+ // 暂存架(2026-08-30):机器到货的默认座 —— 上了墙但还没进版面,等 agent 安置
231
+ const shelf = Object.entries(board.objects || {})
232
+ .filter(([, e]) => e?.seat === 'shelf' && !e.zone && Number.isFinite(e?.x))
233
+ .map(([id]) => id);
234
+ const pend = Array.isArray(board.pending) ? board.pending : [];
235
+ const unplaced = [...shelf, ...pend];
236
+ if (unplaced.length) {
237
+ lines.push('', `📦 暂存架上 ${unplaced.length} 件等安置(机器只码在架上,版面归你):`);
238
+ for (const r of unplaced.slice(0, 8)) lines.push(` ${r}`);
239
+ if (unplaced.length > 8) lines.push(` …还有 ${unplaced.length - 8} 件`);
240
+ lines.push(' 安置的手:open_sheet{plan:[{slot:"图",at:{…},w,h,for:"artifacts"}…]} 规划产物地,'
241
+ + '或逐件 pin_to_board{path,slot} / edit_board{ops:[{op:"move",…}]}。');
242
+ }
243
+ } catch { /* 纸读不出不挡座次 */ }
244
+ }
245
+
246
+ // 版图(2026-08-27 空间规划):线 = 同 tag 的纵列。这是 agent 的符号地图 ——
247
+ // 摆放按关系(续哪条线/岔自哪条)声明,几何机器排,别按坐标猜。
248
+ if (!tag) {
249
+ const laneList = laneSummaries(board);
250
+ if (laneList.length) {
251
+ lines.push('', '线的清单(一条线 = 同一个 tag 的一纵列;{tag,chain:true} 接着写,open_lane 开新的一条):');
252
+ for (const l of laneList) {
253
+ // 收着的线一行带过:细节不进上下文(要看就 read_board tag= 点名,或 unroll)
254
+ const roll = board.rolls?.[l.tag];
255
+ if (roll) {
256
+ const rc = rollCardRect(board, l.tag);
257
+ lines.push(` #${l.tag}:已收卷${roll.label ? `(「${roll.label}」)` : ''},${l.count} 件收在卷里`
258
+ + `${rc ? `,卷卡占位约 @(${rc.x},${rc.y}) ${rc.w}x${rc.h}` : ''}`
259
+ + ` —— 座位和文件都在(Read 照常),edit_board unroll 展开;别往收着的线里接新话`);
260
+ continue;
261
+ }
262
+ const dirTxt = '';
263
+ lines.push(l.registered
264
+ ? ` #${l.tag}:${l.count} 节${l.parent ? `,岔自 ${l.parent}` : ''},列头 (${l.x},${l.y})`
265
+ + `${l.frontier ? `,接着写会落 (${l.frontier.x},${l.frontier.y}) 附近` : ''}${l.lastId ? `,最新 ${l.lastId}` : ''}${dirTxt}`
266
+ : ` #${l.tag}:${l.count} 件(未登记的野线 —— chain:true 照样能续)${dirTxt}`);
267
+ }
268
+ }
269
+ }
164
270
 
165
271
  // 用户视点(有上报才有)
166
272
  if (vp && !tag) {
@@ -183,7 +289,8 @@ on the minimap and listed with what is inside it.`,
183
289
  } catch { /* 关系读不到不挡座次 */ }
184
290
  }
185
291
 
186
- lines.push('', '(口径:稀疏表只列摆过的;层归属为服务端近似;尺寸为形态估算)');
292
+ lines.push('', '(口径:稀疏表只列摆过的;层归属为服务端近似;尺寸=存档真值优先、缺了按形态估;'
293
+ + '角色精灵贴着该角色最新一条板书(那条四周留了 60px 身位);带⚠️的条目=座位与磁盘对不上账)');
187
294
  return { content: [{ type: 'text', text: lines.join('\n') }] };
188
295
  },
189
296
  );
@@ -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)}`);
@@ -31,18 +31,24 @@ export function makeReportIssueTool({ projectId, sessionId, ctx }) {
31
31
  return tool(
32
32
  'report_issue',
33
33
  `Write to the maintainer of this HARNESS — the tools and environment you work
34
- in. This goes to their issue list, not to the user. Three kinds:
34
+ in. This goes to their issue list, not to the user.
35
+
36
+ This harness is in BETA and you are one of its primary testers: the maintainer
37
+ actively wants these reports and reads every one. When you hesitate between
38
+ reporting and not reporting — report. Three kinds:
35
39
 
36
40
  - "bug": something in the harness behaved WRONGLY — a tool crashed on valid
37
41
  input, returned corrupt output, or contradicted its own description.
38
42
  - "friction": it worked, but the path was painful — a missing parameter, a
39
- return shape you had to fight, a workaround you have now done more than once
40
- (the workaround IS the report).
43
+ return shape you had to fight, a workaround (one detour is enough to report;
44
+ the workaround IS the report).
41
45
  - "idea": nothing is broken, but while working you saw something the harness
42
46
  could do better or do at all — a tool that should exist, a flag that would
43
- collapse three steps into one, a default that fights real usage. The
44
- maintainer reads every one of these and good ones get built if a concrete
45
- improvement occurred to you, report it rather than let it evaporate.
47
+ collapse three steps into one, a default that fights real usage, or a
48
+ METHODOLOGY you worked out on the job (a technique, a judgment rule, a
49
+ sequence that reliably works) that the harness should teach every session,
50
+ bake into a skill, or automate. Good ones get built — report it rather than
51
+ let it evaporate.
46
52
 
47
53
  This is NOT a substitute for doing the work. Finish the task the way you would
48
54
  have anyway — route around the problem, deliver the result — and log this on
@@ -50,7 +56,8 @@ the side. Never report and then hand back less than you could have.
50
56
 
51
57
  Do NOT report:
52
58
  - Your own mistakes (wrong path, malformed edit) — not harness issues
53
- - One-off flakes that a retry fixed
59
+ - A one-off flake that a single retry fixed (the automatic layer counts those);
60
+ a flake that RECURS is friction — report it
54
61
  - Anything about the design work itself (that belongs in the conversation)
55
62
  - Praise or vague sentiment ("the tools are great", "X could be better
56
63
  somehow") — a report with no actionable content is noise
@@ -94,6 +101,7 @@ every time; a viewportOnly flag would remove the whole detour" is actionable.`,
94
101
  };
95
102
  }
96
103
  try {
104
+ // ℹ️ 遥测事件:前端刻意不消费 —— 摩擦上报的读者是站主(信箱/审计),不是当场的用户
97
105
  ctx?.emit?.({ type: 'run.friction_reported', kind, summary, toolName: toolName || null, count: rec.count });
98
106
  } catch { /* emit fail-safe */ }
99
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'),