@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
@@ -53,7 +53,7 @@
53
53
  **`linkedToEditId` follow-up comment 的优先级最高**:用户填了"重排 OK" / "保持完全不动" 等明确指令,按 comment 来。没填走默认(你自己判断"尽量不动")
54
54
  - **pending-style** —— 按 `styleDelta` 改 inline style 或对应 Tailwind class(如 styleDelta.marginLeft 改 `ml-*`)。
55
55
  - **自由模式(free position)—— 最小改动原则**:
56
- - `styleDelta` 只含 `{position:'absolute', left, top}` —— 这是用户的真实意图(落位)
56
+ - `styleDelta` 只含 `{position:'absolute', left, top}` —— 这是用户的真实意图(他把东西摆到了哪里)
57
57
  - **不要无脑写整个 source 的样式**。默认只改这 3 个定位字段,**保留 source 原本的 CSS class 行为**(响应式、flex/grid 分配的尺寸、margin、transform 等)
58
58
  - 看 `aiContext.parentNeedsRelative` —— true 时把 `parentAnchor` 父元素加 `position:relative`
59
59
  - **`aiContext.preDragLayout` 是关键决策上下文**——source 拖前的 computed style 快照,让你判断是否需要追加补救:
@@ -43,7 +43,7 @@
43
43
 
44
44
  | 主体 | 来源 | 触发动作 |
45
45
  |---|---|---|
46
- | 用户上传素材(`assets/*.png\|jpg`)| 直接用 | 把路径喂 `referenceImages[]` |
46
+ | 用户上传素材(`用户内容/*.png\|jpg`,老项目在 `assets/`)| 直接用 | 把路径喂 `referenceImages[]` |
47
47
  | 模型脑里有的著名实体(Apple Park / Wong Kar-wai 风 / 艺术流派)| 不必 reference | prompt 直接点名 |
48
48
  | **真实存在但模型不熟**(最新发布产品 / 小众品牌 / 用户自有 IP / 特定型号设备)| **`web_search { include_images:true }`** | 工具自动翻英文 + 下载到 `assets/references/`;选 1-2 张最切题的 `local_path` 喂 `referenceImages[]` |
49
49
  | 抽象概念 / 装饰 / 隐喻 | 不需要 reference | 直接 prompt |
@@ -241,7 +241,7 @@ Avoid: <底线级禁令>
241
241
  |---|---|
242
242
  | **风格一致(跨页锚)** | 第 1 张 cover 当种子,后续 hero / section-divider 都引它 → 整篇像同一部片子 |
243
243
  | **角色一致(多页叙事)** | portrait 跨页引 + 给角色起名("Maya, the woman in Reference 1")|
244
- | **logo / brand 嵌入** | 用户上传 logo 进 `assets/`,prompt 写 "Place the logo from Reference 1 etched into the bottle in Reference 2" |
244
+ | **logo / brand 嵌入** | 用户上传 logo 进 `用户内容/`,prompt 写 "Place the logo from Reference 1 etched into the bottle in Reference 2" |
245
245
  | **精修而非重画** | `screenshot_canvas` 截当前页当 reference,配 § G 的模板 |
246
246
  | **真实主体锚定** | `web_search { include_images: true }` 拿真图 → 喂进来 + "Use the product in Reference 1 as the subject; render it in [场景]" |
247
247
 
@@ -264,7 +264,7 @@ lora_strength: "0.9,0.7"
264
264
 
265
265
  ## 批量与铁律
266
266
 
267
- - stills ≤16 条,每条可再开 `batch` 1-8,逐张上墙
267
+ - stills ≤16 条,每条可再开 `batch` 1-8,逐张出现在画布上
268
268
  - **任何一条失败即中止整批剩余**(后面大概率同因,不空烧)
269
269
  - `name` 只能是字母数字下划线连字符,1-40 字符,**中文名会被直接拒**
270
270
  - 视频关键帧一律 1344x768
