@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
@@ -27,7 +27,17 @@ import { readUiConfigFile, withUiDefaults } from '../../../projects/ui-config.js
27
27
  import { readAssetsSummary } from '../../../projects/assets-summary.js';
28
28
  import { relationsDigest } from '../../../lib/board-relations.js';
29
29
  import { getViewpoint, describeViewpoint } from '../../../projects/viewpoint-store.js';
30
+ import { sheetSummaries, latestSheetId, currentSheet, sheetOfPoint } from '../../../lib/board-sheets.js';
31
+ import { currentSheetIdOf } from '../../../lib/sheet-state.js';
32
+ import { dirtyEvents, describeDirty, splitDirtyByCharge } from '../../../lib/board-dirty.js';
33
+ import { fitFor } from '../../../lib/sketch-layout.js';
34
+ import { readStateVars } from '../../../lib/state-table.js';
35
+ import { parseTriggers, evalTriggers, readLatch, writeLatch } from '../../../lib/state-triggers.js';
30
36
  import { recentChalk, CHALK_DIR } from '../../../lib/chalk.js';
37
+ import { listRoleNames } from '../role-card.js';
38
+ import { stageStatus } from '../stage-status.js';
39
+ import { resetBeat } from '../beat-state.js';
40
+ import { roleLabel } from '../../mcp/actor.js';
31
41
  import { readBoard } from '../../../projects/board-store.js';
32
42
  import { estimateSizeOn } from '../../../lib/board-kind-sizes.js';
33
43
  import { layerOf } from '../../../lib/canvas-id.js';
@@ -105,13 +115,99 @@ async function collectSections({ workspaceRoot, sessionId, projectId }) {
105
115
  w: vp.camera.w, h: vp.camera.h,
106
116
  } } : vp;
107
117
  const line = describeViewpoint(q, rects);
108
- if (line) sections.push({ key: 'viewpoint', title: '用户视点', text: `用户此刻在画布上:${line}。他说「这个/这里/这张」多半指选中的 > 开着的窗 > 视口里的东西;要细节调 read_user_view。` });
118
+ // 纸(2026-08-29 纸范式):报当前有哪些纸、剩多少地 —— 空位建议/走向学习
119
+ // 那套启发式随落位引擎一起退役,agent 的空间账本现在是纸的清单。
120
+ let spot = null;
121
+ // 暂存架(2026-08-30):机器到货的默认座。点名 + 三个安置动词,agent 不动它们就一直在
122
+ const shelfSeats = Object.entries(board.objects || {})
123
+ .filter(([, e]) => e?.seat === 'shelf' && !e.zone && Number.isFinite(e?.x))
124
+ .map(([id]) => id);
125
+ const pendingSeats = Array.isArray(board.pending) ? board.pending : [];
126
+ try {
127
+ const ss = sheetSummaries(board);
128
+ // 「最新」按登记时间取(latestSheetId 一份算法)—— 不是数组末项那张最下面的
129
+ const latest = latestSheetId(board);
130
+ const cur = ss.find(s => s.id === latest) || ss[ss.length - 1];
131
+ if (cur) {
132
+ // 报「还能装下什么」不是裸数字(2026-08-30 容量线):行数是模型真正用来
133
+ // 决策的量纲 —— 顺手教它超过余量别赌一发,flow 会替它拆
134
+ const slots = cur.slots?.length
135
+ ? `;版位 ${cur.slots.map(s => `${s.name} 剩 ~${s.freeLines} 行`).join('、')}`
136
+ : ';这张没规划版位';
137
+ spot = `板上 ${ss.length} 张纸;当前 ${cur.id}${cur.title ? `(${cur.title})` : ''} 还剩 ~${cur.freeH}px 高的空地${slots}`
138
+ + '(内容眼看要超余量就别赌一发 —— 自己多切几个空位分段填(plan/replan 省掉 at 即竖排接放),'
139
+ + '或兜底 flow:true;写满**不会**自动翻页,自己 open_sheet 规划下一页;新话题也是 open_sheet)';
140
+ } else {
141
+ spot = '板上还没铺过纸 —— 第一笔 write_on_board 会自动铺一张在他视口下,或先 open_sheet';
142
+ }
143
+ const unplaced = [...shelfSeats, ...pendingSeats];
144
+ if (unplaced.length) {
145
+ spot += `;📦 ${unplaced.length} 件在暂存架等你安置(${unplaced.slice(0, 3).map(r => r.split('/').pop()).join('、')}${unplaced.length > 3 ? '…' : ''})`
146
+ + ' —— 给它们规划的地:open_sheet{plan:[…{for:"artifacts"}]} 或逐件 pin_to_board{path,slot} / edit_board move。'
147
+ + '架不是版面,东西留在架上就是没摆';
148
+ }
149
+ } catch { /* 纸读不出就不占字 */ }
150
+ const dirs = null;
151
+ /**
152
+ * 手机 / 平板档的版式(2026-08-28 移动端第二轮,用户拍板「一件 = 一屏,纵向单列」)。
153
+ *
154
+ * ⚠️ 这段是**事前**说的,不是靠 write_on_board 的返回文案事后纠正 —— 一块
155
+ * 1700 宽的板书在 390 的屏上已经写出来了,再告诉它"下次窄一点"没有意义,
156
+ * 用户这一轮拿到的就是要横着滑四屏的东西。
157
+ *
158
+ * ⭐ 同时几何那边也在执行(resolvePlacement 的 column:左右侧一律降级成正下方)。
159
+ * 两处都做不是重复:提示词管"它主动写多宽",几何管"它没照做时版面还读得了"。
160
+ */
161
+ const fit = fitFor(vp);
162
+ const laneLine = fit.column
163
+ ? `⚠️ 他在${fit.lane === 'phone' ? '手机' : '平板'}上(屏幕 ${fit.screen?.w}x${fit.screen?.h}px)。`
164
+ + `版面规矩:**一件 = 一屏,纵向单列**。每件宽度 ≤${fit.w}(超了就要横向滑动,手机上没人受得了),`
165
+ + `高度到 ${fit.h} 都行(竖着滚是手机上读长内容的天然姿势)。`
166
+ + `接着写就往**正下方**接,别用 side:'right'/'left' 并排 —— 并排的第二件在他屏幕外。`
167
+ + `宁可多拆几件竖着排,也别把一件写宽。`
168
+ : '';
169
+ if (line) sections.push({ key: 'viewpoint', title: '用户视点', text: `用户此刻在画布上:${line}。${spot ? `${spot}。` : ''}${dirs ? `${dirs}。` : ''}${laneLine}他说「这个/这里/这张」多半指选中的 > 开着的窗 > 视口里的东西;写板走纸(at = 纸内坐标)。要看画面细节才调 read_user_view。` });
109
170
  }
