@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
@@ -20,15 +20,17 @@
20
20
  import { promises as fs } from 'fs';
21
21
  import path from 'path';
22
22
  import { getSharedDir, ensureProjectWorkspace, gitRenamesSince } from './workspace.js';
23
- import { CHALK_DIR } from '../lib/chalk.js';
23
+ import { CHALK_DIR, trashChalkFile } from '../lib/chalk.js';
24
+ import { estimateSizeOn } from '../lib/board-kind-sizes.js';
24
25
 
25
26
  export {
26
27
  DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS,
27
28
  } from './board-limits.js';
28
29
  export { TEXT_FONTS } from './board-sanitize.js';
29
- import { DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS } from './board-limits.js';
30
+ import { DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS, MAX_LANES, MAX_SHEETS } from './board-limits.js';
30
31
  import {
31
- clampNum, sanitizeSize, sanitizeTag, sanitizeObject, sanitizeBinding, sanitizeZone, sanitizeBoard, isSafeCanvasId,
32
+ clampNum, sanitizeSize, sanitizeTag, sanitizeObject, sanitizeBinding, sanitizeZone, sanitizeBoard, sanitizeLane, sanitizeRoll, sanitizeSheet,
33
+ isSafeCanvasId,
32
34
  } from './board-sanitize.js';
33
35
 
34
36
  // 分区自动铺位常数 —— 与前端 BoardCanvas 的 ZONE_* 保持一致(数值约定,非共享代码)
@@ -107,7 +109,14 @@ export function patchBoard(pid, patch) {
107
109
  if (!isSafeCanvasId(rawId)) continue;
108
110
  const id = fwd(rawId);
109
111
  if (o === null) { delete board.objects[id]; removed.add(id); continue; }
110
- const s = sanitizeObject(o, board.size);
112
+ // **合并语义**(2026-08-25):patch 条目盖在已有条目上,缺席字段保留。
113
+ // 病根:前端本地 layout 会落后于服务端(board.json 一次性加载闸门),
114
+ // 入座把 agent 刚写的条目当新客回写一条 {x,y,z} —— 整条替换语义下
115
+ // by/seat/w/h/tag/staging 全被抹掉且零报错(08-25 体检三陷阱之②)。
116
+ // 合并后瘦条目只更新它带来的字段。要删整条传 null;单字段清除走
117
+ // 各自的专用路(commitStaging 清 staging、removeByTag 摘 tag)。
118
+ const merged = board.objects[id] ? { ...board.objects[id], ...o } : o;
119
+ const s = sanitizeObject(merged, board.size);
111
120
  if (s && (board.objects[id] || Object.keys(board.objects).length < MAX_OBJECTS)) board.objects[id] = s;
112
121
  }
113
122
  }
@@ -115,13 +124,81 @@ export function patchBoard(pid, patch) {
115
124
  for (const [id, b] of Object.entries(patch.bindings)) {
116
125
  if (typeof id !== 'string' || id.length > 300) continue;
117
126
  if (b === null) { delete board.bindings[id]; continue; }
118
- const s = sanitizeBinding(b);
127
+ // 合并语义同 objects(08-25):改材质/label 的瘦 patch 别抹掉 follow 等字段
128
+ const s = sanitizeBinding(board.bindings[id] ? { ...board.bindings[id], ...b } : b);
119
129
  if (!s) continue;
120
130
  // 端点也要转发:agent 本轮拿旧路径连的线,落下来必须连到新名字上
121
131
  const fixed = { ...s, from: fwd(s.from), to: fwd(s.to) };
122
132
  if (board.bindings[id] || Object.keys(board.bindings).length < MAX_BINDINGS) board.bindings[id] = fixed;
123
133
  }
124
134
  }
