@xiaobuyu/nodesign 0.0.6 → 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 (383) 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 +26 -0
  7. package/server/api/assets/notes.js +50 -0
  8. package/server/api/assets.js +31 -38
  9. package/server/api/board.js +29 -1
  10. package/server/api/browse.js +3 -2
  11. package/server/api/chatai.js +9 -8
  12. package/server/api/exports/cards.js +7 -6
  13. package/server/api/exports/docx-pdf.js +4 -1
  14. package/server/api/instruction.js +3 -3
  15. package/server/api/local.js +10 -8
  16. package/server/api/projects.js +19 -3
  17. package/server/api/publish.js +3 -2
  18. package/server/api/sessions-rewind.js +220 -0
  19. package/server/api/sessions.js +58 -194
  20. package/server/api/stage.js +35 -0
  21. package/server/api/turn-model-switch.js +109 -0
  22. package/server/api/turn.js +52 -88
  23. package/server/auth/middleware.js +29 -6
  24. package/server/auth/tier.js +27 -7
  25. package/server/auth/users-store.js +14 -1
  26. package/server/engine/agent/actor-trail.js +110 -0
  27. package/server/engine/agent/agent-shared.js +22 -52
  28. package/server/engine/agent/beat-state.js +61 -0
  29. package/server/engine/agent/cast.js +322 -0
  30. package/server/engine/agent/events.js +18 -1
  31. package/server/engine/agent/hooks/beat-gate.js +73 -0
  32. package/server/engine/agent/hooks/failure.js +11 -1
  33. package/server/engine/agent/hooks/lifecycle.js +16 -41
  34. package/server/engine/agent/hooks/post-guidance.js +4 -38
  35. package/server/engine/agent/hooks/post-trim.js +1 -0
  36. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  37. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  38. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  39. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  40. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  41. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  42. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  43. package/server/engine/agent/hooks/slot-alias.js +30 -0
  44. package/server/engine/agent/hooks/user-prompt-submit.js +208 -47
  45. package/server/engine/agent/hooks.js +80 -14
  46. package/server/engine/agent/isolation.js +25 -2
  47. package/server/engine/agent/memory-config.js +87 -0
  48. package/server/engine/agent/model-context.js +102 -18
  49. package/server/engine/agent/model-table.js +361 -58
  50. package/server/engine/agent/prompts/nodesign-prelude.md +579 -70
  51. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  52. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +26 -9
  53. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  54. package/server/engine/agent/prompts/tools/site-reference.md +15 -4
  55. package/server/engine/agent/role-card.js +175 -0
  56. package/server/engine/agent/rp-mode.js +17 -0
  57. package/server/engine/agent/session-loop.js +55 -32
  58. package/server/engine/agent/stage-status.js +73 -0
  59. package/server/engine/agent/subagent-flight.js +73 -0
  60. package/server/engine/agent/system-prompts.js +41 -0
  61. package/server/engine/agent/tool-input-stream.js +165 -0
  62. package/server/engine/agents/explorer.md +1 -1
  63. package/server/engine/agents/index.js +1 -1
  64. package/server/engine/mcp/actor.js +59 -0
  65. package/server/engine/mcp/capability-gate.js +21 -2
  66. package/server/engine/mcp/index.js +118 -33
  67. package/server/engine/mcp/mode-profile.js +139 -0
  68. package/server/engine/mcp/server-name.js +19 -0
  69. package/server/engine/mcp/tools/artifact-session.js +2 -1
  70. package/server/engine/mcp/tools/browse-computer.js +2 -1
  71. package/server/engine/mcp/tools/browse-find-batch.js +95 -16
  72. package/server/engine/mcp/tools/browse.js +25 -3
  73. package/server/engine/mcp/tools/cast-role.js +167 -0
  74. package/server/engine/mcp/tools/crystallize-skill.js +1 -1
  75. package/server/engine/mcp/tools/draw-trend.js +104 -0
  76. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  77. package/server/engine/mcp/tools/edit-board.js +569 -0
  78. package/server/engine/mcp/tools/generate-image.js +50 -70
  79. package/server/engine/mcp/tools/helpers/codex-imagegen.js +139 -0
  80. package/server/engine/mcp/tools/helpers/rembg.js +7 -2
  81. package/server/engine/mcp/tools/helpers/shot-pipeline.js +109 -0
  82. package/server/engine/mcp/tools/list-pages.js +11 -5
  83. package/server/engine/mcp/tools/look-at-board.js +4 -3
  84. package/server/engine/mcp/tools/open-sheet.js +242 -0
  85. package/server/engine/mcp/tools/organize-board.js +45 -6
  86. package/server/engine/mcp/tools/paint-still.js +1 -1
  87. package/server/engine/mcp/tools/pin-to-board.js +103 -19
  88. package/server/engine/mcp/tools/read-board.js +123 -16
  89. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  90. package/server/engine/mcp/tools/report-issue.js +15 -7
  91. package/server/engine/mcp/tools/role-memory.js +83 -0
  92. package/server/engine/mcp/tools/roll-dice.js +82 -0
  93. package/server/engine/mcp/tools/roll-film.js +1 -1
  94. package/server/engine/mcp/tools/screenshot.js +19 -9
  95. package/server/engine/mcp/tools/set-vars.js +150 -0
  96. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  97. package/server/engine/mcp/tools/web-search.js +12 -16
  98. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  99. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  100. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  101. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  102. package/server/engine/mcp/tools/write-on-board.js +568 -99
  103. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  104. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +14 -7
  105. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +44 -5
  106. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
  107. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +24 -8
  108. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  109. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  110. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  111. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  112. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  113. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  114. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  115. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  116. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  117. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  118. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  119. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  120. package/server/engine/runs/board-seater.js +272 -0
  121. package/server/engine/runs/board-tasklist.js +90 -48
  122. package/server/engine/runs/turn-relay.js +24 -2
  123. package/server/engine/runs/user-chalk-echo.js +44 -0
  124. package/server/index.js +2 -2
  125. package/server/lib/artifact-target.js +31 -14
  126. package/server/lib/board-anchor.js +49 -0
  127. package/server/lib/board-dirty.js +93 -0
  128. package/server/lib/board-excerpt.js +80 -0
  129. package/server/lib/board-follow.js +111 -0
  130. package/server/lib/board-groups.js +1 -5
  131. package/server/lib/board-hero.js +3 -1
  132. package/server/lib/board-kind-sizes.js +38 -3
  133. package/server/lib/board-lanes.js +58 -0
  134. package/server/lib/board-obstacles.js +80 -0
  135. package/server/lib/board-relations.js +5 -1
  136. package/server/lib/board-sheets.js +467 -0
  137. package/server/lib/board-shelf.js +86 -0
  138. package/server/lib/board-transform.js +62 -0
  139. package/server/lib/canvas-id.js +46 -4
  140. package/server/lib/chalk-flow.js +88 -0
  141. package/server/lib/chalk-rewrite.js +63 -0
  142. package/server/lib/chalk-size-pref.js +48 -0
  143. package/server/lib/chalk.js +42 -3
  144. package/server/lib/export-collect.js +3 -1
  145. package/server/lib/ingress/forward-openai-chat.js +16 -4
  146. package/server/lib/ingress/openai-chat.js +23 -12
  147. package/server/lib/ingress/session-routes.js +3 -3
  148. package/server/lib/ingress/stream-watchdog.js +53 -0
  149. package/server/lib/ingress/upstream-billing.js +15 -0
  150. package/server/lib/ingress/upstream-error-hints.js +36 -0
  151. package/server/lib/issues-store.js +3 -1
  152. package/server/lib/json-preview.js +58 -0
  153. package/server/lib/kinds/docx.js +11 -4
  154. package/server/lib/kinds/index.js +6 -16
  155. package/server/lib/kinds/site.js +40 -7
  156. package/server/lib/model-ingress.js +26 -1
  157. package/server/lib/moderation.js +10 -5
  158. package/server/lib/quota.js +26 -6
  159. package/server/lib/rect.js +50 -0
  160. package/server/lib/rewrite-refs.js +110 -0
  161. package/server/lib/screen.js +36 -0
  162. package/server/lib/sheet-state.js +19 -0
  163. package/server/lib/sketch-array.js +118 -0
  164. package/server/lib/sketch-hatch.js +99 -0
  165. package/server/lib/sketch-layout.js +269 -69
  166. package/server/lib/sketch-shapes.js +152 -0
  167. package/server/lib/sketch-stencils.js +170 -0
  168. package/server/lib/ssrf-guard.js +9 -0
  169. package/server/lib/state-table.js +237 -0
  170. package/server/lib/state-trend.js +94 -0
  171. package/server/lib/state-triggers.js +199 -0
  172. package/server/lib/task-scan.js +18 -0
  173. package/server/lib/tavern-json.js +48 -0
  174. package/server/lib/user-uploads.js +88 -0
  175. package/server/projects/board-limits.js +5 -3
  176. package/server/projects/board-sanitize.js +220 -10
  177. package/server/projects/board-store.js +137 -7
  178. package/server/projects/memory-migration.js +85 -0
  179. package/server/projects/move-entry.js +11 -1
  180. package/server/projects/session-jsonl.js +110 -0
  181. package/server/projects/store.js +56 -3
  182. package/server/projects/ui-config.js +2 -1
  183. package/server/projects/viewpoint-store.js +35 -3
  184. package/server/projects/workspace-slots.js +30 -0
  185. package/server/projects/workspace-templates.js +42 -10
  186. package/server/projects/workspace.js +25 -26
  187. package/server/runtime/capabilities.js +6 -2
  188. package/server/services/rembg-launcher.js +5 -2
  189. package/server/shared/locales.js +70 -0
  190. package/server/shared/messages-en.js +69 -0
  191. package/server/shared/messages.js +65 -0
  192. package/server/ws/broker.js +3 -0
  193. package/server/ws/index.js +43 -3
  194. package/web/README.md +1 -1
  195. package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-CcaGRWz5.js} +1 -1
  196. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  197. package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B9KZmU5z.js} +2 -2
  198. package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-DrfRnz8E.js} +43 -43
  199. package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-CJOjCF7d.js} +6 -6
  200. package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  201. package/web/dist/assets/{arc-D_khH_xN.js → arc-C1-HSUny.js} +1 -1
  202. package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  203. package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  204. package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  205. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  206. package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  207. package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  208. package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  209. package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  210. package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  211. package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  212. package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  213. package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  214. package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  215. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  216. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  217. package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  218. package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  219. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  220. package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  221. package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  222. package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  223. package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  224. package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  225. package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  226. package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  227. package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  228. package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  229. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  230. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  231. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  232. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  233. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  234. package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  235. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  236. package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  237. package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  238. package/web/dist/assets/{layout-CnXiF_NB.js → layout-Dhgck-Zo.js} +1 -1
  239. package/web/dist/assets/{linear-BMW0u6G6.js → linear-DMSQ-aUw.js} +1 -1
  240. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  241. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  242. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  243. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  314. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  315. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  316. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  317. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  318. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  319. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  320. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  321. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  322. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  323. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  324. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  325. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  326. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  327. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  328. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  329. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  330. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  331. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  332. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  333. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  334. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  335. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  336. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  337. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  338. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  339. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  340. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  341. package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  342. package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  343. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  344. package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  345. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  346. package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  347. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  348. package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-62liz7LX.js} +1 -1
  349. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  350. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  351. package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  352. package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  353. package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  354. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  355. package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  356. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  357. package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  358. package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  359. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  360. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  361. package/web/dist/index.html +2 -2
  362. package/server/api/memory.js +0 -185
  363. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +0 -583
  364. package/server/engine/mcp/tools/arrange-on-board.js +0 -124
  365. package/server/engine/mcp/tools/create-on-board.js +0 -152
  366. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  367. package/server/engine/mcp/tools/record-decision.js +0 -140
  368. package/server/engine/mcp/tools/relate-on-board.js +0 -112
  369. package/server/engine/mcp/tools/sketch-on-board.js +0 -337
  370. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  371. 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
  372. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  373. package/web/dist/assets/DeckWindow-BpLv5082.js +0 -16
  374. package/web/dist/assets/channel-DGkccMO1.js +0 -1
  375. package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +0 -1
  376. package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +0 -1
  377. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  378. package/web/dist/assets/index-GAamh1p4.js +0 -2152
  379. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  380. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  381. package/web/dist/assets/pending-edit-apply-AcdOQ9Ij.js +0 -31
  382. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
  383. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