110
171
  } catch { /* 视点读不到就沉默 */ }
172
+ // 板上动静(2026-08-29 纸范式刀 4):用户拖动/搬家/擦组此前完全静默,agent 只能
173
+ // 撞运气。这里报最近半小时的动静(指纹只在有新动静时变,未变随「未变」行收拢);
174
+ // 回合中途的动静另有 PreToolUse 注入器在 agent 摸板前插话。
175
+ try {
176
+ const evts = dirtyEvents(projectId, 0);
177
+ if (evts.length) {
178
+ const line = describeDirty(evts, { limit: 6 });
179
+ // 有限负责制(刀⑤ 2026-08-30):动静按纸分拣 —— 挪进你当前纸的要接手处理,
180
+ // 挪去别处的是用户自留地,只报不催(板整个是用户随便动,别拔河)。
181
+ let charge = '';
182
+ try {
183
+ const b = await readBoard(projectId);
184
+ const curId = currentSheet(b, currentSheetIdOf(sessionId))?.id || null;
185
+ const sheetOf = (id) => {
186
+ const e = b.objects?.[id];
187
+ if (!e || !Number.isFinite(e.x)) return null;
188
+ const sz = estimateSizeOn(b, id, e);
189
+ return sheetOfPoint(b, { x: e.x + sz.w / 2, y: e.y + sz.h / 2 })?.id || null;
190
+ };
191
+ const { inMine, elsewhere } = splitDirtyByCharge(evts, { sheetOf, currentSheetId: curId });
192
+ if (inMine.length) {
193
+ charge = `\n⚠️ 其中 ${inMine.slice(0, 4).join('、')}${inMine.length > 4 ? ` 等 ${inMine.length} 件` : ''} 现在落在**你正在写的纸(${curId})**上:`
194
+ + '这块工作区的版面归你管 —— 若它挡了你的版位/内容,用 edit_board 给它挪个合适的位置(挪要挪得讲理,别甩出用户视野);用户明说过要放那儿的除外。';
195
+ }
196
+ if (elsewhere.length && inMine.length) {
197
+ charge += `\n其余 ${elsewhere.length} 件在你的纸外 —— 那是用户自留地,不要去动。`;
198
+ } else if (elsewhere.length) {
199
+ charge = '\n这些都在你当前的纸外 —— 用户自留地,看在眼里就好,不要去动。';
200
+ }
201
+ } catch { /* 分拣失败就退回不分拣的报法 */ }
202
+ sections.push({ key: 'boardDirty', title: '板上动静', text:
203
+ `用户最近亲手动过板面:${line}。这些位置以现状为准 —— 摆放前先 read_board,别按你记忆里的旧位置来。${charge}` });
204
+ }
205
+ } catch { /* 动静读不到就沉默 */ }
206
+
111
207
  try {
112
208
  const digest = await relationsDigest(projectId, { limit: 12 });
113
209
  if (digest) {
114
- sections.push({ key: 'relations', title: '画布关系线', text: `画布关系线(用户和你手动画的连线,端点跟着改名走;语义看线上的词):\n${digest}\n 产出新东西后记得用 relate_on_board 把「改自/对照/接着/取材」画上去。` });
210
+ sections.push({ key: 'relations', title: '画布关系线', text: `画布关系线(用户和你手动画的连线,端点跟着改名走;语义看线上的词):\n${digest}\n 产出新东西后记得用 edit_board add_edge 把「改自/对照/接着/取材」画上去。` });
115
211
  }
116
212
  } catch { /* 板读不到就沉默 */ }
