@xiaobuyu/nodesign 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/README.md +266 -79
  2. package/README.zh-CN.md +254 -67
  3. package/package.json +2 -2
  4. package/server/api/admin.js +7 -0
  5. package/server/api/assets/docx-page.js +5 -4
  6. package/server/api/assets/helpers.js +8 -0
  7. package/server/api/assets.js +21 -33
  8. package/server/api/board.js +29 -1
  9. package/server/api/browse.js +3 -2
  10. package/server/api/chatai.js +9 -8
  11. package/server/api/exports/cards.js +7 -6
  12. package/server/api/exports/docx-pdf.js +4 -1
  13. package/server/api/local.js +10 -8
  14. package/server/api/projects.js +19 -3
  15. package/server/api/publish.js +3 -2
  16. package/server/api/sessions-rewind.js +220 -0
  17. package/server/api/sessions.js +58 -194
  18. package/server/api/stage.js +35 -0
  19. package/server/api/turn-model-switch.js +109 -0
  20. package/server/api/turn.js +52 -88
  21. package/server/auth/middleware.js +29 -6
  22. package/server/auth/tier.js +27 -7
  23. package/server/auth/users-store.js +14 -1
  24. package/server/engine/agent/actor-trail.js +110 -0
  25. package/server/engine/agent/agent-shared.js +22 -52
  26. package/server/engine/agent/beat-state.js +61 -0
  27. package/server/engine/agent/cast.js +322 -0
  28. package/server/engine/agent/events.js +9 -2
  29. package/server/engine/agent/hooks/beat-gate.js +73 -0
  30. package/server/engine/agent/hooks/lifecycle.js +6 -2
  31. package/server/engine/agent/hooks/post-trim.js +1 -0
  32. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  33. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  34. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  35. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  36. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  37. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  38. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  39. package/server/engine/agent/hooks/slot-alias.js +30 -0
  40. package/server/engine/agent/hooks/user-prompt-submit.js +197 -7
  41. package/server/engine/agent/hooks.js +76 -6
  42. package/server/engine/agent/isolation.js +25 -2
  43. package/server/engine/agent/memory-config.js +27 -1
  44. package/server/engine/agent/model-context.js +102 -18
  45. package/server/engine/agent/model-table.js +361 -58
  46. package/server/engine/agent/prompts/nodesign-prelude.md +566 -59
  47. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  48. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +2 -2
  49. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  50. package/server/engine/agent/prompts/tools/site-reference.md +2 -1
  51. package/server/engine/agent/role-card.js +175 -0
  52. package/server/engine/agent/rp-mode.js +17 -0
  53. package/server/engine/agent/session-loop.js +26 -14
  54. package/server/engine/agent/stage-status.js +73 -0
  55. package/server/engine/agent/subagent-flight.js +73 -0
  56. package/server/engine/agent/system-prompts.js +41 -0
  57. package/server/engine/agent/tool-input-stream.js +165 -0
  58. package/server/engine/mcp/actor.js +59 -0
  59. package/server/engine/mcp/capability-gate.js +21 -2
  60. package/server/engine/mcp/index.js +116 -24
  61. package/server/engine/mcp/mode-profile.js +139 -0
  62. package/server/engine/mcp/server-name.js +19 -0
  63. package/server/engine/mcp/tools/artifact-session.js +2 -1
  64. package/server/engine/mcp/tools/browse-find-batch.js +76 -14
  65. package/server/engine/mcp/tools/browse.js +1 -1
  66. package/server/engine/mcp/tools/cast-role.js +167 -0
  67. package/server/engine/mcp/tools/draw-trend.js +104 -0
  68. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  69. package/server/engine/mcp/tools/edit-board.js +569 -0
  70. package/server/engine/mcp/tools/look-at-board.js +4 -3
  71. package/server/engine/mcp/tools/open-sheet.js +242 -0
  72. package/server/engine/mcp/tools/organize-board.js +19 -5
  73. package/server/engine/mcp/tools/paint-still.js +1 -1
  74. package/server/engine/mcp/tools/pin-to-board.js +78 -3
  75. package/server/engine/mcp/tools/read-board.js +109 -16
  76. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  77. package/server/engine/mcp/tools/report-issue.js +1 -0
  78. package/server/engine/mcp/tools/role-memory.js +83 -0
  79. package/server/engine/mcp/tools/roll-dice.js +82 -0
  80. package/server/engine/mcp/tools/roll-film.js +1 -1
  81. package/server/engine/mcp/tools/screenshot.js +2 -2
  82. package/server/engine/mcp/tools/set-vars.js +150 -0
  83. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  84. package/server/engine/mcp/tools/web-search.js +1 -1
  85. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  86. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  87. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  88. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  89. package/server/engine/mcp/tools/write-on-board.js +567 -107
  90. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  91. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +11 -3
  92. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +34 -4
  93. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +20 -4
  94. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  95. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  96. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  97. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  98. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  99. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  100. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  101. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  102. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  103. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  104. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  105. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  106. package/server/engine/runs/board-seater.js +272 -0
  107. package/server/engine/runs/board-tasklist.js +90 -48
  108. package/server/engine/runs/turn-relay.js +24 -2
  109. package/server/engine/runs/user-chalk-echo.js +44 -0
  110. package/server/index.js +2 -0
  111. package/server/lib/board-anchor.js +49 -0
  112. package/server/lib/board-dirty.js +93 -0
  113. package/server/lib/board-excerpt.js +80 -0
  114. package/server/lib/board-follow.js +111 -0
  115. package/server/lib/board-groups.js +1 -5
  116. package/server/lib/board-hero.js +3 -1
  117. package/server/lib/board-kind-sizes.js +36 -0
  118. package/server/lib/board-lanes.js +58 -0
  119. package/server/lib/board-obstacles.js +80 -0
  120. package/server/lib/board-relations.js +5 -1
  121. package/server/lib/board-sheets.js +467 -0
  122. package/server/lib/board-shelf.js +86 -0
  123. package/server/lib/board-transform.js +62 -0
  124. package/server/lib/canvas-id.js +18 -1
  125. package/server/lib/chalk-flow.js +88 -0
  126. package/server/lib/chalk-rewrite.js +63 -0
  127. package/server/lib/chalk-size-pref.js +48 -0
  128. package/server/lib/chalk.js +42 -3
  129. package/server/lib/ingress/forward-openai-chat.js +16 -4
  130. package/server/lib/ingress/openai-chat.js +23 -12
  131. package/server/lib/ingress/session-routes.js +3 -3
  132. package/server/lib/ingress/stream-watchdog.js +53 -0
  133. package/server/lib/ingress/upstream-billing.js +15 -0
  134. package/server/lib/ingress/upstream-error-hints.js +36 -0
  135. package/server/lib/json-preview.js +58 -0
  136. package/server/lib/model-ingress.js +26 -1
  137. package/server/lib/moderation.js +10 -5
  138. package/server/lib/quota.js +26 -6
  139. package/server/lib/rect.js +50 -0
  140. package/server/lib/screen.js +36 -0
  141. package/server/lib/sheet-state.js +19 -0
  142. package/server/lib/sketch-array.js +118 -0
  143. package/server/lib/sketch-hatch.js +99 -0
  144. package/server/lib/sketch-layout.js +269 -69
  145. package/server/lib/sketch-shapes.js +152 -0
  146. package/server/lib/sketch-stencils.js +170 -0
  147. package/server/lib/state-table.js +237 -0
  148. package/server/lib/state-trend.js +94 -0
  149. package/server/lib/state-triggers.js +199 -0
  150. package/server/lib/tavern-json.js +48 -0
  151. package/server/lib/user-uploads.js +88 -0
  152. package/server/projects/board-limits.js +5 -3
  153. package/server/projects/board-sanitize.js +220 -10
  154. package/server/projects/board-store.js +95 -6
  155. package/server/projects/session-jsonl.js +110 -0
  156. package/server/projects/store.js +56 -3
  157. package/server/projects/viewpoint-store.js +35 -3
  158. package/server/projects/workspace-slots.js +30 -0
  159. package/server/projects/workspace-templates.js +29 -0
  160. package/server/projects/workspace.js +16 -17
  161. package/server/runtime/capabilities.js +6 -2
  162. package/server/shared/locales.js +70 -0
  163. package/server/shared/messages-en.js +69 -0
  164. package/server/shared/messages.js +65 -0
  165. package/server/ws/broker.js +3 -0
  166. package/server/ws/index.js +43 -3
  167. package/web/README.md +1 -1
  168. package/web/dist/assets/{BrowserWindow-BoOBaIyz.js → BrowserWindow-CcaGRWz5.js} +1 -1
  169. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  170. package/web/dist/assets/{DocxWindow-B_u6aX9o.js → DocxWindow-B9KZmU5z.js} +2 -2
  171. package/web/dist/assets/{MermaidBlock-BO-46Du5.js → MermaidBlock-DrfRnz8E.js} +43 -43
  172. package/web/dist/assets/{SiteWindow-ConPmzdM.js → SiteWindow-CJOjCF7d.js} +6 -6
  173. package/web/dist/assets/{abnfDiagram-VCTEODGH-FCO9yf-n.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  174. package/web/dist/assets/{arc-BLcEOSRX.js → arc-C1-HSUny.js} +1 -1
  175. package/web/dist/assets/{architectureDiagram-5GKGNRK7-kxiX4TeN.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  176. package/web/dist/assets/{blockDiagram-NRAW4CY4-CptFksix.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  177. package/web/dist/assets/{c4Diagram-UCG6FXSJ-Dxmxj34F.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  178. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  179. package/web/dist/assets/{chunk-2Q5K7J3B-D9inx5oo.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  180. package/web/dist/assets/{chunk-5VM5RSS4-DydfHlEc.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  181. package/web/dist/assets/{chunk-F27PBJKO-BO7GxhSv.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  182. package/web/dist/assets/{chunk-G27WJ6UU-BzP6YiQ_.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  183. package/web/dist/assets/{chunk-JWPE2WC7-CHcgcd0s.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  184. package/web/dist/assets/{chunk-LCL6LL3I-D2Wg5JtX.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  185. package/web/dist/assets/{chunk-POPQ4Y6H-B2fCRfpM.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  186. package/web/dist/assets/{chunk-SVP7TREG-JSLcsRlZ.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  187. package/web/dist/assets/{chunk-XXDRQBXY-BCjvaeHm.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  188. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  189. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  190. package/web/dist/assets/{cose-bilkent-JH36ORCC-0uKv6oJ7.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  191. package/web/dist/assets/{cynefin-OW5HDTMX-Bvt-gEmA.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  192. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-C9Ht02qC.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  193. package/web/dist/assets/{dagre-3AP2YEHR-BfxZny4s.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  194. package/web/dist/assets/{diagram-S7CK7UJ4-CrE0ZETE.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  195. package/web/dist/assets/{diagram-UQ7AKVKN-BO8VQ4Fx.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  196. package/web/dist/assets/{diagram-VSXAHHWV-CHUZnR9F.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  197. package/web/dist/assets/{diagram-VX7I27RA-DYQAz1Vu.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  198. package/web/dist/assets/{diagram-Z3DM3KII-BDjivgLr.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  199. package/web/dist/assets/{ebnfDiagram-PWID7BFC-Bpe_QVbH.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  200. package/web/dist/assets/{erDiagram-SSCWMZ5O-BxhwuAu1.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  201. package/web/dist/assets/{flowDiagram-A5DVABFB-CCw2H08-.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  202. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-DtTc7MyI.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  203. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-BvORxMd2.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  204. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  205. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  206. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  207. package/web/dist/assets/{infoDiagram-RXCK75RN-DMcrkypD.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  208. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CZkTIbe-.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  209. package/web/dist/assets/{journeyDiagram-EYS64GPL-DR0XazdE.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  210. package/web/dist/assets/{kanban-definition-3QL26DDD-DN8VbwVu.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  211. package/web/dist/assets/{layout-9_paCbyt.js → layout-Dhgck-Zo.js} +1 -1
  212. package/web/dist/assets/{linear-g6Ct012e.js → linear-DMSQ-aUw.js} +1 -1
  213. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  214. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  215. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  216. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  217. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  218. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  219. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  220. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  221. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  222. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  223. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  224. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  225. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  226. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  227. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  228. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  229. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  230. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  231. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  232. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  233. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  234. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  235. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  236. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  237. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  238. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  239. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  240. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  241. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  242. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  243. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  314. package/web/dist/assets/{mindmap-definition-FBJOCRG2-BGt4omYC.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  315. package/web/dist/assets/{pegDiagram-XKGWAZYB-uSfS-PBE.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  316. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  317. package/web/dist/assets/{pieDiagram-E7YTZNPT-DpeI935H.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  318. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-B7IuNvWa.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  319. package/web/dist/assets/{railroadDiagram-O6MQD6OU-ARzYpPQI.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  320. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-Cj4upkAo.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  321. package/web/dist/assets/{rotate-cw-CXJcyHWk.js → rotate-cw-62liz7LX.js} +1 -1
  322. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-OaVwSnLP.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  323. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-1yrUbq4t.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  324. package/web/dist/assets/{sizeCapture-X5ZJPWSS-nYH5p9D6.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  325. package/web/dist/assets/{square-dashed-mouse-pointer-CTtTVKoH.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  326. package/web/dist/assets/{stateDiagram-HBIQ2CUA-Dj5HvyNq.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  327. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  328. package/web/dist/assets/{swimlanes-XN3QIQJK-umeT0HpB.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  329. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  330. package/web/dist/assets/{timeline-definition-24CTP7MA-MqnQfHKB.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  331. package/web/dist/assets/{vennDiagram-4TSXK5OY-b2liyZra.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  332. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-QMK-zNh1.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  333. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-D5Mc6tfA.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  334. package/web/dist/index.html +2 -2
  335. package/server/engine/mcp/tools/arrange-on-board.js +0 -126
  336. package/server/engine/mcp/tools/create-on-board.js +0 -152
  337. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  338. package/server/engine/mcp/tools/relate-on-board.js +0 -150
  339. package/server/engine/mcp/tools/sketch-on-board.js +0 -345
  340. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  341. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +0 -78
  342. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  343. package/web/dist/assets/DeckWindow-Bg3rgkvi.js +0 -11
  344. package/web/dist/assets/channel-DFyiuGL0.js +0 -1
  345. package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +0 -1
  346. package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +0 -1
  347. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  348. package/web/dist/assets/index-DGG5wIZs.js +0 -2118
  349. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  350. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  351. package/web/dist/assets/pending-edit-apply-uP4uvrOD.js +0 -31
  352. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +0 -1
  353. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +0 -8
@@ -0,0 +1,165 @@
1
+ /**
2
+ * server/engine/agent/tool-input-stream.js —— 真流式工具入参(2026-07-28 建,
3
+ * 2026-08-29 从 agent-shared.js 拆出:行数棘轮,胖了就拆别抬上限)
4
+ *
5
+ * input_json_delta 是半截 JSON 碎片。等拼完再发的话,模型逐 token 生成 new_string
6
+ * 的几十秒里前端只能干转圈。这里对点名的工具累积缓冲区,节流用 partial-json 容错
7
+ * 解析累积串,把目标字段相对上次的**纯文本增量**推给前端(run.delta.tool_input)。
8
+ *
9
+ * 转义符跨块断开由解析器兜住;字段单调增长,万一局部解析短暂回缩就跳过本拍
10
+ * (append 只在变长时发)。
11
+ */
12
+
13
+ import { Events } from './events.js';
14
+ import { toWorkspaceRel } from '../../lib/workspace-path.js';
15
+ import { parse as parsePartialJson, Allow as PartialAllow } from 'partial-json';
16
+
17
+ // ── 真流式工具入参(2026-07-28,工作台舞台层代码直播)──
18
+ // input_json_delta 是半截 JSON 碎片,等拼完再发一次的话,模型逐 token 生成
19
+ // new_string 的几十秒里前端只能干转圈。这里对写代码的工具累积缓冲区,节流用
20
+ // partial-json 容错解析累积串,把目标字段相对上次的纯文本增量推给前端
21
+ // (run.delta.tool_input)。转义符跨块断开由解析器兜住;字段单调增长,
22
+ // 万一局部解析短暂回缩就跳过本拍(append 只在变长时发)。
23
+ /**
24
+ * `spot`(2026-08-29 占位契约刀 C):跟 text 一起抽出来的**位置字段**。
25
+ *
26
+ * 在这之前流式板书的字画在"视口里一块空地"(BoardCanvas 的 liveChalkSpotFor),
27
+ * 跟 agent 给的 at 毫无关系 —— 写完淡出、真卡在真位置接棒,用户看到的是字从假
28
+ * 位置跳到真位置。站主要的是「先把位置选好,内容流到选定位置」。
29
+ *
30
+ * 时机不用另外等:判据跟 file_path 那套一样 —— **目标字段(text)的 key 一出现,
31
+ * 就说明排在它前面的字段都已闭合**(所以 write_on_board 的 schema 把 at/sheet/
32
+ * width 排到了 text 前面)。也就是说框在第一个字到达的那一刻立起来,正好。
33
+ *
34
+ * ⚠️ 不能只看 `at` 在不在:容错解析会把**没写完的数字**也交出来(`{"x": 123`
35
+ * 流到一半是 `{x:12}`,完全合法且看不出来),用它当坐标就是错的位置。
36
+ */
37
+ export const TOOL_INPUT_STREAM_FIELDS = {
38
+ Edit: 'new_string',
39
+ Write: 'content',
40
+ // 板书直播(2026-08-25 流式路 A):write_on_board 的 text 逐 token 流到画布上
41
+ // 的舞台粉笔卡(StageLayer chalk 档)—— 粉笔字在用户眼前一行行长出来。
42
+ mcp__nodesign__write_on_board: { field: 'text', spot: ['slot', 'at', 'sheet', 'width', 'h', 'near', 'side', 'reply_to', 'chain'] },
43
+ // board_batch 批内嵌套(08-25 用户报「流式名存实亡」:skill 教的是一章一次
44
+ // batch,正文藏在 actions[].input.text 里,顶层字段抽取器抓不到 —— 等于亲手
45
+ // 教了大家绕开流式)。batch 档抽**最新一条** write_on_board 动作的 text,
46
+ // 换动作时发 reset 让前端另起一张。
47
+ mcp__nodesign__board_batch: { batch: 'write_on_board', field: 'text', spot: ['slot', 'at', 'sheet', 'width', 'h', 'near', 'side', 'reply_to', 'chain'] },
48
+ };
49
+ const TOOL_INPUT_THROTTLE_MS = 120;
50
+
51
+ export function toolInputStreams(ctx) {
52
+ if (!ctx._toolInputStreams) ctx._toolInputStreams = new Map();
53
+ return ctx._toolInputStreams;
54
+ }
55
+
56
+
57
+ /** 批内嵌套抽取(纯函数好钉测试):最新一条 <tool> 动作的 <field> 字符串与它的序号 */
58
+ export function latestBatchField(obj, toolName, field) {
59
+ const actions = Array.isArray(obj?.actions) ? obj.actions : [];
60
+ for (let i = actions.length - 1; i >= 0; i -= 1) {
61
+ const a = actions[i];
62
+ const name = String(a?.name || '');
63
+ if ((name === toolName || name.endsWith(`__${toolName}`)) && typeof a?.input?.[field] === 'string') {
64
+ return { idx: i, text: a.input[field], input: a.input };
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+
70
+ /**
71
+ * 位置字段抽取(占位契约刀 C)。只在**目标字段已经出现**时调用 —— 那时排在它
72
+ * 前面的位置字段都已闭合,数字不会是流到一半的半截值。
73
+ *
74
+ * ⚠️ 一个位置字段都没有时返回的是**空对象不是 null**(2026-08-29 刀 F):
75
+ * "什么都没点名" 本身就是一条位置信息 —— 那是**纸内顺排**,前端照服务端同一条
76
+ * 规则算得出落点。返回 null 的话这条事件根本不发,前端只能把字先写在视口一块
77
+ * 空地上、等落盘再跳过去(站主看到的"流式完毕之后再移动")。而顺排恰恰是最
78
+ * 常见的调用形态。
79
+ *
80
+ * @returns {object|null} null 只在入参根本不是对象时出现
81
+ */
82
+ export function pickSpot(input, fields) {
83
+ if (!input || typeof input !== 'object') return null;
84
+ const out = {};
85
+ for (const k of fields) {
86
+ const v = input[k];
87
+ if (v === undefined || v === null) continue;
88
+ // at 是 {x,y}:两个坐标都得是有限数才算数(缺一个就是没写完)
89
+ if (k === 'at') {
90
+ if (typeof v === 'object' && Number.isFinite(v.x) && Number.isFinite(v.y)) {
91
+ out.at = { x: v.x, y: v.y };
92
+ }
93
+ continue;
94
+ }
95
+ if (typeof v === 'string' || Number.isFinite(v) || typeof v === 'boolean') out[k] = v;
96
+ }
97
+ return out;
98
+ }
99
+
100
+ export function pumpToolInputStream(ctx, st, flush) {
101
+ const now = Date.now();
102
+ if (!flush && now - st.lastEmit < TOOL_INPUT_THROTTLE_MS) return;
103
+ let obj;
104
+ try { obj = parsePartialJson(st.buf, PartialAllow.ALL); } catch { return; }
105
+ if (!obj || typeof obj !== 'object') return;
106
+ let text = '';
107
+ let reset = false;
108
+ let host = obj; // 位置字段所在的对象(batch 档是那条动作的 input)
109
+ if (st.batch) {
110
+ const hit = latestBatchField(obj, st.batch, st.field);
111
+ if (hit) {
112
+ if (st.actionIdx !== hit.idx) { st.actionIdx = hit.idx; st.sent = 0; st.spotSent = false; reset = true; }
113
+ text = hit.text;
114
+ host = hit.input;
115
+ }
116
+ } else {
117
+ text = typeof obj[st.field] === 'string' ? obj[st.field] : '';
118
+ }
119
+ // 位置(刀 C):目标字段已出现 = 排在它前面的位置字段都闭合了,这一拍可以定框。
120
+ // 一条动作只发一次(batch 换动作时 spotSent 已随 reset 归位)。
121
+ const spot = (!st.spotSent && st.spot && host?.[st.field] !== undefined)
122
+ ? { ...pickSpot(host, st.spot), ...(st.batch ? { batchIdx: st.actionIdx } : {}) } : null;
123
+ // 新纸预告(刀④ 2026-08-30):batch 的典型形状是 [open_sheet, write{slot}, …],
124
+ // 流式发生时**一个动作都没执行**、新纸还没登记 —— 前端只能退到「最新那张纸」
125
+ // (偏一整屏)或空地(叠一堆)。这里把「这条 write 排在一个 open_sheet 后面」
126
+ // 这件事随 spot 带出去,slot 的规划矩形也从流进来的 plan 里抠出来,
127
+ // 前端就能把框立在新纸将要出现的地方。
128
+ if (spot && st.batch && Array.isArray(obj.actions)) {
129
+ const opened = obj.actions.slice(0, st.actionIdx)
130
+ .filter((a) => typeof a?.name === 'string' && a.name.replace(/^mcp__\w+__/, '') === 'open_sheet')
131
+ .pop();
132
+ if (opened) {
133
+ spot.freshSheet = true;
134
+ const plan = Array.isArray(opened.input?.plan) ? opened.input.plan : [];
135
+ const sl = spot.slot ? plan.find((p) => p?.slot === spot.slot) : null;
136
+ if (sl && Number.isFinite(sl.at?.x) && Number.isFinite(sl.at?.y)
137
+ && Number.isFinite(sl.w) && Number.isFinite(sl.h)) {
138
+ spot.planSlot = { x: sl.at.x, y: sl.at.y, w: sl.w, h: sl.h };
139
+ }
140
+ }
141
+ }
142
+ // file_path 只在确定流完后才取:容错解析会把半截字符串也带出来,第一拍常
143
+ // 截在路径中间(e2e 撞过:抽到项目目录名 → 前端物件寻址指错)。目标字段的
144
+ // key 出现(键序在 file_path 之后)或对象已有第二个键 = 路径已闭合。
145
+ const pathComplete = obj[st.field] !== undefined || Object.keys(obj).length >= 2;
146
+ // 发**工作区相对路径**(2026-08-13):前端拿它当画布物件 id 的路径部分,
147
+ // 而 id = 工作区相对路径。以前原样转发绝对路径,前端靠 `tasks/<任务>/`
148
+ // 这个特征段抠相对部分 —— 那一层拆掉后绝对路径里没有可锚定的标志了。
149
+ const rawFilePath = !st.filePathSent && pathComplete && typeof obj.file_path === 'string' ? obj.file_path : null;
150
+ const filePath = rawFilePath ? toWorkspaceRel(rawFilePath, ctx.workspace?.root?.()) : null;
151
+ const append = text.length > st.sent ? text.slice(st.sent) : '';
152
+ if (!append && !filePath && !spot && !flush && !reset) return;
153
+ st.lastEmit = now;
154
+ if (append) st.sent = text.length;
155
+ if (filePath) st.filePathSent = true;
156
+ if (spot) st.spotSent = true;
157
+ ctx.emit(Events.deltaToolInput(ctx.counters.turns, st.id, st.name, {
158
+ ...(filePath ? { filePath } : {}),
159
+ ...(spot ? { spot } : {}),
160
+ ...(append ? { append } : {}),
161
+ ...(reset ? { reset: true } : {}),
162
+ ...(flush ? { done: true } : {}),
163
+ }));
164
+ }
165
+
@@ -0,0 +1,59 @@
1
+ /**
2
+ * engine/mcp/actor.js —— 板上写入的署名(2026-08-26)
3
+ *
4
+ * 板上每一样东西都带 `by`。在常驻角色之前它只有两个值('agent' / 'user'),
5
+ * 现在多了第三类:**具名角色**(叙事者、NPC),值是它的 slug(`rp-moli`)。
6
+ *
7
+ * 为什么署 slug 而不署展示名:slug 是 harness 在派发那一刻盖的章(见 actor-trail.js),
8
+ * 展示名住在角色文件里、而那个文件模型能改。署名要能当证据用,就不能取自被署名者
9
+ * 自己写的文本。展示名由读侧按 slug 查出来(role-card.js),查不到就显示 slug 本身。
10
+ */
11
+
12
+ import { callerOf, agentNameOf } from '../agent/actor-trail.js';
13
+ import { isResidentRole, isSlotType, safeRoleLabel } from '../agent/cast.js';
14
+
15
+ /** MCP handler 的 extra 里取本次 tool_use id(2026-08-26 实测就在这个键上) */
16
+ export function toolUseIdOf(extra) {
17
+ return extra?._meta?.['claudecode/toolUseId'] || null;
18
+ }
19
+
20
+ /**
21
+ * 这次板上写入该署谁的名。
22
+ * @returns {'agent'|string} 主 agent → 'agent';常驻角色 → 它的 slug
23
+ */
24
+ export function byOf(extra) {
25
+ const caller = callerOf(toolUseIdOf(extra));
26
+ const type = caller?.agentType;
27
+ // 落到**演员位**(rp-actor/rp-narrator)说明盖章那一刻别名还没学到实例名。
28
+ // 这里**当场再解一次** —— 别名可能在盖章之后才学到(08-28 真会话实录:
29
+ // 19:59 派发、20:10 角色调 jot_memory 被拒、20:10 GM 一发 SendMessage 别名补上、
30
+ // 20:21 同一个工具就成了。差的就是"用盖章那一刻的快照"还是"用此刻的表")。
31
+ if (isSlotType(type)) {
32
+ const nm = agentNameOf(caller?.agentId);
33
+ if (isResidentRole(nm) && !isSlotType(nm)) return nm;
34
+ }
35
+ return isResidentRole(type) ? type : 'agent';
36
+ }
37
+
38
+ /**
39
+ * 把一个 `by` 渲染成给**这个读者**看的称呼。
40
+ *
41
+ * read_board 的输出可能被主 agent 读,也可能被角色自己读 —— 同一句「你写的」对两个
42
+ * 读者含义相反。所以渲染要带视角:谁在读,就管谁叫「你」。
43
+ *
44
+ * @param by 板上那条东西的署名('user' / 'agent' / 'rp-*')
45
+ * @param viewer 当前读者(同上取值)
46
+ * @param names slug → 展示名(只当展示层,见 role-card.listRoleNames)
47
+ */
48
+ export function describeBy(by, viewer, names) {
49
+ if (by === viewer) return '你';
50
+ if (by === 'user') return '用户';
51
+ if (by === 'agent') return '主控';
52
+ return safeRoleLabel(by, names?.get?.(by));
53
+ }
54
+
55
+ /**
56
+ * 保留字的第二道防线。真正的闸在源头(`listRoleNames` 出口,见 cast.js
57
+ * `safeRoleLabel` 的注释);这里留一层是给「名字没经过那条路」的调用点兜底。
58
+ */
59
+ export { safeRoleLabel as roleLabel } from '../agent/cast.js';
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * engine/mcp/capability-gate.js — 工具 × 本机能力位(runtime/capabilities.js)的唯一对照表 + 闸。
3
3
  *
4
- * 两种模式:
5
- * - block:能力缺席时工具描述前缀「⛔ CURRENTLY UNAVAILABLE + 装法」(照 roll_film 的先例,agent 看到就不会调),
4
+ * 三种模式:
5
+ * - unregister:能力缺席时**整件工具不注册**(连名字都不进模型上下文)。给那种
6
+ * 「缺了就完全没有替代路径、而且短期内不会回来」的工具用——站主的本地
7
+ * GPU 盒子平时不开机,留着名字只会让 agent 反复去撞 SSH 拒连(08-24
8
+ * 信箱实证:paint_still 连撞两次,一次超时一次参数被拒才放弃)。
9
+ * - block:能力缺席时工具描述前缀「⛔ CURRENTLY UNAVAILABLE + 装法」,
6
10
  * 真调了也在这里拦住,返回同一句话让它转告用户——不让裸的 ENOENT / 500 漏到 agent 面前
7
11
  * - note :工具仍可用,只在描述末尾加一句降级说明(build_docx 没有 LibreOffice 时仍能出文件,只是没页图)
8
12
  * 没探过(capabilityState 为 null:单测 / 脚本直接 import)→ 原样放行,这层不猜。
@@ -26,17 +30,32 @@ export const TOOL_CAPABILITIES = Object.freeze({
26
30
  web_search: { cap: 'webSearch', mode: 'block' },
27
31
  generate_image: { cap: 'imageGen', mode: 'block' },
28
32
  publish_site: { cap: 'publish', mode: 'block' },
33
+ // 站主自己的 5090 盒子(NODESIGN_LOCAL_BOX + NODESIGN_H3BOX_SSH)。盒子是手动
34
+ // 开关的,平时关着,所以用 unregister:不在线时连工具名都不给 agent 看见。
35
+ // 通用生图仍有 generate_image 顶着,不存在能力真空。
36
+ paint_still: { cap: 'localBox', mode: 'unregister' },
37
+ roll_film: { cap: 'localBox', mode: 'unregister' },
29
38
  });
30
39
 
31
40
  export function unavailableMessage(c) {
32
41
  return `本机缺 ${c.label}(${c.detail})。装法:${c.fix}`;
33
42
  }
34
43
 
44
+ /** 这件工具在当前能力状态下该不该注册(false = 连名字都不给) */
45
+ export function shouldRegisterTool(toolDef) {
46
+ const spec = TOOL_CAPABILITIES[toolDef.name];
47
+ if (!spec || spec.mode !== 'unregister') return true;
48
+ const c = capabilityState(spec.cap);
49
+ // 没探过(单测 / 脚本直接 import)一律注册 —— 这层不猜,跟 withCapabilityGate 同纪律
50
+ return !c || c.available;
51
+ }
52
+
35
53
  export function withCapabilityGate(toolDef) {
36
54
  const spec = TOOL_CAPABILITIES[toolDef.name];
37
55
  if (!spec) return toolDef;
38
56
  const c = capabilityState(spec.cap);
39
57
  if (!c || c.available) return toolDef;
58
+ // unregister 档走 shouldRegisterTool,走到这里说明调用方没过滤 —— 按 block 兜底
40
59
  const msg = unavailableMessage(c);
41
60
  if (spec.mode === 'note') {
42
61
  return { ...toolDef, description: `${toolDef.description}\n\n⚠️ ${msg} 本工具仍可用,但依赖它的步骤(页图/体检)会缺席。` };
@@ -29,7 +29,9 @@
29
29
 
30
30
  import { createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
31
31
  import { withParamSanitizer } from './param-sanitizer.js';
32
- import { withCapabilityGate } from './capability-gate.js';
32
+ import { withCapabilityGate, shouldRegisterTool } from './capability-gate.js';
33
+ import { shouldRegisterForMode, assertModeProfileNames } from './mode-profile.js';
34
+ import { MCP_SERVER_NAME } from './server-name.js';
33
35
  import { makeScreenshotCanvasTool } from './tools/screenshot.js';
34
36
  import { makeScreenshotUrlTool } from './tools/screenshot-url.js';
35
37
  import { makeExportHandoffTool } from './tools/export-handoff.js';
@@ -47,7 +49,7 @@ import {
47
49
  } from './tools/browse.js';
48
50
  import { makeBrowserScreenshotTool } from './tools/browse-screenshot.js';
49
51
  import { makeBrowserComputerTool } from './tools/browse-computer.js';
50
- import { makeBrowserFindTool, makeBrowserBatchTool } from './tools/browse-find-batch.js';
52
+ import { makeBrowserFindTool, makeBrowserBatchTool, makeBatchTool } from './tools/browse-find-batch.js';
51
53
  import { makeArtifactOpenTool, makeArtifactComputerTool, makeArtifactFindTool, makeArtifactMotionTool, makeArtifactBatchTool } from './tools/artifact-session.js';
52
54
  import { makeGetComputedStylesTool } from './tools/get-computed-styles.js';
53
55
  import { makeNavigateToPageTool } from './tools/navigate-to-page.js';
@@ -60,13 +62,10 @@ import { makeClearPendingChangesTool } from './tools/clear-pending-changes.js';
60
62
  import { makeGenerateImageTool } from './tools/generate-image.js';
61
63
  import { makeRemoveBackgroundTool } from './tools/remove-background.js';
62
64
  import { makePinToBoardTool } from './tools/pin-to-board.js';
63
- import { makeRelateOnBoardTool } from './tools/relate-on-board.js';
65
+ import { makeEditBoardTool } from './tools/edit-board.js';
64
66
  import { makeReadBoardTool } from './tools/read-board.js';
65
- import { makeArrangeOnBoardTool } from './tools/arrange-on-board.js';
66
- import { makeCreateOnBoardTool } from './tools/create-on-board.js';
67
- import { makeSketchOnBoardTool, makeFinishSketchTool } from './tools/sketch-on-board.js';
68
- import { makeEditSketchTool } from './tools/edit-sketch.js';
69
67
  import { makeWriteOnBoardTool } from './tools/write-on-board.js';
68
+ import { makeOpenSheetTool } from './tools/open-sheet.js';
70
69
  import { makeLookAtBoardTool } from './tools/look-at-board.js';
71
70
  import { makeReadUserViewTool } from './tools/read-user-view.js';
72
71
  import { makeOrganizeBoardTool } from './tools/organize-board.js';
@@ -74,6 +73,12 @@ import { makeReadDocumentTool } from './tools/read-document.js';
74
73
  import { makeReadTavernJsonTool } from './tools/read-tavern-json.js';
75
74
  import { makeDeliverFilesTool } from './tools/deliver-files.js';
76
75
  import { makeCrystallizeSkillTool } from './tools/crystallize-skill.js';
76
+ import { makeCastRoleTool } from './tools/cast-role.js';
77
+ import { makeSetVarsTool } from './tools/set-vars.js';
78
+ import { makeDrawTrendTool } from './tools/draw-trend.js';
79
+ import { makeJotMemoryTool } from './tools/role-memory.js';
80
+ import { makeRollDiceTool } from './tools/roll-dice.js';
81
+ import { assertRoleToolsRegistered } from '../agent/cast.js';
77
82
  import { makePublishSiteTool } from './tools/publish-site.js';
78
83
  import { makeReportIssueTool } from './tools/report-issue.js';
79
84
  import { makeRollFilmTool } from './tools/roll-film.js';
@@ -127,12 +132,53 @@ const ALWAYS_LOAD_TOOLS = new Set([
127
132
  // 08-21 产物会话五件:成品检查的交互半边(状态跨调用留着)。artifact_batch 的卖点
128
133
  // "一趟跑完 + 结尾截图"和 live:true 的存在都只在描述里说,常驻。
129
134
  'artifact_open', 'artifact_computer', 'artifact_find', 'artifact_batch', 'artifact_motion',
135
+ // ⭐ 板面核心五件(2026-08-27 用户拍板「板书是基础交互方式」):合计约 3.4k token
136
+ // 的描述量进缓存前缀,每轮增量趋零;换来的是 write 的件数判据/lane/ink、edit 的
137
+ // 18 个 op、batch 的按拍打包这些**只活在描述里的卖点**每回合都在场 —— 此前它们
138
+ // 全在 deferred 区,agent 只看得到名字,教义在 prelude 里教、schema 却要现搜。
139
+ // 膨胀由模式闸控:rp 模式 unregister 掉 artifact/量具族后常驻集自动收缩。
140
+ // read_user_view 不进:视口已经每回合自动进状态块,它降级成"看画面细节"的按需件。
141
+ // board_batch 2026-08-30 暂停(见下面注册处的注释),常驻表同步摘牌。
142
+ 'write_on_board', 'edit_board', 'read_board', 'look_at_board',
143
+ // 纸范式(2026-08-29):铺纸是板面工作的起手式,卖点全在描述里(当前纸/坐标原点/
144
+ // 排不下就拒收、翻页归 agent),必须常驻。
145
+ 'open_sheet',
130
146
  ]);
131
147
 
132
- export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId, sessionId, ctx } = {}) {
148
+ /**
149
+ * 常驻表的启动期对账(08-27 审计补):跟 mode-profile 同病同药 —— 工具改名后
150
+ * 这张表的旧名字不会报错,只会让那件工具静默退回 deferred(agent 又只看得到名字)。
151
+ * 对照的是**过滤前**的全量名单:能力/模式闸下架某件时它照常在 builtTools 里。
152
+ */
153
+ function assertAlwaysLoadNames(registeredNames) {
154
+ const have = new Set(registeredNames);
155
+ const ghosts = [...ALWAYS_LOAD_TOOLS].filter((n) => !have.has(n));
156
+ if (ghosts.length) {
157
+ throw new Error(`[always-load] 常驻表里有注册表不存在的名字: ${ghosts.join(', ')} —— 改名后表没跟上,这些条目在静默空转`);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * 工具名 → 它真正收的参数名集合。装配时回填(见下),PreToolUse 的未知键探针读它。
163
+ * 全进程共享一份:同一份 schema 跟项目无关。
164
+ */
165
+ export const TOOL_PARAM_KEYS = new Map();
166
+
167
+ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId, sessionId, ctx, roleRoster = null, projectMode = 'design' } = {}) {
133
168
  // 浏览通道里能被 browser_batch 串起来的七件:先建一次,batch 拿**同一批实例**
134
169
  // (projectId/ctx 绑在 handler 里,不能再造第二份)。只有 request_help 不进
135
170
  // batch(它阻塞等人);capture 在里面 —— 逐页采 token 正是 batch 要省的那种回合。
171
+ // batch 的运行时解析表(2026-08-27 重置):装配管线(能力闸/模式闸/消毒)跑完后
172
+ // 回填,三个 batch 的子调用一律取**包装后**的实例 —— 08-26「batch 绕闸」挂账在此清账。
173
+ const wrappedByName = new Map();
174
+ const resolveTool = (n) => wrappedByName.get(n);
175
+ const writeOnBoard = makeWriteOnBoardTool({ projectId, sharedRoot: workspaceRoot || sharedRoot, sessionId, ctx });
176
+ const boardBatchable = [
177
+ makeOpenSheetTool({ projectId, sessionId, ctx }),
178
+ writeOnBoard,
179
+ makeEditBoardTool({ projectId, sharedRoot, sessionId, ctx }),
180
+ makeReadBoardTool({ projectId, sharedRoot }),
181
+ ];
136
182
  const browseBatchable = [
137
183
  makeBrowserNavigateTool({ projectId, ctx }),
138
184
  makeBrowserReadTool({ projectId }),
@@ -159,7 +205,7 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
159
205
  // 自己的产物靠什么在动:跟 browser_capture motion 同一份引擎(engine/motion/inventory.js)
160
206
  makeArtifactMotionTool({ projectId, workspaceRoot, sessionId }),
161
207
  ];
162
- const tools = [
208
+ const builtTools = [
163
209
  // C9 screenshot_canvas — playwright headless 截图 → image content block
164
210
  screenshotCanvas,
165
211
 
@@ -181,6 +227,16 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
181
227
  // C10 export_handoff — 复用 exports.js 的 buildHandoffZip,写到 workspace/exports/
182
228
  makeExportHandoffTool({ workspaceRoot, sharedRoot, projectId, sessionId, ctx }),
183
229
 
230
+ // cast_role — 写一张角色卡并登记名字(2026-08-26 建,08-29 简化)。
231
+ // 卡是数据,身体是预注册的角色位;写完当回合就能派,之后靠 SendMessage 叫醒。
232
+ makeCastRoleTool({ workspaceRoot, sessionId, ctx, roster: roleRoster }),
233
+ makeSetVarsTool({ projectId, sharedRoot: workspaceRoot || sharedRoot }),
234
+ makeDrawTrendTool({ projectId, sharedRoot: workspaceRoot || sharedRoot, sessionId, ctx }),
235
+ makeJotMemoryTool({ workspaceRoot }),
236
+ // roll_dice — 服务端真随机骰(08-28 沉浸感机制刀①):模型编的骰运不可信,
237
+ // 这把走 crypto + run.dice 事件直达用户屏幕。GM only(不进角色白名单)。
238
+ makeRollDiceTool({ projectId, ctx }),
239
+
184
240
  // crystallize_skill — 把探索出来的方法论固化成用户自己的 skill + 作品进橱窗
185
241
  // (2026-07-30)。用户明确要求才调;写的是判断依据不是成品 HTML。
186
242
  makeCrystallizeSkillTool({ projectId, sessionId, ctx }),
@@ -255,12 +311,12 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
255
311
  // 或者第二个常驻名额;1 vCPU 上后者不成立。
256
312
  ...browseBatchable,
257
313
  // 一次往返跑一串(串行、遇错即停、结尾补截图)—— 省的是模型回合数
258
- makeBrowserBatchTool({ tools: browseBatchable }),
314
+ makeBrowserBatchTool({ tools: browseBatchable, resolve: resolveTool }),
259
315
  // ── 产物会话(2026-08-21):成品检查的交互半边 ──
260
316
  // artifact_open 把产物开进常驻会话,artifact_computer/find 对着它点、敲、找;
261
317
  // 五个量具 live:true 就量会话里现在这一页;artifact_batch 一趟跑一串。
262
318
  ...artifactSessionTools,
263
- makeArtifactBatchTool({ tools: [...artifactSessionTools, screenshotCanvas, queryElements, getComputedStyles, explainStyle, traceMotion] }),
319
+ makeArtifactBatchTool({ tools: [...artifactSessionTools, screenshotCanvas, queryElements, getComputedStyles, explainStyle, traceMotion], resolve: resolveTool }),
264
320
  // 撞验证墙时举手叫人(阻塞等,默认 120 秒超时 —— 人可能就走了)
265
321
  makeBrowserRequestHelpTool({ projectId, ctx }),
266
322
  getComputedStyles,
@@ -292,29 +348,49 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
292
348
 
293
349
  // 工作台分区画布(2026-07-27):agent 协助摆放 —— 把产物/文档/deck 钉进
294
350
  // 某 session 的工作区。写 board.json(board-store 单锁)+ 广播 board.updated。
295
- makePinToBoardTool({ sharedRoot, projectId, ctx }),
351
+ makePinToBoardTool({ sharedRoot, projectId, sessionId, ctx }),
296
352
 
297
353
  // 关系线(2026-08-07):agent 把「这版改自那版」「这两个是对照」这类
298
354
  // **只有它知道**的关系画到画布上。画布知道每个产物是什么,但不知道它们
299
355
  // 之间是什么关系 —— 那是北极星(排出有版面感的布局)真正缺的那一块。
300
- makeRelateOnBoardTool({ sharedRoot, projectId, ctx }),
301
- // agent 摆位四件(2026-08-14):看版面 / 语义摆位·立主角 / 落手写便签 / 归纳收纳
302
- makeReadBoardTool({ projectId }),
303
- makeArrangeOnBoardTool({ projectId, ctx }),
304
- makeCreateOnBoardTool({ projectId, ctx }),
356
+ // 黑板核心三件(08-25 范式重做,08-28 别名收摊):写=write_on_board(件数
357
+ // 判据分流一句话/一张图,ink:'hand' 收编旧 create),改=edit_board(吞
358
+ // arrange/finish/relate/edit_sketch),看=read_board。board_batch
359
+ // **同一批实例**串行跑(一章 RP 的板面维护八次往返收成一次)。
360
+ ...boardBatchable,
361
+ // board_batch 暂停(2026-08-30 用户拍板):batch 适合语法机械、单步几乎不会
362
+ // 语义性失败的家族(浏览器/产物检查照旧),板面动作不是 —— 拒收/容量/引用
363
+ // 都是单步就可能断的语义闸。真会话对账:batch 内每动作失败 16.1% vs 单调用
364
+ // 9.1%,另有 9.4% 好动作被连坐;今天 glm 一场 board_batch 0/6 全败,sonnet
365
+ // 同一张卡几乎不用 batch 反而顺。是暂停不是拆除:makeBatchTool/流式预知/
366
+ // beat-gate/角色白名单里的 batch 支路全保留原样,恢复 = git 翻回这一块注册
367
+ // (git log -S board_batch -- server/engine/mcp/index.js)+ 常驻表加回名字
368
+ // + prelude/SKILL 把"攒一车"的教学翻回来。
305
369
  makeOrganizeBoardTool({ projectId, ctx }),
306
- // 黑板(2026-08-23):一次落一整张草图 / 落定或擦掉 / 看一眼 / 用户在看哪
307
- makeSketchOnBoardTool({ projectId, ctx }),
308
- makeFinishSketchTool({ projectId, ctx }),
309
- makeEditSketchTool({ projectId, ctx }),
310
- makeWriteOnBoardTool({ projectId, sharedRoot: workspaceRoot || sharedRoot, sessionId, ctx }),
311
370
  makeLookAtBoardTool({ projectId, ctx }),
312
371
  makeReadUserViewTool({ projectId }),
313
372
 
314
373
  // 注:Phase Image-2 的 request_image_approval 工具已废弃(2026-05-06)。
315
374
  // generate_image 的 CallToolResult 已返 image content block,前端自动渲染;
316
375
  // agent 在 caption / 自然回话邀请反馈,下一轮用户 chat 即天然 gate。
317
- ].map((t) => (
376
+ ];
377
+
378
+ // 模式对照表的启动期对账(2026-08-27):必须对**过滤前**的全量名单 —— 表校验的
379
+ // 对象是「注册表里有没有这个名字」,跟本机能力/项目模式无关。工具改名后表没跟上
380
+ // 时这里当场炸,不让下架条目静默空转(判据本身要先验一遍)。
381
+ assertModeProfileNames(builtTools.map((t) => t.name));
382
+ assertAlwaysLoadNames(builtTools.map((t) => t.name));
383
+
384
+ const tools = builtTools.filter((t) => (
385
+ // 本机能力缺席且该工具是 unregister 档 → 整件不注册(连名字都不进上下文)。
386
+ // 现在只有本地 GPU 盒子那两件:盒子手动开关,关着时留名字只会让 agent 去撞
387
+ // SSH 拒连。对照表在 capability-gate.js 一份。
388
+ shouldRegisterTool(t)
389
+ )).filter((t) => (
390
+ // 项目模式闸(2026-08-27):rp 项目下架设计产线(deck/站点/交付/产物量具),
391
+ // 同样是 unregister 语义。对照表在 mode-profile.js 一份,下面还有启动期对账。
392
+ shouldRegisterForMode(t, projectMode)
393
+ )).map((t) => (
318
394
  // SDK 用 _meta['anthropic/alwaysLoad'] 标记常驻(tool() 第 5 参的等价物,
319
395
  // 集中在这打标避免改 16 个工具文件)
320
396
  ALWAYS_LOAD_TOOLS.has(t.name)
@@ -331,8 +407,24 @@ export function createNodesignMcpServer({ workspaceRoot, sharedRoot, projectId,
331
407
  withParamSanitizer(t, { projectId, sessionId })
332
408
  ));
333
409
 
410
+ // batch 解析表回填:此刻 tools 里的实例已过完 能力闸/模式闸/alwaysLoad/消毒 全套
411
+ for (const t of tools) wrappedByName.set(t.name, t);
412
+ // 参数名台账(2026-08-30):给 PreToolUse 的未知键探针用。SDK 在进 handler
413
+ // 之前就按 schema 把未知键 strip 掉了(zod object 默认行为,不报错),所以
414
+ // 工具自己**永远看不见**模型多传了什么 —— 真会话里 write_on_board 被塞过 20 次
415
+ // `facts`(整份剧情事实),一次都没留下痕迹。只有钩子拿得到原始 tool_input。
416
+ for (const t of tools) TOOL_PARAM_KEYS.set(t.name, new Set(Object.keys(t.inputSchema || {})));
417
+
418
+ // 角色工具白名单的启动期对账(2026-08-26):cast_role 会把白名单里的短名写进
419
+ // 角色文件的 frontmatter,名字写错**不会报错**,只会让角色少一只手(CLI 当那个
420
+ // 工具不存在)。所以对着真实注册表核一遍,错了当场炸在启动期。
421
+ assertRoleToolsRegistered(new Set(tools.map((t) => t.name)));
422
+
423
+
334
424
  const server = createSdkMcpServer({
335
- name: 'nodesign',
425
+ // 名字收在 mcp/server-name.js:它同时是 session-loop 里 mcpServers 的键
426
+ // (决定模型看到的 mcp__nodesign__* 前缀)和 isolation.js 那条 allow 规则要匹配的名
427
+ name: MCP_SERVER_NAME,
336
428
  version: '0.1.0',
337
429
  tools,
338
430
  });
@@ -0,0 +1,139 @@
1
+ /**
2
+ * engine/mcp/mode-profile.js — 项目模式 × 工具面的唯一对照表 + 闸(2026-08-27)。
3
+ *
4
+ * 项目分两种模式(projects.mode,真相源在 projects/store.js):
5
+ * - design:设计工作台(现状默认)。工具面不动 —— 含全部 RP 工具,因为存量的
6
+ * 演出全发生在 design 项目里,在这里藏 RP 工具等于把它们全断掉。等演出模式
7
+ * 转正、真会话都迁过去之后,再考虑反向裁剪。
8
+ * - rp:演出(常驻角色演故事)。下架设计产线 —— 不是防误用那么简单:工具名
9
+ * 进上下文就是注意力,prelude 的设计教义也按同一模式剥(system-prompts.js
10
+ * 的 nd:mode 分区)。两边共用画布/黑板/会话/精灵全部基础设施。
11
+ *
12
+ * 跟 capability-gate.js 的关系:同一个注册期 filter 位置、不同判据 ——
13
+ * 那边按「本机能力在不在」,这边按「这个项目在演哪种戏」。都是 unregister 语义
14
+ * (连名字都不给模型看见),先后串联,谁说不注册都不注册。
15
+ *
16
+ * ⚠️ 改这张表必须跑 assertModeProfileNames(mcp/index.js 启动期已挂):
17
+ * 表里的名字对不上真实注册名时它当场炸 —— 不然一次工具改名就让这里静默空转,
18
+ * 「配置读数 ≠ 真在生效」这条线上已经栽过太多次。
19
+ */
20
+
21
+ /**
22
+ * rp 模式下整件不注册的工具。按产线分组 —— 拆的时候用户按功能拍的板
23
+ * (2026-08-27:「文档站点应用都移除,生图黑板精灵和浏览器感知留下」)。
24
+ */
25
+ export const RP_HIDDEN_TOOLS = Object.freeze(new Set([
26
+ // deck / 文档产线
27
+ 'build_docx', 'preview_deck', 'read_page', 'list_pages',
28
+ // 站点发布
29
+ 'publish_site',
30
+ // 设计交付与方法论结晶
31
+ 'export_handoff', 'crystallize_skill',
32
+ // ⚠️ get_pending_changes / clear_pending_changes **不在此列**:DirectEdit 是用户
33
+ // 输入通道(圈图说事 / 双击改字),不是设计产线 —— rp 项目里也得能收用户的圈注。
34
+ // HTML 产物量具(板的眼睛是 look_at_board / read_user_view,不在此列)
35
+ 'screenshot_canvas', 'query_elements', 'get_computed_styles', 'explain_style',
36
+ 'profile_scroll', 'trace_motion',
37
+ // 产物会话五量具
38
+ 'artifact_open', 'artifact_computer', 'artifact_find', 'artifact_motion', 'artifact_batch',
39
+ ]));
40
+
41
+ /** 这件工具在该模式下该不该注册(false = 连名字都不给) */
42
+ export function shouldRegisterForMode(toolDef, mode) {
43
+ if (mode !== 'rp') return true;
44
+ return !RP_HIDDEN_TOOLS.has(toolDef.name);
45
+ }
46
+
47
+ /**
48
+ * 启动期对账:表里每个名字必须真实存在于注册表。
49
+ * @param {string[]} registeredNames design 模式(全量)下的注册名单
50
+ * @throws 表里出现注册表没有的名字(改名 / 删除后表没跟上)
51
+ */
52
+ export function assertModeProfileNames(registeredNames) {
53
+ const have = new Set(registeredNames);
54
+ const ghosts = [...RP_HIDDEN_TOOLS].filter((n) => !have.has(n));
55
+ if (ghosts.length) {
56
+ throw new Error(
57
+ `[mode-profile] RP_HIDDEN_TOOLS 里有注册表不存在的名字: ${ghosts.join(', ')} `
58
+ + '—— 工具改名或下线后对照表没跟上,这些条目在静默空转',
59
+ );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * skill × 模式(2026-08-30)。跟上面那张表同一个语义,另一个载体。
65
+ *
66
+ * ## 为什么 skill 也要按模式筛
67
+ *
68
+ * `session-loop.js` 把 `installed.skills` 整个交给 SDK,而 SDK **只把 description
69
+ * 注进系统提示词**(body 才是按需加载,见 agent/skill.js 头注)。所以每装一个 skill,
70
+ * 它那几百字节的描述就在**每一个会话**里常驻 —— 不分模式。
71
+ *
72
+ * 实测(08-30):RP 会话现在背着 deskskill-engine-mini(562B) / docx-craft(1123B) /
73
+ * site-craft(782B) 三份描述,合计 ~2.4KB,而这三个 skill 要用的工具
74
+ * (`build_docx` / `publish_site` / `preview_deck`)在 RP 模式下**根本没注册**
75
+ * ——上面那张 RP_HIDDEN_TOOLS 已经把它们摘掉了。工具没了描述还在,是纯亏。
76
+ * 反过来同理:演出侧的文风/技法包不该压在设计会话的常驻区里。
77
+ *
78
+ * ## 判据
79
+ *
80
+ * 表里没有的名字**一律放行** —— 用户自己装的 plugin(用户级 / 项目级)不归我们裁,
81
+ * 只管内置这几个。这条别改成白名单:那会让用户装的 skill 静默消失,
82
+ * 而「静默消失」是这条线上最难查的一类。
83
+ */
84
+ export const SKILL_MODES = Object.freeze({
85
+ // 设计产线:工具在 RP 下已下架,描述跟着走
86
+ 'deskskill-engine-mini': 'design',
87
+ 'docx-craft': 'design',
88
+ 'site-craft': 'design',
89
+ // 演出侧
90
+ 'story-voice': 'rp',
91
+ 'story-craft': 'rp',
92
+ 'story-intimacy': 'rp',
93
+ // 两边都要:轻度演故事发生在设计项目里(prelude 设计区明写这条路),
94
+ // 而酒馆卡也常常被丢进设计项目
95
+ 'blackboard-rp': 'both',
96
+ 'story-import': 'both',
97
+ });
98
+
99
+ /** 按模式筛 skill 名单。表里没有的一律保留(用户装的不归我们裁)。 */
100
+ export function filterSkillsForMode(names, mode) {
101
+ const want = mode === 'rp' ? 'rp' : 'design';
102
+ return (Array.isArray(names) ? names : []).filter((n) => {
103
+ const m = SKILL_MODES[n];
104
+ return !m || m === 'both' || m === want;
105
+ });
106
+ }
107
+
108
+ /**
109
+ * 启动期对账:表里每个名字必须真的装着。
110
+ * 跟 assertModeProfileNames 同样的狠法 —— skill 改名/下线后表没跟上就是静默空转。
111
+ * @param {string[]} installedNames 本次会话实际装上的 skill 名单
112
+ */
113
+ export function assertSkillModeNames(installedNames) {
114
+ const have = new Set(installedNames || []);
115
+ const ghosts = Object.keys(SKILL_MODES).filter((n) => !have.has(n));
116
+ if (ghosts.length) {
117
+ throw new Error(
118
+ `[mode-profile] SKILL_MODES 里有没装上的 skill: ${ghosts.join(', ')} `
119
+ + '—— skill 改名或下线后对照表没跟上,这些条目在静默空转',
120
+ );
121
+ }
122
+ }
123
+
124
+ /**
125
+ * skill 按模式筛的总入口(08-30;从 session-loop 拆来 —— 行数棘轮)。
126
+ * SDK 只把 description 注进系统提示词(body 按需加载),所以每个 skill 的几百字节
127
+ * 描述是**每个会话**的常驻成本,不分模式。筛之前 RP 会话背着设计三件
128
+ * (deskskill/docx/site 合计 ~2.4KB)的描述,而它们要用的工具在 RP 下压根没注册。
129
+ * 对账跟 assertModeProfileNames 一样狠:表里的名字没装上就当场炸,别静默空转。
130
+ */
131
+ export function modeSkillsFor(installedNames, mode) {
132
+ assertSkillModeNames(installedNames);
133
+ const out = filterSkillsForMode(installedNames, mode);
134
+ if (out.length !== installedNames.length) {
135
+ const dropped = installedNames.filter((n) => !out.includes(n));
136
+ console.log(`[session-loop] skills 按 mode=${mode} 筛掉 ${dropped.length}: [${dropped.join(', ')}]`);
137
+ }
138
+ return out;
139
+ }