@xiaobuyu/nodesign 0.0.1 → 0.0.3

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 (358) hide show
  1. package/README.md +156 -106
  2. package/bin/nodesign.js +0 -0
  3. package/package.json +1 -1
  4. package/server/api/README.md +45 -45
  5. package/server/api/_guard.js +75 -75
  6. package/server/api/admin.js +187 -187
  7. package/server/api/assets/docx-page.js +103 -103
  8. package/server/api/assets/helpers.js +42 -22
  9. package/server/api/assets/notes.js +194 -144
  10. package/server/api/assets.js +891 -896
  11. package/server/api/board.js +134 -57
  12. package/server/api/browse.js +138 -138
  13. package/server/api/canvas.js +444 -444
  14. package/server/api/chatai.js +324 -324
  15. package/server/api/exports/build-standalone.js +980 -980
  16. package/server/api/exports/cards.js +118 -118
  17. package/server/api/exports/docx-pdf.js +46 -46
  18. package/server/api/exports/export-page.js +142 -142
  19. package/server/api/exports/handoff.js +147 -147
  20. package/server/api/exports.js +690 -690
  21. package/server/api/instruction.js +75 -75
  22. package/server/api/me.js +116 -116
  23. package/server/api/memory.js +185 -185
  24. package/server/api/pending-changes.js +366 -366
  25. package/server/api/plugins.js +164 -164
  26. package/server/api/projects.js +220 -220
  27. package/server/api/publish.js +64 -64
  28. package/server/api/recent.js +73 -73
  29. package/server/api/sessions.js +589 -589
  30. package/server/api/skills.js +77 -77
  31. package/server/api/standalone-fit.js +168 -168
  32. package/server/api/turn-compose.js +180 -180
  33. package/server/api/turn-inflight.js +44 -44
  34. package/server/auth/README.md +17 -17
  35. package/server/auth/middleware.js +138 -138
  36. package/server/auth/origin-guard.js +71 -71
  37. package/server/auth/session.js +108 -108
  38. package/server/auth/tier.js +113 -113
  39. package/server/auth/users-store.js +348 -348
  40. package/server/edit/README.md +29 -29
  41. package/server/engine/README.md +113 -113
  42. package/server/engine/agent/agent-shared.js +550 -550
  43. package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
  44. package/server/engine/agent/auto-mode-rules.js +78 -78
  45. package/server/engine/agent/context.js +418 -418
  46. package/server/engine/agent/events.js +465 -464
  47. package/server/engine/agent/hooks/canvas-validate.js +199 -199
  48. package/server/engine/agent/hooks/failure.js +120 -120
  49. package/server/engine/agent/hooks/file-events.js +75 -75
  50. package/server/engine/agent/hooks/lifecycle.js +233 -233
  51. package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
  52. package/server/engine/agent/hooks/post-guidance.js +206 -206
  53. package/server/engine/agent/hooks/post-subagent-report.js +104 -104
  54. package/server/engine/agent/hooks/post-trim.js +58 -58
  55. package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
  56. package/server/engine/agent/hooks/pre-defaults.js +84 -84
  57. package/server/engine/agent/hooks/pre-injectors.js +277 -277
  58. package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
  59. package/server/engine/agent/hooks/pre-starter-files.js +61 -61
  60. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
  61. package/server/engine/agent/hooks/site-validate.js +94 -94
  62. package/server/engine/agent/hooks/tool-prompts.js +31 -31
  63. package/server/engine/agent/hooks/turn-state-memory.js +56 -56
  64. package/server/engine/agent/hooks/user-prompt-submit.js +258 -215
  65. package/server/engine/agent/hooks.js +354 -354
  66. package/server/engine/agent/init-contract.js +69 -69
  67. package/server/engine/agent/isolation.js +159 -159
  68. package/server/engine/agent/model-table.js +292 -292
  69. package/server/engine/agent/plugin-loader.js +266 -266
  70. package/server/engine/agent/prompts/nodesign-prelude.md +388 -336
  71. package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
  72. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
  73. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
  74. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
  75. package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
  76. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
  77. package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
  78. package/server/engine/agent/prompts/tools/site-reference.md +106 -106
  79. package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
  80. package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
  81. package/server/engine/agent/session-loop.js +1035 -1024
  82. package/server/engine/agent/session-model.js +164 -164
  83. package/server/engine/agent/skill.js +204 -204
  84. package/server/engine/agent/task-events.js +78 -78
  85. package/server/engine/agents/ds-extractor.md +132 -132
  86. package/server/engine/agents/explorer.md +190 -190
  87. package/server/engine/agents/index.js +230 -230
  88. package/server/engine/agents/schemas/design-system.json +156 -156
  89. package/server/engine/agents/schemas/tweak-schema.json +113 -113
  90. package/server/engine/agents/tweak-proposer.md +127 -127
  91. package/server/engine/agents/vision-checker.md +254 -254
  92. package/server/engine/browse/capture.js +422 -422
  93. package/server/engine/browse/card.js +137 -137
  94. package/server/engine/browse/handover.js +59 -59
  95. package/server/engine/browse/page-digest.js +119 -119
  96. package/server/engine/browse/refs.js +196 -196
  97. package/server/engine/browse/registry.js +307 -307
  98. package/server/engine/browse/screencast.js +170 -170
  99. package/server/engine/browse/state.js +121 -121
  100. package/server/engine/chatai/chat-log.js +79 -79
  101. package/server/engine/chatai/index.js +193 -193
  102. package/server/engine/chatai/openai-compat.js +152 -152
  103. package/server/engine/chatai/orchestrate.js +309 -309
  104. package/server/engine/chatai/perform.js +40 -40
  105. package/server/engine/chatai/summarize.js +79 -79
  106. package/server/engine/mcp/capability-gate.js +52 -52
  107. package/server/engine/mcp/index.js +351 -339
  108. package/server/engine/mcp/param-sanitizer.js +142 -142
  109. package/server/engine/mcp/tools/arrange-on-board.js +124 -124
  110. package/server/engine/mcp/tools/artifact-session.js +278 -278
  111. package/server/engine/mcp/tools/browse-computer.js +421 -421
  112. package/server/engine/mcp/tools/browse-find-batch.js +183 -183
  113. package/server/engine/mcp/tools/browse-screenshot.js +157 -157
  114. package/server/engine/mcp/tools/browse.js +480 -480
  115. package/server/engine/mcp/tools/build-docx.js +101 -101
  116. package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
  117. package/server/engine/mcp/tools/create-on-board.js +152 -155
  118. package/server/engine/mcp/tools/crystallize-skill.js +168 -168
  119. package/server/engine/mcp/tools/edit-sketch.js +167 -0
  120. package/server/engine/mcp/tools/explain-style.js +237 -237
  121. package/server/engine/mcp/tools/export-handoff.js +133 -133
  122. package/server/engine/mcp/tools/expose-tweaks.js +149 -149
  123. package/server/engine/mcp/tools/generate-image.js +842 -842
  124. package/server/engine/mcp/tools/get-computed-styles.js +164 -164
  125. package/server/engine/mcp/tools/get-pending-changes.js +205 -205
  126. package/server/engine/mcp/tools/h3box-ssh.js +84 -84
  127. package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
  128. package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
  129. package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
  130. package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
  131. package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
  132. package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
  133. package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
  134. package/server/engine/mcp/tools/highlight.js +68 -68
  135. package/server/engine/mcp/tools/list-pages.js +222 -222
  136. package/server/engine/mcp/tools/look-at-board.js +124 -0
  137. package/server/engine/mcp/tools/lookup-tags.js +59 -59
  138. package/server/engine/mcp/tools/navigate-to-page.js +60 -60
  139. package/server/engine/mcp/tools/organize-board.js +64 -64
  140. package/server/engine/mcp/tools/paint-still.js +434 -434
  141. package/server/engine/mcp/tools/pin-to-board.js +130 -130
  142. package/server/engine/mcp/tools/preview-deck.js +130 -130
  143. package/server/engine/mcp/tools/profile-scroll.js +305 -305
  144. package/server/engine/mcp/tools/publish-site.js +102 -102
  145. package/server/engine/mcp/tools/query-elements.js +195 -195
  146. package/server/engine/mcp/tools/read-board.js +190 -103
  147. package/server/engine/mcp/tools/read-document.js +93 -93
  148. package/server/engine/mcp/tools/read-page.js +264 -264
  149. package/server/engine/mcp/tools/read-tavern-json.js +143 -143
  150. package/server/engine/mcp/tools/read-user-view.js +59 -0
  151. package/server/engine/mcp/tools/record-decision.js +140 -140
  152. package/server/engine/mcp/tools/relate-on-board.js +112 -110
  153. package/server/engine/mcp/tools/report-issue.js +115 -115
  154. package/server/engine/mcp/tools/roll-film.js +266 -266
  155. package/server/engine/mcp/tools/screenshot-docx.js +138 -138
  156. package/server/engine/mcp/tools/screenshot-url.js +195 -195
  157. package/server/engine/mcp/tools/screenshot.js +583 -583
  158. package/server/engine/mcp/tools/sketch-on-board.js +337 -0
  159. package/server/engine/mcp/tools/tier-gate.js +96 -96
  160. package/server/engine/mcp/tools/trace-motion.js +215 -215
  161. package/server/engine/mcp/tools/web-search.js +548 -548
  162. package/server/engine/mcp/tools/write-on-board.js +130 -0
  163. package/server/engine/motion/inventory.js +349 -349
  164. package/server/engine/perception/session.js +235 -235
  165. package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
  166. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +199 -192
  167. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
  168. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
  169. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
  170. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
  171. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
  172. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
  173. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
  174. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +169 -162
  175. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
  176. package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
  177. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +235 -228
  178. 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 +78 -78
  179. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
  180. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +330 -323
  181. package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
  182. package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
  183. package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
  184. package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
  185. package/server/engine/runs/active-runs.js +698 -698
  186. package/server/engine/runs/board-tasklist.js +130 -0
  187. package/server/engine/runs/live-turn.js +252 -252
  188. package/server/engine/runs/store.js +364 -364
  189. package/server/engine/runs/turn-relay.js +217 -217
  190. package/server/engine/runtime/workspace.js +164 -164
  191. package/server/index.js +3 -0
  192. package/server/lib/artifact-file-path.js +80 -80
  193. package/server/lib/artifact-target.js +284 -284
  194. package/server/lib/asset-refs.js +239 -239
  195. package/server/lib/async-queue.js +104 -104
  196. package/server/lib/auto-relations.js +130 -130
  197. package/server/lib/binding-types.js +72 -60
  198. package/server/lib/board-graph-export.js +191 -0
  199. package/server/lib/board-groups.js +167 -0
  200. package/server/lib/board-hero.js +76 -0
  201. package/server/lib/board-kind-sizes.js +85 -62
  202. package/server/lib/board-relations.js +133 -133
  203. package/server/lib/browse-proxy.js +180 -180
  204. package/server/lib/canvas-id.js +36 -36
  205. package/server/lib/chalk.js +121 -0
  206. package/server/lib/cover.js +227 -227
  207. package/server/lib/danbooru-tags.js +291 -291
  208. package/server/lib/doc-extract.js +156 -156
  209. package/server/lib/docx/build-from-source.js +260 -260
  210. package/server/lib/docx/build.js +490 -490
  211. package/server/lib/docx/dump-styles.js +287 -287
  212. package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
  213. package/server/lib/docx/merge-runs.js +129 -129
  214. package/server/lib/docx/numbering.js +218 -218
  215. package/server/lib/docx/order.js +152 -152
  216. package/server/lib/docx/rawzip.js +172 -172
  217. package/server/lib/docx/render.js +92 -92
  218. package/server/lib/docx/text-lint.js +225 -225
  219. package/server/lib/docx/tokens.js +335 -335
  220. package/server/lib/docx/units.js +29 -29
  221. package/server/lib/docx/xml.js +291 -291
  222. package/server/lib/docx-pages.js +187 -187
  223. package/server/lib/export-collect.js +289 -289
  224. package/server/lib/export-package.js +210 -210
  225. package/server/lib/html-srcset.js +145 -145
  226. package/server/lib/image-variant.js +460 -460
  227. package/server/lib/ingress/forward-openai-chat.js +305 -305
  228. package/server/lib/ingress/openai-chat.js +491 -491
  229. package/server/lib/ingress/session-notice.js +66 -66
  230. package/server/lib/ingress/session-routes.js +68 -68
  231. package/server/lib/ingress/slot-probe.js +130 -130
  232. package/server/lib/ingress/upstream-billing.js +52 -52
  233. package/server/lib/ingress/upstream-fail-streak.js +72 -72
  234. package/server/lib/ingress/upstream-truncation.js +48 -48
  235. package/server/lib/issues-store.js +182 -182
  236. package/server/lib/kinds/deck.js +72 -72
  237. package/server/lib/kinds/docx.js +283 -283
  238. package/server/lib/kinds/file-kinds.js +113 -113
  239. package/server/lib/kinds/index.js +202 -202
  240. package/server/lib/kinds/site.js +161 -161
  241. package/server/lib/model-ingress.js +543 -543
  242. package/server/lib/moderation.js +255 -255
  243. package/server/lib/notice-store.js +103 -103
  244. package/server/lib/plugin-install.js +104 -104
  245. package/server/lib/plugin-validator.js +721 -721
  246. package/server/lib/publish-store.js +115 -115
  247. package/server/lib/quick-summary.js +52 -52
  248. package/server/lib/quota.js +270 -270
  249. package/server/lib/rate-window.js +29 -29
  250. package/server/lib/reference-assets.js +92 -92
  251. package/server/lib/region-shot.js +135 -135
  252. package/server/lib/safe-path.js +73 -73
  253. package/server/lib/sdk-session.js +33 -33
  254. package/server/lib/showcase-store.js +92 -92
  255. package/server/lib/site-publish.js +465 -465
  256. package/server/lib/sketch-layout.js +277 -0
  257. package/server/lib/ssrf-guard.js +432 -432
  258. package/server/lib/task-scan.js +158 -158
  259. package/server/lib/tavern-json.js +154 -154
  260. package/server/lib/video-variant.js +237 -237
  261. package/server/lib/workspace-path.js +33 -33
  262. package/server/ops/fix-sdk-musl.mjs +40 -40
  263. package/server/ops/install-macos-fonts.sh +114 -114
  264. package/server/ops/macos-fonts.conf +336 -336
  265. package/server/ops/sandbox-shim/bwrap +57 -57
  266. package/server/projects/assets-summary.js +119 -119
  267. package/server/projects/auto-name.js +53 -53
  268. package/server/projects/board-limits.js +12 -0
  269. package/server/projects/board-sanitize.js +252 -0
  270. package/server/projects/board-store.js +507 -640
  271. package/server/projects/move-entry.js +103 -103
  272. package/server/projects/store.js +270 -270
  273. package/server/projects/ui-config.js +60 -54
  274. package/server/projects/viewpoint-store.js +78 -0
  275. package/server/projects/workspace-templates.js +53 -53
  276. package/server/projects/workspace.js +1025 -1025
  277. package/server/runtime/local-config.js +177 -177
  278. package/server/runtime/profile.js +72 -72
  279. package/server/services/rembg-service.py +334 -334
  280. package/server/shared/README.md +25 -25
  281. package/server/shared/deck.js +54 -54
  282. package/server/shared/time.js +49 -49
  283. package/server/style-pipeline/README.md +53 -53
  284. package/server/ws/broker.js +39 -36
  285. package/server/ws/browse-channel.js +177 -177
  286. package/server/ws/index.js +386 -386
  287. package/web/README.md +89 -89
  288. package/web/dist/assets/BrowserWindow-P_vIdh-G.js +11 -0
  289. package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-BpLv5082.js} +3 -3
  290. package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-DSAU1-tD.js} +4 -4
  291. package/web/dist/assets/MermaidBlock-DR1GikCv.js +313 -0
  292. package/web/dist/assets/SiteWindow-92uBuRRR.js +18 -0
  293. package/web/dist/assets/abnfDiagram-VCTEODGH-uA8Esix4.js +1 -0
  294. package/web/dist/assets/arc-D_khH_xN.js +1 -0
  295. package/web/dist/assets/architectureDiagram-5GKGNRK7-DydJHqOt.js +36 -0
  296. package/web/dist/assets/blockDiagram-NRAW4CY4-B7wBbKiW.js +129 -0
  297. package/web/dist/assets/c4Diagram-UCG6FXSJ-6mrH0x7Y.js +38 -0
  298. package/web/dist/assets/channel-DGkccMO1.js +1 -0
  299. package/web/dist/assets/chunk-2Q5K7J3B-Cuikho5Q.js +1 -0
  300. package/web/dist/assets/chunk-5VM5RSS4-3-KZ5AUz.js +15 -0
  301. package/web/dist/assets/chunk-F27PBJKO-Bkwq2gGU.js +1 -0
  302. package/web/dist/assets/chunk-G27WJ6UU-BLyQzdGE.js +231 -0
  303. package/web/dist/assets/chunk-JWPE2WC7-B7HxxQds.js +1 -0
  304. package/web/dist/assets/chunk-LCL6LL3I-CtXrnUwF.js +206 -0
  305. package/web/dist/assets/chunk-POPQ4Y6H-dxddZlXt.js +1 -0
  306. package/web/dist/assets/chunk-SVP7TREG-DpkmrgHn.js +88 -0
  307. package/web/dist/assets/chunk-XXDRQBXY-BwZ7RbpQ.js +1 -0
  308. package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +1 -0
  309. package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +1 -0
  310. package/web/dist/assets/cose-bilkent-JH36ORCC-DGFOsMUt.js +1 -0
  311. package/web/dist/assets/cynefin-OW5HDTMX-DUwn3ehS.js +178 -0
  312. package/web/dist/assets/cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js +62 -0
  313. package/web/dist/assets/cytoscape.esm-BjAbmLdb.js +331 -0
  314. package/web/dist/assets/dagre-3AP2YEHR-CyourLfz.js +4 -0
  315. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  316. package/web/dist/assets/diagram-S7CK7UJ4-BTMbx2np.js +30 -0
  317. package/web/dist/assets/diagram-UQ7AKVKN-UK94_1-P.js +41 -0
  318. package/web/dist/assets/diagram-VSXAHHWV-C7zS-v9v.js +3 -0
  319. package/web/dist/assets/diagram-VX7I27RA-DKJRa6CK.js +24 -0
  320. package/web/dist/assets/diagram-Z3DM3KII-kUbAfGAp.js +24 -0
  321. package/web/dist/assets/ebnfDiagram-PWID7BFC-CVW16_vb.js +1 -0
  322. package/web/dist/assets/erDiagram-SSCWMZ5O-BRQMFQ5y.js +99 -0
  323. package/web/dist/assets/flowDiagram-A5DVABFB-S34LpLtK.js +168 -0
  324. package/web/dist/assets/ganttDiagram-EL5Y4UJY-BFiqk1-G.js +292 -0
  325. package/web/dist/assets/gitGraphDiagram-WWUBYQGX-LZsrdyyv.js +106 -0
  326. package/web/dist/assets/index-GAamh1p4.js +2152 -0
  327. package/web/dist/assets/infoDiagram-RXCK75RN-0DvN6KIp.js +2 -0
  328. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  329. package/web/dist/assets/ishikawaDiagram-5VMMS53U-CaY3A-Bx.js +70 -0
  330. package/web/dist/assets/journeyDiagram-EYS64GPL-BBeiaCrj.js +139 -0
  331. package/web/dist/assets/kanban-definition-3QL26DDD-ux42DRXQ.js +89 -0
  332. package/web/dist/assets/layout-CnXiF_NB.js +1 -0
  333. package/web/dist/assets/linear-BMW0u6G6.js +1 -0
  334. package/web/dist/assets/mindmap-definition-FBJOCRG2-hUxSpdKn.js +96 -0
  335. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  336. package/web/dist/assets/pegDiagram-XKGWAZYB-aCaQPH0w.js +1 -0
  337. package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-AcdOQ9Ij.js} +6 -6
  338. package/web/dist/assets/pieDiagram-E7YTZNPT-CmOEZ8zg.js +39 -0
  339. package/web/dist/assets/quadrantDiagram-AXDQQJYC-VW6fAhsV.js +7 -0
  340. package/web/dist/assets/railroadDiagram-O6MQD6OU-CKJ7Ba5K.js +1 -0
  341. package/web/dist/assets/requirementDiagram-EFPCY7ZU-pNGkq9Vw.js +84 -0
  342. package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-KAfChbGV.js} +1 -1
  343. package/web/dist/assets/sankeyDiagram-P5KCCOFB-BIMmDTfZ.js +40 -0
  344. package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BBW7wzKY.js +162 -0
  345. package/web/dist/assets/sizeCapture-X5ZJPWSS-DdkXmLYR.js +1 -0
  346. package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-CDUlhUP7.js} +1 -1
  347. package/web/dist/assets/stateDiagram-HBIQ2CUA-W313Uz16.js +1 -0
  348. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +1 -0
  349. package/web/dist/assets/swimlanes-XN3QIQJK-C29TL26K.js +1 -0
  350. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +8 -0
  351. package/web/dist/assets/timeline-definition-24CTP7MA-CF0JMObX.js +120 -0
  352. package/web/dist/assets/vennDiagram-4TSXK5OY-DrDFsaH3.js +34 -0
  353. package/web/dist/assets/wardleyDiagram-VM6X3IG4-CVrnRIS_.js +78 -0
  354. package/web/dist/assets/xychartDiagram-S5SC5T6Z-BvkN1zsT.js +7 -0
  355. package/web/dist/index.html +17 -17
  356. package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
  357. package/web/dist/assets/SiteWindow-D6eS-2gM.js +0 -18
  358. package/web/dist/assets/index-Cbl3ATvp.js +0 -2125