135
+ // 线注册表(08-27 空间规划):合并语义同 objects;parent 也走改名转发
136
+ if (patch?.lanes && typeof patch.lanes === 'object') {
137
+ board.lanes = board.lanes || {};
138
+ for (const [name, l] of Object.entries(patch.lanes)) {
139
+ const tag = sanitizeTag(name);
140
+ if (!tag) continue;
141
+ if (l === null) { delete board.lanes[tag]; continue; }
142
+ const s = sanitizeLane(board.lanes[tag] ? { ...board.lanes[tag], ...l } : l);
143
+ if (!s) continue;
144
+ const fixed = s.parent ? { ...s, parent: fwd(s.parent) } : s;
145
+ if (board.lanes[tag] || Object.keys(board.lanes).length < MAX_LANES) board.lanes[tag] = fixed;
146
+ }
147
+ if (!Object.keys(board.lanes).length) delete board.lanes;
148
+ }
149
+ // 卷(2026-08-27 收纳器):合并语义同 lanes;null = 展开(删条目)。
150
+ // 只动状态位不动成员座位 —— 展开即归位靠的就是这里什么都不搬。
151
+ if (patch?.rolls && typeof patch.rolls === 'object') {
152
+ board.rolls = board.rolls || {};
153
+ for (const [name, r] of Object.entries(patch.rolls)) {
154
+ const tag = sanitizeTag(name);
155
+ if (!tag) continue;
156
+ if (r === null) { delete board.rolls[tag]; continue; }
157
+ const s = sanitizeRoll(board.rolls[tag] ? { ...board.rolls[tag], ...r } : r);
158
+ if (!s) continue;
159
+ if (board.rolls[tag] || Object.keys(board.rolls).length < MAX_LANES) board.rolls[tag] = s;
160
+ }
161
+ if (!Object.keys(board.rolls).length) delete board.rolls;
162
+ }
163
+ // 纸(2026-08-29 纸范式):合并语义同 lanes;null = 撕掉登记(成员座位不动)。
164
+ // 纸名不是路径,不走改名转发。
165
+ if (patch?.sheets && typeof patch.sheets === 'object') {
166
+ board.sheets = board.sheets || {};
167
+ for (const [name, s0] of Object.entries(patch.sheets)) {
168
+ const nm = sanitizeTag(name);
169
+ if (!nm) continue;
170
+ if (s0 === null) { delete board.sheets[nm]; continue; }
171
+ const s = sanitizeSheet(board.sheets[nm] ? { ...board.sheets[nm], ...s0 } : s0);
172
+ if (!s) continue;
173
+ if (board.sheets[nm] || Object.keys(board.sheets).length < MAX_SHEETS) board.sheets[nm] = s;
174
+ }
175
+ if (!Object.keys(board.sheets).length) delete board.sheets;
176
+ }
177
+ // 跟随规则(2026-08-30):`{ 组tag: {target,side?,label?} | null }`。规则和线分开存
178
+ // —— 线要求两端此刻都在板上,规则不要求,所以「开场先立规则再写第一章」这条
179
+ // skill 教了、模型也一直照做的顺序,第一次真的走得通。
180
+ if (patch?.follows && typeof patch.follows === 'object') {
181
+ board.follows = board.follows || {};
182
+ for (const [g, r] of Object.entries(patch.follows)) {
183
+ const gt = sanitizeTag(g);
184
+ if (!gt) continue;
185
+ if (r === null) { delete board.follows[gt]; continue; }
186
+ board.follows[gt] = r;
187
+ }
188
+ const clean = sanitizeBoard({ ...board, follows: board.follows }).follows;
189
+ if (clean) board.follows = clean; else delete board.follows;
190
+ }
191
+ // 待摆产物队列(刀 G):整表替换(写方只有 board-seater 一个,diff 没意义)
192
+ if (patch?.pending !== undefined) {
193
+ const clean = sanitizeBoard({ ...board, pending: patch.pending }).pending;
194
+ if (clean?.length) board.pending = clean; else delete board.pending;
195
+ }
196
+ // 暂存架原点(2026-08-30):整值替换,写方只有 board-seater / board-tasklist;
197
+ // null = 撤架(下批到货重立)
198
+ if (patch?.shelf !== undefined) {
199
+ const clean = sanitizeBoard({ ...board, shelf: patch.shelf }).shelf;
200
+ if (clean) board.shelf = clean; else delete board.shelf;
201
+ }
125
202
  // 主角覆盖:null = 撤销(回到 pickHero 自动推断),字符串 = 显式立主角
126
203
  if (patch?.hero !== undefined) {
127
204
  if (patch.hero === null) delete board.hero;
@@ -205,7 +282,8 @@ export function removeByTag(pid, tag) {
205
282
  // 以进程身份删任意文件;sanitize 现在也拒 `..`,这里是第二道闸)
206
283
  if (id.startsWith(`${CHALK_DIR}/`)) {
207
284
  const abs = chalkAbsPath(pid, id);
208
- if (abs) { try { await fs.unlink(abs); } catch { /* 已经没了 */ } }
285
+ // 软删进 .nd/trash/(08-25:擦掉的板书要捞得回来)
286
+ if (abs) await trashChalkFile(getSharedDir(pid), abs);
209
287
  delete board.objects[id]; gone.add(id); removed += 1; continue;
210
288
  }
211
289
  delete o.tag;
@@ -214,6 +292,8 @@ export function removeByTag(pid, tag) {
214
292
  if (b.tag === t || gone.has(b.from) || gone.has(b.to)) { delete board.bindings[id]; removed += 1; }
215
293
  }
216
294
  if (board.hero && gone.has(board.hero)) delete board.hero;
295
+ // 擦组连卷的状态位一起清(收着的组被 erase_group 后不该留一张空卷卡)
296
+ if (board.rolls?.[t]) { delete board.rolls[t]; if (!Object.keys(board.rolls).length) delete board.rolls; }
217
297
  await writeBoard(pid, board);
218
298
  return { board, removed };
219
299
  });
