@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
@@ -0,0 +1,130 @@
1
+ /**
2
+ * server/engine/runs/board-tasklist.js —— 把 agent 的步骤清单镜像成板书(2026-08-23)
3
+ *
4
+ * 用户定的文化:板书是基础设施。agent 开工时用 TodoWrite 列的步骤不该只活在侧栏的计划
5
+ * 面板里 —— 它是这一轮的骨架,该写在画布上、每一步产出的东西连到对应步骤。
6
+ * 这件事**由 harness 做,不靠 agent 记得**:
7
+ * - run.todo.updated → 一条板书 `notes/板书/<stamp>-步骤.md`(一轮一条、原地更新),
8
+ * Markdown 任务列表:✓ 已做 / → 正在(加粗)/ 待做;落定不走 staging
9
+ * - run.file_changed(有"正在"的步骤时)→ 这条板书到那件产物的 annotates 线,label「第 N 步」,
10
+ * by:'auto'(不算进主角焦点分;同一轮同一件只连一次)
11
+ * - run.done/cancelled/error → 忘掉这一轮的状态(板书留着,它是过程记录)
12
+ * 挂在 project bus 上(broker.getProjectBus 建 bus 时调一次),与 live-turn 折叠器同级。
13
+ * 失败一律吞掉只 warn:镜像挂了不能影响 run。
14
+ */
15
+ import path from 'node:path';
16
+ import { readBoard, patchBoard } from '../../projects/board-store.js';
17
+ import { getSharedDir } from '../../projects/workspace.js';
18
+ import { estimateSizeOn } from '../../lib/board-kind-sizes.js';
19
+ import { layerOf, normalizeCanvasId } from '../../lib/canvas-id.js';
20
+ import { textBox, findSpot } from '../../lib/sketch-layout.js';
21
+ import { getViewpoint } from '../../projects/viewpoint-store.js';
22
+ import { renderChalk, writeChalkFile, CHALK_DIR } from '../../lib/chalk.js';
23
+
24
+ const byRun = new Map(); // runId → { projectId, rel, fileName, items, linked:Set, session }
25
+ const MAX_LINKS_PER_RUN = 24;
26
+
27
+ function renderList(items) {
28
+ const lines = ['### 这一轮的步骤', ''];
29
+ items.forEach((it, i) => {
30
+ const text = String(it.content || it.activeForm || '').trim() || `步骤 ${i + 1}`;
31
+ if (it.status === 'completed') lines.push(`- [x] ${text}`);
32
+ else if (it.status === 'in_progress') lines.push(`- [ ] **→ ${it.activeForm || text}**`);
33
+ else lines.push(`- [ ] ${text}`);
34
+ });
35
+ return lines.join('\n');
36
+ }
37
+
38
+ /** 工作区相对路径 → 画布 id(产物卡的地址):精确 > deck: > 站点/word 根 > 原样 */
39
+ export function canvasIdForRel(board, rel) {
40
+ const r = String(rel || '').replace(/\\/g, '/').replace(/^\.\//, '');
41
+ if (!r) return null;
42
+ const objs = board.objects || {};
43
+ if (objs[r]) return r;
44
+ const norm = normalizeCanvasId(r);
45
+ if (norm && objs[norm]) return norm;
46
+ for (const id of Object.keys(objs)) {
47
+ const m = /^(site|docx):(.+)$/.exec(id);
48
+ if (!m) continue;
49
+ const root = m[2];
50
+ if (r === root || r.startsWith(`${root}/`)) return id;
51
+ }
52
+ // 还没上墙:按形态猜(seated 之后线自然画出来)
53
+ return norm || r;
54
+ }
55
+
56
+ async function upsert(st, items) {
57
+ st.items = items;
58
+ const body = renderList(items);
59
+ const content = renderChalk({ body, by: 'agent', tag: st.tag, sessionId: st.session || null });
60
+ const sharedRoot = getSharedDir(st.projectId);
61
+ if (!st.fileName) st.fileName = `${new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, '').replace('T', '-')}-步骤.md`;
62
+ st.rel = await writeChalkFile(sharedRoot, st.fileName, content, { overwrite: !!st.rel });
63
+ st.fileName = st.rel.split('/').pop(); // 首写可能被加了防撞序号,之后原地覆盖同一个文件
64
+ const board = await readBoard(st.projectId);
65
+ const box = textBox(body, 'md', { md: true, wUnits: 14 });
66
+ const prev = board.objects?.[st.rel];
67
+ if (prev && Number.isFinite(prev.x)) {
68
+ // 已有座位:只改高(行数变了)不碰宽 —— 宽由前端量过真值,回写会跟它来回抖(fable P2)
69
+ await patchBoard(st.projectId, { objects: { [st.rel]: { ...prev, h: box.h } } });
70
+ return;
71
+ }
72
+ // 首次落位:用户视口的空地 > 内容底下(跟 write_on_board 同口径)
73
+ const known = new Set(Object.keys(board.zones || {}));
74
+ const obstacles = Object.entries(board.objects || {})
75
+ .filter(([id, e]) => Number.isFinite(e?.x) && layerOf(id, e, known) === '')
76
+ .map(([id, e]) => ({ x: e.x, y: e.y, ...estimateSizeOn(board, id, e) }));
77
+ let bottom = 0; for (const o of obstacles) bottom = Math.max(bottom, o.y + o.h);
78
+ for (const zz of Object.values(board.zones || {})) if (Number.isFinite(zz?.y)) bottom = Math.max(bottom, zz.y + 240);
79
+ const vp = getViewpoint(st.projectId);
80
+ const vpRect = (vp && !vp.layer && vp.camera) ? vp.camera : null;
81
+ const spot = findSpot({ w: box.w + 24, h: box.h + 24, obstacles, contentBottom: bottom, viewport: vpRect });
82
+ await patchBoard(st.projectId, { objects: { [st.rel]: { x: spot.x + 12, y: spot.y + 12, z: 1, w: box.w, h: box.h, by: 'agent', tag: st.tag } } });
83
+ }
84
+
85
+ export function attachBoardTasklist(bus, projectId) {
86
+ const handle = async (evt) => {
87
+ if (!evt?.runId) return;
88
+ if (evt.type === 'run.todo.updated') {
89
+ const items = Array.isArray(evt.todos) ? evt.todos.filter(t => t && typeof t === 'object') : [];
90
+ if (!items.length) return;
91
+ let st = byRun.get(evt.runId);
92
+ if (!st) {
93
+ st = { projectId, rel: null, fileName: null, items: [], linked: new Set(), session: evt.sessionId || null, tag: `步骤-${String(evt.runId).slice(-6)}` };
94
+ byRun.set(evt.runId, st);
95
+ }
96
+ await upsert(st, items);
97
+ bus.publish({ type: 'board.updated', sessionId: null, runId: evt.runId, summary: '步骤清单上板' });
98
+ return;
99
+ }
100
+ if (evt.type === 'run.file_changed') {
101
+ const st = byRun.get(evt.runId);
102
+ if (!st?.rel || !evt.filePath) return;
103
+ const idx = st.items.findIndex(t => t.status === 'in_progress');
104
+ if (idx < 0) return;
105
+ // 只连工作区内、非隐藏目录的真产物;每轮封顶(否则 /tmp、.nd、.claude 里的写入会灌一堆
106
+ // 指向虚空的 auto 线,MAX_BINDINGS 一到用户自己的线都不收 —— fable 08-23 P2)
107
+ const rel = String(evt.filePath).replace(/\\/g, '/');
108
+ if (rel.startsWith('/') || rel.split('/').some(seg => seg === '..' || seg.startsWith('.') || seg === 'node_modules')) return;
109
+ if (rel.startsWith(`${CHALK_DIR}/`) || rel.startsWith('notes/')) return;
110
+ if (st.linked.size >= MAX_LINKS_PER_RUN) return;
111
+ const board = await readBoard(st.projectId);
112
+ const target = canvasIdForRel(board, evt.filePath);
113
+ if (!target || target === st.rel || target.startsWith(`${CHALK_DIR}/`)) return;
114
+ if (st.linked.has(target)) return;
115
+ st.linked.add(target);
116
+ const id = `b:auto:step:${String(evt.runId).slice(-6)}:${st.linked.size}`;
117
+ await patchBoard(st.projectId, { bindings: { [id]: { type: 'annotates', from: st.rel, to: target, by: 'auto', label: `第 ${idx + 1} 步`, tag: st.tag } } });
118
+ bus.publish({ type: 'board.updated', sessionId: null, runId: evt.runId, summary: `第 ${idx + 1} 步 → ${path.basename(target)}` });
119
+ return;
120
+ }
121
+ if (evt.type === 'run.done' || evt.type === 'run.cancelled' || evt.type === 'run.error') {
122
+ byRun.delete(evt.runId);
123
+ }
124
+ };
125
+ bus.subscribe('*', (evt) => {
126
+ handle(evt).catch(err => console.warn('[board-tasklist]', err?.message || err));
127
+ });
128
+ }
129
+
130
+ export function _resetBoardTasklist() { byRun.clear(); }
@@ -1,252 +1,252 @@
1
- /**
2
- * server/engine/runs/live-turn.js — 进行中 turn 的物化快照(内存态)
3
- *
4
- * 解决的问题:EventBus 是 fire-and-forget + ring buffer(2000 条),token 级
5
- * streaming 一个 turn 就上万条事件 —— 刷新 / 断线重连后"进行中的 turn"在任何
6
- * 可恢复的地方都不存在(jsonl 只在 turn 边界 flush)。老架构靠 ?since= 游标 +
7
- * ring 回放,重连几秒就 gap,gap 补 hydrate 又把已渲染内容反向覆盖。
8
- *
9
- * 新范式(快照 + 尾随,2026-07-27 重构):
10
- * - 本模块订阅每个 project bus,把进行中 turn 的事件**同步折叠**成前端
11
- * display-message 形态的物化状态(跟 ProjectWorkspace handleEvent 的累加
12
- * 逻辑同构)
13
- * - WS 连接时:jsonl hydrate(已完成 turn)→ ws.live_turn 快照(进行中 turn)
14
- * → 从快照 seq 起订阅实时流。ring buffer 大小从此只影响极小的同步窗口,
15
- * 不再影响正确性
16
- * - turn 结束(done / error / cancelled)或 session 结束即清,内存占用 =
17
- * 活跃 turn 数 × 单 turn 消息体量(tool output 截断到 16k)
18
- *
19
- * 折叠规则跟前端 handleEvent 对齐(改前端渲染逻辑时两边同步改):
20
- * run.delta.text / thinking → 同 role 连续累加一条消息
21
- * run.tool_use.started → push tool 消息 status='running'
22
- * run.delta.tool_use → 补 toolInput
23
- * run.delta.tool_result → 补 status / output / error(images 不进快照,太大)
24
- * run.todo.updated → todos 覆盖
25
- * run.context_usage → contextUsage 覆盖
26
- * run.done / error / cancelled → 标记 ended(保留 GRACE 毫秒,见下),不再折叠
27
- * run.query.end → 清
28
- *
29
- * turn 结束后为什么还留一小会(2026-07-28):jsonl 是 SDK 子进程按 block 逐条写的,
30
- * 我们读 jsonl 与 turn 收尾之间有几百毫秒的错位窗口。这段时间内重连,若快照已经
31
- * 没了,尾随订阅会把已经落进 jsonl 的那几条 delta 再放一遍 → 正文重复。留 GRACE
32
- * 毫秒让"本轮由快照权威、hydrate 裁掉本轮"这条规则在收尾瞬间同样成立。
33
- */
34
-
35
- const MAX_TOOL_TEXT = 16_000;
36
- // 3s:够盖住"读 jsonl 的几百毫秒"这个错位窗口,又短到收尾后的重连基本不会
37
- // 落在里面(快照不带 tool_result 的图片,长时间用快照顶替 hydrate 会丢缩略图)。
38
- const ENDED_GRACE_MS = 3_000;
39
-
40
- /** @type {Map<string, object>} sessionId → live turn state */
41
- const liveTurns = new Map();
42
-
43
- /**
44
- * sessionId → 最近一次 run.context_usage 事件(2026-07-30)
45
- *
46
- * 跟 liveTurns 分开活着:turn 一结束 liveTurns 就清,但"这个会话现在装了多少上下文"
47
- * 在两轮之间照样成立 —— 而那恰恰是用户最想看它的时刻(要不要先压缩再开新活)。
48
- *
49
- * query 结束也**不清**:关掉标签页就会触发 grace 超时关 session,而下次打开这个会话
50
- * 时 SDK 从同一份 jsonl resume,上下文体量基本还是那么大。这时给个带"当前没有活跃
51
- * 会话"标注的旧数字(live:false),比一句"还没开始对话"有用得多。只有 query 活着时才向 SDK 现问
52
- * (见 GET /sessions/:sid/context-usage),所以准确的那条路一直优先。
53
- *
54
- * 只活在内存里:进程重启就没了,下一轮 turn 自然补上。
55
- */
56
- const lastUsage = new Map();
57
- const LAST_USAGE_CAP = 200;
58
-
59
- export function getLastContextUsage(sessionId) {
60
- return lastUsage.get(sessionId) || null;
61
- }
62
-
63
- /**
64
- * 不变量守卫:只记 Events.contextUsage 构造出来的那种形状。
65
- *
66
- * `run.context_usage` 曾经有两个生产者,字段名对不上:session-loop 每条 assistant
67
- * message 后发 { totalTokens, maxTokens, percentage }(前端读这套),hooks.js 的 Stop
68
- * handler 每轮收尾发 { used, max, percent, categories }。前端靠 store 的"不覆盖已有值"
69
- * 侥幸没显形,但每轮的**最后**一条恰好总是后者,凡是"取最新一条"的下游拿到的都是
70
- * 一片 undefined —— 这个记忆层就是这么栽的。两个生产者已经合并成一个构造函数
71
- * (2026-07-30),这里留一道守卫:以后再冒出第三种形状,是它被丢掉,不是这里出错数。
72
- */
73
- function rememberUsage(sid, evt) {
74
- if (typeof evt.totalTokens !== 'number') return;
75
- if (lastUsage.size >= LAST_USAGE_CAP && !lastUsage.has(sid)) {
76
- // 先进先出丢一条:进程活得久了不至于攒着所有历史 session
77
- const oldest = lastUsage.keys().next().value;
78
- if (oldest) lastUsage.delete(oldest);
79
- }
80
- lastUsage.set(sid, evt);
81
- }
82
-
83
- function truncate(s) {
84
- if (typeof s !== 'string') return s;
85
- if (s.length <= MAX_TOOL_TEXT) return s;
86
- return s.slice(0, MAX_TOOL_TEXT) + `\n…[截断,共 ${s.length} 字符]`;
87
- }
88
-
89
- /**
90
- * 给 project bus 挂折叠订阅(幂等 —— broker.getProjectBus 每次创建 bus 时调)。
91
- */
92
- export function attachLiveTurnTracker(bus) {
93
- if (bus.__liveTurnTracked) return;
94
- bus.__liveTurnTracked = true;
95
- bus.subscribe('*', fold);
96
- }
97
-
98
- /**
99
- * 取 session 当前 turn 的快照;没有在跑、且上一轮已过 grace 期 → null。
100
- * 返回值直接可作 ws.live_turn 帧 payload(messages 是前端 display 形态)。
101
- *
102
- * running=false 表示这是刚结束那一轮的尾巴(grace 期内),前端据此**只合并消息、
103
- * 不把自己切回 streaming 态**。
104
- */
105
- export function getLiveTurnSnapshot(sessionId) {
106
- const st = liveTurns.get(sessionId);
107
- if (!st) return null;
108
- if (st.endedAt && Date.now() - st.endedAt > ENDED_GRACE_MS) {
109
- liveTurns.delete(sessionId);
110
- return null;
111
- }
112
- return {
113
- runId: st.runId,
114
- seq: st.seq,
115
- startedAt: st.startedAt,
116
- running: !st.endedAt,
117
- messages: st.messages,
118
- todos: st.todos,
119
- contextUsage: st.contextUsage,
120
- };
121
- }
122
-
123
- function fold(evt) {
124
- const sid = evt.sessionId;
125
- if (!sid) return;
126
-
127
- // 记在 liveTurns 之外:turn 结束、快照删掉之后,这个数字还要继续给 composer
128
- // 的 [+] 菜单用。放在 st 判空之前,快照已经清了也照记。
129
- if (evt.type === 'run.context_usage') rememberUsage(sid, evt);
130
-
131
- if (evt.type === 'run.start') {
132
- liveTurns.set(sid, {
133
- runId: evt.runId || null,
134
- startedAt: evt.ts || new Date().toISOString(),
135
- seq: evt.seq || 0,
136
- messages: [],
137
- todos: [],
138
- contextUsage: null,
139
- endedAt: null,
140
- _msgCounter: 0,
141
- });
142
- return;
143
- }
144
-
145
- const st = liveTurns.get(sid);
146
- if (!st) return;
147
-
148
- // 已收尾的那一轮只留着给 grace 期内的重连当权威快照,不再吸新事件
149
- // (新一轮由上面的 run.start 整块换掉)。seq 照常推进,免得尾随订阅从旧游标
150
- // 起把这轮已经放过的事件再放一遍。
151
- if (st.endedAt) {
152
- if (evt.type === 'run.query.end' || Date.now() - st.endedAt > ENDED_GRACE_MS) {
153
- liveTurns.delete(sid);
154
- return;
155
- }
156
- if (typeof evt.seq === 'number' && evt.seq > st.seq) st.seq = evt.seq;
157
- return;
158
- }
159
-
160
- // stale 事件(老 turn 的尾巴)不折叠也不清新 turn 的状态
161
- const runMatches = !evt.runId || !st.runId || evt.runId === st.runId;
162
-
163
- switch (evt.type) {
164
- case 'run.delta.text':
165
- if (runMatches) appendText(st, 'assistant', evt.text, evt.parentToolUseId);
166
- break;
167
- case 'run.delta.thinking':
168
- if (runMatches) appendText(st, 'thinking', evt.text, evt.parentToolUseId);
169
- break;
170
- case 'run.tool_use.started':
171
- if (runMatches && evt.blockId && !st.messages.some(m => m.id === evt.blockId)) {
172
- st.messages.push({
173
- id: evt.blockId, role: 'tool', toolName: evt.name,
174
- toolInput: undefined, status: 'running', runId: st.runId,
175
- ...(evt.parentToolUseId ? { parentToolUseId: evt.parentToolUseId } : {}),
176
- });
177
- }
178
- break;
179
- case 'run.delta.tool_use': {
180
- if (!runMatches) break;
181
- const existing = st.messages.find(m => m.role === 'tool' && m.id === evt.blockId);
182
- if (existing) existing.toolInput = evt.input;
183
- else if (evt.blockId) {
184
- st.messages.push({
185
- id: evt.blockId, role: 'tool', toolName: evt.name,
186
- toolInput: evt.input, status: 'running', runId: st.runId,
187
- ...(evt.parentToolUseId ? { parentToolUseId: evt.parentToolUseId } : {}),
188
- });
189
- }
190
- break;
191
- }
192
- case 'run.delta.tool_result': {
193
- if (!runMatches) break;
194
- const tool = st.messages.find(m => m.role === 'tool' && m.id === evt.blockId);
195
- if (tool) {
196
- tool.status = evt.ok ? 'success' : 'error';
197
- if (evt.output !== undefined) tool.toolOutput = truncate(typeof evt.output === 'string' ? evt.output : JSON.stringify(evt.output));
198
- if (evt.error) tool.toolError = truncate(typeof evt.error === 'string' ? evt.error : JSON.stringify(evt.error));
199
- // images 不进快照:base64 体积大,turn 结束后 jsonl hydrate 会带回来
200
- }
201
- break;
202
- }
203
- case 'run.tool_use_summary': {
204
- if (!runMatches || !evt.summary) break;
205
- const ids = Array.isArray(evt.blockIds) ? evt.blockIds : [];
206
- const hit = st.messages.find(m => m.role === 'tool' && ids.includes(m.id));
207
- if (hit) hit.groupSummary = evt.summary;
208
- break;
209
- }
210
- case 'run.todo.updated':
211
- if (runMatches && Array.isArray(evt.todos)) st.todos = evt.todos;
212
- break;
213
- case 'run.context_usage':
214
- if (runMatches) st.contextUsage = evt;
215
- break;
216
- case 'run.done':
217
- case 'run.error':
218
- case 'run.cancelled':
219
- // 不立刻删:留 grace 期给"收尾瞬间重连"当权威快照(见文件头说明)
220
- if (runMatches) {
221
- st.endedAt = Date.now();
222
- if (typeof evt.seq === 'number' && evt.seq > st.seq) st.seq = evt.seq;
223
- }
224
- return;
225
- case 'run.query.end':
226
- liveTurns.delete(sid);
227
- return;
228
- default:
229
- break;
230
- }
231
-
232
- // 所有本 session 事件都推进快照游标(包括未折叠类型)——
233
- // 重连订阅从 seq 起,不折叠的事件类型(queue depth 等)丢了也无碍(幂等 UI 状态)
234
- if (typeof evt.seq === 'number' && evt.seq > st.seq) st.seq = evt.seq;
235
- }
236
-
237
- function appendText(st, role, text, parentToolUseId = null) {
238
- if (!text) return;
239
- const last = st.messages[st.messages.length - 1];
240
- // 子代理时间轴:不同 parent 的流不互吸(跟前端 lib/chat-stream.js 同构)
241
- if (last && last.role === role && (last.parentToolUseId || null) === (parentToolUseId || null)) {
242
- last.content = (last.content || '') + text;
243
- return;
244
- }
245
- st.messages.push({
246
- id: `${st.runId || 'live'}:m${st._msgCounter++}`,
247
- ...(parentToolUseId ? { parentToolUseId } : {}),
248
- role,
249
- content: text,
250
- runId: st.runId,
251
- });
252
- }
1
+ /**
2
+ * server/engine/runs/live-turn.js — 进行中 turn 的物化快照(内存态)
3
+ *
4
+ * 解决的问题:EventBus 是 fire-and-forget + ring buffer(2000 条),token 级
5
+ * streaming 一个 turn 就上万条事件 —— 刷新 / 断线重连后"进行中的 turn"在任何
6
+ * 可恢复的地方都不存在(jsonl 只在 turn 边界 flush)。老架构靠 ?since= 游标 +
7
+ * ring 回放,重连几秒就 gap,gap 补 hydrate 又把已渲染内容反向覆盖。
8
+ *
9
+ * 新范式(快照 + 尾随,2026-07-27 重构):
10
+ * - 本模块订阅每个 project bus,把进行中 turn 的事件**同步折叠**成前端
11
+ * display-message 形态的物化状态(跟 ProjectWorkspace handleEvent 的累加
12
+ * 逻辑同构)
13
+ * - WS 连接时:jsonl hydrate(已完成 turn)→ ws.live_turn 快照(进行中 turn)
14
+ * → 从快照 seq 起订阅实时流。ring buffer 大小从此只影响极小的同步窗口,
15
+ * 不再影响正确性
16
+ * - turn 结束(done / error / cancelled)或 session 结束即清,内存占用 =
17
+ * 活跃 turn 数 × 单 turn 消息体量(tool output 截断到 16k)
18
+ *
19
+ * 折叠规则跟前端 handleEvent 对齐(改前端渲染逻辑时两边同步改):
20
+ * run.delta.text / thinking → 同 role 连续累加一条消息
21
+ * run.tool_use.started → push tool 消息 status='running'
22
+ * run.delta.tool_use → 补 toolInput
23
+ * run.delta.tool_result → 补 status / output / error(images 不进快照,太大)
24
+ * run.todo.updated → todos 覆盖
25
+ * run.context_usage → contextUsage 覆盖
26
+ * run.done / error / cancelled → 标记 ended(保留 GRACE 毫秒,见下),不再折叠
27
+ * run.query.end → 清
28
+ *
29
+ * turn 结束后为什么还留一小会(2026-07-28):jsonl 是 SDK 子进程按 block 逐条写的,
30
+ * 我们读 jsonl 与 turn 收尾之间有几百毫秒的错位窗口。这段时间内重连,若快照已经
31
+ * 没了,尾随订阅会把已经落进 jsonl 的那几条 delta 再放一遍 → 正文重复。留 GRACE
32
+ * 毫秒让"本轮由快照权威、hydrate 裁掉本轮"这条规则在收尾瞬间同样成立。
33
+ */
34
+
35
+ const MAX_TOOL_TEXT = 16_000;
36
+ // 3s:够盖住"读 jsonl 的几百毫秒"这个错位窗口,又短到收尾后的重连基本不会
37
+ // 落在里面(快照不带 tool_result 的图片,长时间用快照顶替 hydrate 会丢缩略图)。
38
+ const ENDED_GRACE_MS = 3_000;
39
+
40
+ /** @type {Map<string, object>} sessionId → live turn state */
41
+ const liveTurns = new Map();
42
+
43
+ /**
44
+ * sessionId → 最近一次 run.context_usage 事件(2026-07-30)
45
+ *
46
+ * 跟 liveTurns 分开活着:turn 一结束 liveTurns 就清,但"这个会话现在装了多少上下文"
47
+ * 在两轮之间照样成立 —— 而那恰恰是用户最想看它的时刻(要不要先压缩再开新活)。
48
+ *
49
+ * query 结束也**不清**:关掉标签页就会触发 grace 超时关 session,而下次打开这个会话
50
+ * 时 SDK 从同一份 jsonl resume,上下文体量基本还是那么大。这时给个带"当前没有活跃
51
+ * 会话"标注的旧数字(live:false),比一句"还没开始对话"有用得多。只有 query 活着时才向 SDK 现问
52
+ * (见 GET /sessions/:sid/context-usage),所以准确的那条路一直优先。
53
+ *
54
+ * 只活在内存里:进程重启就没了,下一轮 turn 自然补上。
55
+ */
56
+ const lastUsage = new Map();
57
+ const LAST_USAGE_CAP = 200;
58
+
59
+ export function getLastContextUsage(sessionId) {
60
+ return lastUsage.get(sessionId) || null;
61
+ }
62
+
63
+ /**
64
+ * 不变量守卫:只记 Events.contextUsage 构造出来的那种形状。
65
+ *
66
+ * `run.context_usage` 曾经有两个生产者,字段名对不上:session-loop 每条 assistant
67
+ * message 后发 { totalTokens, maxTokens, percentage }(前端读这套),hooks.js 的 Stop
68
+ * handler 每轮收尾发 { used, max, percent, categories }。前端靠 store 的"不覆盖已有值"
69
+ * 侥幸没显形,但每轮的**最后**一条恰好总是后者,凡是"取最新一条"的下游拿到的都是
70
+ * 一片 undefined —— 这个记忆层就是这么栽的。两个生产者已经合并成一个构造函数
71
+ * (2026-07-30),这里留一道守卫:以后再冒出第三种形状,是它被丢掉,不是这里出错数。
72
+ */
73
+ function rememberUsage(sid, evt) {
74
+ if (typeof evt.totalTokens !== 'number') return;
75
+ if (lastUsage.size >= LAST_USAGE_CAP && !lastUsage.has(sid)) {
76
+ // 先进先出丢一条:进程活得久了不至于攒着所有历史 session
77
+ const oldest = lastUsage.keys().next().value;
78
+ if (oldest) lastUsage.delete(oldest);
79
+ }
80
+ lastUsage.set(sid, evt);
81
+ }
82
+
83
+ function truncate(s) {
84
+ if (typeof s !== 'string') return s;
85
+ if (s.length <= MAX_TOOL_TEXT) return s;
86
+ return s.slice(0, MAX_TOOL_TEXT) + `\n…[截断,共 ${s.length} 字符]`;
87
+ }
88
+
89
+ /**
90
+ * 给 project bus 挂折叠订阅(幂等 —— broker.getProjectBus 每次创建 bus 时调)。
91
+ */
92
+ export function attachLiveTurnTracker(bus) {
93
+ if (bus.__liveTurnTracked) return;
94
+ bus.__liveTurnTracked = true;
95
+ bus.subscribe('*', fold);
96
+ }
97
+
98
+ /**
99
+ * 取 session 当前 turn 的快照;没有在跑、且上一轮已过 grace 期 → null。
100
+ * 返回值直接可作 ws.live_turn 帧 payload(messages 是前端 display 形态)。
101
+ *
102
+ * running=false 表示这是刚结束那一轮的尾巴(grace 期内),前端据此**只合并消息、
103
+ * 不把自己切回 streaming 态**。
104
+ */
105
+ export function getLiveTurnSnapshot(sessionId) {
106
+ const st = liveTurns.get(sessionId);
107
+ if (!st) return null;
108
+ if (st.endedAt && Date.now() - st.endedAt > ENDED_GRACE_MS) {
109
+ liveTurns.delete(sessionId);
110
+ return null;
111
+ }
112
+ return {
113
+ runId: st.runId,
114
+ seq: st.seq,
115
+ startedAt: st.startedAt,
116
+ running: !st.endedAt,
117
+ messages: st.messages,
118
+ todos: st.todos,
119
+ contextUsage: st.contextUsage,
120
+ };
121
+ }
122
+
123
+ function fold(evt) {
124
+ const sid = evt.sessionId;
125
+ if (!sid) return;
126
+
127
+ // 记在 liveTurns 之外:turn 结束、快照删掉之后,这个数字还要继续给 composer
128
+ // 的 [+] 菜单用。放在 st 判空之前,快照已经清了也照记。
129
+ if (evt.type === 'run.context_usage') rememberUsage(sid, evt);
130
+
131
+ if (evt.type === 'run.start') {
132
+ liveTurns.set(sid, {
133
+ runId: evt.runId || null,
134
+ startedAt: evt.ts || new Date().toISOString(),
135
+ seq: evt.seq || 0,
136
+ messages: [],
137
+ todos: [],
138
+ contextUsage: null,
139
+ endedAt: null,
140
+ _msgCounter: 0,
141
+ });
142
+ return;
143
+ }
144
+
145
+ const st = liveTurns.get(sid);
146
+ if (!st) return;
147
+
148
+ // 已收尾的那一轮只留着给 grace 期内的重连当权威快照,不再吸新事件
149
+ // (新一轮由上面的 run.start 整块换掉)。seq 照常推进,免得尾随订阅从旧游标
150
+ // 起把这轮已经放过的事件再放一遍。
151
+ if (st.endedAt) {
152
+ if (evt.type === 'run.query.end' || Date.now() - st.endedAt > ENDED_GRACE_MS) {
153
+ liveTurns.delete(sid);
154
+ return;
155
+ }
156
+ if (typeof evt.seq === 'number' && evt.seq > st.seq) st.seq = evt.seq;
157
+ return;
158
+ }
159
+
160
+ // stale 事件(老 turn 的尾巴)不折叠也不清新 turn 的状态
161
+ const runMatches = !evt.runId || !st.runId || evt.runId === st.runId;
162
+
163
+ switch (evt.type) {
164
+ case 'run.delta.text':
165
+ if (runMatches) appendText(st, 'assistant', evt.text, evt.parentToolUseId);
166
+ break;
167
+ case 'run.delta.thinking':
168
+ if (runMatches) appendText(st, 'thinking', evt.text, evt.parentToolUseId);
169
+ break;
170
+ case 'run.tool_use.started':
171
+ if (runMatches && evt.blockId && !st.messages.some(m => m.id === evt.blockId)) {
172
+ st.messages.push({
173
+ id: evt.blockId, role: 'tool', toolName: evt.name,
174
+ toolInput: undefined, status: 'running', runId: st.runId,
175
+ ...(evt.parentToolUseId ? { parentToolUseId: evt.parentToolUseId } : {}),
176
+ });
177
+ }
178
+ break;
179
+ case 'run.delta.tool_use': {
180
+ if (!runMatches) break;
181
+ const existing = st.messages.find(m => m.role === 'tool' && m.id === evt.blockId);
182
+ if (existing) existing.toolInput = evt.input;
183
+ else if (evt.blockId) {
184
+ st.messages.push({
185
+ id: evt.blockId, role: 'tool', toolName: evt.name,
186
+ toolInput: evt.input, status: 'running', runId: st.runId,
187
+ ...(evt.parentToolUseId ? { parentToolUseId: evt.parentToolUseId } : {}),
188
+ });
189
+ }
190
+ break;
191
+ }
192
+ case 'run.delta.tool_result': {
193
+ if (!runMatches) break;
194
+ const tool = st.messages.find(m => m.role === 'tool' && m.id === evt.blockId);
195
+ if (tool) {
196
+ tool.status = evt.ok ? 'success' : 'error';
197
+ if (evt.output !== undefined) tool.toolOutput = truncate(typeof evt.output === 'string' ? evt.output : JSON.stringify(evt.output));
198
+ if (evt.error) tool.toolError = truncate(typeof evt.error === 'string' ? evt.error : JSON.stringify(evt.error));
199
+ // images 不进快照:base64 体积大,turn 结束后 jsonl hydrate 会带回来
200
+ }
201
+ break;
202
+ }
203
+ case 'run.tool_use_summary': {
204
+ if (!runMatches || !evt.summary) break;
205
+ const ids = Array.isArray(evt.blockIds) ? evt.blockIds : [];
206
+ const hit = st.messages.find(m => m.role === 'tool' && ids.includes(m.id));
207
+ if (hit) hit.groupSummary = evt.summary;
208
+ break;
209
+ }
210
+ case 'run.todo.updated':
211
+ if (runMatches && Array.isArray(evt.todos)) st.todos = evt.todos;
212
+ break;
213
+ case 'run.context_usage':
214
+ if (runMatches) st.contextUsage = evt;
215
+ break;
216
+ case 'run.done':
217
+ case 'run.error':
218
+ case 'run.cancelled':
219
+ // 不立刻删:留 grace 期给"收尾瞬间重连"当权威快照(见文件头说明)
220
+ if (runMatches) {
221
+ st.endedAt = Date.now();
222
+ if (typeof evt.seq === 'number' && evt.seq > st.seq) st.seq = evt.seq;
223
+ }
224
+ return;
225
+ case 'run.query.end':
226
+ liveTurns.delete(sid);
227
+ return;
228
+ default:
229
+ break;
230
+ }
231
+
232
+ // 所有本 session 事件都推进快照游标(包括未折叠类型)——
233
+ // 重连订阅从 seq 起,不折叠的事件类型(queue depth 等)丢了也无碍(幂等 UI 状态)
234
+ if (typeof evt.seq === 'number' && evt.seq > st.seq) st.seq = evt.seq;
235
+ }
236
+
237
+ function appendText(st, role, text, parentToolUseId = null) {
238
+ if (!text) return;
239
+ const last = st.messages[st.messages.length - 1];
240
+ // 子代理时间轴:不同 parent 的流不互吸(跟前端 lib/chat-stream.js 同构)
241
+ if (last && last.role === role && (last.parentToolUseId || null) === (parentToolUseId || null)) {
242
+ last.content = (last.content || '') + text;
243
+ return;
244
+ }
245
+ st.messages.push({
246
+ id: `${st.runId || 'live'}:m${st._msgCounter++}`,
247
+ ...(parentToolUseId ? { parentToolUseId } : {}),
248
+ role,
249
+ content: text,
250
+ runId: st.runId,
251
+ });
252
+ }