@@ -1,464 +1,465 @@
1
- /**
2
- * EventBus — agent 事件总线(标准化事件 schema)
3
- *
4
- * 设计:
5
- * - 极简 EventEmitter(基于 Set<listener>),不依赖 node:events 避免 max listener warn
6
- * - 每条事件至少含 { type, runId, ts }
7
- * - 监听器订阅时可选择全订('*')或按前缀('run.delta')或按 type 精确匹配
8
- *
9
- * 标准事件 schema(type):
10
- * ── 主流程 ──
11
- * run.start 开始
12
- * run.round.start { round }
13
- * run.round.end { round, stopReason, usage? }
14
- * run.delta.text { round, text }
15
- * run.tool_use_summary { summary, blockIds } SDK helper 对一批工具调用的一句话总结
16
- * run.delta.thinking { round, text }
17
- * run.delta.tool_use { round, blockId, name, input }
18
- * run.delta.tool_input { round, blockId, name, filePath?, append?, done? } Edit/Write 入参真流式(节流后的字段尾巴增量,工作台舞台层直播代码用)
19
- * run.delta.tool_result { round, blockId, name, ok, output?, error? }
20
- * run.todo.updated { todos }
21
- * run.cancelled { reason }
22
- * run.done { finalText, artifactPath?, snapshot }
23
- * run.error { message, code?, stack? }
24
- *
25
- * ── P0+ stage 1 新增(SDK 28+ 种 message 类型映射)──
26
- * run.tool_progress { blockId, toolName, elapsedSeconds } 工具执行中(>1s 才发)
27
- * run.prompt_suggestion { suggestion } 每轮后预测下条 prompt
28
- * run.task.started { taskId, description, subagentType, taskType } subagent 启动(只发真子代理,bash/workflow 任务被收口)
29
- * run.task.progress { taskId, description, summary?, lastToolName? } subagent 30s 摘要(agentProgressSummaries)
30
- * run.task.updated { taskId, patch } subagent 状态 patch
31
- * run.task.notification { taskId, status, summary } subagent 完成/失败/停止
32
- * run.files_persisted { files: [{ filename, file_id }], failed } agent 写完文件
33
- * run.memory_recall { mode, memories } 自动 memory 召回
34
- * run.notification { key, text, priority } 系统通知(弹 toast)
35
- * run.session_state { state: 'idle' | 'running' | 'requires_action' }
36
- * run.system_init { agents, tools, mcp_servers, model, ... } init 元信息
37
- * run.hook.started { hookName, hookEvent } hook 生命周期(仅 includeHookEvents)
38
- * run.hook.response { hookName, hookEvent, outcome }
39
- * run.compact_boundary { compactMetadata } compact 边界
40
- * run.status { status } compacting / requesting / null
41
- * run.rate_limit { info } rate limit 变化
42
- * run.image_generated { path, sizeBytes, prompt, assetRole, ... } generate_image 完成(Phase A)
43
- * board.updated { objectId, zoneId, summary } pin_to_board 改画布布局(sessionId:null 广播,前端整份重拉 board.json)
44
- * project.active_session { activeSessionId } 项目级会话指针变更(故意不带 sessionId —— 见构造器注释)
45
- *
46
- * 外层把 EventBus 桥接到:
47
- * - WebSocket:subscribe('*') ws.send(JSON.stringify(evt))
48
- * - 文件审计:subscribe('*') → append events.jsonl
49
- * - 内存测试:buffer.push(evt) 然后断言
50
- */
51
-
52
- import { resolveModelContextWindow, brandOfModel } from './model-context.js';
53
-
54
- /**
55
- * Replay buffer 容量:单 project bus 保留最近 N 条事件供 WS 重连回放。
56
- * 一次 turn 可能 emit 上千条 delta(thinking / text / tool);2000 够覆盖典型
57
- * 几秒到几十秒的网络抖动重连窗口。FAR 旧的事件会被 client 通过 Sessions hydrate
58
- * 重新拉,不依赖 buffer。
59
- */
60
- const REPLAY_BUFFER_SIZE = 2000;
61
-
62
- export class EventBus {
63
- constructor() {
64
- /** @type {Set<{ pattern: string, fn: (evt: object) => void }>} */
65
- this._listeners = new Set();
66
- /** 单调递增 seq,每个 publish 自增一;event.seq 已存在则不覆盖 */
67
- this._seq = 0;
68
- /** Ring buffer:保留最近 REPLAY_BUFFER_SIZE 条事件;FIFO 满了就 shift 最旧 */
69
- this._buffer = [];
70
- }
71
-
72
- /**
73
- * 订阅事件。
74
- * @param {string} pattern - '*' 全订 / 'run.delta' 前缀 / 'run.done' 精确
75
- * @param {(evt: object) => void} fn
76
- * @returns {() => void} 取消订阅
77
- */
78
- subscribe(pattern, fn) {
79
- if (typeof pattern !== 'string') throw new Error('EventBus.subscribe: pattern must be string');
80
- if (typeof fn !== 'function') throw new Error('EventBus.subscribe: fn must be function');
81
- const entry = { pattern, fn };
82
- this._listeners.add(entry);
83
- return () => this._listeners.delete(entry);
84
- }
85
-
86
- /**
87
- * 发布事件。listener 抛错会被吞 + 控制台 warn,不影响其他订阅者。
88
- * 副作用:分配 event.seq(若调用方未显式带 seq)+ 进入 ring buffer。
89
- */
90
- publish(event) {
91
- if (event.seq == null) event.seq = ++this._seq;
92
- this._buffer.push(event);
93
- if (this._buffer.length > REPLAY_BUFFER_SIZE) this._buffer.shift();
94
-
95
- for (const { pattern, fn } of this._listeners) {
96
- if (matches(pattern, event.type)) {
97
- try {
98
- fn(event);
99
- } catch (err) {
100
- console.warn(`[EventBus] listener for "${pattern}" threw:`, err.message);
101
- }
102
- }
103
- }
104
- }
105
-
106
- /**
107
- * Replay-then-live 订阅 — WS 重连用。
108
- *
109
- * 流程(单线程 Node 保证原子,subscribe 与 buffer 快照间无 publish 触发):
110
- * 1. subscribe('*') —— listener 把进来的事件先 push 到 queue(live=false)
111
- * 2. 同步遍历 buffer,filter seq > since,对每条调 fndedupe 用 set
112
- * 3. drain queue —— 跳过 seq replay 过的(buffer queue 边界重叠)
113
- * 4. live,listener 直接 fn(evt)
114
- *
115
- * gap 判断:
116
- * - 客户端 since=0 → 首次连,无 replay 需求 → gap=false
117
- * - since > _seq server 重启过,client 看过的 seq 现在不存在 → gap=true
118
- * - buffer 最旧 seq > since+1中间一段被 ring 挤掉了 → gap=true
119
- * - 否则 buffer 完整覆盖 (since, _seq] → gap=false
120
- *
121
- * @param {number} since - 客户端最后看到的 seq(0 = 没看过任何事件)
122
- * @param {(evt: object) => void} fn
123
- * @returns {{ unsubscribe: () => void, replayed: number, gap: boolean }}
124
- */
125
- subscribeFromSeq(since, fn) {
126
- let live = false;
127
- const queue = [];
128
- const off = this.subscribe('*', (evt) => {
129
- if (!live) queue.push(evt);
130
- else fn(evt);
131
- });
132
-
133
- const needsReplay = since > 0 && since < this._seq;
134
- const buffered = this._buffer.length > 0;
135
- const restarted = since > this._seq;
136
- const gap = restarted || (needsReplay && (!buffered || this._buffer[0].seq > since + 1));
137
-
138
- // since=0 也 replay buffer 里 seq>0 的事件 —— ws/index.js 调用前先 await sendHydrate
139
- // 是异步阻塞(拉 jsonl 100~数百 ms),期间若 agent emit 事件进 buffer listener
140
- // 还没 attach,老逻辑 `since > 0 ? ... : []` 让 since=0 时 replay=[] → 这些事件全丢,
141
- // 用户报"发 chat agent 不回应"。改成无条件 filter:since=0 + buffer 空(首连)→
142
- // replay=[] 不变;since=0 + buffer 含 hydrate 期间事件 → 全 replay 推前端。
143
- const replay = this._buffer.filter(e => e.seq > since);
144
- const replayedSeqs = new Set();
145
- for (const evt of replay) {
146
- replayedSeqs.add(evt.seq);
147
- try { fn(evt); }
148
- catch (err) { console.warn(`[EventBus] replay listener threw:`, err.message); }
149
- }
150
- for (const evt of queue) {
151
- if (replayedSeqs.has(evt.seq)) continue;
152
- try { fn(evt); }
153
- catch (err) { console.warn(`[EventBus] queue drain threw:`, err.message); }
154
- }
155
- live = true;
156
- return { unsubscribe: off, replayed: replay.length, gap };
157
- }
158
-
159
- /**
160
- * 同步 collect 所有事件到一个数组(测试用)。返回数组 + 取消函数。
161
- */
162
- collect(pattern = '*') {
163
- const buffer = [];
164
- const off = this.subscribe(pattern, (evt) => buffer.push(evt));
165
- return { buffer, stop: off };
166
- }
167
- }
168
-
169
- function matches(pattern, type) {
170
- if (pattern === '*') return true;
171
- if (pattern === type) return true;
172
- // 前缀匹配:'run.delta' 匹配 'run.delta.text'
173
- return type.startsWith(pattern + '.');
174
- }
175
-
176
- // ── 事件构造器(type-safe helpers,调用方少写字面量)──
177
-
178
- export const Events = {
179
- start: () => ({ type: 'run.start' }),
180
- roundStart: (round) => ({ type: 'run.round.start', round }),
181
- roundEnd: (round, stopReason, usage) => ({ type: 'run.round.end', round, stopReason, usage }),
182
- deltaText: (round, text) => ({ type: 'run.delta.text', round, text }),
183
- // 铅笔精灵的手写短句(2026-08-14 日记本批):回合内回复的压缩旁白,
184
- // 本地整形、一条 assistant 消息一发。⚠️ 2026-08-19 前这里同一 round 会来
185
- // 两发(首句底稿 refined:false + haiku 精修 refined:true,前端后到覆盖)——
186
- // 那发 haiku 写死走订阅、不跟随会话模型,整条线路已拆,`refined` 随之取消。
187
- // 收场 recap(run.recap)是同一条 haiku 通道的另一个出口,同批退役。
188
- spriteSummary: (round, text) => ({ type: 'run.sprite_summary', round, text }),
189
- // SDK helper 生成的一句话工具批摘要(Claude Code 侧栏那种折叠标题)
190
- toolUseSummary: (summary, blockIds) => ({ type: 'run.tool_use_summary', summary, blockIds }),
191
- deltaThinking: (round, text) => ({ type: 'run.delta.thinking', round, text }),
192
- deltaToolUse: (round, blockId, name, input) => ({ type: 'run.delta.tool_use', round, blockId, name, input }),
193
- // 真流式工具入参(2026-07-28):patch = { filePath?, append?, done? }
194
- // append 是抽出字段(Edit.new_string / Write.content)相对上次的纯文本增量
195
- deltaToolInput: (round, blockId, name, patch) => ({ type: 'run.delta.tool_input', round, blockId, name, ...patch }),
196
-
197
- // 工具 streaming 起点 —— SDK content_block_start (type: tool_use) 触发,
198
- // 只携 blockId + name,input 此时还没流完。让前端立刻显示 icon + 名字
199
- // status='running',等后续 deltaToolUse 来时同 blockId update 完整 input。
200
- toolUseStarted: (round, blockId, name) => ({ type: 'run.tool_use.started', round, blockId, name }),
201
- deltaToolResult: (round, blockId, name, ok, output, error, images) => ({
202
- type: 'run.delta.tool_result', round, blockId, name, ok,
203
- ...(output !== undefined ? { output } : {}),
204
- ...(error ? { error } : {}),
205
- // C24:image content blocks(base64)单独传,让前端 <img src="data:..."> 渲染
206
- ...(images && images.length > 0 ? { images } : {}),
207
- }),
208
- todoUpdated: (todos) => ({ type: 'run.todo.updated', todos }),
209
- cancelled: (reason) => ({ type: 'run.cancelled', reason }),
210
- done: (finalText, artifactPath, snapshot) => ({
211
- type: 'run.done', finalText, artifactPath, snapshot,
212
- }),
213
- error: (message, code, stack) => ({ type: 'run.error', message, code, stack }),
214
-
215
- // streamInput 重构(Phase 2):session-level query 起停事件,跟 per-turn
216
- // run.start/done 区分 —— 前端用它判断"会话还活着,能继续追加" vs "会话死了"
217
- querySessionStart: (sessionId) => ({ type: 'run.query.start', sessionId }),
218
- querySessionEnd: (sessionId, reason) => ({ type: 'run.query.end', sessionId, reason }),
219
- // 排队提示:用户在 agent 跑时追加消息,UI 显示"已排队 N 条"
220
- queueDepth: (sessionId, depth) => ({ type: 'run.queue.depth', sessionId, depth }),
221
-
222
- // ── 会话收敛 E1a(2026-08-13):项目级 active session 指针变更广播 ──
223
- //
224
- // 会话真相源收敛到 projects.active_session_id(服务端指针)之后,指针一动
225
- // 就广播这条,同项目的其他标签页靠它把自己对齐到服务端真相。
226
- //
227
- // ⚠️ 这条事件**绝对不能带 sessionId 字段**:ws/index.js 的下发过滤器是
228
- // `event.sessionId && event.sessionId !== sid` —— 带上的话,正开着**别的**
229
- // 会话(或还没有会话)的标签页会把它过滤掉,而那些标签页恰恰是最需要
230
- // 知道"指针已经指向别处"的人。载荷字段叫 activeSessionId,绕开过滤器的
231
- // 同时语义也更准:它是指针的新值,不是"这条事件属于哪个会话"
232
- // activeSessionId 可为 null(指针被清空,比如指向的会话被删)。
233
- projectActiveSession: (activeSessionId) => ({ type: 'project.active_session', activeSessionId }),
234
-
235
- // ── P0+ stage 1 新增(28+ 种 SDK message 翻译)──
236
- toolProgress: (blockId, toolName, elapsedSeconds) => ({
237
- type: 'run.tool_progress', blockId, toolName, elapsedSeconds,
238
- }),
239
- promptSuggestion: (suggestion) => ({ type: 'run.prompt_suggestion', suggestion }),
240
- // toolUseId 关键 —— main agent Task 工具调子代理时,SDK 推 task_*
241
- // events 都带 tool_use_id,前端用它把 task 状态绑到对应的 Task tool message
242
- // ⚠️ 只发真子代理(agent-shared.js isSubagentTask 收口):SDK 的统一任务
243
- // 系统里后台 Bash / Workflow 也发 task_*,那些不进这条事件。
244
- // subagentType = SDK task_started.subagent_type(explorer / vision-checker
245
- // 这种真名);taskType 'local_agent' 这类泛名,只作兜底展示。
246
- taskStarted: (taskId, description, subagentType, taskType, prompt, toolUseId) => ({
247
- type: 'run.task.started', taskId, description, subagentType, taskType, prompt, toolUseId,
248
- }),
249
- taskProgress: (taskId, description, summary, lastToolName, usage, toolUseId) => ({
250
- type: 'run.task.progress', taskId, description, summary, lastToolName, usage, toolUseId,
251
- }),
252
- taskUpdated: (taskId, patch, toolUseId) => ({
253
- type: 'run.task.updated', taskId, patch, toolUseId,
254
- }),
255
- taskNotification: (taskId, status, summary, usage, toolUseId) => ({
256
- type: 'run.task.notification', taskId, status, summary, usage, toolUseId,
257
- }),
258
- filesPersisted: (files, failed) => ({ type: 'run.files_persisted', files, failed }),
259
- memoryRecall: (mode, memories) => ({ type: 'run.memory_recall', mode, memories }),
260
- notification: (key, text, priority, color, timeoutMs) => ({
261
- type: 'run.notification', key, text, priority, color, timeoutMs,
262
- }),
263
- sessionState: (state) => ({ type: 'run.session_state', state }),
264
- systemInit: (info) => ({ type: 'run.system_init', info }),
265
- hookStarted: (hookName, hookEvent) => ({ type: 'run.hook.started', hookName, hookEvent }),
266
- hookResponse: (hookName, hookEvent, outcome, output, exitCode) => ({
267
- type: 'run.hook.response', hookName, hookEvent, outcome, output, exitCode,
268
- }),
269
-
270
- // C4 FileChanged hook → 前端 reload iframe
271
- fileChanged: (filePath, event) => ({ type: 'run.file_changed', filePath, event }),
272
-
273
- // ── Phase 1 翻译补全 ──
274
-
275
- // SDKAPIRetryMessage(sdk.d.ts:2322):API 请求失败可重试。
276
- // 当前 handleSystemMessage 把 'api_retry' subtype 落进 default warn —— 改为 emit
277
- // 让上层(前端 / 监控)能看到"还在重试"。
278
- // error_status: HTTP 状态码,连接错误/超时时为 null(无 HTTP 响应)。
279
- apiRetry: (attempt, maxRetries, retryDelayMs, errorStatus, errorKind) => ({
280
- type: 'run.api_retry',
281
- attempt,
282
- maxRetries,
283
- retryDelayMs,
284
- errorStatus, // number | null
285
- errorKind, // SDKAssistantMessageError union: rate_limit / server_error / ...
286
- }),
287
-
288
- // ── Phase 2 hooks 主动捕事件 ──
289
-
290
- // SessionStart hook:source 'startup' | 'resume' | 'clear' | 'compact'
291
- // 上层据此可决定要不要展示"接续上次会话"提示
292
- sessionStart: (source, agentType, model) => ({
293
- type: 'run.session_start', source, agentType, model,
294
- }),
295
-
296
- // SubagentStart hook(sdk.d.ts:5258):主动捕子代理启动。
297
- // SDK system 'task_started' message 是间接路径(依赖 agentProgressSummaries),
298
- // 这条 hook 是更可靠的主入口。
299
- subagentStart: (agentId, agentType) => ({
300
- type: 'run.subagent.start', agentId, agentType,
301
- }),
302
-
303
- // SubagentStop hook(sdk.d.ts:5269):子代理结束。带 last_assistant_message 字段
304
- // 不用读 transcript 也能拿到收尾文本。
305
- // toolUseId:SDK callback 第 2 参,是触发该 subagent 启动的 main agent
306
- // Task tool_use_id —— 前端能用它把 critique 挂回对应的 Task message。
307
- subagentStop: (agentId, agentType, lastAssistantMessage, transcriptPath, toolUseId) => ({
308
- type: 'run.subagent.stop',
309
- agentId, agentType,
310
- lastAssistantMessage, // 可选;agent 收尾的最后一句
311
- transcriptPath, // 可选;子代理转录文件路径,前端展开"完整对话"用
312
- ...(toolUseId ? { toolUseId } : {}),
313
- }),
314
-
315
- // PostToolUseFailure hook:工具失败,hook 已注入了恢复建议给 agent,
316
- // 上层只需可见"哪个工具失败了"做监控/告警。
317
- toolFailure: (toolName, error) => ({
318
- type: 'run.tool_failure', toolName, error,
319
- }),
320
-
321
- // ── Canvas 焕新升级 S1(2026-05-02)──
322
-
323
- // S1d PostToolUse(Edit|Write canvas.html) hook 自动检测改动的 page 号 emit。
324
- // 前端 SlideNavigator 收到后自动 scrollIntoView + 1.5s pulse 高亮该 section
325
- // pages: number[] —— 一次 Edit 可能跨多页(罕见),通常单元素。
326
- // anchor: 可选,data-anchor 值(如 'cover-title'),前端能精确高亮元素而非整页。
327
- canvasFocusPage: (pages, anchor) => ({
328
- type: 'run.canvas_focus_page',
329
- pages,
330
- ...(anchor ? { anchor } : {}),
331
- }),
332
-
333
- // ── Canvas 焕新 C1(2026-05-02):MCP 工具反向通道 + tweaks/buffer 通知 ──
334
-
335
- // navigate_to_page 工具触发,前端 ProjectWorkspace 收到后切到第 N 页
336
- // (SlideNavigator setActivePage / iframe scrollIntoView section[data-page="N"])
337
- canvasNavigate: (page) => ({ type: 'run.canvas_navigate', page }),
338
-
339
- // preview_deck 工具触发:前端把这张 deck 卡"替用户双击一下"
340
- //(收起态→内嵌渲染;已展开→画布内最大化窗)。path null = 当前会话的 deck
341
- deckPreview: (filePath) => ({ type: 'run.deck_preview', path: filePath || null }),
342
-
343
- // highlight 工具触发,前端在 InspectFloatingCard 同层挂 pulse overlay
344
- canvasHighlight: (selector, durationMs = 1500) => ({
345
- type: 'run.canvas_highlight', selector, durationMs,
346
- }),
347
-
348
- // expose_tweaks 工具完成,前端 TweaksPanel reload spec.tweaks
349
- tweaksExposed: (count, added, replaced) => ({
350
- type: 'run.tweaks_exposed', count, added, replaced,
351
- }),
352
-
353
- // clear_pending_changes 工具完成,前端可清相应本地 pending hint。
354
- // clearedIds 让前端 comments state 精确 filter 出橙色 overlay(前后端 id 已统一)
355
- pendingChangesCleared: (clearedIds, removed, remaining) => ({
356
- type: 'run.pending_changes_cleared', clearedIds, removed, remaining,
357
- }),
358
-
359
- // ── A4.1(2026-05-02):AskUserQuestion 走 SDK canUseTool 路径 ──
360
- //
361
- // SDK binary 把 AskUserQuestion 设为 `shouldDefer: true` + `requiresUserInteraction: true`,
362
- // 工具的 checkPermissions 总返 `behavior: 'ask', message: 'Answer questions?'`,
363
- // host 程序通过 canUseTool callback 返 `behavior: 'allow', updatedInput: {
364
- // ...input, answers: {...} }`,binary 拿到带 answers input 调 tool.call →
365
- // 工具直接返回 answers result(cli.js:GR6.call 实现)。
366
- //
367
- // 我们的 canUseTool(session-loop.js)拦到此事件 → emit run.ask_user_question →
368
- // 前端 AskUserQuestionView 卡片 用户点选项 POST /answer resolve
369
- // canUseTool await,返回 updatedInput。
370
- //
371
- // questions 是从工具 input 摘出的 `{ question, header, options, multiSelect }[]`
372
- // 数组(详见 cli.js:zHK schema),前端按现有卡片字段直接消费。
373
- askUserQuestion: (toolUseId, questions) => ({
374
- type: 'run.ask_user_question',
375
- toolUseId,
376
- questions,
377
- }),
378
-
379
- // ── A1.2(2026-05-02):实时上下文用量 ──
380
- // SDKControlGetContextUsageResponsesdk.d.ts:2451-2541)由 query.getContextUsage()
381
- // 返回。session-loop.js 在每个 assistant message 后 await 一次 emit,前端 ContextUsageBar
382
- // 接事件渲进度条 + breakdown + autoCompact 阈值预警。
383
- //
384
- // 关键字段(为前端做轻量化封装,不全量透传以节省 ws 带宽):
385
- // totalTokens / maxTokens / percentage — 主进度条数据
386
- // autoCompactThreshold — 触发 autoCompact 的阈值(绝对 token)
387
- // isAutoCompactEnabled 当前开关状态(前端预警条件之一)
388
- // model 当前模型 id
389
- // messageBreakdown.toolCallsByType 工具消耗 token 排名(展开看)
390
- // memoryFiles / mcpTools / agents / skills — 各类目消耗(展开看)
391
- //
392
- // categories / gridRows 不传 —— 前端不渲网格,只用 percentage + breakdown。
393
- // ── 图片生成(Phase A,2026-05-06 后段)──
394
- // generate_image MCP 工具调完后 emit。前端可在 chat / timeline 实时插
395
- // thumbnail,并把 path 关联回 spec.json record_decision 节点。
396
- // 字段:
397
- // path agent cwd 相对路径(assets/generated/<file>.png)
398
- // absPath 绝对落地路径(前端不一定用,便于审计)
399
- // sizeBytes png 字节数
400
- // prompt 生成 prompt 全文
401
- // assetRole 语义类(hero/cover/bg/.../pattern),可空
402
- // aspectRatio / imageSize 生成参数
403
- // referenceImageCount 喂进去的参考图数量
404
- // accompanyText 模型可选的文字 commentary(responseModalities 含 TEXT 时才有)
405
- imageGenerated: (info) => ({
406
- type: 'run.image_generated',
407
- path: info.path,
408
- absPath: info.absPath,
409
- sizeBytes: info.sizeBytes,
410
- prompt: info.prompt,
411
- assetRole: info.assetRole || null,
412
- aspectRatio: info.aspectRatio,
413
- imageSize: info.imageSize,
414
- referenceImageCount: info.referenceImageCount || 0,
415
- ...(info.accompanyText ? { accompanyText: info.accompanyText } : {}),
416
- }),
417
-
418
- // appModel: NoDesign 上层真实 model(如 kimi-k2.6)。spoofing 后 SDK 的
419
- // usage.model alias(如 claude-opus-4-7[1m])不可信;调用方需把真 appModel
420
- // 传进来。给定 appModel 时按真实容量算 maxTokens / percentage,前端 ContextUsageBar
421
- // 显的就是 gateway 真正能扛的容量(不是 SDK 内部 compact 触发线)。
422
- contextUsage: (usage, appModel = null) => {
423
- // 这里 import file 顶(已加),避免循环依赖问题
424
- //
425
- // 分母的兜底链只能有一条(2026-07-30):以前 hooks.js 那个生产者自己写了
426
- // 一条更长的(?? rawMaxTokens ?? 256000),两条链在 appModel 缺失时给出不同
427
- // 的分母,同一个会话前后两次百分比对不上。合并到这里,256000 那个 kimi 时代
428
- // 的常量去掉 —— 拿它当 Claude 会话的分母只会算出个假的高百分比。
429
- const realMax = (appModel ? resolveModelContextWindow(appModel) : null)
430
- ?? usage.maxTokens
431
- ?? usage.rawMaxTokens
432
- ?? null;
433
- const totalTokens = usage.totalTokens;
434
- const percentage = realMax > 0 ? Math.round((totalTokens / realMax) * 100) : (usage.percentage || 0);
435
- return {
436
- type: 'run.context_usage',
437
- totalTokens,
438
- maxTokens: realMax, // 真实容量(kimi=256k)—— 前端进度条分母
439
- sdkMaxTokens: usage.maxTokens, // SDK 给的(compact 触发线 230k)—— debug
440
- percentage,
441
- autoCompactThreshold: usage.autoCompactThreshold,
442
- isAutoCompactEnabled: usage.isAutoCompactEnabled,
443
- model: appModel || usage.model,
444
- // 出自谁家(BRANDS 之一)——画布精灵据此换身份标。⚠️ 不能让前端读 SDK 的 usage.model 去认牌子:
445
- // spoofing 之后那是 alias(DeepSeek 行报 claude-opus-4-7[1m]),照它认牌子会把鲸画成星芒
446
- brand: appModel ? brandOfModel(appModel) : null,
447
- // 轻量化 breakdown 前端 hover/expand 能看到细节,不渲细节时不占空间
448
- messageBreakdown: usage.messageBreakdown ? {
449
- toolCallTokens: usage.messageBreakdown.toolCallTokens,
450
- toolResultTokens: usage.messageBreakdown.toolResultTokens,
451
- attachmentTokens: usage.messageBreakdown.attachmentTokens,
452
- assistantMessageTokens: usage.messageBreakdown.assistantMessageTokens,
453
- userMessageTokens: usage.messageBreakdown.userMessageTokens,
454
- // toolCallsByType 是排行(用于"哪个工具吃 token 最多"),保留 top 10
455
- toolCallsByType: (usage.messageBreakdown.toolCallsByType || [])
456
- .slice(0, 10),
457
- } : null,
458
- // 类目级别 token 数(聚合,不传完整数组)
459
- memoryFilesTokens: (usage.memoryFiles || []).reduce((s, m) => s + (m.tokens || 0), 0),
460
- mcpToolsTokens: (usage.mcpTools || []).reduce((s, t) => s + (t.tokens || 0), 0),
461
- agentsTokens: (usage.agents || []).reduce((s, a) => s + (a.tokens || 0), 0),
462
- };
463
- },
464
- };
1
+ /**
2
+ * EventBus — agent 事件总线(标准化事件 schema)
3
+ *
4
+ * 设计:
5
+ * - 极简 EventEmitter(基于 Set<listener>),不依赖 node:events 避免 max listener warn
6
+ * - 每条事件至少含 { type, runId, ts }
7
+ * - 监听器订阅时可选择全订('*')或按前缀('run.delta')或按 type 精确匹配
8
+ *
9
+ * 标准事件 schema(type):
10
+ * ── 主流程 ──
11
+ * run.start 开始
12
+ * run.round.start { round }
13
+ * run.round.end { round, stopReason, usage? }
14
+ * run.delta.text { round, text }
15
+ * run.tool_use_summary { summary, blockIds } SDK helper 对一批工具调用的一句话总结
16
+ * run.delta.thinking { round, text }
17
+ * run.delta.tool_use { round, blockId, name, input }
18
+ * run.delta.tool_input { round, blockId, name, filePath?, append?, done? } Edit/Write 入参真流式(节流后的字段尾巴增量,工作台舞台层直播代码用)
19
+ * run.delta.tool_result { round, blockId, name, ok, output?, error? }
20
+ * run.todo.updated { todos }
21
+ * run.cancelled { reason }
22
+ * run.done { finalText, artifactPath?, snapshot }
23
+ * run.error { message, code?, stack? }
24
+ *
25
+ * ── P0+ stage 1 新增(SDK 28+ 种 message 类型映射)──
26
+ * run.tool_progress { blockId, toolName, elapsedSeconds } 工具执行中(>1s 才发)
27
+ * run.prompt_suggestion { suggestion } 每轮后预测下条 prompt
28
+ * run.task.started { taskId, description, subagentType, taskType } subagent 启动(只发真子代理,bash/workflow 任务被收口)
29
+ * run.task.progress { taskId, description, summary?, lastToolName? } subagent 30s 摘要(agentProgressSummaries)
30
+ * run.task.updated { taskId, patch } subagent 状态 patch
31
+ * run.task.notification { taskId, status, summary } subagent 完成/失败/停止
32
+ * run.files_persisted { files: [{ filename, file_id }], failed } agent 写完文件
33
+ * run.memory_recall { mode, memories } 自动 memory 召回
34
+ * run.notification { key, text, priority } 系统通知(弹 toast)
35
+ * run.session_state { state: 'idle' | 'running' | 'requires_action' }
36
+ * run.system_init { agents, tools, mcp_servers, model, ... } init 元信息
37
+ * run.hook.started { hookName, hookEvent } hook 生命周期(仅 includeHookEvents)
38
+ * run.hook.response { hookName, hookEvent, outcome }
39
+ * run.compact_boundary { compactMetadata } compact 边界
40
+ * run.status { status } compacting / requesting / null
41
+ * run.rate_limit { info } rate limit 变化
42
+ * run.image_generated { path, sizeBytes, prompt, assetRole, ... } generate_image 完成(Phase A)
43
+ * board.updated { objectId, zoneId, summary } pin_to_board 改画布布局(sessionId:null 广播,前端整份重拉 board.json)
44
+ * board.focus { rect, tag, layer } agent 落了一张草图(sessionId:null 广播;前端只在黑板模式跟镜头)
45
+ * project.active_session { activeSessionId } 项目级会话指针变更(故意不带 sessionId —— 见构造器注释)
46
+ *
47
+ * 外层把 EventBus 桥接到:
48
+ * - WebSocket:subscribe('*') → ws.send(JSON.stringify(evt))
49
+ * - 文件审计:subscribe('*') → append events.jsonl
50
+ * - 内存测试:buffer.push(evt) 然后断言
51
+ */
52
+
53
+ import { resolveModelContextWindow, brandOfModel } from './model-context.js';
54
+
55
+ /**
56
+ * Replay buffer 容量:单 project bus 保留最近 N 条事件供 WS 重连回放。
57
+ * 一次 turn 可能 emit 上千条 delta(thinking / text / tool);2000 够覆盖典型
58
+ * 几秒到几十秒的网络抖动重连窗口。FAR 旧的事件会被 client 通过 Sessions hydrate
59
+ * 重新拉,不依赖 buffer。
60
+ */
61
+ const REPLAY_BUFFER_SIZE = 2000;
62
+
63
+ export class EventBus {
64
+ constructor() {
65
+ /** @type {Set<{ pattern: string, fn: (evt: object) => void }>} */
66
+ this._listeners = new Set();
67
+ /** 单调递增 seq,每个 publish 自增一;event.seq 已存在则不覆盖 */
68
+ this._seq = 0;
69
+ /** Ring buffer:保留最近 REPLAY_BUFFER_SIZE 条事件;FIFO 满了就 shift 最旧 */
70
+ this._buffer = [];
71
+ }
72
+
73
+ /**
74
+ * 订阅事件。
75
+ * @param {string} pattern - '*' 全订 / 'run.delta' 前缀 / 'run.done' 精确
76
+ * @param {(evt: object) => void} fn
77
+ * @returns {() => void} 取消订阅
78
+ */
79
+ subscribe(pattern, fn) {
80
+ if (typeof pattern !== 'string') throw new Error('EventBus.subscribe: pattern must be string');
81
+ if (typeof fn !== 'function') throw new Error('EventBus.subscribe: fn must be function');
82
+ const entry = { pattern, fn };
83
+ this._listeners.add(entry);
84
+ return () => this._listeners.delete(entry);
85
+ }
86
+
87
+ /**
88
+ * 发布事件。listener 抛错会被吞 + 控制台 warn,不影响其他订阅者。
89
+ * 副作用:分配 event.seq(若调用方未显式带 seq)+ 进入 ring buffer。
90
+ */
91
+ publish(event) {
92
+ if (event.seq == null) event.seq = ++this._seq;
93
+ this._buffer.push(event);
94
+ if (this._buffer.length > REPLAY_BUFFER_SIZE) this._buffer.shift();
95
+
96
+ for (const { pattern, fn } of this._listeners) {
97
+ if (matches(pattern, event.type)) {
98
+ try {
99
+ fn(event);
100
+ } catch (err) {
101
+ console.warn(`[EventBus] listener for "${pattern}" threw:`, err.message);
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Replay-then-live 订阅 — WS 重连用。
109
+ *
110
+ * 流程(单线程 Node 保证原子,subscribe buffer 快照间无 publish 触发):
111
+ * 1. subscribe('*') —— listener 把进来的事件先 push 到 queuelive=false
112
+ * 2. 同步遍历 buffer,filter seq > since,对每条调 fn(dedupe set)
113
+ * 3. drain queue —— 跳过 seq 已 replay 过的(buffer 和 queue 边界重叠)
114
+ * 4. 切 live,listener 直接 fn(evt)
115
+ *
116
+ * gap 判断:
117
+ * - 客户端 since=0首次连,无 replay 需求 → gap=false
118
+ * - since > _seqserver 重启过,client 看过的 seq 现在不存在 → gap=true
119
+ * - buffer 最旧 seq > since+1 中间一段被 ring 挤掉了 → gap=true
120
+ * - 否则 buffer 完整覆盖 (since, _seq] → gap=false
121
+ *
122
+ * @param {number} since - 客户端最后看到的 seq(0 = 没看过任何事件)
123
+ * @param {(evt: object) => void} fn
124
+ * @returns {{ unsubscribe: () => void, replayed: number, gap: boolean }}
125
+ */
126
+ subscribeFromSeq(since, fn) {
127
+ let live = false;
128
+ const queue = [];
129
+ const off = this.subscribe('*', (evt) => {
130
+ if (!live) queue.push(evt);
131
+ else fn(evt);
132
+ });
133
+
134
+ const needsReplay = since > 0 && since < this._seq;
135
+ const buffered = this._buffer.length > 0;
136
+ const restarted = since > this._seq;
137
+ const gap = restarted || (needsReplay && (!buffered || this._buffer[0].seq > since + 1));
138
+
139
+ // since=0 replay buffer seq>0 的事件 —— ws/index.js 调用前先 await sendHydrate
140
+ // 是异步阻塞(拉 jsonl 100~数百 ms),期间若 agent emit 事件进 buffer listener
141
+ // 还没 attach,老逻辑 `since > 0 ? ... : []` 让 since=0 replay=[] → 这些事件全丢,
142
+ // 用户报"发 chat 后 agent 不回应"。改成无条件 filter:since=0 + buffer 空(首连)→
143
+ // replay=[] 不变;since=0 + buffer hydrate 期间事件 → 全 replay 推前端。
144
+ const replay = this._buffer.filter(e => e.seq > since);
145
+ const replayedSeqs = new Set();
146
+ for (const evt of replay) {
147
+ replayedSeqs.add(evt.seq);
148
+ try { fn(evt); }
149
+ catch (err) { console.warn(`[EventBus] replay listener threw:`, err.message); }
150
+ }
151
+ for (const evt of queue) {
152
+ if (replayedSeqs.has(evt.seq)) continue;
153
+ try { fn(evt); }
154
+ catch (err) { console.warn(`[EventBus] queue drain threw:`, err.message); }
155
+ }
156
+ live = true;
157
+ return { unsubscribe: off, replayed: replay.length, gap };
158
+ }
159
+
160
+ /**
161
+ * 同步 collect 所有事件到一个数组(测试用)。返回数组 + 取消函数。
162
+ */
163
+ collect(pattern = '*') {
164
+ const buffer = [];
165
+ const off = this.subscribe(pattern, (evt) => buffer.push(evt));
166
+ return { buffer, stop: off };
167
+ }
168
+ }
169
+
170
+ function matches(pattern, type) {
171
+ if (pattern === '*') return true;
172
+ if (pattern === type) return true;
173
+ // 前缀匹配:'run.delta' 匹配 'run.delta.text'
174
+ return type.startsWith(pattern + '.');
175
+ }
176
+
177
+ // ── 事件构造器(type-safe helpers,调用方少写字面量)──
178
+
179
+ export const Events = {
180
+ start: () => ({ type: 'run.start' }),
181
+ roundStart: (round) => ({ type: 'run.round.start', round }),
182
+ roundEnd: (round, stopReason, usage) => ({ type: 'run.round.end', round, stopReason, usage }),
183
+ deltaText: (round, text) => ({ type: 'run.delta.text', round, text }),
184
+ // 铅笔精灵的手写短句(2026-08-14 日记本批):回合内回复的压缩旁白,
185
+ // 本地整形、一条 assistant 消息一发。⚠️ 2026-08-19 前这里同一 round 会来
186
+ // 两发(首句底稿 refined:false + haiku 精修 refined:true,前端后到覆盖)——
187
+ // 那发 haiku 写死走订阅、不跟随会话模型,整条线路已拆,`refined` 随之取消。
188
+ // 收场 recap(run.recap)是同一条 haiku 通道的另一个出口,同批退役。
189
+ spriteSummary: (round, text) => ({ type: 'run.sprite_summary', round, text }),
190
+ // SDK helper 生成的一句话工具批摘要(Claude Code 侧栏那种折叠标题)
191
+ toolUseSummary: (summary, blockIds) => ({ type: 'run.tool_use_summary', summary, blockIds }),
192
+ deltaThinking: (round, text) => ({ type: 'run.delta.thinking', round, text }),
193
+ deltaToolUse: (round, blockId, name, input) => ({ type: 'run.delta.tool_use', round, blockId, name, input }),
194
+ // 真流式工具入参(2026-07-28):patch = { filePath?, append?, done? }。
195
+ // append 是抽出字段(Edit.new_string / Write.content)相对上次的纯文本增量
196
+ deltaToolInput: (round, blockId, name, patch) => ({ type: 'run.delta.tool_input', round, blockId, name, ...patch }),
197
+
198
+ // 工具 streaming 起点 —— SDK content_block_start (type: tool_use) 触发,
199
+ // 只携 blockId + name,input 此时还没流完。让前端立刻显示 icon + 名字
200
+ // status='running',等后续 deltaToolUse 来时同 blockId update 完整 input。
201
+ toolUseStarted: (round, blockId, name) => ({ type: 'run.tool_use.started', round, blockId, name }),
202
+ deltaToolResult: (round, blockId, name, ok, output, error, images) => ({
203
+ type: 'run.delta.tool_result', round, blockId, name, ok,
204
+ ...(output !== undefined ? { output } : {}),
205
+ ...(error ? { error } : {}),
206
+ // C24:image content blocks(base64)单独传,让前端 <img src="data:..."> 渲染
207
+ ...(images && images.length > 0 ? { images } : {}),
208
+ }),
209
+ todoUpdated: (todos) => ({ type: 'run.todo.updated', todos }),
210
+ cancelled: (reason) => ({ type: 'run.cancelled', reason }),
211
+ done: (finalText, artifactPath, snapshot) => ({
212
+ type: 'run.done', finalText, artifactPath, snapshot,
213
+ }),
214
+ error: (message, code, stack) => ({ type: 'run.error', message, code, stack }),
215
+
216
+ // streamInput 重构(Phase 2):session-level query 起停事件,跟 per-turn
217
+ // run.start/done 区分 —— 前端用它判断"会话还活着,能继续追加" vs "会话死了"
218
+ querySessionStart: (sessionId) => ({ type: 'run.query.start', sessionId }),
219
+ querySessionEnd: (sessionId, reason) => ({ type: 'run.query.end', sessionId, reason }),
220
+ // 排队提示:用户在 agent 跑时追加消息,UI 显示"已排队 N 条"
221
+ queueDepth: (sessionId, depth) => ({ type: 'run.queue.depth', sessionId, depth }),
222
+
223
+ // ── 会话收敛 E1a(2026-08-13):项目级 active session 指针变更广播 ──
224
+ //
225
+ // 会话真相源收敛到 projects.active_session_id(服务端指针)之后,指针一动
226
+ // 就广播这条,同项目的其他标签页靠它把自己对齐到服务端真相。
227
+ //
228
+ // ⚠️ 这条事件**绝对不能带 sessionId 字段**:ws/index.js 的下发过滤器是
229
+ // `event.sessionId && event.sessionId !== sid` —— 带上的话,正开着**别的**
230
+ // 会话(或还没有会话)的标签页会把它过滤掉,而那些标签页恰恰是最需要
231
+ // 知道"指针已经指向别处"的人。载荷字段叫 activeSessionId,绕开过滤器的
232
+ // 同时语义也更准:它是指针的新值,不是"这条事件属于哪个会话"。
233
+ // activeSessionId 可为 null(指针被清空,比如指向的会话被删)。
234
+ projectActiveSession: (activeSessionId) => ({ type: 'project.active_session', activeSessionId }),
235
+
236
+ // ── P0+ stage 1 新增(28+ 种 SDK message 翻译)──
237
+ toolProgress: (blockId, toolName, elapsedSeconds) => ({
238
+ type: 'run.tool_progress', blockId, toolName, elapsedSeconds,
239
+ }),
240
+ promptSuggestion: (suggestion) => ({ type: 'run.prompt_suggestion', suggestion }),
241
+ // toolUseId 关键 —— main agent Task 工具调子代理时,SDK 推 task_*
242
+ // events 都带 tool_use_id,前端用它把 task 状态绑到对应的 Task tool message
243
+ // ⚠️ 只发真子代理(agent-shared.js isSubagentTask 收口):SDK 的统一任务
244
+ // 系统里后台 Bash / Workflow 也发 task_*,那些不进这条事件。
245
+ // subagentType = SDK task_started.subagent_type(explorer / vision-checker
246
+ // 这种真名);taskType 'local_agent' 这类泛名,只作兜底展示。
247
+ taskStarted: (taskId, description, subagentType, taskType, prompt, toolUseId) => ({
248
+ type: 'run.task.started', taskId, description, subagentType, taskType, prompt, toolUseId,
249
+ }),
250
+ taskProgress: (taskId, description, summary, lastToolName, usage, toolUseId) => ({
251
+ type: 'run.task.progress', taskId, description, summary, lastToolName, usage, toolUseId,
252
+ }),
253
+ taskUpdated: (taskId, patch, toolUseId) => ({
254
+ type: 'run.task.updated', taskId, patch, toolUseId,
255
+ }),
256
+ taskNotification: (taskId, status, summary, usage, toolUseId) => ({
257
+ type: 'run.task.notification', taskId, status, summary, usage, toolUseId,
258
+ }),
259
+ filesPersisted: (files, failed) => ({ type: 'run.files_persisted', files, failed }),
260
+ memoryRecall: (mode, memories) => ({ type: 'run.memory_recall', mode, memories }),
261
+ notification: (key, text, priority, color, timeoutMs) => ({
262
+ type: 'run.notification', key, text, priority, color, timeoutMs,
263
+ }),
264
+ sessionState: (state) => ({ type: 'run.session_state', state }),
265
+ systemInit: (info) => ({ type: 'run.system_init', info }),
266
+ hookStarted: (hookName, hookEvent) => ({ type: 'run.hook.started', hookName, hookEvent }),
267
+ hookResponse: (hookName, hookEvent, outcome, output, exitCode) => ({
268
+ type: 'run.hook.response', hookName, hookEvent, outcome, output, exitCode,
269
+ }),
270
+
271
+ // C4 FileChanged hook 前端 reload iframe
272
+ fileChanged: (filePath, event) => ({ type: 'run.file_changed', filePath, event }),
273
+
274
+ // ── Phase 1 翻译补全 ──
275
+
276
+ // SDKAPIRetryMessage(sdk.d.ts:2322):API 请求失败可重试。
277
+ // 当前 handleSystemMessage 把 'api_retry' subtype 落进 default warn —— 改为 emit
278
+ // 让上层(前端 / 监控)能看到"还在重试"。
279
+ // error_status: HTTP 状态码,连接错误/超时时为 null(无 HTTP 响应)。
280
+ apiRetry: (attempt, maxRetries, retryDelayMs, errorStatus, errorKind) => ({
281
+ type: 'run.api_retry',
282
+ attempt,
283
+ maxRetries,
284
+ retryDelayMs,
285
+ errorStatus, // number | null
286
+ errorKind, // SDKAssistantMessageError union: rate_limit / server_error / ...
287
+ }),
288
+
289
+ // ── Phase 2 hooks 主动捕事件 ──
290
+
291
+ // SessionStart hook:source 'startup' | 'resume' | 'clear' | 'compact'
292
+ // 上层据此可决定要不要展示"接续上次会话"提示
293
+ sessionStart: (source, agentType, model) => ({
294
+ type: 'run.session_start', source, agentType, model,
295
+ }),
296
+
297
+ // SubagentStart hook(sdk.d.ts:5258):主动捕子代理启动。
298
+ // SDK system 'task_started' message 是间接路径(依赖 agentProgressSummaries),
299
+ // 这条 hook 是更可靠的主入口。
300
+ subagentStart: (agentId, agentType) => ({
301
+ type: 'run.subagent.start', agentId, agentType,
302
+ }),
303
+
304
+ // SubagentStop hook(sdk.d.ts:5269):子代理结束。带 last_assistant_message 字段
305
+ // 不用读 transcript 也能拿到收尾文本。
306
+ // toolUseId:SDK callback 2 参,是触发该 subagent 启动的 main agent
307
+ // Task tool_use_id —— 前端能用它把 critique 挂回对应的 Task message。
308
+ subagentStop: (agentId, agentType, lastAssistantMessage, transcriptPath, toolUseId) => ({
309
+ type: 'run.subagent.stop',
310
+ agentId, agentType,
311
+ lastAssistantMessage, // 可选;agent 收尾的最后一句
312
+ transcriptPath, // 可选;子代理转录文件路径,前端展开"完整对话"用
313
+ ...(toolUseId ? { toolUseId } : {}),
314
+ }),
315
+
316
+ // PostToolUseFailure hook:工具失败,hook 已注入了恢复建议给 agent,
317
+ // 上层只需可见"哪个工具失败了"做监控/告警。
318
+ toolFailure: (toolName, error) => ({
319
+ type: 'run.tool_failure', toolName, error,
320
+ }),
321
+
322
+ // ── Canvas 焕新升级 S1(2026-05-02)──
323
+
324
+ // S1d PostToolUse(Edit|Write canvas.html) hook 自动检测改动的 page emit
325
+ // 前端 SlideNavigator 收到后自动 scrollIntoView + 1.5s pulse 高亮该 section。
326
+ // pages: number[] —— 一次 Edit 可能跨多页(罕见),通常单元素。
327
+ // anchor: 可选,data-anchor 值(如 'cover-title'),前端能精确高亮元素而非整页。
328
+ canvasFocusPage: (pages, anchor) => ({
329
+ type: 'run.canvas_focus_page',
330
+ pages,
331
+ ...(anchor ? { anchor } : {}),
332
+ }),
333
+
334
+ // ── Canvas 焕新 C1(2026-05-02):MCP 工具反向通道 + tweaks/buffer 通知 ──
335
+
336
+ // navigate_to_page 工具触发,前端 ProjectWorkspace 收到后切到第 N
337
+ // (SlideNavigator setActivePage / iframe scrollIntoView section[data-page="N"])
338
+ canvasNavigate: (page) => ({ type: 'run.canvas_navigate', page }),
339
+
340
+ // preview_deck 工具触发:前端把这张 deck 卡"替用户双击一下"
341
+ //(收起态→内嵌渲染;已展开→画布内最大化窗)。path null = 当前会话的 deck
342
+ deckPreview: (filePath) => ({ type: 'run.deck_preview', path: filePath || null }),
343
+
344
+ // highlight 工具触发,前端在 InspectFloatingCard 同层挂 pulse overlay
345
+ canvasHighlight: (selector, durationMs = 1500) => ({
346
+ type: 'run.canvas_highlight', selector, durationMs,
347
+ }),
348
+
349
+ // expose_tweaks 工具完成,前端 TweaksPanel reload spec.tweaks
350
+ tweaksExposed: (count, added, replaced) => ({
351
+ type: 'run.tweaks_exposed', count, added, replaced,
352
+ }),
353
+
354
+ // clear_pending_changes 工具完成,前端可清相应本地 pending hint。
355
+ // clearedIds 让前端 comments state 精确 filter 出橙色 overlay(前后端 id 已统一)
356
+ pendingChangesCleared: (clearedIds, removed, remaining) => ({
357
+ type: 'run.pending_changes_cleared', clearedIds, removed, remaining,
358
+ }),
359
+
360
+ // ── A4.1(2026-05-02):AskUserQuestion 走 SDK canUseTool 路径 ──
361
+ //
362
+ // SDK binary AskUserQuestion 设为 `shouldDefer: true` + `requiresUserInteraction: true`,
363
+ // 工具的 checkPermissions 总返 `behavior: 'ask', message: 'Answer questions?'`,
364
+ // host 程序通过 canUseTool callback `behavior: 'allow', updatedInput: {
365
+ // ...input, answers: {...} }`,binary 拿到带 answers 的 input → 调 tool.call
366
+ // 工具直接返回 answers 作 result(cli.js:GR6.call 实现)。
367
+ //
368
+ // 我们的 canUseTool(session-loop.js)拦到此事件emit run.ask_user_question
369
+ // 前端 AskUserQuestionView 卡片 → 用户点选项 → POST /answer → resolve
370
+ // canUseTool 的 await,返回 updatedInput。
371
+ //
372
+ // questions 是从工具 input 摘出的 `{ question, header, options, multiSelect }[]`
373
+ // 数组(详见 cli.js:zHK schema),前端按现有卡片字段直接消费。
374
+ askUserQuestion: (toolUseId, questions) => ({
375
+ type: 'run.ask_user_question',
376
+ toolUseId,
377
+ questions,
378
+ }),
379
+
380
+ // ── A1.22026-05-02):实时上下文用量 ──
381
+ // SDKControlGetContextUsageResponse(sdk.d.ts:2451-2541)由 query.getContextUsage()
382
+ // 返回。session-loop.js 在每个 assistant message await 一次 emit,前端 ContextUsageBar
383
+ // 接事件渲进度条 + breakdown + autoCompact 阈值预警。
384
+ //
385
+ // 关键字段(为前端做轻量化封装,不全量透传以节省 ws 带宽):
386
+ // totalTokens / maxTokens / percentage — 主进度条数据
387
+ // autoCompactThreshold 触发 autoCompact 的阈值(绝对 token)
388
+ // isAutoCompactEnabled 当前开关状态(前端预警条件之一)
389
+ // model 当前模型 id
390
+ // messageBreakdown.toolCallsByType — 工具消耗 token 排名(展开看)
391
+ // memoryFiles / mcpTools / agents / skills — 各类目消耗(展开看)
392
+ //
393
+ // categories / gridRows 不传 —— 前端不渲网格,只用 percentage + breakdown。
394
+ // ── 图片生成(Phase A,2026-05-06 后段)──
395
+ // generate_image MCP 工具调完后 emit。前端可在 chat / timeline 实时插
396
+ // thumbnail,并把 path 关联回 spec.json 的 record_decision 节点。
397
+ // 字段:
398
+ // path agent cwd 相对路径(assets/generated/<file>.png)
399
+ // absPath 绝对落地路径(前端不一定用,便于审计)
400
+ // sizeBytes png 字节数
401
+ // prompt 生成 prompt 全文
402
+ // assetRole 语义类(hero/cover/bg/.../pattern),可空
403
+ // aspectRatio / imageSize 生成参数
404
+ // referenceImageCount 喂进去的参考图数量
405
+ // accompanyText 模型可选的文字 commentary(responseModalities TEXT 时才有)
406
+ imageGenerated: (info) => ({
407
+ type: 'run.image_generated',
408
+ path: info.path,
409
+ absPath: info.absPath,
410
+ sizeBytes: info.sizeBytes,
411
+ prompt: info.prompt,
412
+ assetRole: info.assetRole || null,
413
+ aspectRatio: info.aspectRatio,
414
+ imageSize: info.imageSize,
415
+ referenceImageCount: info.referenceImageCount || 0,
416
+ ...(info.accompanyText ? { accompanyText: info.accompanyText } : {}),
417
+ }),
418
+
419
+ // appModel: NoDesign 上层真实 model(如 kimi-k2.6)。spoofing 后 SDK 的
420
+ // usage.model alias(如 claude-opus-4-7[1m])不可信;调用方需把真 appModel
421
+ // 传进来。给定 appModel 时按真实容量算 maxTokens / percentage,前端 ContextUsageBar
422
+ // 显的就是 gateway 真正能扛的容量(不是 SDK 内部 compact 触发线)。
423
+ contextUsage: (usage, appModel = null) => {
424
+ // 这里 import 在 file 顶(已加),避免循环依赖问题
425
+ //
426
+ // 分母的兜底链只能有一条(2026-07-30):以前 hooks.js 那个生产者自己写了
427
+ // 一条更长的(?? rawMaxTokens ?? 256000),两条链在 appModel 缺失时给出不同
428
+ // 的分母,同一个会话前后两次百分比对不上。合并到这里,256000 那个 kimi 时代
429
+ // 的常量去掉 —— 拿它当 Claude 会话的分母只会算出个假的高百分比。
430
+ const realMax = (appModel ? resolveModelContextWindow(appModel) : null)
431
+ ?? usage.maxTokens
432
+ ?? usage.rawMaxTokens
433
+ ?? null;
434
+ const totalTokens = usage.totalTokens;
435
+ const percentage = realMax > 0 ? Math.round((totalTokens / realMax) * 100) : (usage.percentage || 0);
436
+ return {
437
+ type: 'run.context_usage',
438
+ totalTokens,
439
+ maxTokens: realMax, // 真实容量(kimi=256k)—— 前端进度条分母
440
+ sdkMaxTokens: usage.maxTokens, // SDK 给的(compact 触发线 230k)—— debug
441
+ percentage,
442
+ autoCompactThreshold: usage.autoCompactThreshold,
443
+ isAutoCompactEnabled: usage.isAutoCompactEnabled,
444
+ model: appModel || usage.model,
445
+ // 出自谁家(BRANDS 之一)——画布精灵据此换身份标。⚠️ 不能让前端读 SDK 的 usage.model 去认牌子:
446
+ // spoofing 之后那是 alias(DeepSeek 行报 claude-opus-4-7[1m]),照它认牌子会把鲸画成星芒
447
+ brand: appModel ? brandOfModel(appModel) : null,
448
+ // 轻量化 breakdown — 前端 hover/expand 能看到细节,不渲细节时不占空间
449
+ messageBreakdown: usage.messageBreakdown ? {
450
+ toolCallTokens: usage.messageBreakdown.toolCallTokens,
451
+ toolResultTokens: usage.messageBreakdown.toolResultTokens,
452
+ attachmentTokens: usage.messageBreakdown.attachmentTokens,
453
+ assistantMessageTokens: usage.messageBreakdown.assistantMessageTokens,
454
+ userMessageTokens: usage.messageBreakdown.userMessageTokens,
455
+ // toolCallsByType 是排行(用于"哪个工具吃 token 最多"),保留 top 10
456
+ toolCallsByType: (usage.messageBreakdown.toolCallsByType || [])
457
+ .slice(0, 10),
458
+ } : null,
459
+ // 类目级别 token 数(聚合,不传完整数组)
460
+ memoryFilesTokens: (usage.memoryFiles || []).reduce((s, m) => s + (m.tokens || 0), 0),
461
+ mcpToolsTokens: (usage.mcpTools || []).reduce((s, t) => s + (t.tokens || 0), 0),
462
+ agentsTokens: (usage.agents || []).reduce((s, a) => s + (a.tokens || 0), 0),
463
+ };
464
+ },
465
+ };