@@ -451,6 +531,8 @@ export async function reconcileBoardRenames(pid) {
451
531
  * 文件夹没坐标(还没被摆过)时就摆到桌面上,前端下一轮会给它安排位置。
452
532
  */
453
533
  export function pinToZone(pid, { objectId, zoneId = '' }) {
534
+ // 08-27 审计修:这里是 patchBoard 之外唯一的 objects 写入口,原先没有数量闸 ——
535
+ // 反复 pin 新 id 可把 board.objects 撑过 MAX_OBJECTS(只剩 2MB 字节兜底)。
454
536
  return withBoardLock(pid, async () => {
455
537
  const board = await readBoard(pid);
456
538
  const now = Date.now();
@@ -494,12 +576,19 @@ export function pinToZone(pid, { objectId, zoneId = '' }) {
494
576
  }
495
577
  if (!slot) slot = { x: PAD, y: PAD };
496
578
 
579
+ if (!board.objects[oid] && Object.keys(board.objects).length >= MAX_OBJECTS) {
580
+ throw new Error(`board 已有 ${MAX_OBJECTS} 件,pin 不进新条目(失控兜底,正常用不该撞到)`);
581
+ }
497
582
  const zMax = Math.max(10, ...Object.values(board.objects).map(o => o.z || 0));
583
+ // 落座即写估算 w/h(2026-08-29 纸范式刀 3):此前 pin 出来的条目只有坐标,
584
+ // read_board 只能报形态表猜值。估算先落盘、前端渲染后按真值回写(useMeasuredSize)。
585
+ const sz = estimateSizeOn(board, oid, board.objects[oid] || null);
498
586
  board.objects[oid] = {
499
587
  ...(board.objects[oid] || {}),
500
588
  x: clampNum(slot.x, 0, board.size.w, PAD),
501
589
  y: clampNum(slot.y, 0, board.size.h, PAD),
502
590
  z: zMax + 1,
591
+ ...(Number.isFinite(board.objects[oid]?.w) ? {} : { w: Math.round(sz.w), h: Math.round(sz.h) }),
503
592
  };
504
593
  await writeBoard(pid, board);
505
594
  return { board, zone: zid ? { id: zid } : null, placed: board.objects[oid] };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * server/projects/session-jsonl.js — SDK 会话转录(jsonl)的文件层(2026-08-30 拆出)。
3
+ *
4
+ * 这里只有"那个文件在哪、在不在、怎么安全地截短"这三件事,跟任何业务无关。
5
+ * 分开住是因为读它的人不止一个:回退要截断它,模型切换要靠它判断"这个会话跑过没有"
6
+ * (没跑过的会话没有历史,跨通路闸不该拦),以后想做分支浏览的也得从它读。
7
+ *
8
+ * 转录的位置由 SDK 定:`<CLAUDE_CONFIG_DIR>/projects/<encodeCwdForSDK(cwd)>/<sid>.jsonl`。
9
+ * 扁平化之后同一个项目所有会话共用一个 cwd,所以它们的 jsonl 都在同一个目录下、按 sid 分文件。
10
+ */
11
+
12
+ import path from 'path';
13
+ import { promises as fs } from 'fs';
14
+ import { encodeCwdForSDK } from './workspace.js';
15
+ import { platform } from '../runtime/platform.js';
16
+
17
+ const GLOBAL_CLAUDE_CONFIG_DIR = platform.claudeConfigDir;
18
+
19
+ /** 这个会话的转录文件路径。 */
20
+ export function sessionJsonlPath(sessionRoot, sid) {
21
+ return path.join(GLOBAL_CLAUDE_CONFIG_DIR, 'projects', encodeCwdForSDK(sessionRoot), `${sid}.jsonl`);
22
+ }
23
+
24
+ /** 转录在不在 —— 等价于"这个会话跑过至少一轮"。 */
25
+ export async function jsonlExistsForSession(sessionRoot, sid) {
26
+ try {
27
+ await fs.access(sessionJsonlPath(sessionRoot, sid));
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 对话层回滚(2026-08-08):把 jsonl 截断到 userMessageId 那条之前(含它与其后全部)。
36
+ * jsonl 是追加式日志,截到 prefix = 它历史上真实存在过的状态,resume 天然自洽;
37
+ * 之后的 file-history-snapshot 属于被撤销的编辑,一并丢弃是正确语义。
38
+ * 原子写(tmp+rename)。找不到该 uuid 返 null(fail-soft:文件回滚仍算成功)。
39
+ */
40
+ export async function truncateJsonlAtMessage(sessionRoot, sid, userMessageId) {
41
+ const jsonlPath = sessionJsonlPath(sessionRoot, sid);
42
+ try {
43
+ const raw = await fs.readFile(jsonlPath, 'utf8');
44
+ const lines = raw.split('\n');
45
+ let cut = -1;
46
+ for (let i = 0; i < lines.length; i++) {
47
+ if (!lines[i] || !lines[i].includes(userMessageId)) continue;
48
+ try { if (JSON.parse(lines[i]).uuid === userMessageId) { cut = i; break; } } catch { /* 非 JSON 行跳过 */ }
49
+ }
50
+ if (cut < 0) {
51
+ console.warn(`[sessions.rewind] uuid ${userMessageId.slice(0, 8)} 不在 jsonl 里,跳过对话截断`);
52
+ return null;
53
+ }
54
+ const kept = lines.slice(0, cut).join('\n');
55
+ const tmp = `${jsonlPath}.tmp-rewind`;
56
+ await fs.writeFile(tmp, kept ? `${kept}\n` : '');
57
+ await fs.rename(tmp, jsonlPath);
58
+ return lines.filter(Boolean).length - lines.slice(0, cut).filter(Boolean).length;
59
+ } catch (err) {
60
+ console.warn(`[sessions.rewind] jsonl 截断失败(不影响文件回滚):${err.message}`);
61
+ return null;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * 砍掉转录末尾的那条用户消息(连同它之后的一切)。
67
+ *
68
+ * 专给 fork 用。SDK 的 `forkSession(upToMessageId)` 是**含**那条的(08-30 探针实测:
69
+ * fork 到第二条用户消息,新转录里那条还在),而「从这里分叉」用户点的是**自己那句
70
+ * 说错的话** —— 含着它 fork 出来等于什么都没改。所以 fork 完再补这一刀。
71
+ *
72
+ * 为什么不能拿原来那个 uuid 去截:fork 会 **remap 每条消息的 uuid**(SDK 文档原话
73
+ * "remapping every message UUID"),原 id 在新文件里根本不存在。而 fork 既然截止在
74
+ * 那条消息,它必然就是新转录里最后一条**真**用户消息 —— tool_result 也顶着
75
+ * type='user' 的壳,所以要把那种排掉,认的是带 text block(或纯字符串正文)的那条。
76
+ *
77
+ * @returns {Promise<number|null>} 删掉的行数;没找到用户消息返回 null(fail-soft)
78
+ */
79
+ export async function truncateJsonlAtLastUserMessage(sessionRoot, sid) {
80
+ const jsonlPath = sessionJsonlPath(sessionRoot, sid);
81
+ try {
82
+ const raw = await fs.readFile(jsonlPath, 'utf8');
83
+ const lines = raw.split('\n');
84
+ let cut = -1;
85
+ for (let i = lines.length - 1; i >= 0; i--) {
86
+ if (!lines[i]) continue;
87
+ let o;
88
+ try { o = JSON.parse(lines[i]); } catch { continue; }
89
+ if (o.type !== 'user') continue;
90
+ const c = o.message?.content;
91
+ const isRealUserText = typeof c === 'string'
92
+ || (Array.isArray(c) && c.some((b) => b?.type === 'text'));
93
+ if (!isRealUserText) continue; // tool_result 也是 type='user'
94
+ cut = i;
95
+ break;
96
+ }
97
+ if (cut < 0) {
98
+ console.warn(`[fork] ${sid.slice(0, 8)} 的转录里找不到用户消息,跳过补刀`);
99
+ return null;
100
+ }
101
+ const kept = lines.slice(0, cut).join('\n');
102
+ const tmp = `${jsonlPath}.tmp-fork`;
103
+ await fs.writeFile(tmp, kept ? `${kept}\n` : '');
104
+ await fs.rename(tmp, jsonlPath);
105
+ return lines.filter(Boolean).length - lines.slice(0, cut).filter(Boolean).length;
106
+ } catch (err) {
107
+ console.warn(`[fork] 转录补刀失败:${err.message}`);
108
+ return null;
109
+ }
110
+ }
@@ -82,6 +82,15 @@ if (!projectsColNames.has('auto_named')) {
82
82
  console.log('[projects/store] projects.auto_named column added');
83
83
  }
84
84
 
85
+ // 模式分离(2026-08-27):design(设计工作台,现状)vs rp(演出:常驻角色演故事)。
86
+ // 两种模式共用全部基础设施(画布/黑板/会话/精灵),差异只在提示词分区与工具面
87
+ // (对照表在 engine/mcp/mode-profile.js 一份)。默认 'design' 让全部存量项目零行为变化。
88
+ // 会话启动时读一次 —— 切模式**下个会话生效**,跟"改人设下次上场生效"同一节奏。
89
+ if (!projectsColNames.has('mode')) {
90
+ db.exec("ALTER TABLE projects ADD COLUMN mode TEXT NOT NULL DEFAULT 'design'");
91
+ console.log('[projects/store] projects.mode column added (default design)');
92
+ }
93
+
85
94
  // 多用户内测(2026-07-30):项目归属。NULL 的存量行由 bootstrapAuth() 回填 admin
86
95
  if (!projectsColNames.has('owner_id')) {
87
96
  db.exec('ALTER TABLE projects ADD COLUMN owner_id TEXT');
@@ -151,6 +160,7 @@ function rowToProject(row) {
151
160
  skillId: row.skill_id,
152
161
  description: row.description || null,
153
162
  kind: row.kind || 'project',
163
+ mode: row.mode || 'design',
154
164
  autoNamed: !!row.auto_named,
155
165
  ownerId: row.owner_id || null,
156
166
  activeSessionId: row.active_session_id,
@@ -204,6 +214,7 @@ export function createProject({
204
214
  skillId = 'deskskill-engine-mini',
205
215
  description = null,
206
216
  kind = 'project',
217
+ mode = 'design',
207
218
  autoNamed = false,
208
219
  ownerId = null,
209
220
  }) {
@@ -211,15 +222,18 @@ export function createProject({
211
222
  if (kind !== 'project' && kind !== 'quick') {
212
223
  throw new Error(`createProject: kind 非法 (${kind})`);
213
224
  }
225
+ if (mode !== 'design' && mode !== 'rp') {
226
+ throw new Error(`createProject: mode 非法 (${mode})`);
227
+ }
214
228
  const id = newProjectId();
215
229
  const desc = (typeof description === 'string' && description.trim()) ? description.trim() : null;
216
230
  db.prepare(
217
- `INSERT INTO projects (id, name, skill_id, description, kind, auto_named, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?)`,
218
- ).run(id, name.trim(), skillId, desc, kind, autoNamed ? 1 : 0, ownerId);
231
+ `INSERT INTO projects (id, name, skill_id, description, kind, mode, auto_named, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
232
+ ).run(id, name.trim(), skillId, desc, kind, mode, autoNamed ? 1 : 0, ownerId);
219
233
  return getProject(id);
220
234
  }
221
235
 
222
- /** 更新(仅允许 name / skill_id / description / active_session_id / kind / autoNamed) */
236
+ /** 更新(仅允许 name / skill_id / description / active_session_id / kind / mode / autoNamed) */
223
237
  export function updateProject(id, patch) {
224
238
  validateProjectId(id);
225
239
  // 用户显式改名 = 这名字他自己定了,系统不再拿摘要覆盖
@@ -228,12 +242,16 @@ export function updateProject(id, patch) {
228
242
  if ('kind' in patch && patch.kind !== 'project' && patch.kind !== 'quick') {
229
243
  throw new Error(`updateProject: kind 非法 (${patch.kind})`);
230
244
  }
245
+ if ('mode' in patch && patch.mode !== 'design' && patch.mode !== 'rp') {
246
+ throw new Error(`updateProject: mode 非法 (${patch.mode})`);
247
+ }
231
248
  const map = {
232
249
  name: 'name',
233
250
  skillId: 'skill_id',
234
251
  description: 'description',
235
252
  activeSessionId: 'active_session_id',
236
253
  kind: 'kind',
254
+ mode: 'mode',
237
255
  autoNamed: 'auto_named',
238
256
  };
239
257
  const sets = [];
@@ -264,6 +282,41 @@ export function setActiveSession(projectId, sessionId) {
264
282
  }
265
283
 
266
284
  /** 列 project 关联的 runs(用于级联清理) */
285
+ /**
286
+ * 两个模式各自的用量(控制台那两张卡)。
287
+ *
288
+ * ⛔ **排掉站主自己**:他一个人的设计项目就比所有用户加起来还多,混进去这两个数
289
+ * 就只是在量他自己。同仓口径,见 feedback-creative-tool-metrics。
290
+ *
291
+ * ⚠️ 两条查询分开跑不合并:runs 要 join run_model_usage 才拿得到钱,而那张表
292
+ * 一个 run 有多行(一轮里换过几个模型就几行),并到项目那条里会把 projects
293
+ * 乘出好几份。COUNT(DISTINCT) 能救回来,但读的人得先看出这里有个陷阱。
294
+ *
295
+ * @returns {Array<{mode, projects, users, runs, costUsd, firstAt}>}
296
+ */
297
+ export function modeStats() {
298
+ const HUMANS = "p.owner_id IN (SELECT id FROM users WHERE role <> 'admin')";
299
+ const out = new Map();
300
+ for (const r of db.prepare(
301
+ `SELECT p.mode AS mode, COUNT(*) AS projects, COUNT(DISTINCT p.owner_id) AS users,
302
+ MIN(p.created_at) AS firstAt
303
+ FROM projects p WHERE ${HUMANS} GROUP BY p.mode`,
304
+ ).all()) out.set(r.mode, { ...r, runs: 0, costUsd: 0 });
305
+
306
+ for (const r of db.prepare(
307
+ `SELECT p.mode AS mode, COUNT(DISTINCT r.id) AS runs,
308
+ COALESCE(SUM(m.cost_usd), 0) AS costUsd
309
+ FROM runs r
310
+ JOIN projects p ON p.id = r.project_id
311
+ LEFT JOIN run_model_usage m ON m.run_id = r.id
312
+ WHERE ${HUMANS} GROUP BY p.mode`,
313
+ ).all()) {
314
+ const e = out.get(r.mode) || { mode: r.mode, projects: 0, users: 0, firstAt: null };
315
+ out.set(r.mode, { ...e, runs: r.runs, costUsd: r.costUsd });
316
+ }
317
+ return [...out.values()];
318
+ }
319
+
267
320
  export function listRunsForProject(projectId) {
268
321
  validateProjectId(projectId);
269
322
  return db.prepare('SELECT id FROM runs WHERE project_id = ? ORDER BY created_at DESC').all(projectId);
@@ -30,9 +30,27 @@ function sanitizeViewpoint(raw, userId) {
30
30
  } : null;
31
31
  if (camera && Object.values(camera).some(v => v === null)) return null;
32
32
  const str = (v, n) => (typeof v === 'string' && v.length <= n ? v : null);
33
+ /**
34
+ * 设备档(2026-08-28 移动端第二轮)。**浏览器判好了报上来**,这边只验不算 ——
35
+ * 真屏幕多大、是不是手指,只有浏览器知道;服务端手里只有相机矩形和缩放,
36
+ * 反推屏幕要除以缩放,而且分不清「390 宽的手机」和「拖窄了的桌面窗口」。
37
+ * 认不出的档一律当桌面:宁可给手机用户一份桌面版式(能用,只是要缩放),
38
+ * 也别给桌面用户一份 342 宽的窄条(那是坏的)。
39
+ */
40
+ const dc = raw.device && typeof raw.device === 'object' ? raw.device : null;
41
+ const dw = num(dc?.w, 200, 8000); const dh = num(dc?.h, 200, 8000);
42
+ // ⚠️ 夹持在这儿是错的判法:一个夹出来的 8000 会变成 8000 宽的版式建议。
43
+ // 尺寸不在真实屏幕的范围里就**整个丢掉 device**,退回相机×缩放那条老路。
44
+ const device = (dc && dw !== null && dh !== null && dw === Number(dc.w) && dh === Number(dc.h)) ? {
45
+ class: (dc.class === 'phone' || dc.class === 'tablet') ? dc.class : 'desktop',
46
+ w: dw, h: dh,
47
+ dpr: num(dc.dpr, 0.5, 8),
48
+ coarse: !!dc.coarse,
49
+ } : null;
33
50
  return {
34
51
  userId: userId || null,
35
52
  camera,
53
+ device,
36
54
  zoom: num(raw.zoom, 0.01, 100),
37
55
  layer: str(raw.layer, 300) || '',
38
56
  openWindow: str(raw.openWindow, 300),
@@ -56,18 +74,32 @@ export function getViewpoint(pid) {
56
74
  return v;
57
75
  }
58
76
 
77
+ /** 档位的人话(注入和工具共用一份措辞;桌面不说 —— 那是默认情况,说了是噪音) */
78
+ export function describeDevice(v) {
79
+ const d = v?.device;
80
+ if (!d || d.class === 'desktop') return null;
81
+ const name = d.class === 'phone' ? '手机' : '平板';
82
+ return `${name}(屏幕 ${d.w}x${d.h}px,触屏)`;
83
+ }
84
+
59
85
  /** 一行人话(注入/工具共用一份措辞)。rects 可选:给了就报视口里有什么 */
60
86
  export function describeViewpoint(v, rects = null) {
61
87
  if (!v) return null;
62
88
  const bits = [];
89
+ const dev = describeDevice(v);
90
+ if (dev) bits.push(`用的是${dev}`);
63
91
  if (v.layer) bits.push(`在文件夹「${v.layer}」里`);
64
92
  if (v.camera) {
65
93
  bits.push(`视口 (${Math.round(v.camera.x)},${Math.round(v.camera.y)}) ${Math.round(v.camera.w)}x${Math.round(v.camera.h)}${v.zoom ? ` 缩放 ${Number(v.zoom).toFixed(2)}` : ''}`);
66
94
  if (rects) {
67
95
  const c = v.camera;
68
- const inside = rects.filter(r => !(r.x + r.w < c.x || r.x > c.x + c.w || r.y + r.h < c.y || r.y > c.y + c.h)).map(r => r.id);
69
- if (inside.length) bits.push(`视口里有 ${inside.length} 件:${inside.slice(0, 10).join('、')}${inside.length > 10 ? ' 等' : ''}`);
70
- else bits.push('视口里是空地');
96
+ const inside = rects.filter(r => !(r.x + r.w < c.x || r.x > c.x + c.w || r.y + r.h < c.y || r.y > c.y + c.h));
97
+ // 带坐标和占位(08-27 用户提):agent 摆放要知道视口里谁占了哪,不该再专门调工具去问
98
+ if (inside.length) {
99
+ bits.push(`视口里 ${inside.length} 件(id@(x,y)宽x高):${inside.slice(0, 12)
100
+ .map(r => `${r.id}@(${Math.round(r.x)},${Math.round(r.y)})${Math.round(r.w)}x${Math.round(r.h)}`)
101
+ .join('、')}${inside.length > 12 ? ' 等' : ''}`);
102
+ } else bits.push('视口里是空地');
71
103
  }
72
104
  }
73
105
  if (v.openWindow) bits.push(`开着窗:${v.openWindow}${v.openPage ? `(${v.openPage})` : ''}`);
@@ -0,0 +1,30 @@
1
+ /**
2
+ * projects/workspace-slots.js —— 角色位铺装(2026-08-28 建;08-29 收成一个位)
3
+ *
4
+ * 预注册的角色子代理定义(rp-role)**必须在会话启动前就在盘上**:会话中途写入的
5
+ * agent 定义只有当时活着的那个 CLI 进程认得,换进程后永久 not found(08-28 三场
6
+ * 真会话勘定,机制见 engine/agent/cast.js)。所以铺装挂在 ensureProjectWorkspace 里,
7
+ * 跟着每次 ensure 走。
8
+ *
9
+ * 内容以代码为准:教义升级后重启即生效,也顺手把模型对定义体的手改抹平
10
+ * (判据不建在模型可写的东西上 —— 派发闸照读文件核 tools,这里保证文件是正版)。
11
+ *
12
+ * 退役的旧位(rp-actor / rp-narrator)在这里**删文件**:留着的话老名字仍然派得出去,
13
+ * 派出来的还是旧分工(写场面的那支笔已经收回主持人手里了)。
14
+ */
15
+ import { promises as fs } from 'fs';
16
+ import path from 'path';
17
+ import { ROLE_SLOT, RETIRED_SLOTS, slotAgentFile } from '../engine/agent/cast.js';
18
+ import { MCP_SERVER_NAME } from '../engine/mcp/server-name.js';
19
+
20
+ export async function ensureActorSlots(root) {
21
+ const dir = path.join(root, '.claude', 'agents');
22
+ const file = path.join(dir, `${ROLE_SLOT}.md`);
23
+ const want = slotAgentFile(MCP_SERVER_NAME);
24
+ let have = null;
25
+ try { have = await fs.readFile(file, 'utf8'); } catch { /* 还没有 */ }
26
+ if (have !== want) await fs.writeFile(file, want, 'utf8');
27
+ for (const old of RETIRED_SLOTS) {
28
+ try { await fs.unlink(path.join(dir, `${old}.md`)); } catch { /* 本来就没有 */ }
29
+ }
30
+ }
@@ -54,3 +54,32 @@ export const DEFAULT_CLAUDE_MD = `# 项目档案
54
54
  ## 用户习惯
55
55
  (称呼、语气、工作方式偏好。)
56
56
  `;
57
+
58
+ // rp(演出)项目的档案模板(2026-08-28;08-30 随角色子代理停用改口径)。
59
+ // ⚠️ 原注:这份文件**每个角色子代理也强制吃**(项目 CLAUDE.md 进所有子代理上下文,
60
+ // omitClaudeMd 透不过去 —— 08-26 实测)。08-30 起主 agent 代演全场,暂时没有子代理
61
+ // 在读它,但这条约束等角色回来还成立,别按"只有一个读者"重排栏目。
62
+ // 详细设定别塞 —— 放世界书文件按需 grep,这份是每轮都重发的常驻开销。
63
+ export const DEFAULT_CLAUDE_MD_RP = `# 演出档案
64
+
65
+ 这份文件每次会话都完整进入 agent 的上下文。放**定了就不常变**的东西;
66
+ 会生长的记忆 agent 自己记在 记忆/ 里。详细设定放世界书文件(用到时 grep),
67
+ 别塞进这里 —— 这份是每轮都重发的常驻开销。改硬约束要经用户点头。
68
+
69
+ ## 世界观锚
70
+ (一句话定调:什么世界、什么时代、什么基调。例:低魔剑与魔法,冒险者公会体系。
71
+ 从酒馆世界书搬来的常驻条目也放这,十条以内 —— 其余降级成触发文件。)
72
+
73
+ ## 台面规矩
74
+ (这一场怎么玩:想跟谁演(一对一 / 一群人)/ 难度档(爽·写实·严酷)/
75
+ 代笔档(全代入·大纲)/ 骰子规则 / 文风与叙事禁令 / 禁区题材。)
76
+
77
+ ## 人物
78
+ (在场人物一览:每人三行核心 —— 是谁、什么关系、绝不做什么。这里是名录;
79
+ 每个人完整的人设在 角色/<名>/角色卡.md(cast_role 写的那张),写他的话之前先读。)
80
+
81
+ ## 称呼与习惯
82
+ (用户扮演谁、怎么称呼;语气与节奏偏好。)
83
+ `;
84
+ // ⚠️ 上面四节的节名是「演出档案骨架」的唯一真相 —— blackboard-rp SKILL 的酒馆导入
85
+ // 落点表按同一套节名指路(08-28 对账:此前两处各写一套骨架,字段对不上)。改节名两处同改。
@@ -42,8 +42,9 @@ import { spawn } from 'child_process';
42
42
  import path from 'path';
43
43
  import { fileURLToPath } from 'url';
44
44
  import { mutex } from 'async-mutex-lite';
45
- import { validateProjectId } from './store.js';
45
+ import { validateProjectId, getProject } from './store.js';
46
46
  import { resolveModelContextWindow } from '../engine/agent/model-context.js';
47
+ import { ensureActorSlots } from './workspace-slots.js';
47
48
 
48
49
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
49
50
 
@@ -87,7 +88,7 @@ export function validateSessionId(sid) {
87
88
  }
88
89
 
89
90
  import {
90
- DEFAULT_GITIGNORE, DEFAULT_CLAUDE_MD,
91
+ DEFAULT_GITIGNORE, DEFAULT_CLAUDE_MD, DEFAULT_CLAUDE_MD_RP,
91
92
  } from './workspace-templates.js';
92
93
  import { migrateMemoryLayout } from './memory-migration.js';
93
94
 
@@ -219,16 +220,11 @@ export function getSessionMetaDir(projectId, sessionId) {
219
220
  // ── ensure ──
220
221
 
221
222
  /**
222
- * 创建项目工作区(幂等)。完成后保证:
223
- * - .claude/{CLAUDE.md, settings.json} 模板写入(仅不存在时)
224
- * - .claude/{skills, agents, agent-memory} 目录存在
225
- * - assets/ 存在、.gitignore 写入
226
- * - 旧的三层结构(tasks/ + per-session 沙盒)已扁平化
227
- * - .git 存在(项目级历史;扁平化前是 per-session 的)
228
- *
229
- * 扁平化跟着 ensure 走而不是单独一次性脚本:跟 removeRootLegacyArtifacts 同一个
230
- * 范式 —— 幂等、按项目惰性触发、跑过一次之后是 no-op。这样线上不需要停机窗口,
231
- * 也不存在"迁移脚本漏了哪个项目"。
223
+ * 创建项目工作区(幂等)。完成后保证:.claude/{CLAUDE.md, settings.json} 模板
224
+ * (仅不存在时)、.claude/{skills, agents, agent-memory} 与 assets/ 目录、演员位
225
+ * 定义、.gitignore、旧三层结构已扁平化、.git 存在(项目级历史)。
226
+ * 扁平化跟着 ensure 走而不是一次性脚本:幂等、按项目惰性触发、跑过即 no-op ——
227
+ * 线上不需要停机窗口,也不存在"迁移脚本漏了哪个项目"。
232
228
  */
233
229
  export async function ensureProjectWorkspace(projectId) {
234
230
  await removeRootLegacyArtifacts(projectId);
@@ -239,16 +235,19 @@ export async function ensureProjectWorkspace(projectId) {
239
235
  await fs.mkdir(path.join(root, '.claude', 'agent-memory'), { recursive: true });
240
236
  await fs.mkdir(path.join(root, 'assets'), { recursive: true });
241
237
 
238
+ // 演员位(2026-08-28 重构):预注册 RP 子代理定义,必须先于任何会话在盘上(拆件见文件)
239
+ await ensureActorSlots(root);
240
+
242
241
  // .gitignore 每次比对而不是"不存在才写":扁平化新增了 .claude/projects/ 和
243
242
  // .nd/ 两条,老项目的文件里没有,不补的话 SDK 转录会被 commit 进项目历史。
244
243
  await ensureGitignore(path.join(root, '.gitignore'));
245
- // 记忆体系改版迁移(2026-08-24,幂等:源不在了就什么都不做)——
246
- // CLAUDE.md .claude/ 挪到工作区根(画布可见,SDK 两处都读、根优先级同级),
247
- // 老的偏好/风格档案并进去,SDK auto-memory 的存量从 .claude/agent-memory/auto
248
- // 搬到画布可见的 记忆/。三步全是"搬走后源删除",跑几遍结果一样。
244
+ // 记忆体系改版迁移(2026-08-24,幂等:源不在了就什么都不做;三步全是"搬走后源删除")
245
+ // —— CLAUDE.md 挪工作区根、旧偏好/风格档案并入、auto-memory 存量搬 记忆/。细节见函数文档。
249
246
  await migrateMemoryLayout(root, { fileExists });
250
247
  if (!(await fileExists(path.join(root, 'CLAUDE.md')))) {
251
- await fs.writeFile(path.join(root, 'CLAUDE.md'), DEFAULT_CLAUDE_MD, 'utf8');
248
+ // rp 项目的档案按"戏"设栏(templates 注释);已有 CLAUDE.md 的一字不动,用户内容优先于模板换代
249
+ const isRp = (getProject(projectId)?.mode || 'design') === 'rp';
250
+ await fs.writeFile(path.join(root, 'CLAUDE.md'), isRp ? DEFAULT_CLAUDE_MD_RP : DEFAULT_CLAUDE_MD, 'utf8');
252
251
  }
253
252
  // settings.json:每次 merge defaults 让代码层 default 升级时现存 project 自动跟上
254
253
  // (用户字段优先,缺失的 NoDesign default 字段补进去)
@@ -17,6 +17,9 @@ import fs from 'node:fs';
17
17
  import os from 'node:os';
18
18
  import path from 'node:path';
19
19
  import { isAvailable as rembgAvailable, REMBG_SETUP_HINT } from '../engine/mcp/tools/helpers/rembg.js';
20
+ // 盒子开没开机只有一个判据 —— h3box-ssh.localBoxEnabled。这里曾经自己写了一遍
21
+ // `=== 'on'`,跟那边 `!== 'off'`(默认开)的默认值正好相反,同一个变量两套读法。
22
+ import { localBoxEnabled } from '../engine/mcp/tools/h3box-ssh.js';
20
23
 
21
24
  const isWin = process.platform === 'win32';
22
25
 
@@ -96,8 +99,9 @@ export const CAPABILITY_DEFS = Object.freeze([
96
99
  } },
97
100
  { id: 'localBox', kind: 'service', level: 'feature', label: '本地 GPU 盒子(paint_still / roll_film)', uses: '自部署生图 / 文生视频',
98
101
  fix: 'NODESIGN_LOCAL_BOX=on + NODESIGN_H3BOX_SSH(站主自己的 5090 盒子;一般用户用不上)',
99
- probe: () => (process.env.NODESIGN_LOCAL_BOX === 'on' && process.env.NODESIGN_H3BOX_SSH
100
- ? { available: true, detail: process.env.NODESIGN_H3BOX_SSH } : { available: false, detail: process.env.NODESIGN_LOCAL_BOX === 'on' ? 'NODESIGN_H3BOX_SSH 为空' : 'NODESIGN_LOCAL_BOX 未开' }) },
102
+ probe: () => (localBoxEnabled() && process.env.NODESIGN_H3BOX_SSH
103
+ ? { available: true, detail: process.env.NODESIGN_H3BOX_SSH }
104
+ : { available: false, detail: !localBoxEnabled() ? '站主标记为关机(NODESIGN_LOCAL_BOX=off)' : 'NODESIGN_H3BOX_SSH 为空' }) },
101
105
  ]);
102
106
 
103
107
  function bin(name, extra = []) {