@@ -42,24 +42,31 @@ import {
42
42
  import { createRun } from '../engine/runs/store.js';
43
43
  import { runSession } from '../engine/agent/session-loop.js';
44
44
  import {
45
- cancelRun, provideAnswer, getQuery, provideElicitation, hasActiveQuerySession, getQuerySession, closeQuerySession, setSessionPermissionMode, getSessionIdByRunId,
45
+ cancelRun, provideAnswer, getQuery, provideElicitation, hasActiveQuerySession, getQuerySession, closeQuerySession, setSessionPermissionMode,
46
46
  } from '../engine/runs/active-runs.js';
47
47
  import { pushUserMessage, getQueueDepth } from '../engine/runs/turn-relay.js';
48
48
  import { applySessionModel, resolveSessionModel } from '../engine/agent/session-model.js';
49
49
  import { lruGet, lruPut, inflightTurns, INFLIGHT_RETENTION_MS } from './turn-inflight.js';
50
- import { allowedModelsFor, isModelLockedFor, defaultModelFor, modelIsFree, resolveSdkSpoofModel, hasSubscriptionAccess, crossLaneSwitchReason } from '../engine/agent/model-context.js';
50
+ import { allowedModelsFor, isModelLockedFor, defaultModelFor, modelIsFree, hasSubscriptionAccess, modelSwitchRejection, resolveModelRoute } from '../engine/agent/model-context.js';
51
51
  import { AsyncQueue } from '../lib/async-queue.js';
52
52
  import { checkQuota, checkFreeQuota, checkConcurrency, fmtUsd } from '../lib/quota.js';
53
53
  import { shouldModerate, moderateText, recordViolation, levelFor } from '../lib/moderation.js';
54
54
  import { getProjectBus } from '../ws/broker.js';
55
55
  import { Events } from '../engine/agent/events.js';
56
56
  import { readPendingSummary } from './pending-changes.js';
57
- import { pendingRewinds } from './sessions.js';
57
+ import { pendingRewinds } from './sessions-rewind.js';
58
58
  import { platform } from '../runtime/platform.js';
59
59
  import { composeUserMessage } from './turn-compose.js';
60
60
 
61
61
  const router = express.Router();
62
62
 
63
+ /**
64
+ * SDK uuid 形态。用户消息的 id 在三处必须是**同一个** 36-char uuid:jsonl 里那条
65
+ * user 记录的 uuid、rewindFiles(userMessageId) 的入参、fork 的 upToMessageId。
66
+ * 前端同一份判据在 web/src/components/chat/Message.jsx(改一处记得对另一处)。
67
+ */
68
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
69
+
63
70
  /**
64
71
  * Emit run.permission_mode_changed —— 广播 SDK 实际 permissionMode 的变化。唯一的
65
72
  * 切换路径(turn 入口 mode 校正)调完 setPermissionMode 后 emit 一次。
@@ -95,7 +102,7 @@ router.post('/:pid/turn', async (req, res, next) => {
95
102
  const project = guardProject(req, res);
96
103
  if (!project) return;
97
104
 
98
- const { chat, attachments, skillId, sessionId, permissionMode, requestId, raw } = req.body || {};
105
+ const { chat, attachments, skillId, sessionId, permissionMode, requestId, raw, userMessageUuid } = req.body || {};
99
106
  // 只发附件不打字也是一条完整消息(2026-08-17,issue #1 第 8 条):拖张参考图
100
107
  // 进来就该能发,逼用户补一句"看看这个"是白要的动作。
101
108
  // 空文字 **且** 空附件才是空消息 —— 那个仍然拦。
@@ -112,14 +119,14 @@ router.post('/:pid/turn', async (req, res, next) => {
112
119
  if (typeof requestId === 'string' && requestId) {
113
120
  const cached = lruGet(requestId);
114
121
  if (cached && cached.pid === project.id) {
115
- return res.status(202).json({ runId: cached.runId, sessionId: cached.sessionId, deduped: true });
122
+ return res.status(202).json({ runId: cached.runId, sessionId: cached.sessionId, userMessageId: cached.userMessageId, deduped: true });
116
123
  }
117
124
  const inflight = inflightTurns.get(requestId);
118
125
  if (inflight) {
119
126
  try {
120
127
  const r = await inflight;
121
128
  if (r && r.pid === project.id) {
122
- return res.status(202).json({ runId: r.runId, sessionId: r.sessionId, deduped: true });
129
+ return res.status(202).json({ runId: r.runId, sessionId: r.sessionId, userMessageId: r.userMessageId, deduped: true });
123
130
  }
124
131
  } catch { /* first POST failed → fall through 让本 POST 重新跑 */ }