@@ -113,4 +113,5 @@ WebGL/Three.js(含 R3F)的一个静默坑:渲染器属性(如剖切要
113
113
  - **字体链少了 CJK 那段**:`'Inter', sans-serif` 换台机器中文就掉到系统默认字体。
114
114
  每段 latin family 后面必须跟 `'PingFang SC', 'Noto Sans SC'`
115
115
  - **改了 style.css 预览没变**:不会。html/css/js 走 `no-cache`,写完即时刷新
116
- - **半角标点**:中文正文里的 `,` `:` 很扎眼,用全角 `,` `:`
116
+ - **标点跟正文主体语言走**:中文正文里的 `,` `:` 很扎眼,用全角 `,` `:`;
117
+ 英文正文反过来,全角标点混进去一样扎眼,用半角加空格
@@ -0,0 +1,175 @@
1
+ /**
2
+ * engine/agent/role-card.js —— 读角色文件(2026-08-26)
3
+ *
4
+ * 角色文件(`.claude/agents/rp-*.md`)**是模型可写的**:`cast_role` 是正门,但主 agent
5
+ * 手里有 Write/Edit/Bash,`.claude/agents/` 就在工作区内,workspace-scope-guard 不拦它。
6
+ * 所以「角色只能拿板上工具」这条教义如果只写在 cast_role 里,等于没写 —— 绕过它只要
7
+ * 手写一份 md。
8
+ *
9
+ * 收口在**派发时**(见 hooks/pre-defaults.js 的 rp 分支):不管这份文件是谁写的、
10
+ * 怎么落的盘,派它之前都按同一张白名单核一遍。
11
+ *
12
+ * ## 三种要拦的写法
13
+ *
14
+ * tools: mcp__nodesign__publish_site 点名要外发工具
15
+ * tools: mcp__nodesign **server 通配** = 这个 server 的全部工具
16
+ * (压根没有 tools 行) SDK 语义 = **继承父代理的全部工具**
17
+ *
18
+ * 最后那条最容易漏:少写一行比多写一行危险。所以缺失 = 不合法,不是"用默认值"。
19
+ *
20
+ * ## 解析为什么是保守的
21
+ *
22
+ * frontmatter 真的走 YAML 解析(CLI 侧 `Bun.YAML.parse`),合法写法不止一种
23
+ * (`a, b` / `[a, b]` / 多行 `- a`)。这里三种都认;认不出的形状一律**当不合法**
24
+ * 而不是当空 —— 判据看不懂的东西不能默认放行。角色文件本来就该由 cast_role 写,
25
+ * 手写出奇怪形状时 deny 会指回正门。
26
+ */
27
+
28
+ import path from 'node:path';
29
+ import { promises as fs } from 'node:fs';
30
+ import { isResidentRole, isSlotType, safeRoleLabel } from './cast.js';
31
+
32
+ const AGENTS_DIR = '.claude/agents';
33
+ const REGISTRY_REL = '.nd/cast.json';
34
+
35
+ /**
36
+ * 演员位登记表(2026-08-28 重构):cast_role 写的 slug → {name, duty, pen, card}。
37
+ * 没有/坏了 = 空表 —— 登记是展示层,fail-soft,坏了角色照样上场(署名退回 slug)。
38
+ */
39
+ export async function readCastRegistry(workspaceRoot) {
40
+ try {
41
+ const raw = await fs.readFile(path.join(workspaceRoot, REGISTRY_REL), 'utf8');
42
+ const data = JSON.parse(raw);
43
+ return data && typeof data.roles === 'object' && data.roles ? data : { version: 1, roles: {} };
44
+ } catch { return { version: 1, roles: {} }; }
45
+ }
46
+ const FM_RE = /^---\r?\n([\s\S]{0,4000}?)\r?\n---/;
47
+
48
+ /** 从 frontmatter 文本里取一个键的原始值(只认顶层、单行键) */
49
+ function rawValue(fm, key) {
50
+ const re = new RegExp(`^${key}:[ \\t]*(.*)$`, 'm');
51
+ const m = re.exec(fm);
52
+ return m ? m[1] : null;
53
+ }
54
+
55
+ /**
56
+ * 解析 tools 声明。
57
+ * @returns {{ kind: 'list', tools: string[] } | { kind: 'missing' } | { kind: 'unparsable' }}
58
+ */
59
+ export function parseToolsDeclaration(fm) {
60
+ const raw = rawValue(fm, 'tools');
61
+ if (raw === null) return { kind: 'missing' };
62
+ const inline = raw.trim();
63
+
64
+ if (inline === '') {
65
+ // 多行数组:tools:\n - a\n - b
66
+ const after = fm.slice(fm.indexOf('\ntools:') + 1).split('\n').slice(1);
67
+ const items = [];
68
+ for (const line of after) {
69
+ if (/^\s*-\s+/.test(line)) { items.push(line.replace(/^\s*-\s+/, '').trim().replace(/^["']|["']$/g, '')); continue; }
70
+ if (/^\S/.test(line)) break; // 下一个顶层键
71
+ if (line.trim() === '') continue;
72
+ return { kind: 'unparsable' };
73
+ }
74
+ return items.length ? { kind: 'list', tools: items } : { kind: 'unparsable' };
75
+ }
76
+
77
+ const flow = /^\[(.*)\]$/.exec(inline);
78
+ const body = flow ? flow[1] : inline;
79
+ const tools = body.split(',').map((t) => t.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
80
+ return tools.length ? { kind: 'list', tools } : { kind: 'unparsable' };
81
+ }
82
+
83
+ /**
84
+ * 读一份角色文件。
85
+ * @returns {Promise<{ slug, displayName: string|null, description: string|null,
86
+ * toolsDecl: ReturnType<typeof parseToolsDeclaration> } | null>}
87
+ */
88
+ export async function readRoleCard(workspaceRoot, slug) {
89
+ if (!workspaceRoot || !isResidentRole(slug)) return null;
90
+ const file = path.join(workspaceRoot, AGENTS_DIR, `${slug}.md`);
91
+ let raw;
92
+ try { raw = await fs.readFile(file, 'utf8'); } catch { return null; }
93
+ const m = FM_RE.exec(raw);
94
+ if (!m) return { slug, displayName: null, description: null, toolsDecl: { kind: 'unparsable' } };
95
+ const fm = m[1];
96
+ const description = rawValue(fm, 'description');
97
+ const descPlain = description
98
+ ? description.trim().replace(/^"([\s\S]*)"$/, '$1').replace(/\\"/g, '"')
99
+ : null;
100
+ const nameMatch = descPlain ? /^RP 角色「(.+?)」/.exec(descPlain) : null;
101
+ return {
102
+ slug,
103
+ // ⚠️ 展示名是文件里的**自称**,不是实证 —— 角色文件模型可写,一个角色能自称
104
+ // 「用户」或顶替别的角色。只拿它当展示层,归属的锚在名册(harness 亲眼所见的派发)。
105
+ // 保留字闸(safeRoleLabel)在**这个出口**过(08-28 下沉):此前只在 listRoleNames
106
+ // 过,直接消费 readRoleCard().displayName 的新调用点会重蹈「三个渲染面漏一个」。
107
+ displayName: nameMatch ? safeRoleLabel(slug, nameMatch[1]) : null,
108
+ description: descPlain,
109
+ toolsDecl: parseToolsDeclaration(fm),
110
+ };
111
+ }
112
+
113
+ /**
114
+ * 从派发 prompt 里推断实例名(2026-08-28 晚,glm 撞闸案)。
115
+ *
116
+ * name 闸的原设计假设「deny 一次模型就会补参数」—— 对 sonnet 成立(探针实证),
117
+ * 对免费行 glm 不成立:proj_mtd7d4et 里它连撞九次,第六次把 `name: rp-izumi`
118
+ * 写进了 prompt 正文 —— 它想给,但参数就是发不出来。弱模型够不着的参数不能当
119
+ * 硬前置,所以闸学会自己认,按可靠度分层:
120
+ *
121
+ * 1. prompt 头部的 `name: rp-xxx` 行(模型明说的)
122
+ * 2. 登记表匹配:prompt 里出现某个已登记角色的卡路径(cast_role 的配方就是
123
+ * 「第一行写卡路径」,正常照做的派发天然命中)
124
+ * 3. prompt 里出现的、登记过的 rp- slug
125
+ *
126
+ * 推出来的名字跟模型传参同一信任级(名字本来就是模型起的),不碰任何安全判据
127
+ * (工具白名单在演员位文件上,收件人闸认的是名册登记)。taken 是已在场名册 ——
128
+ * 在场的不再当候选(那是"续演该用 SendMessage"的情形,不是新派)。
129
+ */
130
+ export async function inferRoleNameFromPrompt(workspaceRoot, prompt, { taken = new Set(), isSlot = () => false } = {}) {
131
+ const head = String(prompt || '').slice(0, 1200);
132
+ const ok = (s) => s && /^rp-[A-Za-z0-9][A-Za-z0-9_-]{0,60}$/.test(s) && !isSlot(s) && !taken.has(s);
133
+ const explicit = /name[::]\s*["'「]?(rp-[A-Za-z0-9][A-Za-z0-9_-]*)/.exec(head);
134
+ if (explicit && ok(explicit[1])) return explicit[1];
135
+ const reg = await readCastRegistry(workspaceRoot);
136
+ const byCard = Object.entries(reg.roles)
137
+ .filter(([slug, r]) => ok(slug) && typeof r?.card === 'string' && r.card && head.includes(r.card));
138
+ if (byCard.length === 1) return byCard[0][0];
139
+ const mentioned = Object.keys(reg.roles).filter((slug) => ok(slug) && head.includes(slug));
140
+ if (mentioned.length === 1) return mentioned[0];
141
+ return null;
142
+ }
143
+
144
+ /**
145
+ * 列出这个工作区里所有角色的展示名(slug → 展示名)。
146
+ *
147
+ * 只当**展示层**用:展示名取自角色文件,而那份文件模型能写 —— 一个角色可以自称
148
+ * 「用户」。所以这张表不许用来做判断(路由、权限、归属),只用来把 slug 渲染得好看。
149
+ * 判断一律用 slug 本身(harness 盖的章)。
150
+ */
151
+ export async function listRoleNames(workspaceRoot) {
152
+ const out = new Map();
153
+ if (!workspaceRoot) return out;
154
+ // 旧式一角一定义(会话启动前就存在的 .claude/agents/rp-*.md):仍然认,
155
+ // 但演员位本身(rp-actor / rp-narrator)是位置不是人,不进名册。
156
+ let names;
157
+ try { names = await fs.readdir(path.join(workspaceRoot, AGENTS_DIR)); } catch { names = []; }
158
+ for (const n of names) {
159
+ if (!n.endsWith('.md')) continue;
160
+ const slug = n.slice(0, -3);
161
+ if (!isResidentRole(slug) || isSlotType(slug)) continue;
162
+ const card = await readRoleCard(workspaceRoot, slug);
163
+ // 保留字闸已下沉到 readRoleCard 出口(08-28)—— 这里拿到的已是洗过的名字。
164
+ if (card?.displayName) out.set(slug, card.displayName);
165
+ }
166
+ // 登记表(08-28 演员位重构的主源):同 slug 时登记表赢 —— cast_role 是正门。
167
+ // 登记表也是模型可写的(.nd 在工作区里),展示名同样只当展示层、过保留字闸。
168
+ const reg = await readCastRegistry(workspaceRoot);
169
+ for (const [slug, r] of Object.entries(reg.roles)) {
170
+ if (!isResidentRole(slug) || isSlotType(slug)) continue;
171
+ const label = safeRoleLabel(slug, r?.name);
172
+ if (label) out.set(slug, label);
173
+ }
174
+ return out;
175
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * engine/agent/rp-mode.js —— 「RP 编排机械开不开」的唯一谓词(2026-08-28 归一)
3
+ *
4
+ * 此前这个判断散在三处、口径不一:stage-broadcast 判 mode==='rp',scene.js 和
5
+ * roles.js 不判 —— design 项目手动 cast_role 之后进入「半条腿」状态:say 能开轮、
6
+ * onRoleWait 能推进,广播却整机不转,没有任何一处报错。
7
+ *
8
+ * 归一口径(08-28):**编排机械(台上广播 / 轮次机 / set_scene)只在 rp 模式活**;
9
+ * 直投通道(say 私语、cue_role、SendMessage 召回)任何模式都通 —— design 项目里
10
+ * 轻度 RP(blackboard-rp skill + cast_role)照样能跟角色说话,只是没有机器替它转。
11
+ */
12
+
13
+ import { getProject } from '../../projects/store.js';
14
+
15
+ export function isRpProject(projectId) {
16
+ return !!projectId && (getProject(projectId)?.mode || 'design') === 'rp';
17
+ }
@@ -31,6 +31,7 @@ import { AgentContext, freshTurnCounters } from './context.js';
31
31
  import { Events } from './events.js';
32
32
  import { createRun, markRunStarted, markRunSucceeded, markRunFailed, mergeRunMetadata, setRunMetrics, setRunModelUsage } from '../runs/store.js';
33
33
  import { getProject } from '../../projects/store.js';
34
+ import { modeSkillsFor } from '../mcp/mode-profile.js';
34
35
  import { randomUUID } from 'node:crypto';
35
36
  import {
36
37
  registerQuerySession,
@@ -47,6 +48,7 @@ import {
47
48
  } from '../runs/active-runs.js';
48
49
  import {
49
50
  promoteNextPendingRunId, claimRunByUuid, releaseCurrentTurnRunId, getPendingRunCount,
51
+ isBackgroundTurnOpener,
50
52
  closeMergedRun, publishQueueDepth, pushUnclaimedMessage,
51
53
  } from '../runs/turn-relay.js';
52
54
  // skill 起手文件拷贝已挪 hooks.js PreToolUse(Skill/Bash)(2026-07-27),
@@ -56,7 +58,10 @@ import { createHooks } from './hooks.js';
56
58
  import { buildIsolationOptions, prepareAgentDirs, sandboxShimEnv } from './isolation.js';
57
59
  import { MEMORY_EXTRA_GUIDELINES, mergeAgentSettings } from './memory-config.js';
58
60
  import { createNodesignMcpServer } from '../mcp/index.js';
61
+ import { MCP_SERVER_NAME } from '../mcp/server-name.js';
59
62
  import { assertInitContract } from './init-contract.js';
63
+ import { clearSessionFlights } from './subagent-flight.js'; import { clearStageStatus } from './stage-status.js'; import { clearBeatState } from './beat-state.js';
64
+ import { createRoleRoster } from './cast.js';
60
65
  import { createAgents, resolveDefaultFastModel } from '../agents/index.js';
61
66
  import { resolveSdkSpoofModel, pickThinkingConfig, resolveModelRoute, isUncensoredModel } from './model-context.js';
62
67
  import { resolveSessionModel } from './session-model.js';
@@ -119,11 +124,6 @@ import { levelFor } from '../../lib/moderation.js';
119
124
  * 旁路事件**不算** —— 通知之后 SDK 不一定真的唤起模型,铸了 run 却等不来
120
125
  * result 收尾就是僵尸 run。
121
126
  */
122
- function isBackgroundTurnOpener(message) {
123
- return message?.type === 'assistant'
124
- || message?.type === 'stream_event'
125
- || message?.type === 'user';
126
- }
127
127
 
128
128
  export async function runSession({
129
129
  sessionId,
@@ -325,8 +325,11 @@ export async function runSession({
325
325
 
326
326
  // MCP server 实例落变量:开局契约自检要从**传给 query 的同一个实例**上取预期
327
327
  // 工具名(server.toolNames,见 mcp/index.js)——不另立第二份清单。
328
- const nodesignServer = createNodesignMcpServer({ workspaceRoot: wsRoot, sharedRoot, projectId, sessionId, ctx: sharedCtx });
329
-
328
+ // 常驻角色名册:一会话一份,hooks MCP 工具共用同一引用(见 cast.js createRoleRoster)
329
+ const roleRoster = createRoleRoster();
330
+ // 项目模式(08-27):启动时读一次(切换下个会话生效),同一读数喂工具面(mode-profile.js)和提示词面(nd:mode 分区),两面不岔开
331
+ const projectMode = (projectId ? getProject(projectId)?.mode : null) || 'design';
332
+ const nodesignServer = createNodesignMcpServer({ workspaceRoot: wsRoot, sharedRoot, projectId, sessionId, ctx: sharedCtx, roleRoster, projectMode });
330
333
  // npm 缓存 + 沙盒可写 tmp($TMPDIR / pip 缓存):细节与教训见 isolation.js
331
334
  const agentDirs = await prepareAgentDirs({ dataRoot: PROJECTS_DATA_ROOT, projectId, sessionId });
332
335
 
@@ -421,8 +424,8 @@ export async function runSession({
421
424
  systemPrompt: {
422
425
  type: 'preset',
423
426
  preset: 'claude_code',
424
- // 成人段随项目 owner 的外审档联动(agent-shared.renderPrelude);
425
- // 找不到 owner 时落 loose 默认,绝不落 off
427
+ // 成人段随项目 owner 的外审档联动(renderPrelude);无主项目落 tier.js 的 strict,绝不落 off。
428
+ // ⚠️ 08-30 默认档按通路拆成两栏(订阅 strict / 非订阅 off)→ 同一个项目换个模型跑成人段就换一档。
426
429
  //
427
430
  // uncensored 走模型表的标记位(model-context.isUncensoredModel),不在这里
428
431
  // 判模型名 —— 那是模型属性,写在这儿就是给那张表开第二个真相源。为 true 的
@@ -432,13 +435,18 @@ export async function runSession({
432
435
  const owner = projectId ? getUserById(getProject(projectId)?.ownerId) : null;
433
436
  // 档位按模型通路取旋钮(08-20 两旋钮:订阅 / 本地与中转),model 是上面已解析的会话模型
434
437
  // 无主项目 fail-closed 到 tier.js 的默认(strict),别落 loose(生产 08-21 实查 0 个无主项目)
438
+ // locale:项目 owner 在账号上记的界面语言。null(没表过态)时 renderPrelude
439
+ // 落中文默认 —— 服务端拿不到浏览器语言,这里不猜,猜错比给中文更糟。
435
440
  return renderPrelude(owner ? levelFor(owner, model) : defaultModerationLevel(null), {
436
441
  uncensored: isUncensoredModel(model),
442
+ locale: owner?.locale || undefined,
443
+ // 项目模式分区(nd:mode 标记块)—— 跟工具面用的是同一次读数,两面不会岔开
444
+ mode: projectMode,
437
445
  });
438
446
  })(),
439
447
  },
440
448
  plugins: installed.plugins,
441
- skills: installed.skills,
449
+ skills: modeSkillsFor(installed.skills, projectMode), // 按模式筛+对账(拆件见 mode-profile)
442
450
 
443
451
  // 2026-05-18 安全:关 inline shell execution。SDK 默认允许 skill / slash command 内
444
452
  // inline shell 命令(Anthropic 标准 skill 协议的一部分,如 setup script)—— 但 NoDesign
@@ -591,6 +599,7 @@ export async function runSession({
591
599
  ...isolation,
592
600
  settings: mergeAgentSettings(isolation.settings, {
593
601
  skipWebFetchPreflight: platform.skipWebFetchPreflight, sharedRoot,
602
+ crossSessionInbound: 'refuse', // 理由见 memory-config.js 那个键的注释
594
603
  }),
595
604
  };
596
605
  })(),
@@ -601,10 +610,12 @@ export async function runSession({
601
610
 
602
611
  // projectId 要传:PostToolUseFailure 记问题库时用它标归属(漏传的话
603
612
  // issues 行的 project_id 全是 null,事后追不回是哪个项目踩的)
604
- hooks: createHooks({ ctx: sharedCtx, workspaceRoot: wsRoot, sharedRoot, sessionId, projectId }),
613
+ hooks: createHooks({ ctx: sharedCtx, workspaceRoot: wsRoot, sharedRoot, sessionId, projectId, roleRoster }),
605
614
 
606
615
  mcpServers: {
607
- nodesign: nodesignServer,
616
+ // 键名 = 模型眼里的 `mcp__<名>__<工具>` 前缀,也是 isolation.js 那条
617
+ // permissions.allow 规则要匹配的名字 —— 两个读者,收在 mcp/server-name.js
618
+ [MCP_SERVER_NAME]: nodesignServer,
608
619
  },
609
620
 
610
621
  // mainModel = appModel ('kimi-k2.6'),sdkModel = SDK 视角 alias ('claude-opus-4-7[1m]')。
@@ -739,8 +750,8 @@ export async function runSession({
739
750
  await commitWorkspace(projectId, sessionId, `turn ${status}: ${new Date().toISOString()}`, { author: 'agent' })
740
751
  .catch((err) => console.warn('[git] turn commit failed:', err.message));
741
752
 
742
- // 黑板草稿兜底落定(2026-08-23):agent 这一轮 sketch_on_board 留下的 staging
743
- // 物件,没调 finish_sketch 也在回合结束时变实 —— 草稿态是"正在画"的信号,
753
+ // 黑板草稿兜底落定(2026-08-23):agent 这一轮 write_on_board 画图留下的 staging
754
+ // 物件,没调 edit_board commit 也在回合结束时变实 —— 草稿态是"正在画"的信号,
744
755
  // 回合都结束了还半透明就是幽灵。取消/出错同样落定:画了就是画了。
745
756
  if (projectId) {
746
757
  try {
@@ -992,6 +1003,7 @@ export async function runSession({
992
1003
  unregisterIngressSession(sessionId); // API 会话的 fast 兜底路由配对注销(订阅会话 noop)
993
1004
  unregisterSessionNotice(sessionId, noticeHandler); // ingress → 会话的通知通道配对注销(按身份,别删掉新会话的)
994
1005
  takeUpstreamTruncation(sessionId); // 半截标记跟会话同生命周期,别留
1006
+ clearSessionFlights(sessionId); clearStageStatus(projectId); clearBeatState(sessionId); // 在飞台账 + 台上一览 + 收尾闸记账,都跟会话同寿命
995
1007
  // 带 token 比对:sid 若已被新 register 占用(closeQuerySession 已同步让位 +
996
1008
  // 用户重发起新 runSession),unregister 看到 _token 不匹配 → noop 不误删新 entry
997
1009
  unregisterQuerySession(sessionId, sessionToken);
@@ -0,0 +1,73 @@
1
+ /**
2
+ * engine/agent/stage-status.js —— 「台上现在有谁、谁在写、谁刚写完」(2026-08-29)
3
+ *
4
+ * ## 它补的洞
5
+ *
6
+ * 主持人此前对角色的状态是半盲的:派发的返回、SendMessage 的返回、角色结束时的
7
+ * task-notification —— 全是**事件的瞬间**,没有任何地方能回答「现在怎么样了」。
8
+ * 2026-08-28 真会话里用户自己问出了这个洞(「感觉说书人是不是都需要重启一下?」),
9
+ * 而主持人手里最接近的工具 read_scene 只会背一遍它自己声明过的东西。
10
+ *
11
+ * ## 判据只用 harness 盖的章
12
+ *
13
+ * 在写 SubagentStart 到了、SubagentStop 还没到
14
+ * 刚写完 SubagentStop(`last_assistant_message` 是它收笔时说的话,SDK 现成给)
15
+ *
16
+ * 两个都不是模型能写的(feedback-verify-the-instrument)。这里**不记**「它写了什么
17
+ * 板书」—— 那份真相在画布上,每回合状态块本来就注入最近板书,重复一份就会有两份
18
+ * 各执一词的账。
19
+ *
20
+ * 生命周期:内存态、跟会话走,runSession 的 finally 里清。
21
+ */
22
+
23
+ const stages = new Map(); // projectId → Map<slug, { writing, startedAt, finishedAt, lastLine }>
24
+
25
+ function tableFor(projectId) {
26
+ if (!stages.has(projectId)) stages.set(projectId, new Map());
27
+ return stages.get(projectId);
28
+ }
29
+
30
+ /** 角色起飞(SubagentStart,名字由别名桥解析出来) */
31
+ export function noteRoleStart(projectId, slug) {
32
+ if (!projectId || !slug) return;
33
+ const t = tableFor(projectId);
34
+ const prev = t.get(slug) || {};
35
+ t.set(slug, { ...prev, writing: true, startedAt: Date.now(), finishedAt: null });
36
+ }
37
+
38
+ /** 角色收笔(SubagentStop)。lastLine = 它这一轮最后说的话,截短存 */
39
+ export function noteRoleFinish(projectId, slug, lastLine = null) {
40
+ if (!projectId || !slug) return;
41
+ const t = tableFor(projectId);
42
+ const prev = t.get(slug) || {};
43
+ t.set(slug, {
44
+ ...prev,
45
+ writing: false,
46
+ finishedAt: Date.now(),
47
+ lastLine: lastLine ? String(lastLine).replace(/\s+/g, ' ').trim().slice(0, 120) : (prev.lastLine || null),
48
+ });
49
+ }
50
+
51
+ /**
52
+ * 台上一览(给主持人的每回合状态块用)。
53
+ * @returns {Array<{slug, writing, lastLine, idleMs}>} 按「在写的排前面」排序
54
+ */
55
+ export function stageStatus(projectId) {
56
+ const t = stages.get(projectId);
57
+ if (!t || !t.size) return [];
58
+ const now = Date.now();
59
+ return [...t.entries()]
60
+ .map(([slug, v]) => ({
61
+ slug,
62
+ writing: !!v.writing,
63
+ lastLine: v.lastLine || null,
64
+ idleMs: v.finishedAt ? now - v.finishedAt : 0,
65
+ }))
66
+ .sort((a, b) => Number(b.writing) - Number(a.writing) || a.slug.localeCompare(b.slug));
67
+ }
68
+
69
+ /** 会话收摊 */
70
+ export function clearStageStatus(projectId) { stages.delete(projectId); }
71
+
72
+ /** 测试用 */
73
+ export function _resetStageStatus() { stages.clear(); }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * engine/agent/subagent-flight.js —— 「这个会话还有后台角色在干活吗」(2026-08-28)
3
+ *
4
+ * ## 为什么需要这条路
5
+ *
6
+ * ws/index.js 的 grace 闸(07-27 建)只认 turn:用户关 tab / 切走后 turn 还在飞就
7
+ * 续命,等它干完再回收 SDK 进程。演员位范式造出了**第二种在飞工作** —— GM 的场务
8
+ * turn 几秒钟就结束,后台角色还要写好几分钟。闸不认识它,于是 08-28 泉此方场实录:
9
+ *
10
+ * 17:47:39 角色派出 → 17:48:17 GM turn 结束 → 17:49:29 grace 到期关会话
11
+ * → 角色被腰斩在第三个 Read 上,板上永远没有第二拍,日志里连个 warn 都没有。
12
+ *
13
+ * 从用户视角看就是「角色不写了」。这正是 07-27 那道闸要保住的东西("用户关 tab 时
14
+ * 后端应该把活干完"),只是当时在飞工作只有 turn 一种形态。
15
+ *
16
+ * ## 判据(2026-08-29 简化)
17
+ *
18
+ * **在飞 = SubagentStart 过且 SubagentStop 还没到**,就这一条。
19
+ *
20
+ * 08-29 之前它还要减掉「候场」——那时角色写完一段会挂在 await_user 上不收回合,
21
+ * 一个挂着的角色就能让会话永远关不掉(每个 SDK 进程 ~250MB 且 RSS 单调不减,
22
+ * 这台盒子 1vCPU/8G、swap=0)。新回路里角色写一段就结束这一轮,「永不收回合」这个
23
+ * 形态不存在了,减数也就跟着没了。
24
+ *
25
+ * 信号是 harness 在 spawn / 结束那一刻亲眼所见并盖的章,不是模型能写的东西。
26
+ *
27
+ * ## 生命周期
28
+ *
29
+ * 会话内存态,runSession 的 finally 里跟收件箱一起清。**这里不设时间上限** ——
30
+ * 兜底封顶在 ws/index.js 的续命次数上(一处判据一个真相源,别在两边各写一份)。
31
+ */
32
+
33
+ import { agentNameOf } from './actor-trail.js';
34
+
35
+ const flights = new Map(); // sessionId -> Map<agentId, { agentType, startedAt }>
36
+
37
+ /** 子代理起飞。agentId 来自 SubagentStart hook input,harness 盖的章 */
38
+ export function noteSubagentStart(sessionId, agentId, agentType = null) {
39
+ if (!sessionId || !agentId) return;
40
+ let m = flights.get(sessionId);
41
+ if (!m) { m = new Map(); flights.set(sessionId, m); }
42
+ m.set(agentId, { agentType, startedAt: Date.now() });
43
+ }
44
+
45
+ /** 子代理落地 */
46
+ export function noteSubagentStop(sessionId, agentId) {
47
+ if (!sessionId || !agentId) return;
48
+ const m = flights.get(sessionId);
49
+ if (!m) return;
50
+ m.delete(agentId);
51
+ if (m.size === 0) flights.delete(sessionId);
52
+ }
53
+
54
+ /**
55
+ * 这个会话里还在飞的后台子代理。
56
+ *
57
+ * name 是实例名(角色)——翻不出来的(普通干活子代理)也照样算在飞,只是没名字。
58
+ *
59
+ * @param {string} sessionId
60
+ * @param {string|null} _projectId 旧签名的兼容位(候场判定退役后不再需要)
61
+ * @returns {Array<{ agentId, agentType, name, startedAt }>}
62
+ */
63
+ export function workingSubagents(sessionId, _projectId = null) {
64
+ const m = flights.get(sessionId);
65
+ if (!m || m.size === 0) return [];
66
+ return [...m].map(([agentId, info]) => ({ agentId, name: agentNameOf(agentId), ...info }));
67
+ }
68
+
69
+ /** 会话收摊 */
70
+ export function clearSessionFlights(sessionId) { flights.delete(sessionId); }
71
+
72
+ /** 测试用:清空全局态 */
73
+ export function _resetSubagentFlight() { flights.clear(); }
@@ -14,6 +14,7 @@
14
14
  import path from 'node:path';
15
15
  import fs from 'node:fs';
16
16
  import { fileURLToPath } from 'node:url';
17
+ import { DEFAULT_LOCALE } from '../../shared/locales.js';
17
18
 
18
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
20
 
@@ -40,6 +41,13 @@ const ADULT_POLICY = {
40
41
  strict: '露骨的成人内容在这个账号档位下不写——涉及时收敛处理、用留白带过。',
41
42
  };
42
43
 
44
+ // 界面语言的人话名(2026-08-26 i18n)。注给模型看的是「中文(zh-CN)」这种带 id 的写法:
45
+ // 光写「中文」模型不知道对应哪个 tag,光写 tag 又不如人话直白。
46
+ const UI_LOCALE_NAME = {
47
+ 'zh-CN': '中文(zh-CN)',
48
+ en: 'English(en)',
49
+ };
50
+
43
51
  /**
44
52
  * 「底线」那一节在 prelude 里有**两份并排写着**的版本,标记块框起来:
45
53
  * `nd:policy:full`(对外开放平台的完整产物政策)和 `nd:policy:min`。渲染时留一份、
@@ -51,6 +59,14 @@ const ADULT_POLICY = {
51
59
  */
52
60
  const POLICY_BLOCK = /<!-- nd:policy:(full|min):start -->\n([\s\S]*?)<!-- nd:policy:\1:end -->\n/g;
53
61
 
62
+ /**
63
+ * 项目模式分区(2026-08-27):prelude 里设计道专属的节框在 `nd:mode:design` 块里,
64
+ * 演出(常驻角色)专属的节框在 `nd:mode:rp` 块里,渲染时按项目模式留一族、删一族。
65
+ * 没框的部分是共用骨架。同一个文件里可以有**多个**同名标记块(设计道的节不连续)。
66
+ * 为什么是标记块不是两份 md:见 POLICY_BLOCK 注释 —— 单一真相源 + 切错当场炸。
67
+ */
68
+ const MODE_BLOCK = /<!-- nd:mode:(design|rp):start -->\n([\s\S]*?)<!-- nd:mode:\1:end -->\n/g;
69
+
54
70
  // 加载期断言:两份都必须在。少一份说明有人编辑 prelude 时把标记删了,那时候
55
71
  // 正则会静默退化成"一份都不删"(uncensored 路径拿到完整底线)或"整节消失"。
56
72
  {
@@ -60,6 +76,23 @@ const POLICY_BLOCK = /<!-- nd:policy:(full|min):start -->\n([\s\S]*?)<!-- nd:pol
60
76
  throw new Error(`[system-prompts] nodesign-prelude.md 缺少 nd:policy:${want} 标记块 —— 提示词渲染会静默走错版本`);
61
77
  }
62
78
  }
79
+ const modes = [...NODESIGN_PRELUDE.matchAll(MODE_BLOCK)].map((m) => m[1]);
80
+ for (const want of ['design', 'rp']) {
81
+ if (NODESIGN_PRELUDE && !modes.includes(want)) {
82
+ throw new Error(`[system-prompts] nodesign-prelude.md 缺少 nd:mode:${want} 标记块 —— 模式分区渲染会静默失效`);
83
+ }
84
+ }
85
+ }
86
+
87
+ // 占位符也要断言:`.replace('{{X}}', v)` 在占位符不存在时**静默什么都不做**,
88
+ // 于是线上会悄悄发出一份没有成人档、或者没有语言指令的提示词,不报错。
89
+ // 标记块那套已经这么防了,占位符这两个之前漏了(08-26 补)。
90
+ {
91
+ for (const ph of ['{{ADULT_POLICY}}', '{{UI_LOCALE}}']) {
92
+ if (NODESIGN_PRELUDE && !NODESIGN_PRELUDE.includes(ph)) {
93
+ throw new Error(`[system-prompts] nodesign-prelude.md 缺少 ${ph} 占位符 —— 渲染会静默少一段`);
94
+ }
95
+ }
63
96
  }
64
97
 
65
98
  /**
@@ -76,11 +109,19 @@ const POLICY_BLOCK = /<!-- nd:policy:(full|min):start -->\n([\s\S]*?)<!-- nd:pol
76
109
  * 前提不成立。留下的一条不随档位变,也不随谁在用变。
77
110
  *
78
111
  * ⚠️ 默认 false。调用方拿不到模型名时落**完整**那份,绝不落 min。
112
+ *
113
+ * `mode`:项目模式(projects.mode)。'design'(默认)留设计道分区,'rp' 留
114
+ * 演出分区。认不出的值落 design —— 存量项目全是 design,猜错方向宁可多给不少给。
79
115
  */
80
116
  export function renderPrelude(level = 'loose', opts = {}) {
81
117
  const keep = opts.uncensored === true ? 'min' : 'full';
118
+ const keepMode = opts.mode === 'rp' ? 'rp' : 'design';
119
+ // 认不出的 locale 落中文:这个产品是中文优先的,拿不准时给中文不给英文。
120
+ const localeName = UI_LOCALE_NAME[opts.locale] || UI_LOCALE_NAME[DEFAULT_LOCALE];
82
121
  return NODESIGN_PRELUDE
83
122
  .replace(POLICY_BLOCK, (_all, which, body) => (which === keep ? body : ''))
123
+ .replace(MODE_BLOCK, (_all, which, body) => (which === keepMode ? body : ''))
84
124
  .replace('{{ADULT_POLICY}}', ADULT_POLICY[level] || ADULT_POLICY.loose)
125
+ .replace('{{UI_LOCALE}}', localeName)
85
126
  .trim();
86
127
  }