117
213
 
@@ -140,22 +236,114 @@ async function collectSections({ workspaceRoot, sessionId, projectId }) {
140
236
  // 最近板书(2026-08-23;08-24 记忆改版时被误删,同日修回):你/用户在画布上
141
237
  // 说过的最近几句 —— 对话在板上,得记得板上说了什么
142
238
  try {
143
- const recent = await recentChalk(workspaceRoot, { limit: 8 });
239
+ const all = await recentChalk(workspaceRoot, { limit: 24 });
240
+ const roleNames = await listRoleNames(workspaceRoot);
241
+ // 前 8 条 + 保底:每个角色最新那条就算被 GM 自己的章节/状态板挤出前 8 也要在 ——
242
+ // GM 需要知道台上角色说了什么(08-28 用户拍板),漏了它就会旁白转述/代笔补戏。
243
+ const recent = all.slice(0, 8);
244
+ const seenRole = new Set(recent.map(c => c.by));
245
+ for (const c of all.slice(8)) {
246
+ if (c.by && c.by !== 'agent' && c.by !== 'user' && !seenRole.has(c.by)) {
247
+ recent.push(c); seenRole.add(c.by);
248
+ }
249
+ }
144
250
  if (recent.length) {
145
- const lines = recent.map(c => ` ${c.path}(${c.by === 'user' ? '用户' : '你'}${c.anchor ? `,关于 ${c.anchor}` : ''}${c.replyTo ? `,回应 ${c.replyTo.replace(`${CHALK_DIR}/`, '')}` : ''})「${c.first}」`);
251
+ // 这段是注给**主 agent** 的,所以「你」= agent;角色写的板书报它的名字,
252
+ // 否则主 agent 会把角色写的东西当成自己写的(RP 线里那是大部分板书)。
253
+ const lines = recent.map(c => ` ${c.path}(${c.by === 'user' ? '用户' : c.by && c.by !== 'agent' ? roleLabel(c.by, roleNames.get(c.by)) : '你'}${c.anchor ? `,关于 ${c.anchor}` : ''}${c.replyTo ? `,回应 ${c.replyTo.replace(`${CHALK_DIR}/`, '')}` : ''})「${c.first}」`);
146
254
  sections.push({ key: 'chalk', title: '最近板书', text: `画布上最近的板书(${CHALK_DIR}/,新在前;正文 Read 文件):\n${lines.join('\n')}`, items: recent.map(c => c.path) });
147
255
  }
148
256
  } catch { /* 板书读不到就沉默 */ }
149
257
 
258
+ // 状态表(2026-08-30):板上那张 `| 键 | 值 |` 的现值,每轮开头端到手边 ——
259
+ // 演出里"会变、要记得"的东西(好感度/时间/线索)不该靠模型自己在上下文里背。
260
+ //
261
+ // ⚠️ 这一节**不给 items**,走整节 hash 比对(renderTurnState 的默认分支)。
262
+ // 给了 items 会被 diffItems 渲染成「新增 好感度=4;移除 好感度=3」—— 信息对、
263
+ // 话是错的(那个渲染器被 9 个节共用,为一张小表去改它不值)。而且对读者来说,
264
+ // 变了的时候看到**整表现值**比看到增量有用:它按现值行事,不按 delta 行事。
265
+ //
266
+ // ⛔ 「解析不出来」必须出声。collectSections 的房规是"采不到就不出现",对普通节
267
+ // 是对的,对这一节就是坑:表被 set_text / 用户手改写坏之后会静默消失,而写口闸
268
+ // 只守得住 set_vars 那一路(另外两路是合法的写入方)。所以 broken 一律推一节。
269
+ try {
270
+ const st = await readStateVars(workspaceRoot);
271
+ if (st.state === 'ok' && st.rows.length) {
272
+ const table = st.rows.map(r => ` ${r.key} = ${r.value}`).join('\n');
273
+ const parts = [];
274
+
275
+ // 条件触发器(2026-08-30):求值点 = 注入点。只在这里求一次,命中就写进
276
+ // 这一轮的状态块 —— 不在 set_vars 里求值攒到下一轮,那样中间一次进程重启
277
+ // 就静默吞掉一次触发。沿状态落 .nd/(派生记账,丢了只是"上膛不击发")。
278
+ let trig = null;
279
+ try {
280
+ const parsed = parseTriggers(st.body || '');
281
+ if (parsed.triggers.length || parsed.errors.length) {
282
+ const { latch, fresh } = await readLatch(workspaceRoot);
283
+ trig = evalTriggers(parsed.triggers, st.rows, latch, { fresh });
284
+ try { await writeLatch(workspaceRoot, trig.latch); } catch (e) {
285
+ console.warn('[vars] 沿状态写不进去,下一轮可能重复触发:', e.message);
286
+ }
287
+ if (trig.fired.length) {
288
+ parts.push(`⚡ 这一拍有 ${trig.fired.length} 个条件命中了(你之前挂的):\n`
289
+ + trig.fired.map(f => ` · ${f.message}\n (条件:${f.raw})`).join('\n'));
290
+ }
291
+ const roster = [];
292
+ if (trig.armed) roster.push(`${trig.armed} 条挂着`);
293
+ if (trig.retired) roster.push(`${trig.retired} 条已退休`);
294
+ if (roster.length) parts.push(` 触发器:${roster.join('、')}(声明在 ${st.rel} 的 \`\`\`nd:triggers 围栏里,删一行就是撤一条)`);
295
+ for (const e of [...parsed.errors, ...trig.errors]) {
296
+ parts.push(` ⚠️ 触发器写错了,这条一直不会响:${e}`);
297
+ }
298
+ }
299
+ } catch (e) { console.warn('[vars] 触发器求值失败:', e.message); }
300
+
301
+ parts.push(`状态表现值(${st.rel},共 ${st.rows.length} 格):\n${table}\n`
302
+ + ` 改数字用 set_vars(只动那一格);改表的结构/加说明文字才用 edit_board 的 set_text。`);
303
+ sections.push({ key: 'vars', title: '状态表', text: parts.join('\n') });
304
+ } else if (st.state === 'broken') {
305
+ sections.push({
306
+ key: 'vars',
307
+ title: '状态表',
308
+ text: `⚠️ 状态表读不出来了:${st.why}\n`
309
+ + ` 在修好之前 set_vars 会一直拒绝(它不会"尽力写",那只会把表写得更坏)。`
310
+ + `${st.rel ? ` Read 一下 ${st.rel} 看看表被改成什么样了。` : ''}`,
311
+ });
312
+ }
313
+ } catch { /* 状态表这一节自己不能变成故障源 */ }
314
+
315
+ // 台上(2026-08-29):角色现在的状态。判据全是 harness 盖的章(SubagentStart/Stop,
316
+ // 见 stage-status.js)—— 此前主持人对这件事是半盲的:只在角色**结束**时收到一条
317
+ // 通知,中间既不知道谁在写,也不知道谁写完在等它接。08-28 真会话里用户替它问出了
318
+ // 这个洞(「说书人是不是要重启一下?」),而主持人手上没有任何工具能回答。
319
+ try {
320
+ const st = stageStatus(projectId);
321
+ if (st.length) {
322
+ const names = await listRoleNames(workspaceRoot);
323
+ const mins = (ms) => Math.round(ms / 60000);
324
+ const lines = st.map((r) => {
325
+ const who = roleLabel(r.slug, names.get(r.slug));
326
+ if (r.writing) return ` ${who}(${r.slug})正在写`;
327
+ const idle = r.idleMs > 90000 ? `,${mins(r.idleMs)} 分钟没动了` : '';
328
+ return ` ${who}(${r.slug})写完了这一段${idle}${r.lastLine ? `,收笔时说:「${r.lastLine}」` : ''}`;
329
+ });
330
+ sections.push({ key: 'stage', title: '台上', text:
331
+ `这场故事里的角色:\n${lines.join('\n')}\n`
332
+ + `写完的角色这一轮已经结束了 —— 要它接着演,SendMessage 寄给它的名字(当场醒,记得之前所有事);`
333
+ + `不用重新派(重派会新起一个失忆的同名角色顶掉它)。` });
334
+ }
335
+ } catch { /* 台上读不到就沉默 */ }
336
+
150
337
  // 黑板模式(2026-08-23;08-24 起默认开 —— 没写过这个键的按开算,显式 false 才算关)
151
338
  try {
152
339
  const cfg = withUiDefaults(await readUiConfigFile(workspaceRoot));
153
340
  if (cfg.blackboard_mode === true) {
154
341
  sections.push({ key: 'blackboard', title: '黑板模式', text:
155
- '【黑板模式:开】用户此刻在画布上专注思考。这一轮默认这么做:想事情就画成图(sketch_on_board,'
156
- + '小改动用 edit_sketch 原地改别重画);做完一件东西在它旁边写一条板书(write_on_board near=);'
342
+ '【黑板模式:开】用户此刻在画布上专注思考。这一轮默认这么做:先有纸再动笔(新话题 open_sheet);'
343
+ + '想事情就画成图(write_on_board nodes/edges,小改动用 edit_board 原地改别重画);'
344
+ + '做完一件东西在它旁边写一条板书(near= 连线说明它说的是谁);'
157
345
  + '用户标注了板上的东西就接在那条下面回(reply_to=)。侧栏照常回复,但板上已经写的别大段重复。'
158
- + '尺寸守规范(0.8 倍一屏可读、正文 md 起、一条板书说一件事);画完 look_at_board 看一眼再收。' });
346
+ + '尺寸守规范(一张纸 = 一屏、正文 md 起、一条板书说一件事);画完 look_at_board 看一眼再收。' });
159
347
  }
