@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,92 +1,92 @@
1
- /**
2
- * server/lib/reference-assets.js — 参考素材清单(2026-08-18)
3
- *
4
- * `assets/references/**` 里的东西:web-search 下载的参考图、`browser_capture` 从
5
- * 参照站带回来的截图与调色板/字体/结构 json。
6
- *
7
- * ## 它们此前完全看不见,原因不是"kinds 不认图片"
8
- *
9
- * 图片有自己的 file-kind(`kinds/file-kinds.js`,IMAGE_EXTS 六种齐全)。真正的断点
10
- * 是**扫描根**:`api/assets.js` 的 `/assets` 端点只 readdir 顶层文件,而产物清单那遍
11
- * `scanDir` 用的是默认 `depth = 0`(`depth <= 0` 就不进子目录)——
12
- * `assets/generated` 和 `assets/notes` 能上墙只因为它们被**单独列了一行**,
13
- * `assets/references/` 没被列。所以修法是加一个扫描口,不是改形态注册表。
14
- *
15
- * ## 走抽屉不走画布(用户 2026-08-18 拍板)
16
- *
17
- * 每逛一站甩十几张卡到画布上是噪音,而 web-search 下载的参考图还会成倍冒出来
18
- * (它按 `count*2` 下载但只上报 `count` 张,磁盘上本来就有 agent 不知道的孤儿)。
19
- * 收进「参考素材」抽屉杂波小得多。
20
- */
21
-
22
- import path from 'node:path';
23
- import fs from 'node:fs/promises';
24
-
25
- /**
26
- * 最多下探几层:`references/`(0) → `web/`(1) → `<站名>/`(2) → 文件。
27
- *
28
- * ⭐ 2026-08-18 下午 +1:`browser_capture` 改成**一站一文件夹**(按站点产物那条
29
- * 范式:目录 = 单位)。深度不够的话整棵采集树在清单里凭空消失 —— 而"扫描根/深度
30
- * 不够"正是当初参考素材完全看不见的真因(见文件头),同一个坑别踩第二次。
31
- */
32
- const MAX_DEPTH = 3;
33
-
34
- /** 这份素材属于哪个参照站(`assets/references/web/<站名>/…` 的那一段) */
35
- function siteOf(rel) {
36
- const m = /^assets\/references\/web\/([^/]+)\//.exec(rel);
37
- return m ? m[1] : null;
38
- }
39
-
40
- /**
41
- * @param {string} assetsDir `<工作区>/assets` 的绝对路径
42
- * @returns {Promise<Array<{rel,name,size,mtime,source?,lookingFor?,capturedAt?}>>}
43
- * 按 mtime 倒序。读不到就返回空数组 —— 素材列不出来不该让整份 /assets 响应失败。
44
- */
45
- export async function listReferences(assetsDir) {
46
- const out = [];
47
-
48
- const walk = async (dir, relPrefix, depth) => {
49
- let ents;
50
- try { ents = await fs.readdir(dir, { withFileTypes: true }); } catch { return; }
51
- for (const e of ents) {
52
- if (e.name === '.meta') continue; // sidecar 目录本身不列
53
- if (e.isDirectory()) {
54
- if (depth > 0) await walk(path.join(dir, e.name), `${relPrefix}/${e.name}`, depth - 1);
55
- continue;
56
- }
57
- if (!e.isFile() || e.name.startsWith('.')) continue;
58
- let st;
59
- try { st = await fs.stat(path.join(dir, e.name)); } catch { continue; }
60
-
61
- // 出处:照 generate-image 的 `.meta/<主干>.json` 约定读(不另发明一套)。
62
- // 没有出处很正常 —— web-search 下载的那些本来就没写 sidecar。
63
- let meta = null;
64
- try {
65
- const stem = e.name.replace(/\.[^.]+$/, '');
66
- meta = JSON.parse(await fs.readFile(path.join(dir, '.meta', `${stem}.json`), 'utf8'));
67
- } catch { /* 无出处 */ }
68
-
69
- const rel = `${relPrefix}/${e.name}`;
70
- out.push({
71
- rel,
72
- name: e.name,
73
- site: siteOf(rel),
74
- // 类别写在文件名里(`.screenshot.webp` / `.palette.json` / `.css` …),
75
- // sidecar 里也记着一份 —— 文件名优先,它是用户和 agent 直接看到的那个
76
- category: /\.(screenshot)\.\w+$/.test(e.name) ? 'screenshot'
77
- : (/\.(palette|fonts|skeleton)\.json$/.exec(e.name)?.[1] || (e.name.endsWith('.css') ? 'css' : null)),
78
- size: st.size,
79
- mtime: st.mtime.toISOString(),
80
- ...(meta ? {
81
- source: meta.sourceUrl || null,
82
- lookingFor: meta.lookingFor || null,
83
- capturedAt: meta.capturedAt || null,
84
- } : {}),
85
- });
86
- }
87
- };
88
-
89
- await walk(path.join(assetsDir, 'references'), 'assets/references', MAX_DEPTH);
90
- out.sort((a, b) => (a.mtime < b.mtime ? 1 : -1));
91
- return out;
92
- }
1
+ /**
2
+ * server/lib/reference-assets.js — 参考素材清单(2026-08-18)
3
+ *
4
+ * `assets/references/**` 里的东西:web-search 下载的参考图、`browser_capture` 从
5
+ * 参照站带回来的截图与调色板/字体/结构 json。
6
+ *
7
+ * ## 它们此前完全看不见,原因不是"kinds 不认图片"
8
+ *
9
+ * 图片有自己的 file-kind(`kinds/file-kinds.js`,IMAGE_EXTS 六种齐全)。真正的断点
10
+ * 是**扫描根**:`api/assets.js` 的 `/assets` 端点只 readdir 顶层文件,而产物清单那遍
11
+ * `scanDir` 用的是默认 `depth = 0`(`depth <= 0` 就不进子目录)——
12
+ * `assets/generated` 和 `assets/notes` 能上墙只因为它们被**单独列了一行**,
13
+ * `assets/references/` 没被列。所以修法是加一个扫描口,不是改形态注册表。
14
+ *
15
+ * ## 走抽屉不走画布(用户 2026-08-18 拍板)
16
+ *
17
+ * 每逛一站甩十几张卡到画布上是噪音,而 web-search 下载的参考图还会成倍冒出来
18
+ * (它按 `count*2` 下载但只上报 `count` 张,磁盘上本来就有 agent 不知道的孤儿)。
19
+ * 收进「参考素材」抽屉杂波小得多。
20
+ */
21
+
22
+ import path from 'node:path';
23
+ import fs from 'node:fs/promises';
24
+
25
+ /**
26
+ * 最多下探几层:`references/`(0) → `web/`(1) → `<站名>/`(2) → 文件。
27
+ *
28
+ * ⭐ 2026-08-18 下午 +1:`browser_capture` 改成**一站一文件夹**(按站点产物那条
29
+ * 范式:目录 = 单位)。深度不够的话整棵采集树在清单里凭空消失 —— 而"扫描根/深度
30
+ * 不够"正是当初参考素材完全看不见的真因(见文件头),同一个坑别踩第二次。
31
+ */
32
+ const MAX_DEPTH = 3;
33
+
34
+ /** 这份素材属于哪个参照站(`assets/references/web/<站名>/…` 的那一段) */
35
+ function siteOf(rel) {
36
+ const m = /^assets\/references\/web\/([^/]+)\//.exec(rel);
37
+ return m ? m[1] : null;
38
+ }
39
+
40
+ /**
41
+ * @param {string} assetsDir `<工作区>/assets` 的绝对路径
42
+ * @returns {Promise<Array<{rel,name,size,mtime,source?,lookingFor?,capturedAt?}>>}
43
+ * 按 mtime 倒序。读不到就返回空数组 —— 素材列不出来不该让整份 /assets 响应失败。
44
+ */
45
+ export async function listReferences(assetsDir) {
46
+ const out = [];
47
+
48
+ const walk = async (dir, relPrefix, depth) => {
49
+ let ents;
50
+ try { ents = await fs.readdir(dir, { withFileTypes: true }); } catch { return; }
51
+ for (const e of ents) {
52
+ if (e.name === '.meta') continue; // sidecar 目录本身不列
53
+ if (e.isDirectory()) {
54
+ if (depth > 0) await walk(path.join(dir, e.name), `${relPrefix}/${e.name}`, depth - 1);
55
+ continue;
56
+ }
57
+ if (!e.isFile() || e.name.startsWith('.')) continue;
58
+ let st;
59
+ try { st = await fs.stat(path.join(dir, e.name)); } catch { continue; }
60
+
61
+ // 出处:照 generate-image 的 `.meta/<主干>.json` 约定读(不另发明一套)。
62
+ // 没有出处很正常 —— web-search 下载的那些本来就没写 sidecar。
63
+ let meta = null;
64
+ try {
65
+ const stem = e.name.replace(/\.[^.]+$/, '');
66
+ meta = JSON.parse(await fs.readFile(path.join(dir, '.meta', `${stem}.json`), 'utf8'));
67
+ } catch { /* 无出处 */ }
68
+
69
+ const rel = `${relPrefix}/${e.name}`;
70
+ out.push({
71
+ rel,
72
+ name: e.name,
73
+ site: siteOf(rel),
74
+ // 类别写在文件名里(`.screenshot.webp` / `.palette.json` / `.css` …),
75
+ // sidecar 里也记着一份 —— 文件名优先,它是用户和 agent 直接看到的那个
76
+ category: /\.(screenshot)\.\w+$/.test(e.name) ? 'screenshot'
77
+ : (/\.(palette|fonts|skeleton)\.json$/.exec(e.name)?.[1] || (e.name.endsWith('.css') ? 'css' : null)),
78
+ size: st.size,
79
+ mtime: st.mtime.toISOString(),
80
+ ...(meta ? {
81
+ source: meta.sourceUrl || null,
82
+ lookingFor: meta.lookingFor || null,
83
+ capturedAt: meta.capturedAt || null,
84
+ } : {}),
85
+ });
86
+ }
87
+ };
88
+
89
+ await walk(path.join(assetsDir, 'references'), 'assets/references', MAX_DEPTH);
90
+ out.sort((a, b) => (a.mtime < b.mtime ? 1 : -1));
91
+ return out;
92
+ }
@@ -1,135 +1,135 @@
1
- /**
2
- * lib/region-shot.js —— 把用户在预览上圈的那一块截下来。
3
- *
4
- * 为什么在服务端截而不是前端:产物是 iframe 里的**真实网页**,前端要把它变成
5
- * 位图只能靠 html2canvas 那类重写渲染器的库 —— 那玩意儿在有 3D、滤镜、
6
- * 自定义字体、伪元素的页面上出来的东西跟屏幕上不是一回事,而这张图恰恰是要
7
- * 给 agent 当"眼见为实"的证据用的。chromium 真跑一遍才是它自己的样子,
8
- * 而且这台机器上封面和 screenshot_canvas 早就是这么干的(lib/cover.js)。
9
- *
10
- * 取景:以圈为中心带一圈余量。只截圈内的话 agent 拿到一块没有上下文的碎片,
11
- * 分不清"这是页面哪儿";余量给到它周围一点参照。
12
- */
13
-
14
- import path from 'node:path';
15
- import { openArtifactPage, launchPerceptionBrowser, degradedNote } from '../engine/mcp/tools/helpers/perception-page.js';
16
- import fs from 'node:fs/promises';
17
-
18
- /** 圈外多留这么多像素当上下文 */
19
- export const PADDING = 32;
20
-
21
- /** 出图长边上限,跟 screenshot 工具同口径(API 侧本来就会缩到这个规格) */
22
- export const OUT_LONG_EDGE = 1200;
23
-
24
- /** 截图串行:一台 1 核机器上同时开两个 chromium 会双双卡住 */
25
- let chain = Promise.resolve();
26
- function serialize(fn) {
27
- const next = chain.then(fn, fn);
28
- chain = next.catch(() => {});
29
- return next;
30
- }
31
-
32
- /**
33
- * @param {object} p
34
- * @param {string} p.absPath 产物页面的绝对路径
35
- * @param {{x,y,w,h}} p.region 页面坐标(CSS px,含滚动量)
36
- * @param {{width,height}} p.viewport 用户当时的取景宽高
37
- * @param {string} [p.projectId] 有它就走 http(与用户预览同源)
38
- * @param {string} [p.workspaceRoot] 同上,两个都给才走 http
39
- * @returns {Promise<{ buffer: Buffer, clip: {x,y,width,height}, degraded: string|null }>}
40
- */
41
- export async function renderRegionShot({ absPath, region, viewport, projectId, workspaceRoot }) {
42
- return serialize(async () => {
43
- let browser;
44
- try {
45
- browser = await launchPerceptionBrowser();
46
- // 圈选截的是**用户圈的那一块**,所以更要跟用户看到的同源:file:// 下
47
- // fetch 回来的内容不会出现在图里,agent 会以为用户圈了一块空白。
48
- const opened = await openArtifactPage(browser, {
49
- projectId, workspaceRoot, absPath, viewport, deviceScaleFactor: 1, timeout: 20_000,
50
- });
51
- const ctx = opened.context;
52
- const page = opened.page;
53
- await opened.goto();
54
- // 字体加载完再截(CJK 子集是 lazy 的,不显式 load 会截到 fallback 字形)
55
- await page.evaluate(async () => {
56
- document.body.offsetHeight;
57
- await Promise.all(Array.from(document.fonts).map(f => f.load().catch(() => {})));
58
- await document.fonts.ready;
59
- await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
60
- }).catch(() => { /* 页面自己抛错也照截 */ });
61
- // deck 的 standalone-fit 会在真实 viewport 下再缩一次,抹掉(同 cover.js)
62
- await page.addStyleTag({ content: `
63
- body { margin: 0 !important; padding: 0 !important; }
64
- body.__nd-fit-active > .__nd-deck-wrap > .__nd-page-frame {
65
- width: ${viewport.width}px !important; height: ${viewport.height}px !important;
66
- display: block !important; overflow: visible !important;
67
- }
68
- body.__nd-fit-active section[data-page] { transform: none !important; }
69
- ` }).catch(() => {});
70
- await page.waitForTimeout(500);
71
-
72
- const want = {
73
- x: Math.max(0, region.x - PADDING),
74
- y: Math.max(0, region.y - PADDING),
75
- w: region.w + PADDING * 2,
76
- h: region.h + PADDING * 2,
77
- };
78
-
79
- // 滚到目标位置再按视口坐标裁。**滚动会被文档末尾夹住**,所以要把真实
80
- // 落点读回来算裁剪框,不然页尾附近截出来的是错位的一块。
81
- const scroll = await page.evaluate(([x, y]) => {
82
- window.scrollTo(x, y);
83
- const e = document.scrollingElement || document.documentElement;
84
- return [e.scrollLeft, e.scrollTop];
85
- }, [want.x, want.y]);
86
-
87
- const clip = {
88
- x: Math.max(0, want.x - scroll[0]),
89
- y: Math.max(0, want.y - scroll[1]),
90
- width: Math.min(want.w, viewport.width),
91
- height: Math.min(want.h, viewport.height),
92
- };
93
- clip.width = Math.max(1, Math.min(clip.width, viewport.width - clip.x));
94
- clip.height = Math.max(1, Math.min(clip.height, viewport.height - clip.y));
95
-
96
- // ⚠️ 滚完要**等一拍**再截。上面那个 500ms 花在滚动**之前**,滚完立刻截 →
97
- // 视口里新进来的懒加载图(`loading=lazy` / IntersectionObserver)还没解码,
98
- // 图里是空的。而 `get_pending_changes` 把这张图当作"用户所见的 ground truth"
99
- // 交给 agent —— 空白处会被当成"用户圈了一块空白"。
100
- await page.evaluate(async () => {
101
- // 给 IO 回调一次机会,再等两帧确保解码后的图上了屏
102
- await new Promise(r => setTimeout(r, 250));
103
- await Promise.all([...document.images].filter(i => !i.complete)
104
- .map(i => new Promise((res) => { i.addEventListener('load', res, { once: true });
105
- i.addEventListener('error', res, { once: true }); setTimeout(res, 1200); })));
106
- await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
107
- }).catch(() => { /* 页面自己抛错也照截 */ });
108
-
109
- const png = await page.screenshot({ type: 'png', clip });
110
- await ctx.close();
111
-
112
- const { default: sharp } = await import('sharp');
113
- const buffer = await sharp(png)
114
- .resize({ width: OUT_LONG_EDGE, height: OUT_LONG_EDGE, fit: 'inside', withoutEnlargement: true })
115
- .webp({ quality: 82 })
116
- .toBuffer();
117
- // note 非空 = 没走成 http。圈选截图尤其在意这个:file:// 下 fetch 回来的
118
- // 内容不会出现在图里,而这张图是被当"ground truth"用的
119
- return { buffer, clip, degraded: degradedNote(opened) };
120
- } finally {
121
- await browser?.close().catch(() => {});
122
- }
123
- });
124
- }
125
-
126
- /** 圈选截图在会话工作区里的落点(agent 按这个相对路径找得到) */
127
- export const SHOT_DIR = 'region-shots';
128
-
129
- export async function saveRegionShot(sessionRoot, id, buffer) {
130
- const dir = path.join(sessionRoot, SHOT_DIR);
131
- await fs.mkdir(dir, { recursive: true });
132
- const rel = `${SHOT_DIR}/${id}.webp`;
133
- await fs.writeFile(path.join(sessionRoot, rel), buffer);
134
- return rel;
135
- }
1
+ /**
2
+ * lib/region-shot.js —— 把用户在预览上圈的那一块截下来。
3
+ *
4
+ * 为什么在服务端截而不是前端:产物是 iframe 里的**真实网页**,前端要把它变成
5
+ * 位图只能靠 html2canvas 那类重写渲染器的库 —— 那玩意儿在有 3D、滤镜、
6
+ * 自定义字体、伪元素的页面上出来的东西跟屏幕上不是一回事,而这张图恰恰是要
7
+ * 给 agent 当"眼见为实"的证据用的。chromium 真跑一遍才是它自己的样子,
8
+ * 而且这台机器上封面和 screenshot_canvas 早就是这么干的(lib/cover.js)。
9
+ *
10
+ * 取景:以圈为中心带一圈余量。只截圈内的话 agent 拿到一块没有上下文的碎片,
11
+ * 分不清"这是页面哪儿";余量给到它周围一点参照。
12
+ */
13
+
14
+ import path from 'node:path';
15
+ import { openArtifactPage, launchPerceptionBrowser, degradedNote } from '../engine/mcp/tools/helpers/perception-page.js';
16
+ import fs from 'node:fs/promises';
17
+
18
+ /** 圈外多留这么多像素当上下文 */
19
+ export const PADDING = 32;
20
+
21
+ /** 出图长边上限,跟 screenshot 工具同口径(API 侧本来就会缩到这个规格) */
22
+ export const OUT_LONG_EDGE = 1200;
23
+
24
+ /** 截图串行:一台 1 核机器上同时开两个 chromium 会双双卡住 */
25
+ let chain = Promise.resolve();
26
+ function serialize(fn) {
27
+ const next = chain.then(fn, fn);
28
+ chain = next.catch(() => {});
29
+ return next;
30
+ }
31
+
32
+ /**
33
+ * @param {object} p
34
+ * @param {string} p.absPath 产物页面的绝对路径
35
+ * @param {{x,y,w,h}} p.region 页面坐标(CSS px,含滚动量)
36
+ * @param {{width,height}} p.viewport 用户当时的取景宽高
37
+ * @param {string} [p.projectId] 有它就走 http(与用户预览同源)
38
+ * @param {string} [p.workspaceRoot] 同上,两个都给才走 http
39
+ * @returns {Promise<{ buffer: Buffer, clip: {x,y,width,height}, degraded: string|null }>}
40
+ */
41
+ export async function renderRegionShot({ absPath, region, viewport, projectId, workspaceRoot }) {
42
+ return serialize(async () => {
43
+ let browser;
44
+ try {
45
+ browser = await launchPerceptionBrowser();
46
+ // 圈选截的是**用户圈的那一块**,所以更要跟用户看到的同源:file:// 下
47
+ // fetch 回来的内容不会出现在图里,agent 会以为用户圈了一块空白。
48
+ const opened = await openArtifactPage(browser, {
49
+ projectId, workspaceRoot, absPath, viewport, deviceScaleFactor: 1, timeout: 20_000,
50
+ });
51
+ const ctx = opened.context;
52
+ const page = opened.page;
53
+ await opened.goto();
54
+ // 字体加载完再截(CJK 子集是 lazy 的,不显式 load 会截到 fallback 字形)
55
+ await page.evaluate(async () => {
56
+ document.body.offsetHeight;
57
+ await Promise.all(Array.from(document.fonts).map(f => f.load().catch(() => {})));
58
+ await document.fonts.ready;
59
+ await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
60
+ }).catch(() => { /* 页面自己抛错也照截 */ });
61
+ // deck 的 standalone-fit 会在真实 viewport 下再缩一次,抹掉(同 cover.js)
62
+ await page.addStyleTag({ content: `
63
+ body { margin: 0 !important; padding: 0 !important; }
64
+ body.__nd-fit-active > .__nd-deck-wrap > .__nd-page-frame {
65
+ width: ${viewport.width}px !important; height: ${viewport.height}px !important;
66
+ display: block !important; overflow: visible !important;
67
+ }
68
+ body.__nd-fit-active section[data-page] { transform: none !important; }
69
+ ` }).catch(() => {});
70
+ await page.waitForTimeout(500);
71
+
72
+ const want = {
73
+ x: Math.max(0, region.x - PADDING),
74
+ y: Math.max(0, region.y - PADDING),
75
+ w: region.w + PADDING * 2,
76
+ h: region.h + PADDING * 2,
77
+ };
78
+
79
+ // 滚到目标位置再按视口坐标裁。**滚动会被文档末尾夹住**,所以要把真实
80
+ // 落点读回来算裁剪框,不然页尾附近截出来的是错位的一块。
81
+ const scroll = await page.evaluate(([x, y]) => {
82
+ window.scrollTo(x, y);
83
+ const e = document.scrollingElement || document.documentElement;
84
+ return [e.scrollLeft, e.scrollTop];
85
+ }, [want.x, want.y]);
86
+
87
+ const clip = {
88
+ x: Math.max(0, want.x - scroll[0]),
89
+ y: Math.max(0, want.y - scroll[1]),
90
+ width: Math.min(want.w, viewport.width),
91
+ height: Math.min(want.h, viewport.height),
92
+ };
93
+ clip.width = Math.max(1, Math.min(clip.width, viewport.width - clip.x));
94
+ clip.height = Math.max(1, Math.min(clip.height, viewport.height - clip.y));
95
+
96
+ // ⚠️ 滚完要**等一拍**再截。上面那个 500ms 花在滚动**之前**,滚完立刻截 →
97
+ // 视口里新进来的懒加载图(`loading=lazy` / IntersectionObserver)还没解码,
98
+ // 图里是空的。而 `get_pending_changes` 把这张图当作"用户所见的 ground truth"
99
+ // 交给 agent —— 空白处会被当成"用户圈了一块空白"。
100
+ await page.evaluate(async () => {
101
+ // 给 IO 回调一次机会,再等两帧确保解码后的图上了屏
102
+ await new Promise(r => setTimeout(r, 250));
103
+ await Promise.all([...document.images].filter(i => !i.complete)
104
+ .map(i => new Promise((res) => { i.addEventListener('load', res, { once: true });
105
+ i.addEventListener('error', res, { once: true }); setTimeout(res, 1200); })));
106
+ await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
107
+ }).catch(() => { /* 页面自己抛错也照截 */ });
108
+
109
+ const png = await page.screenshot({ type: 'png', clip });
110
+ await ctx.close();
111
+
112
+ const { default: sharp } = await import('sharp');
113
+ const buffer = await sharp(png)
114
+ .resize({ width: OUT_LONG_EDGE, height: OUT_LONG_EDGE, fit: 'inside', withoutEnlargement: true })
115
+ .webp({ quality: 82 })
116
+ .toBuffer();
117
+ // note 非空 = 没走成 http。圈选截图尤其在意这个:file:// 下 fetch 回来的
118
+ // 内容不会出现在图里,而这张图是被当"ground truth"用的
119
+ return { buffer, clip, degraded: degradedNote(opened) };
120
+ } finally {
121
+ await browser?.close().catch(() => {});
122
+ }
123
+ });
124
+ }
125
+
126
+ /** 圈选截图在会话工作区里的落点(agent 按这个相对路径找得到) */
127
+ export const SHOT_DIR = 'region-shots';
128
+
129
+ export async function saveRegionShot(sessionRoot, id, buffer) {
130
+ const dir = path.join(sessionRoot, SHOT_DIR);
131
+ await fs.mkdir(dir, { recursive: true });
132
+ const rel = `${SHOT_DIR}/${id}.webp`;
133
+ await fs.writeFile(path.join(sessionRoot, rel), buffer);
134
+ return rel;
135
+ }
@@ -1,73 +1,73 @@
1
- /**
2
- * lib/safe-path.js — 工作区内路径的越界检查。
3
- *
4
- * ⚠️ **词法检查不够。** `path.resolve` + `path.relative` 挡得住 `../..`,
5
- * 挡不住工作区里一个**指向外面的软链** —— 那条路径逐字看都在工作区内,
6
- * 解引用之后在 `/etc` 或者 `.env` 上。
7
- *
8
- * 这个洞在这个仓库里已经出现过两次形状相同的实例(导出收集器、docx 页图路由),
9
- * 第二次是照着第一次的**调用形状**抄的、没抄它的**正确性**。所以判据收成一份:
10
- * 谁要在工作区里按用户给的相对路径开文件,就 import 这里,别再各写一遍。
11
- *
12
- * 读和写的判据不一样,分两个函数:
13
- * - 读:目标必须存在,realpath 之后仍在工作区内
14
- * - 写:目标可能还不存在(正要创建),所以查**父目录**的 realpath;
15
- * 另外目标自己若已经是软链,一律拒 —— 顺着它写等于把别人的文件覆盖掉
16
- */
17
-
18
- import path from 'node:path';
19
- import { promises as fs } from 'node:fs';
20
-
21
- /** 词法层:拼出绝对路径并确认字面上没跑出去。跑出去返回 null */
22
- function lexical(rootAbs, rel) {
23
- const abs = path.resolve(rootAbs, String(rel || ''));
24
- const within = path.relative(rootAbs, abs);
25
- if (within.startsWith('..') || path.isAbsolute(within)) return null;
26
- return abs;
27
- }
28
-
29
- /**
30
- * 读用:解析一个工作区内的相对路径。
31
- * @returns {Promise<string|null>} 绝对路径;越界(含软链穿透)返回 null
32
- * 目标不存在**不算越界** —— 让调用方自己去 stat 报 404,这里只管边界
33
- */
34
- export async function safeResolveRead(workspaceRoot, rel) {
35
- const abs = lexical(path.resolve(workspaceRoot), rel);
36
- if (!abs) return null;
37
- try {
38
- const realRoot = await fs.realpath(workspaceRoot);
39
- const real = await fs.realpath(abs);
40
- const rw = path.relative(realRoot, real);
41
- if (rw.startsWith('..') || path.isAbsolute(rw)) return null;
42
- } catch (err) {
43
- // ENOENT:文件还没写出来,边界上没问题,留给调用方 stat
44
- if (err?.code !== 'ENOENT') return null;
45
- }
46
- return abs;
47
- }
48
-
49
- /**
50
- * 写用:解析一个工作区内的写入目标。
51
- * @returns {Promise<string|null>} 绝对路径;越界 / 目标是软链 返回 null
52
- */
53
- export async function safeResolveWrite(workspaceRoot, rel) {
54
- const abs = lexical(path.resolve(workspaceRoot), rel);
55
- if (!abs) return null;
56
- try {
57
- const realRoot = await fs.realpath(workspaceRoot);
58
- // 目标本身若已存在且是软链 → 拒。顺着它写会覆盖软链指向的那个文件,
59
- // 而那个文件可以在工作区外(lstat 不解引用,这是判据的关键)
60
- try {
61
- if ((await fs.lstat(abs)).isSymbolicLink()) return null;
62
- } catch (err) {
63
- if (err?.code !== 'ENOENT') return null; // 目标还不存在是正常的
64
- }
65
- // 父目录必须真的在工作区内(父目录是软链的话,新建的文件会落到外面)
66
- const realParent = await fs.realpath(path.dirname(abs));
67
- const rw = path.relative(realRoot, realParent);
68
- if (rw.startsWith('..') || path.isAbsolute(rw)) return null;
69
- } catch {
70
- return null; // 父目录都解析不了,不给写
71
- }
72
- return abs;
73
- }
1
+ /**
2
+ * lib/safe-path.js — 工作区内路径的越界检查。
3
+ *
4
+ * ⚠️ **词法检查不够。** `path.resolve` + `path.relative` 挡得住 `../..`,
5
+ * 挡不住工作区里一个**指向外面的软链** —— 那条路径逐字看都在工作区内,
6
+ * 解引用之后在 `/etc` 或者 `.env` 上。
7
+ *
8
+ * 这个洞在这个仓库里已经出现过两次形状相同的实例(导出收集器、docx 页图路由),
9
+ * 第二次是照着第一次的**调用形状**抄的、没抄它的**正确性**。所以判据收成一份:
10
+ * 谁要在工作区里按用户给的相对路径开文件,就 import 这里,别再各写一遍。
11
+ *
12
+ * 读和写的判据不一样,分两个函数:
13
+ * - 读:目标必须存在,realpath 之后仍在工作区内
14
+ * - 写:目标可能还不存在(正要创建),所以查**父目录**的 realpath;
15
+ * 另外目标自己若已经是软链,一律拒 —— 顺着它写等于把别人的文件覆盖掉
16
+ */
17
+
18
+ import path from 'node:path';
19
+ import { promises as fs } from 'node:fs';
20
+
21
+ /** 词法层:拼出绝对路径并确认字面上没跑出去。跑出去返回 null */
22
+ function lexical(rootAbs, rel) {
23
+ const abs = path.resolve(rootAbs, String(rel || ''));
24
+ const within = path.relative(rootAbs, abs);
25
+ if (within.startsWith('..') || path.isAbsolute(within)) return null;
26
+ return abs;
27
+ }
28
+
29
+ /**
30
+ * 读用:解析一个工作区内的相对路径。
31
+ * @returns {Promise<string|null>} 绝对路径;越界(含软链穿透)返回 null
32
+ * 目标不存在**不算越界** —— 让调用方自己去 stat 报 404,这里只管边界
33
+ */
34
+ export async function safeResolveRead(workspaceRoot, rel) {
35
+ const abs = lexical(path.resolve(workspaceRoot), rel);
36
+ if (!abs) return null;
37
+ try {
38
+ const realRoot = await fs.realpath(workspaceRoot);
39
+ const real = await fs.realpath(abs);
40
+ const rw = path.relative(realRoot, real);
41
+ if (rw.startsWith('..') || path.isAbsolute(rw)) return null;
42
+ } catch (err) {
43
+ // ENOENT:文件还没写出来,边界上没问题,留给调用方 stat
44
+ if (err?.code !== 'ENOENT') return null;
45
+ }
46
+ return abs;
47
+ }
48
+
49
+ /**
50
+ * 写用:解析一个工作区内的写入目标。
51
+ * @returns {Promise<string|null>} 绝对路径;越界 / 目标是软链 返回 null
52
+ */
53
+ export async function safeResolveWrite(workspaceRoot, rel) {
54
+ const abs = lexical(path.resolve(workspaceRoot), rel);
55
+ if (!abs) return null;
56
+ try {
57
+ const realRoot = await fs.realpath(workspaceRoot);
58
+ // 目标本身若已存在且是软链 → 拒。顺着它写会覆盖软链指向的那个文件,
59
+ // 而那个文件可以在工作区外(lstat 不解引用,这是判据的关键)
60
+ try {
61
+ if ((await fs.lstat(abs)).isSymbolicLink()) return null;
62
+ } catch (err) {
63
+ if (err?.code !== 'ENOENT') return null; // 目标还不存在是正常的
64
+ }
65
+ // 父目录必须真的在工作区内(父目录是软链的话,新建的文件会落到外面)
66
+ const realParent = await fs.realpath(path.dirname(abs));
67
+ const rw = path.relative(realRoot, realParent);
68
+ if (rw.startsWith('..') || path.isAbsolute(rw)) return null;
69
+ } catch {
70
+ return null; // 父目录都解析不了,不给写
71
+ }
72
+ return abs;
73
+ }