125
132
  }
@@ -183,20 +190,27 @@ router.post('/:pid/turn', async (req, res, next) => {
183
190
  // 解析出来的模型一律过白名单(不只 body.model):旧覆盖/资格收回/无 select 裸名都在此拦(fable P0)
184
191
  if (isModelLockedFor(modelUser, turnModel)) {
185
192
  return res.status(403).json({
186
- error: `这个模型(${turnModel})仅限 Pro 档,暂未对外开放。换成免费模型继续`,
193
+ error: msg(req, '这个模型({model})仅限 Pro 档,暂未对外开放。换成免费模型继续', { model: turnModel }),
187
194
  code: 'MODEL_LOCKED', model: turnModel,
188
195
  });
189
196
  }
190
197
  if (!allowedModelsFor(modelUser).some((m) => m.id === turnModel)) { // 清单整个为空(本地版没 key/没登录/没插槽)→ 指路设置页,别让人去选择器里找
191
- return res.status(403).json(allowedModelsFor(modelUser).length ? { error: `这个会话指向的模型(${turnModel})现在不可用,请在模型选择器里换一个`, code: 'MODEL_NOT_ALLOWED', model: turnModel } : { error: '还没有可用的模型:到「设置」填 API Key(或本机 claude login),或者配一个模型插槽', code: 'NO_MODEL_CONFIGURED', model: turnModel });
192
- }
193
- if (requestedModelEarly && sessionModelEarly.override && crossLaneSwitchReason(sessionModelEarly.override, requestedModelEarly)) {
194
- return res.status(409).json({ error: crossLaneSwitchReason(sessionModelEarly.override, requestedModelEarly), code: 'LANE_SWITCH' });
198
+ return res.status(403).json(allowedModelsFor(modelUser).length ? { error: msg(req, '这个会话指向的模型({model})现在不可用,请在模型选择器里换一个', { model: turnModel }), code: 'MODEL_NOT_ALLOWED', model: turnModel } : { error: msg(req, '还没有可用的模型:到「设置」填 API Key(或本机 claude login),或者配一个模型插槽'), code: 'NO_MODEL_CONFIGURED', model: turnModel });
195
199
  }
200
+ // ⛔ 08-25 修:原来这条带着 `sessionModelEarly.override &&`,于是**跑在全局默认上的会话(override=null)
201
+ // 整个逃过检查** —— 而站上默认恰恰是免费的 Ox(openai-chat),正是这条闸要防的那一头。
202
+ // 改成拿**当前有效模型**(override → env → 兜底,resolveSessionModel 已经算好)去比;新会话不拦,
203
+ // 它还没有历史,而这条闸防的是历史里没有 signature 的 thinking 块被回传给真 Anthropic。
204
+ // ⚠️ 已知的一处宽严不匹配:这里的"新会话"判据是 `!resumeSessionId`,而不是 sessions.js 那条 PUT 用的
205
+ // "jsonl 存不存在"。差别只在一种情况——**会话建了但一次没跑过、第一轮就显式换到另一通路**,
206
+ // 这里会多拦一次(409 说"新开一个会话",人照做就没事)。sessionRoot 要到本函数后半段才拿得到,
207
+ // 为这一种情况把 ensureSessionWorkspace 提前不划算。
208
+ const laneWhy = modelSwitchRejection({ from: sessionModelEarly.model, to: requestedModelEarly, hasHistory: !isNewSession });
209
+ if (laneWhy) return res.status(409).json({ error: laneWhy, code: 'LANE_SWITCH' });
196
210
  if (modelIsFree(turnModel)) {
197
211
  const fq = checkFreeQuota(req.user);
198
212
  if (!fq.ok) {
199
- return res.status(429).json({ error: `今天的免费轮次用完了(${fq.used} / ${fq.limit}),明天零点刷新`, code: 'QUOTA_EXCEEDED', kind: fq.kind, used: fq.used, limit: fq.limit });
213
+ return res.status(429).json({ error: msg(req, '今天的免费轮次用完了({used} / {limit}),明天零点刷新', { used: fq.used, limit: fq.limit }), code: 'QUOTA_EXCEEDED', kind: fq.kind, used: fq.used, limit: fq.limit });
200
214
  }
201
215
  }
202
216
  const quota = modelIsFree(turnModel) ? { ok: true } : checkQuota(req.user);
@@ -214,9 +228,11 @@ router.post('/:pid/turn', async (req, res, next) => {
214
228
  });
215
229
  }
216
230
  // 并发:只拦"会立刻开跑"的 turn;session 正忙时这条消息进排队(既有串行语义,
217
- // 不产生新并发)。免费行不受全局固定数限制,只受内存闸(quota.js
231
+ // 不产生新并发)。**非订阅行**不受全局固定数限制,只受内存闸 + 防失控上限(quota.js)。
232
+ // ⚠️ 判据是"花不花站主订阅",不是"这一轮花不花钱"(08-30 改):那个固定数 3 护的是订阅,
233
+ // 一条 $0.015/M 的 API 行塞进那一档只会把站点默认路径的天花板从 12 砍到 3。
218
234
  if (!getQuerySession(sid)?.currentRunId) {
219
- const gate = checkConcurrency(req.user, { free: modelIsFree(turnModel) });
235
+ const gate = checkConcurrency(req.user, { offSubscription: resolveModelRoute(turnModel).mode === 'api' });
220
236
  if (!gate.ok) {
221
237
  return res.status(429).json({ error: gate.message, code: gate.code });
222
238
  }
@@ -302,6 +318,18 @@ router.post('/:pid/turn', async (req, res, next) => {
302
318
  console.info(`[turn.compose] sid=${sid.slice(0, 8)} blocks=[${summary.join(', ')}]`);
303
319
  }
304
320
 
321
+ // 这条用户消息的 uuid(2026-08-30「回退标识要刷新才出现」案)。
322
+ //
323
+ // 它是 SDKUserMessage.uuid,CLI 会**原样**写进 jsonl(探针实证:push 时盖的
324
+ // uuid 与 jsonl 里那条 user 记录的 uuid 一致),而 rewindFiles / fork 的
325
+ // upToMessageId / truncateJsonlAtMessage 认的都是 jsonl 里那个 uuid。
326
+ //
327
+ // 以前它由 pushUserMessage 在 202 之后临时生成(新会话首条甚至不盖,由 CLI 自己
328
+ // 生成),前端无从知晓 → 乐观插入的气泡只能用本地 `msg_xxx` 做 id → 回退/分叉
329
+ // 按钮的 UUID 判据不认它 → **必须刷新页面**等 hydrate 从 jsonl 读回真 uuid 才出现。
330
+ // 现在改成前端先生成、随请求带来(老前端不带则这里生成),202 也回传一份。
331
+ const userMsgUuid = UUID_RE.test(String(userMessageUuid || '')) ? userMessageUuid : randomUUID();
332
+
305
333
  // 创建 run(pending)— per-turn record,displayText 落 brief 字段做审计
306
334
  const run = createRun({
307
335
  skillId: finalSkillId, brief: displayText, projectId: project.id,
@@ -312,21 +340,24 @@ router.post('/:pid/turn', async (req, res, next) => {
312
340
  // 同时 resolve in-flight Promise 通知正在 await 的并发 POST,5s 后清 in-flight
313
341
  // entry(让 LRU 接管后续 dedup 查询)。
314
342
  if (typeof requestId === 'string' && requestId) {
315
- lruPut(requestId, { pid: project.id, runId: run.id, sessionId: sid });
343
+ lruPut(requestId, { pid: project.id, runId: run.id, sessionId: sid, userMessageId: userMsgUuid });
316
344
  if (inflightResolve) {
317
- inflightResolve({ pid: project.id, runId: run.id, sessionId: sid });
345
+ inflightResolve({ pid: project.id, runId: run.id, sessionId: sid, userMessageId: userMsgUuid });
318
346
  }
319
347
  setTimeout(() => inflightTurns.delete(requestId), INFLIGHT_RETENTION_MS);
320
348
  }
321
349
 
322
350
  // 立即返回,agent 后台跑
323
- res.status(202).json({ runId: run.id, sessionId: sid });
351
+ res.status(202).json({ runId: run.id, sessionId: sid, userMessageId: userMsgUuid });
324
352
  const bus = getProjectBus(project.id);
325
353
 
326
354
  const sdkUserMessage = {
327
355
  type: 'user',
328
356
  message: { role: 'user', content: blocks },
329
357
  parent_tool_use_id: null,
358
+ // 两条路径都盖:pushUserMessage 走 runIdByUuid 认领(它见 uuid 已在就不再生成),
359
+ // startNewRunSession 那条不进认领表(claimRunByUuid 返 null,照旧走 initialRunId)。
360
+ uuid: userMsgUuid,
330
361
  };
331
362
 
332
363
  if (hasActiveQuerySession(sid)) {
@@ -587,78 +618,11 @@ router.post('/:pid/runs/:runId/rewind', async (req, res, next) => {
587
618
  } catch (err) { next(err); }
588
619
  });
589
620
 
590
- /**
591
- * POST /api/projects/:pid/runs/:runId/model
592
- *
593
- * 运行中切 model(SDK query.setModel,当场对下一次 LLM 调用生效)。
594
- *
595
- * **目前只有 API 能到这里**:前端 picker 在 turn 运行中是禁用的,它走
596
- * PUT /sessions/:sid/model(那条等空闲才重启 query)。这条留着是因为"turn 跑到
597
- * 一半换模型"是它独有的能力,PUT 那条按设计做不到。前端那个没人调的 Turn.setModel
598
- * 绑定已删(doc 里还写着 kimi 时代的 model 名,留着只会误导下一个人)。
599
- *
600
- * 2026-07-30:切完**必须同时落 session-config**。原来这条只改运行时不写文件,
601
- * 于是"当前这轮是 Opus、下次 resume 变回 Sonnet",而且界面无从得知;跟另外两条
602
- * 写模型的路加起来,同一个事实有三个互不知情的写者。现在统一走 applySessionModel,
603
- * 它自己会判断要不要重启空闲 query(这里 query 正在跑,不会重启)。
604
- *
605
- * Body: { model: string | null } - null = 清掉覆盖回到全局默认
606
- */
607
- router.post('/:pid/runs/:runId/model', async (req, res, next) => {
608
- try {
609
- const project = guardProject(req, res);
610
- if (!project) return;
611
- if (!guardRunInProject(req, res)) return;
612
-
613
- const { runId } = req.params;
614
- const { model } = req.body || {};
615
- if (model !== null && model !== undefined && typeof model !== 'string') {
616
- return res.status(400).json({ error: 'model must be string or null' });
617
- }
621
+ import { hotSwitchModelHandler } from './turn-model-switch.js';
622
+ import { msg } from '../shared/messages.js';
618
623
 
619
- // 08-21:热切路以前不过白名单(任意字符串直达 SDK)。与 PUT /sessions/:sid/model 同口径
620
- if (typeof model === 'string' && model.trim()) {
621
- const modelUser = modelUserFor(req, project); // 资格按项目 owner 算(_guard.js)
622
- if (isModelLockedFor(modelUser, model.trim())) {
623
- return res.status(403).json({ error: '这个模型仅限 Pro 档,暂未对外开放', code: 'MODEL_LOCKED', model: model.trim() });
624
- }
625
- if (!allowedModelsFor(modelUser).some((m) => m.id === model.trim())) {
626
- return res.status(400).json({ error: `unknown model: ${model}`, code: 'UNKNOWN_MODEL' });
627
- }
628
- const sidForLane = getSessionIdByRunId(runId);
629
- if (sidForLane) {
630
- const cur = await resolveSessionModel(getSessionMetaDir(project.id, sidForLane));
631
- const why = crossLaneSwitchReason(cur.model, model.trim());
632
- if (why) return res.status(409).json({ error: why, code: 'LANE_SWITCH' });
633
- }
634
- }
635
-
636
- const query = getQuery(runId);
637
- if (!query) {
638
- return res.status(404).json({
639
- error: 'run not active',
640
- code: 'RUN_NOT_ACTIVE',
641
- });
642
- }
643
- if (typeof query.setModel !== 'function') {
644
- return res.status(501).json({
645
- error: 'SDK query handle missing setModel method',
646
- code: 'METHOD_NOT_AVAILABLE',
647
- });
648
- }
649
-
650
- const sid = getSessionIdByRunId(runId);
651
- // API 行喂 SDK 的是 spoof 名(session-loop 同款),appModel 本身 binary 不认识
652
- await query.setModel(model ? resolveSdkSpoofModel(model.trim()) : undefined);
653
- // 运行时切完再落盘:setModel 失败就不该留下"配置说切了"的假象
654
- let persisted = null;
655
- if (sid) {
656
- await ensureSessionWorkspace(project.id, sid);
657
- persisted = await applySessionModel(sid, getSessionMetaDir(project.id, sid), model ?? null, 'runtime');
658
- }
659
- res.json({ ok: true, model: persisted?.model ?? model, override: persisted?.override ?? null });
660
- } catch (err) { next(err); }
661
- });
624
+ /** 运行中热切模型(实现连同它那三道闸搬去了 turn-model-switch.js) */
625
+ router.post('/:pid/runs/:runId/model', hotSwitchModelHandler);
662
626
 
663
627
 
664
628
  // 注:POST /image-approval 路由已删除(2026-05-06)。原本配 ImageApprovalBanner
@@ -15,9 +15,11 @@ import express from 'express';
15
15
  import {
16
16
  authEnabled, mintToken, requestUser, cookieSerialize, cookieClear,
17
17
  } from './session.js';
18
- import { getCredential, verifyPassword, getUserById, registerUser, openRegistrationEnabled } from './users-store.js';
18
+ import { getCredential, verifyPassword, getUserById, registerUser, openRegistrationEnabled, updateUser } from './users-store.js';
19
+ import { LOCALES, isLocale } from '../shared/locales.js';
19
20
  import { makeRateWindow } from '../lib/rate-window.js';
20
21
  import { platform } from '../runtime/platform.js';
22
+ import { msg } from '../shared/messages.js';
21
23
 
22
24
  const MAX_FAILS = 10;
23
25
  const LOCK_MS = 15 * 60 * 1000;
@@ -56,7 +58,8 @@ function recordFail(ip) {
56
58
  failures.set(ip, next);
57
59
  }
58
60
 
59
- const publicUser = (u) => (u ? { id: u.id, username: u.username, role: u.role } : null);
61
+ // locale:界面语言偏好,null = 没表过态(前端这时落浏览器语言,见 lib/i18n.js detect)
62
+ const publicUser = (u) => (u ? { id: u.id, username: u.username, role: u.role, locale: u.locale ?? null } : null);
60
63
 
61
64
  export const authRouter = express.Router();
62
65
 
@@ -66,12 +69,32 @@ authRouter.get('/status', (req, res) => {
66
69
  res.json({ required: authEnabled(), authed: !!user, user: publicUser(user), openRegistration: openRegistrationEnabled(), profile: platform.profile });
67
70
  });
68
71
 
72
+ /**
73
+ * 记住界面语言(2026-08-26 i18n)。
74
+ *
75
+ * 只有登录用户才写账号;没登录的人切语言只落 localStorage(前端自己管),
76
+ * 这里 401 让前端知道"没存上",但前端**不该因此把切换器禁掉** —— 登录墙外面
77
+ * 也要能换语言,不然英文用户连注册按钮都读不懂。
78
+ *
79
+ * body: { locale: 'zh-CN' | 'en' | null }。null = 清掉偏好,回到跟浏览器走。
80
+ */
81
+ authRouter.put('/locale', (req, res) => {
82
+ const user = requestUser(req);
83
+ if (!user) return res.status(401).json({ error: msg(req, '没登录,语言只记在这台机器上') });
84
+ const { locale } = req.body || {};
85
+ if (locale !== null && !isLocale(locale)) {
86
+ return res.status(400).json({ error: msg(req, 'locale 需为 {allowed} 或 null', { allowed: LOCALES.join(' / ') }) });
87
+ }
88
+ updateUser(user.id, { locale });
89
+ res.json({ ok: true, locale: locale ?? null });
90
+ });
91
+
69
92
  authRouter.post('/login', (req, res) => {
70
93
  if (!authEnabled()) return res.json({ ok: true, note: 'auth disabled' });
71
94
 
72
95
  const ip = clientIp(req);
73
96
  const waitMin = locked(ip);
74
- if (waitMin) return res.status(429).json({ error: `尝试次数过多,${waitMin} 分钟后再试` });
97
+ if (waitMin) return res.status(429).json({ error: msg(req, '尝试次数过多,{waitMin} 分钟后再试', { waitMin }) });
75
98
 
76
99
  const { username, password } = req.body || {};
77
100
  const cred = typeof username === 'string' ? getCredential(username.trim()) : null;
@@ -80,7 +103,7 @@ authRouter.post('/login', (req, res) => {
80
103
  && verifyPassword(typeof password === 'string' ? password : '', cred.passwordHash);
81
104
  if (!ok) {
82
105
  recordFail(ip);
83
- return res.status(401).json({ error: '用户名或密码错误' });
106
+ return res.status(401).json({ error: msg(req, '用户名或密码错误') });
84
107
  }
85
108
 
86
109
  failures.delete(ip);
@@ -91,13 +114,13 @@ authRouter.post('/login', (req, res) => {
91
114
  authRouter.post('/register', (req, res) => {
92
115
  const ip = clientIp(req);
93
116
  const waitMin = locked(ip);
94
- if (waitMin) return res.status(429).json({ error: `尝试次数过多,${waitMin} 分钟后再试` });
117
+ if (waitMin) return res.status(429).json({ error: msg(req, '尝试次数过多,{waitMin} 分钟后再试', { waitMin }) });
95
118
 
96
119
  const { username, password, inviteCode } = req.body || {};
97
120
  const hasInvite = typeof inviteCode === 'string' && inviteCode.trim();
98
121
  const perIpLimit = Number(process.env.NODESIGN_REGISTER_PER_IP_PER_DAY) || 5;
99
122
  if (!hasInvite && registerWindow.count(ip) >= perIpLimit) {
100
- return res.status(429).json({ error: '这个网络今天开的号太多了,明天再来', code: 'REGISTER_RATE_LIMITED' });
123
+ return res.status(429).json({ error: msg(req, '这个网络今天开的号太多了,明天再来'), code: 'REGISTER_RATE_LIMITED' });
101
124
  }
102
125
  try {
103
126
  const user = registerUser({
@@ -35,16 +35,29 @@ export const PLANS = Object.freeze(['pro', 'basic']);
35
35
  * localGen 本地产线资格(paint_still / roll_film / 演出端点 / 本地无审查模型行);
36
36
  * pro 档还要叠 allowLocalGen 批准,见 localGenApproved
37
37
  * publishSite publish_site 上 CF Pages(占站主四级域名 + 每账户 100 站硬上限)
38
- * moderationDefault 外审默认档(用户没被显式钉档时生效)
38
+ * moderationDefault 订阅通路的外审默认档(用户没被显式钉档时生效)
39
+ * moderationDefaultApi API 通路(非订阅模型)的外审默认档 —— **两条通路各一个默认**
39
40
  * webSearchDailyCap basic 档 web_search 每日上限(null = 不限);env NODESIGN_BASIC_WEB_SEARCH_PER_DAY 可调
41
+ *
42
+ * ⭐⭐ **外审默认档 08-30 拆成两个**(用户拍板:「新账户默认按订阅严格、其他模型关闭」)。
43
+ * 在此之前一格值同时管两条通路,于是"订阅要严、非订阅不审"这个口径根本表达不出来 ——
44
+ * 只能一个人一个人去 admin 台钉 moderation_level_api,钉过 41 个号,钉漏的还在按 strict 跑。
45
+ * 现在两条通路各有各的默认,口径的家在这张表里,不在 88 行用户数据里。
46
+ *
47
+ * 为什么这个口径讲得通:外审那道闸(lib/moderation.js)的存在理由是**违规消息不能进站主的
48
+ * Claude 订阅**(骑的是站主账号,封号风险在站主头上)。非订阅模型走的是各家 API,
49
+ * 各家自己有内容策略、出事也是那家的账 —— 我们再叠一层 GPT 外审只是在误伤创作。
50
+ * ⚠️ 连带效果不止于"不审":同一格值也管 prelude 的成人段(agent/system-prompts.js),
51
+ * 'off' 那档写的是「成人内容明确允许,不用迂回」。改这一格 = 同时改口径和提示词。
40
52
  */
41
53
  const CAPABILITIES = Object.freeze({
42
- admin: Object.freeze({ subscription: true, webSearch: true, imageGen: true, localGen: true, publishSite: true, moderationDefault: 'off', webSearchDailyCap: null }),
54
+ admin: Object.freeze({ subscription: true, webSearch: true, imageGen: true, localGen: true, publishSite: true, moderationDefault: 'off', moderationDefaultApi: 'off', webSearchDailyCap: null }),
43
55
  // 08-21 晚用户拍板「所有审查都开到严格」:pro 默认档 loose → strict(admin 仍免审)
44
- pro: Object.freeze({ subscription: true, webSearch: true, imageGen: true, localGen: true, publishSite: true, moderationDefault: 'strict', webSearchDailyCap: null }),
56
+ // 08-30 起这条只管订阅通路;API 通路见 moderationDefaultApi
57
+ pro: Object.freeze({ subscription: true, webSearch: true, imageGen: true, localGen: true, publishSite: true, moderationDefault: 'strict', moderationDefaultApi: 'off', webSearchDailyCap: null }),
45
58
  // 08-21 深夜用户拍板:basic 是今后唯一对外分发的档(pro 不再新发,只手动给);basic 可用 Ox 免费行 + OpenCode Go 付费行 +
46
59
  // 生图($0.20/张计入同一本账),每人每天 $5 总额度(注册时写 dailyCostLimitUsd,见 basicDefaultDailyUsd);订阅 Claude / 本地产线 / 发布仍不开
47
- basic: Object.freeze({ subscription: false, webSearch: true, imageGen: true, localGen: false, publishSite: false, moderationDefault: 'strict', webSearchDailyCap: 'env' }),
60
+ basic: Object.freeze({ subscription: false, webSearch: true, imageGen: true, localGen: false, publishSite: false, moderationDefault: 'strict', moderationDefaultApi: 'off', webSearchDailyCap: 'env' }),
48
61
  });
49
62
 
50
63
  /** basic 档注册时写入的每日总额度(美元)。env NODESIGN_BASIC_DEFAULT_DAILY_USD;0 或非法 = 不写(走全局默认日限) */
@@ -75,10 +88,17 @@ export function can(user, capability) {
75
88
  return CAPABILITIES[tier][capability] === true;
76
89
  }
77
90
 
78
- /** 外审默认档(off | loose | strict)。null 用户 → 'off' 的老语义留给 moderation.js 自己判。 */
79
- export function defaultModerationLevel(user) {
91
+ /**
92
+ * 外审默认档(off | loose | strict)。null 用户 → 'off' 的老语义留给 moderation.js 自己判。
93
+ *
94
+ * @param {object|null} user
95
+ * @param {'subscription'|'api'} [knob] 哪条通路的默认。**不给 = 订阅** —— 拼错 / 没传
96
+ * 只能落到管站主账号的那一边,不能落到更松的一边(同 moderationKnobFor 的口径)。
97
+ */
98
+ export function defaultModerationLevel(user, knob = 'subscription') {
80
99
  const tier = tierOf(user);
81
- return tier ? CAPABILITIES[tier].moderationDefault : 'strict';
100
+ const key = knob === 'api' ? 'moderationDefaultApi' : 'moderationDefault';
101
+ return tier ? CAPABILITIES[tier][key] : 'strict';
82
102
  }
83
103
 
84
104
  /** basic 档 web_search 每日上限;其它档 null(不限)。 */
@@ -19,6 +19,7 @@
19
19
  import crypto from 'node:crypto';
20
20
  import db from '../engine/runs/store.js';
21
21
  import { PLANS, basicDefaultDailyUsd } from './tier.js';
22
+ import { LOCALES } from '../shared/locales.js';
22
23
  import { platform } from '../runtime/platform.js';
23
24
 
24
25
  /**
@@ -116,6 +117,13 @@ if (!userCols.has('plan')) {
116
117
  const n = db.prepare("UPDATE users SET plan = 'pro' WHERE allow_subscription = 1").run().changes;
117
118
  console.log(`[users-store] users.plan column added(${n} 个账号按 allow_subscription 迁成 pro,其余 basic)`);
118
119
  }
120
+ // 08-26 i18n:界面语言偏好。NULL = 没表过态(前端落浏览器语言),'zh-CN' / 'en' = 显式选过。
121
+ // 不给 NOT NULL DEFAULT:存量账号必须是"没表过态",不能被当成选了中文 —— 一个用惯英文
122
+ // 浏览器的老用户下次进来该看见英文,而不是被这次迁移钉死在中文上。
123
+ if (!userCols.has('locale')) {
124
+ db.exec('ALTER TABLE users ADD COLUMN locale TEXT');
125
+ console.log('[users-store] users.locale column added');
126
+ }
119
127
 
120
128
  const inviteCols = new Set(db.prepare('PRAGMA table_info(invites)').all().map(c => c.name));
121
129
  if (!inviteCols.has('grant_lifetime_usd')) {
@@ -171,6 +179,7 @@ function rowToUser(row) {
171
179
  allowLocalGen: !!row.local_gen, // 本地产线逐人批准(叠在档位之上;见 auth/tier.js localGenApproved)
172
180
  plan: row.plan === 'pro' ? 'pro' : 'basic', // 档位真相源:pro(邀请码)| basic(公开注册);admin 看 role。能力问 auth/tier.js
173
181
  disabled: !!row.disabled,
182
+ locale: row.locale || null, // 界面语言偏好;null = 没表过态,前端落浏览器语言
174
183
  inviteCode: row.invite_code || null,
175
184
  createdAt: row.created_at,
176
185
  };
@@ -215,13 +224,17 @@ export function listUsers() {
215
224
  return db.prepare('SELECT * FROM users ORDER BY created_at ASC').all().map(rowToUser);
216
225
  }
217
226
 
218
- export function updateUser(id, { disabled, dailyTokenLimit, dailyCostLimitUsd, lifetimeCostLimitUsd, role, moderationLevel, moderationLevelApi, localGen, plan } = {}) {
227
+ export function updateUser(id, { disabled, dailyTokenLimit, dailyCostLimitUsd, lifetimeCostLimitUsd, role, moderationLevel, moderationLevelApi, localGen, plan, locale } = {}) {
219
228
  const sets = [];
220
229
  const args = [];
221
230
  if (disabled !== undefined) { sets.push('disabled = ?'); args.push(disabled ? 1 : 0); }
222
231
  if (moderationLevel !== undefined) { sets.push('moderation_level = ?'); args.push(moderationLevel ?? null); }
223
232
  if (moderationLevelApi !== undefined) { sets.push('moderation_level_api = ?'); args.push(moderationLevelApi ?? null); }
224
233
  if (localGen !== undefined) { sets.push('local_gen = ?'); args.push(localGen ? 1 : 0); }
234
+ if (locale !== undefined) {
235
+ if (locale !== null && !LOCALES.includes(locale)) throw new Error(`updateUser: locale 需为 ${LOCALES.join('/')} 或 null,收到 '${locale}'`);
236
+ sets.push('locale = ?'); args.push(locale ?? null);
237
+ }
225
238
  if (plan !== undefined) {
226
239
  if (!PLANS.includes(plan)) throw new Error(`updateUser: plan 需为 ${PLANS.join('/')},收到 '${plan}'`);
227
240
  sets.push('plan = ?'); args.push(plan);
@@ -0,0 +1,110 @@
1
+ /**
2
+ * engine/agent/actor-trail.js —— 「这次工具调用是谁发起的」(2026-08-26)
3
+ *
4
+ * ## 为什么需要这条路
5
+ *
6
+ * MCP 工具的 handler 拿不到调用者身份:`extra` 里只有 `_meta['claudecode/toolUseId']`、
7
+ * sessionId 那些,没有 agent_id / agent_type(2026-08-26 实测)。而 **hook 拿得到** ——
8
+ * `PreToolUse` 的 input 带 `agent_id` / `agent_type`,第二个参数就是 toolUseId。
9
+ *
10
+ * 所以:hook 在工具执行前把 (toolUseId → 谁) 记下来,MCP handler 用 extra 里的
11
+ * toolUseId 查回去。一条短命的旁路,不改 MCP 协议、不动工具签名。
12
+ *
13
+ * ## 归属的锚为什么必须是这条路,而不是角色文件
14
+ *
15
+ * 角色文件(`.claude/agents/rp-*.md`)**是模型可写的**,里面的展示名是**自称**:
16
+ * 一个角色可以把自己的 description 写成「RP 角色「用户」」,也可以顶替别的角色的名字。
17
+ * 拿它当归属 = 谁都能冒充谁。而 `agent_type` 是 harness 在派发那一刻亲眼所见并盖章的,
18
+ * 模型改不了。**权威是 slug,文件里的展示名只当展示层。**
19
+ *
20
+ * ## 生命周期
21
+ *
22
+ * 一次工具调用的寿命就是它的寿命。留一个上限防跑量(长会话里工具调用成千上万,
23
+ * 不清理就是内存漏),按插入序淘汰最旧的 —— 淘汰掉的最坏后果是那条板书落回
24
+ * 'agent' 署名,不影响正确性。
25
+ */
26
+
27
+ const MAX = 500;
28
+ const trail = new Map(); // toolUseId -> { agentId, agentType }
29
+
30
+ /** hook 侧:工具执行前盖章 */
31
+ export function noteToolCaller(toolUseId, { agentId = null, agentType = null } = {}) {
32
+ if (!toolUseId) return;
33
+ if (!agentId && !agentType) return; // 主线程调用不必记,查不到就是主 agent
34
+ trail.set(toolUseId, { agentId, agentType });
35
+ if (trail.size > MAX) {
36
+ const oldest = trail.keys().next().value;
37
+ trail.delete(oldest);
38
+ }
39
+ }
40
+
41
+ /** 工具侧:查这次调用是谁 */
42
+ export function callerOf(toolUseId) {
43
+ return (toolUseId && trail.get(toolUseId)) || null;
44
+ }
45
+
46
+ /**
47
+ * 演员位实例别名(2026-08-28 重构):agentId → 实例名(rp-cheng-wan)。
48
+ *
49
+ * ## 为什么需要这张表
50
+ *
51
+ * 同型多实例之后,hook input 里的 `agent_type` 是演员位(rp-actor),**没有实例名
52
+ * 字段**(2026-08-28 探针:SubagentStart/PreToolUse/SubagentStop 三处 keys 全录,
53
+ * 只有 agent_id / agent_type)。而收件箱、名册、板书署名全按实例名走 —— 不桥接的话
54
+ * 所有角色的 byOf 塌成 'rp-actor',收件箱共用一个坑。
55
+ *
56
+ * ## 学名字的两条路(都在 PostToolUse,见 hooks/slot-alias.js)
57
+ *
58
+ * 派发:Agent 的 tool_result 里有 `agentId: <id>`,tool_input.name 是实例名
59
+ * 唤醒:SendMessage 按名寄的 tool_result 里有 `resumedAgentId` + pin.name
60
+ *
61
+ * 表是会话内存态:服务器重启后角色本来就要重新派(名册同界),别名随新派发重新学。
62
+ * 上限防跑量同 trail —— 淘汰最旧的最坏后果是署名落回 'rp-actor',不影响正确性。
63
+ */
64
+ const agentNames = new Map(); // agentId -> 实例名
65
+ export function noteAgentName(agentId, name) {
66
+ if (!agentId || !name) return;
67
+ agentNames.set(String(agentId), String(name));
68
+ if (agentNames.size > MAX) {
69
+ const oldest = agentNames.keys().next().value;
70
+ agentNames.delete(oldest);
71
+ }
72
+ }
73
+ export function agentNameOf(agentId) {
74
+ return (agentId && agentNames.get(String(agentId))) || null;
75
+ }
76
+
77
+ /**
78
+ * 派发闸认领了名字、还等着跟 agent_id 配对的队列(2026-08-29)。
79
+ *
80
+ * ## 为什么换掉旧桥
81
+ *
82
+ * 旧桥(hooks/slot-alias.js 的派发分支)从 **tool_result 的文本**里正则抠 `agentId:`。
83
+ * 2026-08-28 真会话实录:六条板书里五条署名落成演员位(`rp-narrator`),也就是那条
84
+ * 桥在生产上没接上 —— 而它一断,byOf 就塌回演员位,署名、去向判据、前端点名全认错人。
85
+ *
86
+ * 新桥不解析任何文本:派发闸认领名字时把名字排进队列,`SubagentStart` 那一刻
87
+ * harness 亲手给 `agent_id` + `agent_type`,两边一配就绑定。顺序天然对得上 ——
88
+ * 闸跑在工具执行前,子代理起飞在工具执行后,FIFO 就是派发顺序。
89
+ */
90
+ const pendingRoleNames = [];
91
+ const PENDING_MAX = 16;
92
+
93
+ /** 派发闸侧:认领了这个名字,等它起飞 */
94
+ export function notePendingRoleName(name) {
95
+ if (!name) return;
96
+ pendingRoleNames.push(String(name));
97
+ // 上限防跑量:派出去却从没起飞的名字(派发失败)会留在队里,超了就丢最旧的。
98
+ // 最坏后果是某个角色的署名落回演员位 —— 跟旧桥断掉时一样,不会更糟。
99
+ if (pendingRoleNames.length > PENDING_MAX) pendingRoleNames.shift();
100
+ }
101
+
102
+ /** SubagentStart 侧:取走队头的名字 */
103
+ export function takePendingRoleName() {
104
+ return pendingRoleNames.shift() || null;
105
+ }
106
+
107
+ /** 测试用 */
108
+ export function _resetActorTrail() {
109
+ trail.clear(); agentNames.clear(); pendingRoleNames.length = 0;
110
+ }