160
348
  } catch { /* 读失败:不注入 */ }
161
349
 
@@ -212,6 +400,8 @@ export function renderTurnState(sections, prev) {
212
400
 
213
401
  export function makeUserPromptSubmitHandler({ ctx: _ctx, workspaceRoot, sessionId, projectId }) {
214
402
  return async (_input, _toolUseId, _options) => {
403
+ // 新的一轮:收尾闸重新记账(判的是「这一轮」,见 beat-state.js)
404
+ resetBeat(sessionId);
215
405
  try {
216
406
  if (!workspaceRoot) return {};
217
407
  const sections = await collectSections({ workspaceRoot, sessionId, projectId });
@@ -46,6 +46,7 @@
46
46
  import {
47
47
  makePreToolUseAgentForceForegroundHandler,
48
48
  makePreToolUseGrepContentDefaultHandler,
49
+ makePreToolUseActorStamp,
49
50
  } from './hooks/pre-defaults.js';
50
51
  import {
51
52
  makePreToolUseGetPendingChangesProtocolInjector,
@@ -63,6 +64,13 @@ import {
63
64
  makePreToolUseBashStarterFilesFallback,
64
65
  } from './hooks/pre-starter-files.js';
65
66
  import { makePreToolUseBoardNeighborhoodInjector } from './hooks/pre-board-neighborhood.js';
67
+ import { makePreToolUseBoardDirtyInjector } from './hooks/pre-board-dirty.js';
68
+ import { makePreToolUseUnknownParamsProbe } from './hooks/pre-unknown-params.js';
69
+ import { makePreToolUseSendMessageRecipientGuard } from './hooks/pre-peer-guard.js';
70
+ import { createRoleRoster } from './cast.js';
71
+ import { makePostToolUseFailureRoleRelease, makeSubagentStopRoleNotice, makeSubagentStartRoleAlias } from './hooks/resident-role-lifecycle.js';
72
+ import { makePostToolUseBeatWrite, makePostToolUseBeatHandoff, makeStopBeatGate } from './hooks/beat-gate.js';
73
+ import { makePostToolUseSlotAliasHandler } from './hooks/slot-alias.js';
66
74
  import { makePreToolUsePerformanceLogGuard } from './hooks/pre-performance-log-guard.js';
67
75
  import { makePreToolUseWorkspaceScopeGuard } from './hooks/pre-workspace-scope-guard.js';
68
76
  import { PROJECTS_DATA_ROOT } from '../../projects/workspace.js';
@@ -99,7 +107,14 @@ import { makePostToolUseSubagentReportRecovery } from './hooks/post-subagent-rep
99
107
  * @param {string} [deps.projectId]
100
108
  * @returns {Partial<Record<string, Array<{ matcher?: string, hooks: Function[], timeout?: number }>>>}
101
109
  */
102
- export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, projectId } = {}) {
110
+ export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, projectId, roleRoster: injected = null } = {}) {
111
+ // 常驻角色名册:**一个会话一份**(闭包级,不是全局表)。派发时登记、收件人闸按它放行。
112
+ // 两个 handler 必须拿同一个引用 —— 各建各的等于闸永远看到空名册,症状是所有角色
113
+ // 都寄不出信(fail-closed,至少不静默漏)。见 cast.js createRoleRoster 的头注释。
114
+ // 名册由 session-loop 建好传进来(cast_role 也要用同一份 —— 它记"这一回合造的角色",
115
+ // 派发闸据此拦掉本回合内注定失败的派发)。没传就自建,单测/脚本直接调时仍成立。
116
+ const roleRoster = injected || createRoleRoster();
117
+
103
118
  return assertHooksWellFormed({
104
119
  // ── P0+ stage 1(不动)──
105
120
 
@@ -138,10 +153,31 @@ export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, project
138
153
  }, {
139
154
  matcher: 'Task|Agent',
140
155
  hooks: [
141
- makePreToolUseAgentForceForegroundHandler(),
156
+ makePreToolUseAgentForceForegroundHandler({ roster: roleRoster, workspaceRoot, ctx }),
142
157
  // vision-checker 派遣 prompt 模板首次注入(仅当 subagent_type='vision-checker')
143
158
  makePreToolUseTaskVisionCheckerDispatchInjector(),
144
159
  ],
160
+ }, {
161
+ // SendMessage 收件人闸:只放行 main / 本项目常驻角色 / 本会话 agentId。
162
+ // 这个工具的另一半寻址空间是**同机其他 Claude 会话**(探针实测 ListAgents
163
+ // 列得出来),跨会话寄信 = 一个用户的 agent 给另一个人的 agent 递指令。
164
+ // SDK 的 crossSessionInbound 只管收不管发,发这侧得自己拦。见 pre-peer-guard.js
165
+ matcher: 'SendMessage',
166
+ hooks: [makePreToolUseSendMessageRecipientGuard({ roster: roleRoster })],
167
+ }, {
168
+ // 板上写入的署名:工具执行前把「这次是谁调的」记下来(MCP handler 自己拿不到
169
+ // agent 身份,hook 拿得到)。常驻角色写的板书要署它的名,不能全算主 agent 头上。
170
+ // 见 agent/actor-trail.js —— 权威是 harness 盖的章,不是角色文件里的自称。
171
+ //
172
+ // ⛔ 这里曾是手写工具名单,漏过两次,两次症状都是「闸装着、判据永远落 'agent'」:
173
+ // - 08-26 漏 await_user/check_inbox/read_board → 整条收件箱回路在生产里是死的
174
+ // - 08-28 审出漏场务四件 → 对真角色恒拒(它们永远被当成主控)
175
+ // (两族工具 08-29 随编排收敛一起退役,教训留着:通配挂法就是从这两次来的)
176
+ // 手写名单和「写死表家族」同病:加新工具必漏。改成全量盖章 —— 盖章本身只是往
177
+ // 500 容量的 LRU Map 记一条,多盖无副作用,凡是可能用 byOf 的工具永远在闸内。
178
+ // 装配层有测试钉着(hooks-assembly.test.js):别再收窄回名单。
179
+ matcher: 'mcp__nodesign__.*',
180
+ hooks: [makePreToolUseActorStamp()],
145
181
  }, {
146
182
  // ⚠️ Grep 的输入改写只许有这一个 handler——两个各返回 updatedInput 会互相抹掉
147
183
  matcher: 'Grep',
@@ -190,6 +226,16 @@ export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, project
190
226
  // UserPromptSubmit 的全图摘要截断后,这里做精确补充。每个文件一个会话只注一次。
191
227
  matcher: 'Read|Edit|Write',
192
228
  hooks: [makePreToolUseBoardNeighborhoodInjector({ workspaceRoot, projectId })],
229
+ }, {
230
+ // 板上动静(2026-08-29 纸范式刀 4):用户回合中途拖动/搬家/擦组,agent 下一次
231
+ // 摸板前插话(按会话恰好一次,台账在 lib/board-dirty.js)。
232
+ matcher: 'mcp__nodesign__(write_on_board|edit_board|read_board|board_batch|open_sheet|organize_board|look_at_board)',
233
+ hooks: [makePreToolUseBoardDirtyInjector({ projectId, sessionId })],
234
+ }, {
235
+ // 未知参数探针(2026-08-30):zod 把 schema 里没有的键静默 strip 掉,工具自己
236
+ // 看不见 —— 只有钩子拿得到原始 tool_input。挂全部 nodesign 工具。
237
+ matcher: 'mcp__nodesign__.*',
238
+ hooks: [makePreToolUseUnknownParamsProbe()],
193
239
  }, {
194
240
  matcher: 'Write',
195
241
  hooks: [
@@ -204,9 +250,11 @@ export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, project
204
250
  ],
205
251
  }],
206
252
 
207
- // Stop —— agent 准备结束 query 时触发,发自检事件给前端
253
+ // Stop —— agent 准备结束 query 时触发,发自检事件给前端。
254
+ // 第二个是演出模式的收尾闸:这一拍既没给按钮也没交给角色就不许收工
255
+ // (08-28 真会话的头号病:写完两千字正文玩家没有任何把手)。
208
256
  Stop: [{
209
- hooks: [makeStopReflectionHandler({ ctx, workspaceRoot })],
257
+ hooks: [makeStopReflectionHandler({ ctx, workspaceRoot }), makeStopBeatGate({ sessionId, projectId })],
210
258
  }],
211
259
 
212
260
  // PostCompact —— compact 后把摘要写入 spec.json 长期记忆
@@ -231,6 +279,18 @@ export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, project
231
279
  // PostToolUse —— 按 MCP 工具名分别注 additionalContext,引导 agent 利用
232
280
  // 工具结果。matcher 字段是 SDK 标准(与 PreToolUse 'Bash' 同语义)。
233
281
  PostToolUse: [
282
+ // 演员位实例学名(2026-08-28 重构):hook input 没有实例名字段,名字只在
283
+ // 派发/唤醒的 tool_result 里露面 —— 从那里学 agentId→实例名(slot-alias.js)。
284
+ // 收件箱、板书署名、退场标记全靠这张表把 rp-actor 解析回具体角色。
285
+ {
286
+ matcher: 'Task|Agent|SendMessage',
287
+ hooks: [makePostToolUseSlotAliasHandler(), makePostToolUseBeatHandoff({ sessionId, projectId })],
288
+ },
289
+ // 收尾闸的另一半:主持人往画布上写了什么、带没带按钮(见 hooks/beat-gate.js)
290
+ {
291
+ matcher: 'mcp__nodesign__write_on_board|mcp__nodesign__board_batch',
292
+ hooks: [makePostToolUseBeatWrite({ sessionId, projectId })],
293
+ },
234
294
  // Edit/Write 后干掉 tool_response.originalFile:FileEditOutput/FileWriteOutput
235
295
  // 默认含完整原文件(sdk-tools.d.ts:2270, 2328),这是上下文累积大头。
236
296
  // canvas.html 25KB 一次 Edit ≈ 6k tokens,30 turn 累积可达 180k+ → 触发 256k 上限。
@@ -303,15 +363,25 @@ export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, project
303
363
  // 注入恢复建议(避免重试同样的错)。
304
364
  PostToolUseFailure: [{
305
365
  hooks: [makePostToolUseFailureHandler({ ctx, projectId, sessionId })],
366
+ }, {
367
+ // 常驻角色派发失败 → 把名字从名册里撤回。claim 在 PreToolUse(派发**之前**),
368
+ // 不撤的话这个名字整个会话被 brick,而且收件人闸会对一个并不存在的 agent 放行
369
+ // (裸名回落全机解析 = H1 那个洞在窄窗口里重开)。见 resident-role-lifecycle.js
370
+ matcher: 'Task|Agent',
371
+ hooks: [makePostToolUseFailureRoleRelease({ roster: roleRoster })],
306
372
  }],
307
373
 
308
374
  // SubagentStart / SubagentStop —— 主动捕子代理生命周期。当前只 emit 事件
309
375
  // 给上层观察;不阻塞流程。stage 2 真接通子代理时这里加调度逻辑。
310
376
  SubagentStart: [{
311
- hooks: [makeSubagentStartHandler({ ctx })],
377
+ // 第二个 handler 是署名的正门:agent_id ↔ 角色名在起飞这一刻绑定
378
+ // (见 resident-role-lifecycle.js;旧的"从 tool_result 抠 id"已退役)
379
+ hooks: [makeSubagentStartHandler({ ctx, sessionId }), makeSubagentStartRoleAlias({ projectId })],
312
380
  }],
313
381
  SubagentStop: [{
314
- hooks: [makeSubagentStopHandler({ ctx })],
382
+ // 常驻角色退场时补一句「怎么把它叫回来」给主控。放在通用 emit 之后:
383
+ // 那个只 emit 事件不返输出,两者不抢 systemMessage。见 resident-role-lifecycle.js
384
+ hooks: [makeSubagentStopHandler({ ctx, sessionId }), makeSubagentStopRoleNotice({ projectId })],
315
385
  }],
316
386
  });
317
387
  }
@@ -25,6 +25,7 @@ import os from 'node:os';
25
25
  import fs from 'node:fs/promises';
26
26
  import { platform } from '../../runtime/platform.js';
27
27
  import { autoModeSettings } from './auto-mode-rules.js';
28
+ import { MCP_ALLOW_RULE } from '../mcp/server-name.js';
28
29
 
29
30
  /**
30
31
  * 会话要用的缓存/临时目录一次备齐(2026-08-19 从 session-loop 下沉)。
@@ -129,7 +130,15 @@ export function buildIsolationOptions({ cwdRoot, sharedRoot, npmCacheDir, agentT
129
130
  // 没有它 Bash 的 tmp 只读:venv 静默失败、pip --user EROFS、npm 装不上
130
131
  ...(agentTmpDir ? [agentTmpDir] : []),
131
132
  ],
132
- denyWrite: ['/etc', '/usr', '/bin', '/sbin', '/private/etc'],
133
+ // 角色文件不许模型写(2026-08-26):它是派发闸的判据本身,模型能改就等于
134
+ // 自己给自己发工具权限(TOCTOU + 解析器分歧两条绕法,详见
135
+ // hooks/pre-workspace-scope-guard.js 那段注释)。
136
+ // 这半只管 Bash —— Write/Edit 是进程内工具**不进 bwrap**,那半在上面那道闸。
137
+ // 正门 cast_role 走服务端 fs,不经沙盒,照写不误。
138
+ denyWrite: [
139
+ '/etc', '/usr', '/bin', '/sbin', '/private/etc',
140
+ ...(cwdRoot ? [path.join(cwdRoot, '.claude', 'agents')] : []),
141
+ ],
133
142
  // 数据根整个盖住、再用 allowRead 给自己的工作区开天窗。
134
143
  // ⚠️ 口径要准(08-18 上生产时实测纠正):`ls 数据根` **看得见别的项目的
135
144
  // 目录名**(沙盒里实测列出了另一个 pid),拦住的是**进去读**——
@@ -149,7 +158,21 @@ export function buildIsolationOptions({ cwdRoot, sharedRoot, npmCacheDir, agentT
149
158
  },
150
159
  },
151
160
  settings: {
152
- permissions: { deny: platform.protectedPathRules({ dataRoot }) },
161
+ permissions: {
162
+ deny: platform.protectedPathRules({ dataRoot }),
163
+ // 本平台自己的 MCP 工具整服务放行,不过 auto 模式分类器(2026-08-25 用户拍板)。
164
+ //
165
+ // 为什么:这些工具是我们自己写的,每一件的边界在服务端**已经**有闸
166
+ // —— 出网走 ssrf-guard,publish_site / 本地产线走 owner + 档位闸,
167
+ // 生图走额度闸,路径走 permissions.deny + 沙盒。让一个模型分类器
168
+ // 再判一遍 `read_board` 安不安全,既判不出新东西,还多一次外部依赖。
169
+ // 真实账:真用户会话里分类器**从没拦下过一次真越界**(唯一那条是
170
+ // Stage 2 classifier error 误伤 deliver_files 交稿),却因为自身不可用
171
+ // 挡掉过 8 个会话的正经活(08-24 一天 15 次)。闸门的净效用是负的。
172
+ // Bash **不在**名单里:它跑的是任意命令,语义判断正是分类器的本职。
173
+ // 内置的 Read/Write/Edit/WebFetch 等也照旧不动。
174
+ allow: [MCP_ALLOW_RULE],
175
+ },
153
176
  // auto 模式的分类器规则(只在开了 auto 时注入,省得白占 settings)。
154
177
  // ⚠️ 按节替换不是追加 —— 为什么只覆盖 environment / hard_deny 两节,
155
178
  // 见 auto-mode-rules.js 文件头。
@@ -45,10 +45,32 @@ export const MEMORY_EXTRA_GUIDELINES = [
45
45
  * SDK 手里(agent 照 SDK 默认路径写记忆被沙盒拒、~/.claude 下堆了 187 个空目录)。
46
46
  * 「两处同名键静默互吞」没有任何报错,出口断言在这里兜。
47
47
  */
48
- export function mergeAgentSettings(isolationSettings, { skipWebFetchPreflight, sharedRoot }) {
48
+ export function mergeAgentSettings(isolationSettings, extra = {}) {
49
+ // ⚠️ 这个第二参是**显式白名单**(它存在的理由就是防 settings 互吞)。所以它有一个
50
+ // 结构性陷阱:往调用点里塞一个这里没解构的新键 = 那个键被静默丢弃,零症状。
51
+ // 2026-08-26 真踩:`crossSessionInbound: 'refuse'`(跨会话入向闸)在调用点写得好好的,
52
+ // 到了 SDK 手里根本不存在 —— 修互吞案的函数自己吞了新键。
53
+ // 所以未知键**当场炸**:白名单的代价必须由加键的人当场付,不能由线上静默付。
54
+ const { skipWebFetchPreflight, sharedRoot, crossSessionInbound, ...unknown } = extra;
55
+ const unknownKeys = Object.keys(unknown);
56
+ if (unknownKeys.length) {
57
+ throw new Error(
58
+ `[memory-config] mergeAgentSettings 收到不认识的键:${unknownKeys.join(', ')} —— `
59
+ + '这个第二参是显式白名单,加新键要同时改这里的解构和下面的出口断言,别指望它透传',
60
+ );
61
+ }
62
+
49
63
  const settings = {
50
64
  ...isolationSettings,
51
65
  skipWebFetchPreflight,
66
+ // 跨会话入向(2026-08-26):实测——不是推断——Nodesign 的每个会话都会以 cwd
67
+ // 派生的名字注册进**本机 peer 名册**(生产上正在跑的用户会话当时显示为
68
+ // `shared-6d`),同机任何一个 Claude 会话 ListAgents 都看得见、都能按名字寄信。
69
+ // SDK 默认「权限模式相同就自动投递」(bypass↔bypass),而我们正是
70
+ // bypassPermissions → 本机任何 bypass 会话都能把文本注进用户会话的上下文。
71
+ // 出向那侧我们自己拦(hooks/pre-peer-guard.js),入向只有这一个旋钮。
72
+ // ⛔ 别写 'hold':那是「存起来等人批准」,而服务端没有人在批。
73
+ ...(crossSessionInbound ? { crossSessionInbound } : {}),
52
74
  ...(sharedRoot ? {
53
75
  autoMemoryEnabled: true,
54
76
  autoMemoryDirectory: memoryDirFor(sharedRoot),
@@ -57,5 +79,9 @@ export function mergeAgentSettings(isolationSettings, { skipWebFetchPreflight, s
57
79
  if (sharedRoot && !settings.autoMemoryDirectory) {
58
80
  throw new Error('[memory-config] settings.autoMemoryDirectory 被吞了 —— 检查 settings 合并处');
59
81
  }
82
+ // 出口断言按「传进来的每个键都要在出口活着」逐条对,不是只看一个样本键
83
+ if (crossSessionInbound && settings.crossSessionInbound !== crossSessionInbound) {
84
+ throw new Error('[memory-config] settings.crossSessionInbound 被吞了 —— 跨会话入向闸会静默失效');
85
+ }
60
86
  return settings;
61
87
  }