@xiaobuyu/nodesign 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/README.md +266 -79
  2. package/README.zh-CN.md +254 -67
  3. package/package.json +2 -2
  4. package/server/api/admin.js +7 -0
  5. package/server/api/assets/docx-page.js +5 -4
  6. package/server/api/assets/helpers.js +8 -0
  7. package/server/api/assets.js +21 -33
  8. package/server/api/board.js +29 -1
  9. package/server/api/browse.js +3 -2
  10. package/server/api/chatai.js +9 -8
  11. package/server/api/exports/cards.js +7 -6
  12. package/server/api/exports/docx-pdf.js +4 -1
  13. package/server/api/local.js +10 -8
  14. package/server/api/projects.js +19 -3
  15. package/server/api/publish.js +3 -2
  16. package/server/api/sessions-rewind.js +220 -0
  17. package/server/api/sessions.js +58 -194
  18. package/server/api/stage.js +35 -0
  19. package/server/api/turn-model-switch.js +109 -0
  20. package/server/api/turn.js +52 -88
  21. package/server/auth/middleware.js +29 -6
  22. package/server/auth/tier.js +27 -7
  23. package/server/auth/users-store.js +14 -1
  24. package/server/engine/agent/actor-trail.js +110 -0
  25. package/server/engine/agent/agent-shared.js +22 -52
  26. package/server/engine/agent/beat-state.js +61 -0
  27. package/server/engine/agent/cast.js +322 -0
  28. package/server/engine/agent/events.js +9 -2
  29. package/server/engine/agent/hooks/beat-gate.js +73 -0
  30. package/server/engine/agent/hooks/lifecycle.js +6 -2
  31. package/server/engine/agent/hooks/post-trim.js +1 -0
  32. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  33. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  34. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  35. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  36. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  37. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  38. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  39. package/server/engine/agent/hooks/slot-alias.js +30 -0
  40. package/server/engine/agent/hooks/user-prompt-submit.js +197 -7
  41. package/server/engine/agent/hooks.js +76 -6
  42. package/server/engine/agent/isolation.js +25 -2
  43. package/server/engine/agent/memory-config.js +27 -1
  44. package/server/engine/agent/model-context.js +102 -18
  45. package/server/engine/agent/model-table.js +361 -58
  46. package/server/engine/agent/prompts/nodesign-prelude.md +566 -59
  47. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  48. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +2 -2
  49. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  50. package/server/engine/agent/prompts/tools/site-reference.md +2 -1
  51. package/server/engine/agent/role-card.js +175 -0
  52. package/server/engine/agent/rp-mode.js +17 -0
  53. package/server/engine/agent/session-loop.js +26 -14
  54. package/server/engine/agent/stage-status.js +73 -0
  55. package/server/engine/agent/subagent-flight.js +73 -0
  56. package/server/engine/agent/system-prompts.js +41 -0
  57. package/server/engine/agent/tool-input-stream.js +165 -0
  58. package/server/engine/mcp/actor.js +59 -0
  59. package/server/engine/mcp/capability-gate.js +21 -2
  60. package/server/engine/mcp/index.js +116 -24
  61. package/server/engine/mcp/mode-profile.js +139 -0
  62. package/server/engine/mcp/server-name.js +19 -0
  63. package/server/engine/mcp/tools/artifact-session.js +2 -1
  64. package/server/engine/mcp/tools/browse-find-batch.js +76 -14
  65. package/server/engine/mcp/tools/browse.js +1 -1
  66. package/server/engine/mcp/tools/cast-role.js +167 -0
  67. package/server/engine/mcp/tools/draw-trend.js +104 -0
  68. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  69. package/server/engine/mcp/tools/edit-board.js +569 -0
  70. package/server/engine/mcp/tools/look-at-board.js +4 -3
  71. package/server/engine/mcp/tools/open-sheet.js +242 -0
  72. package/server/engine/mcp/tools/organize-board.js +19 -5
  73. package/server/engine/mcp/tools/paint-still.js +1 -1
  74. package/server/engine/mcp/tools/pin-to-board.js +78 -3
  75. package/server/engine/mcp/tools/read-board.js +109 -16
  76. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  77. package/server/engine/mcp/tools/report-issue.js +1 -0
  78. package/server/engine/mcp/tools/role-memory.js +83 -0
  79. package/server/engine/mcp/tools/roll-dice.js +82 -0
  80. package/server/engine/mcp/tools/roll-film.js +1 -1
  81. package/server/engine/mcp/tools/screenshot.js +2 -2
  82. package/server/engine/mcp/tools/set-vars.js +150 -0
  83. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  84. package/server/engine/mcp/tools/web-search.js +1 -1
  85. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  86. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  87. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  88. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  89. package/server/engine/mcp/tools/write-on-board.js +567 -107
  90. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  91. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +11 -3
  92. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +34 -4
  93. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +20 -4
  94. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  95. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  96. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  97. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  98. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  99. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  100. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  101. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  102. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  103. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  104. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  105. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  106. package/server/engine/runs/board-seater.js +272 -0
  107. package/server/engine/runs/board-tasklist.js +90 -48
  108. package/server/engine/runs/turn-relay.js +24 -2
  109. package/server/engine/runs/user-chalk-echo.js +44 -0
  110. package/server/index.js +2 -0
  111. package/server/lib/board-anchor.js +49 -0
  112. package/server/lib/board-dirty.js +93 -0
  113. package/server/lib/board-excerpt.js +80 -0
  114. package/server/lib/board-follow.js +111 -0
  115. package/server/lib/board-groups.js +1 -5
  116. package/server/lib/board-hero.js +3 -1
  117. package/server/lib/board-kind-sizes.js +36 -0
  118. package/server/lib/board-lanes.js +58 -0
  119. package/server/lib/board-obstacles.js +80 -0
  120. package/server/lib/board-relations.js +5 -1
  121. package/server/lib/board-sheets.js +467 -0
  122. package/server/lib/board-shelf.js +86 -0
  123. package/server/lib/board-transform.js +62 -0
  124. package/server/lib/canvas-id.js +18 -1
  125. package/server/lib/chalk-flow.js +88 -0
  126. package/server/lib/chalk-rewrite.js +63 -0
  127. package/server/lib/chalk-size-pref.js +48 -0
  128. package/server/lib/chalk.js +42 -3
  129. package/server/lib/ingress/forward-openai-chat.js +16 -4
  130. package/server/lib/ingress/openai-chat.js +23 -12
  131. package/server/lib/ingress/session-routes.js +3 -3
  132. package/server/lib/ingress/stream-watchdog.js +53 -0
  133. package/server/lib/ingress/upstream-billing.js +15 -0
  134. package/server/lib/ingress/upstream-error-hints.js +36 -0
  135. package/server/lib/json-preview.js +58 -0
  136. package/server/lib/model-ingress.js +26 -1
  137. package/server/lib/moderation.js +10 -5
  138. package/server/lib/quota.js +26 -6
  139. package/server/lib/rect.js +50 -0
  140. package/server/lib/screen.js +36 -0
  141. package/server/lib/sheet-state.js +19 -0
  142. package/server/lib/sketch-array.js +118 -0
  143. package/server/lib/sketch-hatch.js +99 -0
  144. package/server/lib/sketch-layout.js +269 -69
  145. package/server/lib/sketch-shapes.js +152 -0
  146. package/server/lib/sketch-stencils.js +170 -0
  147. package/server/lib/state-table.js +237 -0
  148. package/server/lib/state-trend.js +94 -0
  149. package/server/lib/state-triggers.js +199 -0
  150. package/server/lib/tavern-json.js +48 -0
  151. package/server/lib/user-uploads.js +88 -0
  152. package/server/projects/board-limits.js +5 -3
  153. package/server/projects/board-sanitize.js +220 -10
  154. package/server/projects/board-store.js +95 -6
  155. package/server/projects/session-jsonl.js +110 -0
  156. package/server/projects/store.js +56 -3
  157. package/server/projects/viewpoint-store.js +35 -3
  158. package/server/projects/workspace-slots.js +30 -0
  159. package/server/projects/workspace-templates.js +29 -0
  160. package/server/projects/workspace.js +16 -17
  161. package/server/runtime/capabilities.js +6 -2
  162. package/server/shared/locales.js +70 -0
  163. package/server/shared/messages-en.js +69 -0
  164. package/server/shared/messages.js +65 -0
  165. package/server/ws/broker.js +3 -0
  166. package/server/ws/index.js +43 -3
  167. package/web/README.md +1 -1
  168. package/web/dist/assets/{BrowserWindow-BoOBaIyz.js → BrowserWindow-CcaGRWz5.js} +1 -1
  169. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  170. package/web/dist/assets/{DocxWindow-B_u6aX9o.js → DocxWindow-B9KZmU5z.js} +2 -2
  171. package/web/dist/assets/{MermaidBlock-BO-46Du5.js → MermaidBlock-DrfRnz8E.js} +43 -43
  172. package/web/dist/assets/{SiteWindow-ConPmzdM.js → SiteWindow-CJOjCF7d.js} +6 -6
  173. package/web/dist/assets/{abnfDiagram-VCTEODGH-FCO9yf-n.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  174. package/web/dist/assets/{arc-BLcEOSRX.js → arc-C1-HSUny.js} +1 -1
  175. package/web/dist/assets/{architectureDiagram-5GKGNRK7-kxiX4TeN.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  176. package/web/dist/assets/{blockDiagram-NRAW4CY4-CptFksix.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  177. package/web/dist/assets/{c4Diagram-UCG6FXSJ-Dxmxj34F.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  178. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  179. package/web/dist/assets/{chunk-2Q5K7J3B-D9inx5oo.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  180. package/web/dist/assets/{chunk-5VM5RSS4-DydfHlEc.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  181. package/web/dist/assets/{chunk-F27PBJKO-BO7GxhSv.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  182. package/web/dist/assets/{chunk-G27WJ6UU-BzP6YiQ_.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  183. package/web/dist/assets/{chunk-JWPE2WC7-CHcgcd0s.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  184. package/web/dist/assets/{chunk-LCL6LL3I-D2Wg5JtX.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  185. package/web/dist/assets/{chunk-POPQ4Y6H-B2fCRfpM.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  186. package/web/dist/assets/{chunk-SVP7TREG-JSLcsRlZ.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  187. package/web/dist/assets/{chunk-XXDRQBXY-BCjvaeHm.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  188. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  189. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  190. package/web/dist/assets/{cose-bilkent-JH36ORCC-0uKv6oJ7.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  191. package/web/dist/assets/{cynefin-OW5HDTMX-Bvt-gEmA.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  192. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-C9Ht02qC.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  193. package/web/dist/assets/{dagre-3AP2YEHR-BfxZny4s.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  194. package/web/dist/assets/{diagram-S7CK7UJ4-CrE0ZETE.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  195. package/web/dist/assets/{diagram-UQ7AKVKN-BO8VQ4Fx.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  196. package/web/dist/assets/{diagram-VSXAHHWV-CHUZnR9F.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  197. package/web/dist/assets/{diagram-VX7I27RA-DYQAz1Vu.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  198. package/web/dist/assets/{diagram-Z3DM3KII-BDjivgLr.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  199. package/web/dist/assets/{ebnfDiagram-PWID7BFC-Bpe_QVbH.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  200. package/web/dist/assets/{erDiagram-SSCWMZ5O-BxhwuAu1.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  201. package/web/dist/assets/{flowDiagram-A5DVABFB-CCw2H08-.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  202. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-DtTc7MyI.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  203. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-BvORxMd2.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  204. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  205. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  206. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  207. package/web/dist/assets/{infoDiagram-RXCK75RN-DMcrkypD.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  208. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CZkTIbe-.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  209. package/web/dist/assets/{journeyDiagram-EYS64GPL-DR0XazdE.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  210. package/web/dist/assets/{kanban-definition-3QL26DDD-DN8VbwVu.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  211. package/web/dist/assets/{layout-9_paCbyt.js → layout-Dhgck-Zo.js} +1 -1
  212. package/web/dist/assets/{linear-g6Ct012e.js → linear-DMSQ-aUw.js} +1 -1
  213. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  214. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  215. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  216. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  217. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  218. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  219. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  220. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  221. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  222. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  223. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  224. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  225. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  226. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  227. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  228. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  229. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  230. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  231. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  232. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  233. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  234. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  235. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  236. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  237. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  238. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  239. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  240. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  241. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  242. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  243. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  314. package/web/dist/assets/{mindmap-definition-FBJOCRG2-BGt4omYC.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  315. package/web/dist/assets/{pegDiagram-XKGWAZYB-uSfS-PBE.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  316. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  317. package/web/dist/assets/{pieDiagram-E7YTZNPT-DpeI935H.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  318. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-B7IuNvWa.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  319. package/web/dist/assets/{railroadDiagram-O6MQD6OU-ARzYpPQI.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  320. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-Cj4upkAo.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  321. package/web/dist/assets/{rotate-cw-CXJcyHWk.js → rotate-cw-62liz7LX.js} +1 -1
  322. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-OaVwSnLP.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  323. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-1yrUbq4t.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  324. package/web/dist/assets/{sizeCapture-X5ZJPWSS-nYH5p9D6.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  325. package/web/dist/assets/{square-dashed-mouse-pointer-CTtTVKoH.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  326. package/web/dist/assets/{stateDiagram-HBIQ2CUA-Dj5HvyNq.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  327. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  328. package/web/dist/assets/{swimlanes-XN3QIQJK-umeT0HpB.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  329. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  330. package/web/dist/assets/{timeline-definition-24CTP7MA-MqnQfHKB.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  331. package/web/dist/assets/{vennDiagram-4TSXK5OY-b2liyZra.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  332. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-QMK-zNh1.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  333. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-D5Mc6tfA.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  334. package/web/dist/index.html +2 -2
  335. package/server/engine/mcp/tools/arrange-on-board.js +0 -126
  336. package/server/engine/mcp/tools/create-on-board.js +0 -152
  337. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  338. package/server/engine/mcp/tools/relate-on-board.js +0 -150
  339. package/server/engine/mcp/tools/sketch-on-board.js +0 -345
  340. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  341. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +0 -78
  342. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  343. package/web/dist/assets/DeckWindow-Bg3rgkvi.js +0 -11
  344. package/web/dist/assets/channel-DFyiuGL0.js +0 -1
  345. package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +0 -1
  346. package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +0 -1
  347. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  348. package/web/dist/assets/index-DGG5wIZs.js +0 -2118
  349. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  350. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  351. package/web/dist/assets/pending-edit-apply-uP4uvrOD.js +0 -31
  352. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +0 -1
  353. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +0 -8
@@ -0,0 +1,29 @@
1
+ /**
2
+ * PreToolUse(板工具族) —— 纸内动静插话(2026-08-29 纸范式刀 4)。
3
+ *
4
+ * 用户在 agent 干活期间拖动/搬家/擦组,下一轮注入才说太晚 —— agent 这一轮接着
5
+ * 摆就是按旧位置摆。这里在它**下一次摸板**(write/edit/read/batch/open_sheet/
6
+ * organize)前插一句「用户刚动过什么」,按会话恰好一次(markSeen 台账):说过的
7
+ * 不重复,新动静再说新的。fail-soft —— 注不上不能挡工具。
8
+ */
9
+ import { dirtyEvents, describeDirty, lastSeen, markSeen } from '../../../lib/board-dirty.js';
10
+
11
+ export function makePreToolUseBoardDirtyInjector({ projectId, sessionId }) {
12
+ return async () => {
13
+ try {
14
+ if (!projectId || !sessionId) return {};
15
+ const since = lastSeen(projectId, sessionId);
16
+ const evts = dirtyEvents(projectId, since);
17
+ if (!evts.length) return {};
18
+ markSeen(projectId, sessionId, evts[evts.length - 1].seq);
19
+ const line = describeDirty(evts, { limit: 6 });
20
+ return {
21
+ hookSpecificOutput: {
22
+ hookEventName: 'PreToolUse',
23
+ permissionDecision: 'allow',
24
+ additionalContext: `⚠ 用户刚在板上动过手:${line}。位置以现状为准(拿不准先 read_board),别把他挪过的东西又搬回去。`,
25
+ },
26
+ };
27
+ } catch { return {}; }
28
+ };
29
+ }
@@ -3,6 +3,10 @@
3
3
  * (2026-08-14 可维护性行动:从 hooks.js 原样迁出,语义零改动)
4
4
  */
5
5
 
6
+ import { isResidentRole, isSlotType, ROLE_SLUG_RE, resolveRoleTools } from '../cast.js';
7
+ import { readRoleCard, inferRoleNameFromPrompt } from '../role-card.js';
8
+ import { noteToolCaller, agentNameOf, notePendingRoleName } from '../actor-trail.js';
9
+ import { MCP_SERVER_NAME } from '../../mcp/server-name.js';
6
10
 
7
11
  /**
8
12
  * PreToolUse(Agent) 强制前台 —— 透明改 input,不 hard deny。
@@ -31,10 +35,185 @@
31
35
  * 兜底另有一层:DEFAULT_TOOL_ALLOWLIST 里挂了 `TaskOutput`,万一还是漏成后台
32
36
  * (比如 isolation:'remote' 强制后台),主 agent 能凭 task_id 把报告捞回来。
33
37
  */
34
- export function makePreToolUseAgentForceForegroundHandler() {
35
- return async (input) => {
38
+ /**
39
+ * 这个角色声明的工具合不合法。合法返回 null,不合法返回一句给模型看的原因。
40
+ * 读不到文件时放行 —— 那说明角色压根不存在,派发自己会失败(失败会把名字从名册撤回)。
41
+ */
42
+ async function illegalRoleTools(workspaceRoot, slug) {
43
+ if (!workspaceRoot) return null;
44
+ const card = await readRoleCard(workspaceRoot, slug);
45
+ if (!card) return null;
46
+ const decl = card.toolsDecl;
47
+
48
+ if (decl.kind === 'missing') {
49
+ return `角色「${slug}」的文件里没有 tools 那一行 —— 那在 SDK 语义里等于**继承你的全部工具**,`
50
+ + `包括外发和花钱的那些。用 cast_role 重新造这个角色(它会写一份只含板上工具的声明)。`;
51
+ }
52
+ if (decl.kind === 'unparsable') {
53
+ return `角色「${slug}」的 tools 声明看不懂,为安全起见不派。用 cast_role 重新造它。`;
54
+ }
55
+ const { rejected } = resolveRoleTools(decl.tools, MCP_SERVER_NAME);
56
+ if (rejected.length) {
57
+ return `角色「${slug}」声明了不该给角色的工具:${rejected.join('、')}。`
58
+ + `角色只能通过画布表达自己 —— 外发、花钱、改工作区结构的工具一律不发给它。`
59
+ + `用 cast_role 重新造它,或把这些工具从它的文件里去掉。`;
60
+ }
61
+ return null;
62
+ }
63
+
64
+ export function makePreToolUseAgentForceForegroundHandler({ roster = null, workspaceRoot = null, ctx = null } = {}) {
65
+ return async (input, toolUseId) => {
36
66
  const t = input?.tool_input;
37
67
  if (!t || typeof t !== 'object') return {};
68
+
69
+ // ── 常驻角色(rp-*)走反方向:强制**后台** + 钉死名字 ──
70
+ //
71
+ // 为什么它跟上面那段的结论相反:干活型子代理的产出是 tool_result 里的报告,
72
+ // 前台等着才拿得到;常驻角色(叙事者 / NPC)的产出走**画布和收件箱**,
73
+ // 它压根没打算结束回合 —— 前台跑等于把整条主线卡在一个不会返回的角色上。
74
+ //
75
+ // 名字必须由 harness 钉,不能指望模型传:2026-08-26 探针里三次明确要求
76
+ // 「name 参数必须写全」,模型三次都省掉了(还振振有词说 schema 里没有)。
77
+ // 没名字就只能用 agentId 寻址,而 agentId 只在那一条 tool_result 里出现过 ——
78
+ // 主代理换个话题就再也叫不回这个角色了。钉成 subagent_type 本身:
79
+ // 一个角色一个常驻实例,寻址名 = 角色名,两边不用对表。
80
+ // ── 演员位(2026-08-28 重构):subagent_type 是预注册的 rp-actor / rp-narrator,
81
+ // 实例身份由 name 决定。name 闸写成硬 deny:探针实测模型漏传 name 被拒一次
82
+ // 就会补上(写成话术它不听,写成闸它就听 —— castedInRun 时代同款教训)。
83
+ if (isSlotType(t.subagent_type)) {
84
+ let name = typeof t.name === 'string' ? t.name.trim() : '';
85
+ let inferred = false;
86
+ if (!ROLE_SLUG_RE.test(name) || isSlotType(name)) {
87
+ // 参数没给/给坏了 → 从 prompt 推(glm 撞闸案:弱模型够不着的参数不当硬前置)
88
+ const taken = new Set(roster?.list?.() || []);
89
+ name = await inferRoleNameFromPrompt(workspaceRoot, t.prompt, { taken, isSlot: isSlotType });
90
+ inferred = !!name;
91
+ }
92
+ if (!name) {
93
+ return {
94
+ hookSpecificOutput: {
95
+ hookEventName: 'PreToolUse',
96
+ permissionDecision: 'deny',
97
+ // nd:rp-prompt —— 给两条出路:参数,或(弱模型也走得通的)prompt 配方
98
+ permissionDecisionReason:
99
+ `派演员位要一个独一无二的角色名(rp- 开头的小写 slug)。两种给法任选:`
100
+ + `① name 参数写上它;② prompt 第一行照 cast_role 的配方写`
101
+ + `「你的角色卡:角色/<名>/角色卡.md」(登记过的卡,闸会自己认出名字)。`
102
+ + `它就是这个角色之后的收件地址(SendMessage 寄给它用)。`,
103
+ },
104
+ };
105
+ }
106
+ if (roster && roster.has(name)) {
107
+ return {
108
+ hookSpecificOutput: {
109
+ hookEventName: 'PreToolUse',
110
+ permissionDecision: 'deny',
111
+ permissionDecisionReason:
112
+ `「${name}」已经在场了,不用重新派。要它接着演就用 SendMessage 寄给这个名字`
113
+ + `(它记得之前所有事)。重新派会新起一个失忆的同名角色并顶掉现在这个。`
114
+ + `真要另起一个角色,换个名字。`,
115
+ },
116
+ };
117
+ }
118
+ // 白名单仍在派发时收口 —— 演员位文件也在工作区里、模型也能改,判据不因换了
119
+ // 载体而松:谁改装了演员位的 tools 行,派发时照样被拦。
120
+ const illegal = await illegalRoleTools(workspaceRoot, t.subagent_type);
121
+ if (illegal) {
122
+ return {
123
+ hookSpecificOutput: {
124
+ hookEventName: 'PreToolUse',
125
+ permissionDecision: 'deny',
126
+ permissionDecisionReason: illegal,
127
+ },
128
+ };
129
+ }
130
+ if (roster) roster.claim(name);
131
+ // agent_id ↔ 名字的桥:名字排队等 SubagentStart 那一刻配对(actor-trail.js)。
132
+ // 不从 tool_result 文本里抠 id —— 那条旧桥 08-28 在生产上断过,症状是署名
133
+ // 整片落成演员位而没有任何报错。
134
+ notePendingRoleName(name);
135
+ // 盖章按**实例名**:byOf / 收件箱 / 板书署名全按名字走,不认演员位(rp-actor
136
+ // 是位置不是人)。agentId→名字的别名在 PostToolUse 学(hooks/slot-alias.js)。
137
+ noteToolCaller(toolUseId || input?.tool_use_id, { agentType: name });
138
+ if (!inferred && t.run_in_background === true && !t.model) return {};
139
+ // model 参数剥掉(proj_mtd7d4et:glm 会话的 GM 给角色点 sonnet):角色跟会话
140
+ // 模型走 —— 免费行的角色拿别名去要订阅模型,打到该会话的 ingress 上游轻则
141
+ // 400 重则记账错位。演员位定义 model: inherit 是教义,这里是闸。
142
+ // isolation 一起剥(2026-08-28 真会话:GM 给角色传了 isolation:"worktree",
143
+ // meta 里 worktreeCleanlyRemoved:true —— 角色被派进一个**用完就删**的 git 副本。
144
+ // 常驻角色要在真工作区里活着:它的卡、记忆、板书都在那儿,而 worktree 会被回收。
145
+ // 跟 model 同一类:模型够得着但不该由它决定的入参,闸上剥掉而不是写进话术。
146
+ const { model: _dropModel, isolation: _dropIsolation, ...rest } = t;
147
+ return {
148
+ hookSpecificOutput: {
149
+ hookEventName: 'PreToolUse',
150
+ permissionDecision: 'allow',
151
+ // 推断出的名字必须回写进 input —— CLI 按它建实例,SendMessage 按它寻址
152
+ updatedInput: { ...rest, name, run_in_background: true },
153
+ // nd:rp-prompt
154
+ additionalContext:
155
+ `「${name}」已按后台派出${inferred ? '(名字是从你 prompt 里的角色卡认出来的)' : ''}。`
156
+ + `以后要它说话/接着演,用 SendMessage 寄给 ${name}`
157
+ + `(它记得之前所有事),不要重新派 —— 重派会新起一个失忆的同名角色顶掉旧的。`,
158
+ },
159
+ };
160
+ }
161
+
162
+ if (isResidentRole(t.subagent_type)) {
163
+ // 旧式一角一定义(会话启动前就躺在 .claude/agents/ 里的角色文件)仍然放行 ——
164
+ // 它们在初始快照里,派得动。会话中途造新定义的路已随 cast_role 重写关闭
165
+ //(中途写入的文件只有当时活着的进程认得,resume 后永久 not found —— 08-28 勘定)。
166
+ // 重派同名角色 = 静默失忆:CLI 的 latest wins 让新角色顶掉这个名字,
167
+ // 旧角色连同它演过的全部剧情一起失联,而且不报错。只靠提示词劝拦不住,所以硬拦。
168
+ // ⭐ 工具白名单收口在**派发时**,不是写入时。cast_role 是造角色的正门,但主 agent
169
+ // 手里有 Write/Edit/Bash,`.claude/agents/` 就在工作区内 —— 手写一份 md 就能
170
+ // 给自己造一个拿着 publish_site 的"角色"。拦写入拦不干净(Write 拦了还有 Bash,
171
+ // Bash 拦了还有别的落盘路径),拦派发只有这一个口子。
172
+ // 见 role-card.js:缺 tools 行 = SDK 语义上继承父代理的全部工具,所以缺失也不放行。
173
+ const illegal = await illegalRoleTools(workspaceRoot, t.subagent_type);
174
+ if (illegal) {
175
+ return {
176
+ hookSpecificOutput: {
177
+ hookEventName: 'PreToolUse',
178
+ permissionDecision: 'deny',
179
+ // nd:rp-prompt
180
+ permissionDecisionReason: illegal,
181
+ },
182
+ };
183
+ }
184
+
185
+ if (roster && roster.has(t.subagent_type)) {
186
+ return {
187
+ hookSpecificOutput: {
188
+ hookEventName: 'PreToolUse',
189
+ permissionDecision: 'deny',
190
+ permissionDecisionReason:
191
+ `「${t.subagent_type}」已经在场了,不用重新派。要它接着演就用 `
192
+ + `SendMessage 寄给这个名字(它记得之前所有事)。重新派会新起一个失忆的同名角色`
193
+ + `并顶掉现在这个,之前演过的全部剧情都会失联。真要另起一个角色,换个名字。`,
194
+ },
195
+ };
196
+ }
197
+ if (roster) roster.claim(t.subagent_type);
198
+ // ⭐ 派发这次调用的 toolUseId,正是这个角色之后所有事件的 parentToolUseId
199
+ //(SDK 用它标"这条消息来自哪个子代理")。在这儿盖一次章,画布那边就能
200
+ // 认出"这个事件是墨璃干的",不用再建第二套映射。见 actor-trail.js
201
+ noteToolCaller(toolUseId || input?.tool_use_id, { agentType: t.subagent_type });
202
+ if (t.run_in_background === true && t.name === t.subagent_type) return {}; // 已经对了,别重复改
203
+ return {
204
+ hookSpecificOutput: {
205
+ hookEventName: 'PreToolUse',
206
+ permissionDecision: 'allow',
207
+ updatedInput: { ...t, run_in_background: true, name: t.subagent_type },
208
+ // nd:rp-prompt —— 这句话是 RP 教义的一部分,等提示词层专门过一遍时一起调
209
+ additionalContext:
210
+ `「${t.subagent_type}」是常驻角色:已按后台派出并把它的名字钉成 ${t.subagent_type}。`
211
+ + `以后要它说话/接着演,用 SendMessage 寄给这个名字(它记得之前所有事),不要重新派一次 —— `
212
+ + `重派会新起一个失忆的同名角色并顶掉旧的。`,
213
+ },
214
+ };
215
+ }
216
+
38
217
  // 显式前台,不动
39
218
  if (t.run_in_background === false) return {};
40
219
  return {
@@ -82,3 +261,28 @@ export function makePreToolUseGrepContentDefaultHandler() {
82
261
  };
83
262
  };
84
263
  }
264
+
265
+ /**
266
+ * PreToolUse:把「这次工具调用是谁发起的」记下来,给 MCP 工具署名用。
267
+ *
268
+ * MCP handler 拿不到 agent_id / agent_type(extra 里没有),hook 拿得到 —— 这条旁路是
269
+ * 板上归属的唯一可信来源。**不要改成从角色文件读**:那份文件模型能改,自称不是实证。
270
+ * 见 actor-trail.js 的头注释。
271
+ *
272
+ * 挂通配(mcp__nodesign__.*,见 hooks.js):这里曾按「板上写入类才需要」挂手写名单,
273
+ * 漏过两次(08-26 收件箱三件、08-28 场务四件),每次症状都是 byOf 静默落回 'agent'、
274
+ * 依赖它的守卫整条失效。一次 Map.set 的代价换「凡用 byOf 的工具永远在闸内」,值。
275
+ */
276
+ export function makePreToolUseActorStamp() {
277
+ // 具名函数:装配测试(hooks-assembly.test.js)按 fn.name 认出这道闸挂没挂对
278
+ return async function actorStamp(input, toolUseId) {
279
+ noteToolCaller(toolUseId || input?.tool_use_id, {
280
+ agentId: input?.agent_id || null,
281
+ // 演员位实例:agent_type 是位置(rp-actor),实例名走别名表解析(08-28 探针
282
+ // 实证 hook input 没有名字字段)。别名还没学到时落回 agent_type —— 署名难看
283
+ // 但不失真;下一次 SendMessage/派发的 PostToolUse 会把名字补上。
284
+ agentType: agentNameOf(input?.agent_id) || input?.agent_type || null,
285
+ });
286
+ return {};
287
+ };
288
+ }
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import path from 'node:path';
10
10
  import { toWorkspaceRel } from '../../../lib/workspace-path.js';
11
- import { kindOfPath, kindDef, KIND_DECK } from '../../../lib/artifact-target.js';
11
+ import { kindOfPath, kindDef, KIND_DECK, KIND_SITE } from '../../../lib/artifact-target.js';
12
12
  import { loadToolPrompt } from './tool-prompts.js';
13
13
 
14
14
  /**
@@ -43,6 +43,9 @@ export function makePreToolUseGetPendingChangesProtocolInjector() {
43
43
  * 常驻在 prelude 里是 1.2k tokens,但一次会话里可能一次都用不上。
44
44
  */
45
45
  export function makePreToolUseAskUserQuestionProtocolInjector() {
46
+ // 08-28 用户拍板还原:撤掉 08-27 的「提问上板」deny 闸,AskUserQuestion 走回
47
+ // 侧栏卡片本职。上板问选择仍然可以(nd:controls 是现役件),但那是 agent 的
48
+ // 版面选择,不再由闸强制。
46
49
  let alreadyInjected = false;
47
50
  return async (_input, _toolUseId, _options) => {
48
51
  if (alreadyInjected) return {};
@@ -77,7 +80,21 @@ export function makePreToolUseHybridReferenceInjector({ workspaceRoot } = {}) {
77
80
  const fp = input?.tool_input?.file_path;
78
81
  if (typeof fp !== 'string') return {};
79
82
  const rel = workspaceRoot ? toWorkspaceRel(fp, workspaceRoot) : fp;
80
- const kind = workspaceRoot ? await kindOfPath(workspaceRoot, rel) : KIND_DECK;
83
+ let kind = workspaceRoot ? await kindOfPath(workspaceRoot, rel) : KIND_DECK;
84
+ /**
85
+ * 散装 .html 的内容嗅探(2026-08-30)。kindOfPath 对清单里没有、又不叫
86
+ * index/canvas 的 .html 一律回落 deck —— 而 site-craft 的试作恰恰规定放
87
+ * `_drafts/<名字>.html`,于是**每场按方法论走的站点会话,第一笔试作都被
88
+ * 塞一份 deck 参考**(data-page/importmap/Babel,文不对题),该到的 site
89
+ * 参考(相对路径铁律/目录约定)反而没到(真案 proj_mtfz7n8p)。
90
+ * 嗅探判据:deck 有模板 verbatim 军规兜底,正身必带 __nd-deck-wrap /
91
+ * data-page;一个都没有的散装 .html 当 site 对待。
92
+ */
93
+ if (kind === KIND_DECK && path.extname(fp).toLowerCase() === '.html') {
94
+ const body = typeof input?.tool_input?.content === 'string' ? input.tool_input.content : '';
95
+ const base = path.basename(fp).toLowerCase();
96
+ if (base !== 'canvas.html' && !body.includes('__nd-deck-wrap') && !body.includes('data-page=')) kind = KIND_SITE;
97
+ }
81
98
  // 触发条件从「写 .html」改成「写的文件跟这个形态的入口同类型」(2026-08-01)。
82
99
  // 写死 .html 的年代只有 deck 和 site,两者入口都是 html 所以恰好没错;非 html 入口的形态
83
100
  // 的入口不是 .html,于是它的技术参考**永远不会被注入**,agent 一辈子不知道
@@ -0,0 +1,88 @@
1
+ /**
2
+ * hooks/pre-peer-guard.js —— SendMessage 收件人闸(2026-08-26)
3
+ *
4
+ * ## 为什么要有
5
+ *
6
+ * 把 `SendMessage` 挂进 DEFAULT_TOOL_ALLOWLIST(常驻角色的唯一叫醒方式)的同时,
7
+ * 也就打开了这个工具的**另一半寻址空间**:同一台机器上其他 Claude 会话。
8
+ * 2026-08-26 探针实测,`ListAgents` 在这台机器上列出了 4 个 peer session
9
+ * (站主自己的交互会话、tmux 里的、别的项目的),SendMessage 的 `to` 是自由字符串,
10
+ * 猜中一个名字就寄得过去。
11
+ *
12
+ * 对 Nodesign 这是明确不能有的东西:会话属于不同用户,跨会话寄信 =
13
+ * 一个用户的 agent 能给另一个人的 agent 递指令。SDK 侧的 `crossSessionInbound`
14
+ * 只管**收**(我们收不收别人的),管不了**发**。所以发这一侧自己装闸。
15
+ *
16
+ * ## 判据(白名单,不是黑名单)
17
+ *
18
+ * 只放行三种收件人:
19
+ * 1. `main` —— 子代理跟主代理说话(CLI 保留字,寄到主对话)
20
+ * 2. `rp-*` —— 本会话的常驻角色(见 cast.js)
21
+ * 3. `a<16 位十六进制>` —— 本会话某个子代理的 agentId(派发时那条 tool_result 给的,
22
+ * 天生只在本会话内有意义)
23
+ * 其余一律 deny。**黑名单在这里必然漏**:peer session 的名字是别人机器/别人会话
24
+ * 起的,形状不受我们控制,枚举不完。
25
+ *
26
+ * ⚠️ 判据要能被攻:探针 _probe-resident-agent.mjs 里有一条「寄给 peer session 名字
27
+ * 必须被拒」的反向断言 —— 只看"它没放过坏东西"是不够的,得给它一个**按规定必须拦
28
+ * 的东西**看它拦不拦(记忆 feedback-verify-guards-by-attacking)。
29
+ */
30
+
31
+ /* 判据是「本会话真的派过它」,不是「名字长得像角色」—— 理由见 cast.js 的 createRoleRoster */
32
+
33
+ /**
34
+ * 本会话子代理的 agentId 形状(派发 tool_result 里的 `agentId: a67e7b9568aa4698b`)。
35
+ * ⚠️ 这是 CLI 2.1.237 的实测形状;SendMessage 的 schema 文档把格式写成 `a...-...`
36
+ * (存在带连字符的变体)。CLI 换版若改长度或加连字符,合法 agentId 会被误拒 ——
37
+ * 方向是过拦、症状可见(deny 会把原因回显给模型),不会静默放行,所以钉死可接受。
38
+ */
39
+ const AGENT_ID_RE = /^a[0-9a-f]{16}$/;
40
+
41
+ /** CLI 保留字:寄到主对话 */
42
+ const MAIN = 'main';
43
+
44
+ export function makePreToolUseSendMessageRecipientGuard({ roster = null } = {}) {
45
+ return async (input) => {
46
+ const to = input?.tool_input?.to;
47
+ // 形状不对也拒:白名单闸的缺省必须是拒。真 schema 里 `to` 是 string 且
48
+ // additionalProperties:false,所以今天走不到这里 —— 但那是**CLI 版本事实**,
49
+ // 不是我们的不变量,翻成 deny 零成本。
50
+ if (typeof to !== 'string') return deny(String(to));
51
+ const target = to.trim();
52
+
53
+ // 闸判的是 trim 后的值,工具收到的是原值 —— 两个字符串不一致就是两套判据。
54
+ // allow 时顺手回写(SendMessage matcher 下只有这一个 handler,不踩 updatedInput 互抹的坑)。
55
+ const pass = () => (target === to ? {} : {
56
+ hookSpecificOutput: {
57
+ hookEventName: 'PreToolUse',
58
+ permissionDecision: 'allow',
59
+ updatedInput: { ...input.tool_input, to: target },
60
+ },
61
+ });
62
+
63
+ if (target === MAIN || AGENT_ID_RE.test(target)) return pass();
64
+ // ⭐ 关键:问名册「这个角色是不是本会话派的」,不是问名字长得像不像。
65
+ // 形状判据会把裸名放给 CLI 去全机器解析,本会话没有同名 agent 时它会落到
66
+ // 别人的会话上(多用户共机 + 统一命名教义 = 必然撞名)。
67
+ // 没接名册时**不退回形状判据**:那是静默降级(闸看起来还在,判据已经换成不安全的那个)。
68
+ // 漏接线的症状必须是「角色寄不出信」这种看得见的坏,不是悄悄放宽。
69
+ if (roster?.has(target)) return pass();
70
+
71
+ return deny(target);
72
+ };
73
+ }
74
+
75
+ function deny(target) {
76
+ return {
77
+ hookSpecificOutput: {
78
+ hookEventName: 'PreToolUse',
79
+ permissionDecision: 'deny',
80
+ // nd:rp-prompt —— 拒绝话术属于 RP 教义,等提示词层专门过时一起调
81
+ permissionDecisionReason:
82
+ `收件人「${target}」不在本会话里。SendMessage 只能寄给:main(主对话)、`
83
+ + `本会话已经派出的常驻角色、或本会话子代理的 agentId(形如 a1234567890abcdef,`
84
+ + `裸 id 不要带 [ref] 后缀)。这台机器上可能有别人的会话,跨会话寄信一律不放行;`
85
+ + `角色如果还没派出来(比如服务重启过),先派它,不要直接寄信。`,
86
+ },
87
+ };
88
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * PreToolUse(nodesign 工具族) —— 未知参数不许静默消失(2026-08-30)
3
+ *
4
+ * 病根:SDK 按 tool() 的 schema 校验入参,zod 的 object **默认 strip 未知键**
5
+ * —— 不报错、不警告、不留痕。于是模型自造的参数连同它承载的内容一起蒸发,
6
+ * 那一步照跑,只丢半个意图。
7
+ *
8
+ * 真会话实锤(全库 358 份转录):`write_on_board.facts` 直调 18 次 + batch 内 2 次,
9
+ * 每次都是一长串「本章确立的事实」,一个字没落盘、一句话没报回去。另有
10
+ * `edit_board.op/on`(模型把单条 op 摊平写)4 次、`edit_board.screenshotAfter` 4 次。
11
+ *
12
+ * 工具自己看不见这些键(进 handler 前就没了),只有钩子拿得到原始 tool_input。
13
+ * 所以探针挂这儿,判据只有一条:**这个键不在该工具的 schema 里**。
14
+ *
15
+ * 不 block —— 拦下来会让整批停摆,而多数情况正事是做得成的。做法是放行 + 点名:
16
+ * 「你传的 X 没有生效,内容没落盘」,模型下一轮自己补。
17
+ * 归属:`feedback-tool-input-no-silent-drop`(入参不许静默丢/静默改口径)。
18
+ */
19
+ import { TOOL_PARAM_KEYS } from '../../mcp/index.js';
20
+
21
+ const BATCH_TOOLS = new Set(['mcp__nodesign__board_batch', 'mcp__nodesign__browser_batch', 'mcp__nodesign__artifact_batch']);
22
+ /** batch 自己的旋钮,不算未知 */
23
+ const BATCH_OWN = new Set(['actions', 'screenshotAfter']);
24
+
25
+ /**
26
+ * 一个工具的未知键(schema 没登记过的)。台账里没有这个工具 → 返回空,别瞎报。
27
+ *
28
+ * ⚠️ 名字两种写法都要认:台账按 tool() 的**裸名**建(write_on_board),而钩子拿到的
29
+ * tool_name 是**带前缀**的(mcp__nodesign__write_on_board),batch 里模型两种都写过。
30
+ * 第一版只查带前缀那一种,于是钩子从来不响 —— 我自己的冒烟测试给 map 塞的正好是
31
+ * 带前缀的键,把这个洞盖住了(判据本身要先验一遍,量具错得比 bug 还多)。
32
+ */
33
+ const PREFIX = 'mcp__nodesign__';
34
+ function schemaOf(name) {
35
+ if (typeof name !== 'string') return null;
36
+ return TOOL_PARAM_KEYS.get(name) || TOOL_PARAM_KEYS.get(name.startsWith(PREFIX) ? name.slice(PREFIX.length) : `${PREFIX}${name}`) || null;
37
+ }
38
+ function unknownKeysOf(name, input) {
39
+ const own = schemaOf(name);
40
+ if (!own || !own.size || !input || typeof input !== 'object') return [];
41
+ return Object.keys(input).filter((k) => !own.has(k) && !k.startsWith('__'));
42
+ }
43
+
44
+ export function makePreToolUseUnknownParamsProbe() {
45
+ return async (input) => {
46
+ try {
47
+ const name = input?.tool_name;
48
+ if (typeof name !== 'string' || !name.startsWith('mcp__nodesign__')) return {};
49
+ const args = input?.tool_input;
50
+ if (!args || typeof args !== 'object') return {};
51
+ const lines = [];
52
+
53
+ if (BATCH_TOOLS.has(name)) {
54
+ for (const k of Object.keys(args)) {
55
+ if (!BATCH_OWN.has(k) && !k.startsWith('__')) lines.push(`batch 这一层的 \`${k}\` 不是它的参数`);
56
+ }
57
+ const actions = Array.isArray(args.actions) ? args.actions : [];
58
+ actions.forEach((a, i) => {
59
+ if (!a || typeof a !== 'object') return;
60
+ const bad = unknownKeysOf(a.name, a.input);
61
+ if (bad.length) lines.push(`第 ${i + 1} 步 ${a.name} 的 \`${bad.join('`/`')}\``);
62
+ });
63
+ } else {
64
+ const bad = unknownKeysOf(name, args);
65
+ if (bad.length) lines.push(`\`${bad.join('`/`')}\``);
66
+ }
67
+ if (!lines.length) return {};
68
+
69
+ const own = BATCH_TOOLS.has(name) ? null : schemaOf(name);
70
+ const take = own?.size ? `${name.replace(PREFIX, '')} 收的是:${[...own].join(' / ')}。` : '';
71
+ return {
72
+ hookSpecificOutput: {
73
+ hookEventName: 'PreToolUse',
74
+ permissionDecision: 'allow',
75
+ additionalContext: `⛔ 这次调用里 ${lines.join(';')} —— 不是这个工具的参数,**整个被丢掉了**,`
76
+ + `里面的内容一个字都没到工具手上(这一步其余部分照跑)。${take}`
77
+ + '要保留那些内容就换个真实存在的参数重写一遍,别以为它已经生效了。',
78
+ },
79
+ };
80
+ } catch { return {}; }
81
+ };
82
+ }
@@ -18,8 +18,11 @@
18
18
  * - 自己出错就放行(fail-open)—— 闸崩了不该把整个会话堵死。
19
19
  */
20
20
 
21
+ import fs from 'node:fs';
21
22
  import os from 'node:os';
22
23
  import path from 'node:path';
24
+ import { CHALK_DIR } from '../../../lib/chalk.js';
25
+ import { ROLE_SLUG_RE } from '../cast.js';
23
26
 
24
27
  const TARGET_FIELDS = ['file_path', 'path', 'notebook_path'];
25
28
  const WRITE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit']);
@@ -46,6 +49,36 @@ export function checkWorkspaceScope(toolInput, { workspaceRoot, dataRoot, toolNa
46
49
  const v = toolInput?.[field];
47
50
  if (typeof v !== 'string' || !v) continue;
48
51
  const abs = path.resolve(ws, v); // 相对路径按工作区解析
52
+ // ⛔ 角色文件是**判据本身**,不许模型手写(2026-08-26 fable 验收)。
53
+ //
54
+ // 派发闸靠读 `.claude/agents/<slug>.md` 的 tools 行决定放不放行,可那份文件
55
+ // 模型自己能改 —— 于是闸校验的和 CLI 真正使用的**不是同一份内容**:
56
+ // ① TOCTOU:写宽版 → 结束回合(CLI 缓存了宽版)→ 覆写成窄版 → 同回合派发。
57
+ // 我们现读磁盘拿窄版放行,CLI 用回合开工的宽版快照 → 角色真拿到外发/花钱工具。
58
+ // ② 解析器分歧:重复的 `tools:` 键,我们的正则取第一处、CLI 的 YAML 取最后一处。
59
+ // 两条都不是对主 agent 提权(它本来就有这些工具),但这条线的正常用法是**导入
60
+ // 酒馆卡/世界书**——外来文本能借主 agent 的手造出一个拿着外发工具的角色。
61
+ // 修法只能是「让判据不可被它改」:正门 cast_role 走服务端 fs(不过这道闸),
62
+ // 模型这侧一律拒。⚠️ Bash 不归这道闸管,那半靠沙盒(isolation.js)。
63
+ if (isWrite && insideDir(abs, path.join(ws, '.claude', 'agents'))) {
64
+ return '角色文件不能手写 —— 用 cast_role。'
65
+ + '那个目录里的文件同时是「这个角色能用哪些工具」的判据,'
66
+ + '手写等于自己给自己发权限,所以一律拒绝(改已有角色也走 cast_role)。';
67
+ }
68
+ // 接续权的文件面(2026-08-27 编排):角色写的板书文件,主 agent 不许 Edit/Write。
69
+ // 板上那半(reply_to/chain)在 write-on-board 闸;这里堵的是「直接改文件正文」——
70
+ // 改别人的台词比接别人的话头更越界。作者看文件自己的 by: 章(harness 盖的,
71
+ // 角色没有 Write 工具伪造不了)。读不到/没有章 → 放行(fail-open,主 agent 自己的板书本来就能改)。
72
+ if (isWrite && insideDir(abs, path.join(ws, ...CHALK_DIR.split('/')))) {
73
+ try {
74
+ const m = fs.readFileSync(abs, 'utf8').slice(0, 400).match(/^by:\s*(\S+)/m);
75
+ // 判据统一走 ROLE_SLUG_RE(08-27 审计):真角色名必过它
76
+ if (m && ROLE_SLUG_RE.test(m[1])) {
77
+ return `这份板书是角色「${m[1]}」写的,它的话不是你的稿子 —— 不改、不润色、不代笔。`
78
+ + '想让它改:把意见寄给它(SendMessage)或让用户直接跟它说。';
79
+ }
80
+ } catch { /* 新建/读不到 → 放行 */ }
81
+ }
49
82
  if (insideDir(abs, ws)) continue; // 自己的工作区,放行
50
83
  if (isWrite) {
51
84
  if (tempDirs().some(d => insideDir(abs, d))) continue; // 临时文件随便写
@@ -0,0 +1,100 @@
1
+ /**
2
+ * hooks/resident-role-lifecycle.js —— 常驻角色名册的退场那一侧(2026-08-26)
3
+ *
4
+ * 进场在 `pre-defaults.js`(PreToolUse 的 rp 分支 `roster.claim()`),退场在这里。
5
+ * 两边分开是因为事件族不同,不是因为职责不同 —— 改任何一边都要看另一边。
6
+ *
7
+ * ## 为什么必须有退场
8
+ *
9
+ * claim 发生在 **Agent 工具执行之前**(PreToolUse)。派发要是失败了(最现实的路径:
10
+ * 模型在角色的 `.claude/agents/<name>.md` 写出来之前就先派了这个还不存在的类型),
11
+ * 名字已经登记进名册,而进程里根本没有这个 agent。后果两条,都很迷惑:
12
+ *
13
+ * 1. **收件人闸的洞在这个窄窗口里重开**:闸看名册说「在场」→ 放行 → 而本会话没有
14
+ * 同名 in-process agent → CLI 的裸名解析回落到**全机范围** → 信可能落到别人的会话。
15
+ * (这正是 H1 的病,名册修好了主干,这里是它的补角。)
16
+ * 2. **这个名字整个会话被 brick**:重派被硬 deny 且文案谎报「已经在场了」,
17
+ * SendMessage 又叫不醒,模型只剩换名一条路。
18
+ *
19
+ * claim 留在 PreToolUse 不动(它挡住了同一回合并发派两次同名的竞态,这点比挪到
20
+ * PostToolUse 更值),失败时在这里补一次 release —— 进场乐观、退场兜底。
21
+ */
22
+
23
+ import { isResidentRole, isSlotType } from '../cast.js';
24
+ import { agentNameOf, noteAgentName, takePendingRoleName } from '../actor-trail.js';
25
+ import { noteRoleStart, noteRoleFinish } from '../stage-status.js';
26
+
27
+ /**
28
+ * SubagentStart:把这个 agent_id 认成刚派出去的那个角色(2026-08-29)。
29
+ *
30
+ * hook input 只有 `agent_id` + `agent_type`(演员位),没有实例名 —— 名字在派发闸
31
+ * 那一刻就知道了,所以闸把它排进队列(actor-trail.notePendingRoleName),这里取队头
32
+ * 配对。**这是署名的正门**:harness 在起飞那一刻亲手给的 id,不解析任何模型可写的文本。
33
+ *
34
+ * 只认演员位起飞:干活型子代理(vision-checker 那类)不进别名表。
35
+ */
36
+ export function makeSubagentStartRoleAlias({ projectId = null } = {}) {
37
+ return async function subagentStartRoleAlias(input) {
38
+ if (!isSlotType(input?.agent_type) || !input?.agent_id) return {};
39
+ const known = agentNameOf(input.agent_id); // 已经认过(唤醒重入)
40
+ const name = known || takePendingRoleName();
41
+ if (!name) return {};
42
+ if (!known) noteAgentName(input.agent_id, name);
43
+ noteRoleStart(projectId, name); // 台上一览:它开始写了
44
+ return {};
45
+ };
46
+ }
47
+
48
+ export function makePostToolUseFailureRoleRelease({ roster = null } = {}) {
49
+ return async (input) => {
50
+ if (!roster) return {};
51
+ // 演员位派发失败:登记的是实例名(tool_input.name),撤的也是它
52
+ const type = isSlotType(input?.tool_input?.subagent_type)
53
+ ? input?.tool_input?.name
54
+ : input?.tool_input?.subagent_type;
55
+ if (!isResidentRole(type)) return {};
56
+ if (!roster.release(type)) return {};
57
+
58
+ // 「还没拾取到」跟「角色不存在」是两回事,指引也不同。前者**必须原样重派**,
59
+ // 而模型不会自己这么做:2026-08-26 探针里它撞了 not found 之后既没重试,
60
+ // 还回了一句「已派」谎报成功 —— 所以这句话要说得比"你可以再派一次"硬。
61
+ const notFound = /not found/i.test(String(input?.error || ''));
62
+ return {
63
+ hookSpecificOutput: {
64
+ hookEventName: 'PostToolUseFailure',
65
+ // nd:rp-prompt
66
+ additionalContext: notFound
67
+ ? `「${type}」没派出来:CLI 还没拾取到它的角色文件(监视器有几秒防抖)。`
68
+ + `**原样再派一次**,别改参数、别跳过、更不要说"已派"—— 它现在还没上场。`
69
+ : `「${type}」没派出来,已经把这个名字从在场名册里撤了 —— 你可以再派一次。`
70
+ + `(如果是因为这个角色还不存在,先用 cast_role 把它的角色文件写出来。)`,
71
+ },
72
+ };
73
+ };
74
+ }
75
+
76
+ /**
77
+ * SubagentStop:角色收笔了,记一笔进台上一览(2026-08-26 建;08-29 改写)。
78
+ *
79
+ * 08-29 之前这里做两件事:标「散场」+ 用 systemMessage 教主控怎么召回。两件都随
80
+ * 常驻/散场概念一起退役 —— 新回路里角色本来就写一段结束一轮,「结束」是正常节拍
81
+ * 不是事故。而且 systemMessage 这条路 08-28 真会话里查无痕迹(SDK 的 SubagentStop
82
+ * 只有 additionalContext,且按类型定义那是**发给子代理**的),主持人真正收到的是
83
+ * SDK 自带的 task-notification。所以这里不再试图跟主持人说话,只记账 ——
84
+ * 状态由每回合状态块的「台上」一节统一注入(user-prompt-submit.js)。
85
+ *
86
+ * ⛔ **不 release 名字**:角色收笔 ≠ 不存在了。它的转录还在,SendMessage 叫得回来;
87
+ * 把名字放回去等于允许重派,而重派会新起一个失忆的同名角色顶掉它。
88
+ */
89
+ export function makeSubagentStopRoleNotice({ projectId = null } = {}) {
90
+ return async (input) => {
91
+ // 角色位实例:agent_type 是位置(rp-role),实例名走别名表。别名没学到就跳过
92
+ // (不知道是谁收的笔 —— 别把 'rp-role' 当角色记进台上一览)。
93
+ const name = isSlotType(input?.agent_type)
94
+ ? agentNameOf(input?.agent_id)
95
+ : input?.agent_type;
96
+ if (!name || !isResidentRole(name) || isSlotType(name)) return {};
97
+ noteRoleFinish(projectId, name, input?.last_assistant_message || null);
98
+ return {};
99
+ };
100
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * hooks/slot-alias.js —— 唤醒时重新认一遍角色的 agent_id(2026-08-28 建;08-29 收窄)
3
+ *
4
+ * 派发那条路 08-29 搬去 `SubagentStart`(harness 亲手给 id,不解析文本,见
5
+ * resident-role-lifecycle.js 的 makeSubagentStartRoleAlias)。这里只剩**唤醒**一条:
6
+ * `SendMessage({to:"rp-x"})` 的结果 JSON 带 `resumedAgentId`,服务器重启后别名表
7
+ * 清零、而角色还活着时,靠这一条重新学回来。
8
+ *
9
+ * 匹配用正则怼序列化文本而不是解构对象:tool_response 的形状(数组/对象/字符串)
10
+ * 随 SDK 版本漂,id 的形状(十六进制串)比包装稳定得多。
11
+ */
12
+
13
+ import { isSlotType, ROLE_SLUG_RE } from '../cast.js';
14
+ import { noteAgentName } from '../actor-trail.js';
15
+
16
+ // 引号写成 \\?":结果 JSON 常作为字符串再被包一层(stringify 转义成 \"),两层都认
17
+ const RESUMED_RE = /\\?"resumedAgentId\\?"\s*:\s*\\?"([0-9a-f]{8,32})\\?"/;
18
+
19
+ export function makePostToolUseSlotAliasHandler() {
20
+ return async function slotAlias(input) {
21
+ const t = input?.tool_input;
22
+ if (!t || typeof t !== 'object') return {};
23
+ // 按名寄信的恢复结果 → 重新学(覆盖重启后别名表清零的窗口)
24
+ if (typeof t.to === 'string' && ROLE_SLUG_RE.test(t.to) && !isSlotType(t.to)) {
25
+ const m = RESUMED_RE.exec(JSON.stringify(input?.tool_response ?? ''));
26
+ if (m) noteAgentName(m[1], t.to);
27
+ }
28
+ return {};
29
+ };
30
+ }