@xiaobuyu/nodesign 0.0.1 → 0.0.2

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 (282) 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 +22 -22
  9. package/server/api/assets/notes.js +144 -144
  10. package/server/api/assets.js +896 -896
  11. package/server/api/board.js +57 -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 +464 -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 +215 -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 +336 -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 +1024 -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 +339 -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 +155 -155
  118. package/server/engine/mcp/tools/crystallize-skill.js +168 -168
  119. package/server/engine/mcp/tools/explain-style.js +237 -237
  120. package/server/engine/mcp/tools/export-handoff.js +133 -133
  121. package/server/engine/mcp/tools/expose-tweaks.js +149 -149
  122. package/server/engine/mcp/tools/generate-image.js +842 -842
  123. package/server/engine/mcp/tools/get-computed-styles.js +164 -164
  124. package/server/engine/mcp/tools/get-pending-changes.js +205 -205
  125. package/server/engine/mcp/tools/h3box-ssh.js +84 -84
  126. package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
  127. package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
  128. package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
  129. package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
  130. package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
  131. package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
  132. package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
  133. package/server/engine/mcp/tools/highlight.js +68 -68
  134. package/server/engine/mcp/tools/list-pages.js +222 -222
  135. package/server/engine/mcp/tools/lookup-tags.js +59 -59
  136. package/server/engine/mcp/tools/navigate-to-page.js +60 -60
  137. package/server/engine/mcp/tools/organize-board.js +64 -64
  138. package/server/engine/mcp/tools/paint-still.js +434 -434
  139. package/server/engine/mcp/tools/pin-to-board.js +130 -130
  140. package/server/engine/mcp/tools/preview-deck.js +130 -130
  141. package/server/engine/mcp/tools/profile-scroll.js +305 -305
  142. package/server/engine/mcp/tools/publish-site.js +102 -102
  143. package/server/engine/mcp/tools/query-elements.js +195 -195
  144. package/server/engine/mcp/tools/read-board.js +103 -103
  145. package/server/engine/mcp/tools/read-document.js +93 -93
  146. package/server/engine/mcp/tools/read-page.js +264 -264
  147. package/server/engine/mcp/tools/read-tavern-json.js +143 -143
  148. package/server/engine/mcp/tools/record-decision.js +140 -140
  149. package/server/engine/mcp/tools/relate-on-board.js +110 -110
  150. package/server/engine/mcp/tools/report-issue.js +115 -115
  151. package/server/engine/mcp/tools/roll-film.js +266 -266
  152. package/server/engine/mcp/tools/screenshot-docx.js +138 -138
  153. package/server/engine/mcp/tools/screenshot-url.js +195 -195
  154. package/server/engine/mcp/tools/screenshot.js +583 -583
  155. package/server/engine/mcp/tools/tier-gate.js +96 -96
  156. package/server/engine/mcp/tools/trace-motion.js +215 -215
  157. package/server/engine/mcp/tools/web-search.js +548 -548
  158. package/server/engine/motion/inventory.js +349 -349
  159. package/server/engine/perception/session.js +235 -235
  160. package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
  161. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -192
  162. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
  163. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
  164. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
  165. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
  166. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
  167. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
  168. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
  169. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -162
  170. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
  171. package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
  172. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -228
  173. 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
  174. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
  175. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -323
  176. package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
  177. package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
  178. package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
  179. package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
  180. package/server/engine/runs/active-runs.js +698 -698
  181. package/server/engine/runs/live-turn.js +252 -252
  182. package/server/engine/runs/store.js +364 -364
  183. package/server/engine/runs/turn-relay.js +217 -217
  184. package/server/engine/runtime/workspace.js +164 -164
  185. package/server/lib/artifact-file-path.js +80 -80
  186. package/server/lib/artifact-target.js +284 -284
  187. package/server/lib/asset-refs.js +239 -239
  188. package/server/lib/async-queue.js +104 -104
  189. package/server/lib/auto-relations.js +130 -130
  190. package/server/lib/binding-types.js +60 -60
  191. package/server/lib/board-kind-sizes.js +62 -62
  192. package/server/lib/board-relations.js +133 -133
  193. package/server/lib/browse-proxy.js +180 -180
  194. package/server/lib/canvas-id.js +36 -36
  195. package/server/lib/cover.js +227 -227
  196. package/server/lib/danbooru-tags.js +291 -291
  197. package/server/lib/doc-extract.js +156 -156
  198. package/server/lib/docx/build-from-source.js +260 -260
  199. package/server/lib/docx/build.js +490 -490
  200. package/server/lib/docx/dump-styles.js +287 -287
  201. package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
  202. package/server/lib/docx/merge-runs.js +129 -129
  203. package/server/lib/docx/numbering.js +218 -218
  204. package/server/lib/docx/order.js +152 -152
  205. package/server/lib/docx/rawzip.js +172 -172
  206. package/server/lib/docx/render.js +92 -92
  207. package/server/lib/docx/text-lint.js +225 -225
  208. package/server/lib/docx/tokens.js +335 -335
  209. package/server/lib/docx/units.js +29 -29
  210. package/server/lib/docx/xml.js +291 -291
  211. package/server/lib/docx-pages.js +187 -187
  212. package/server/lib/export-collect.js +289 -289
  213. package/server/lib/export-package.js +210 -210
  214. package/server/lib/html-srcset.js +145 -145
  215. package/server/lib/image-variant.js +460 -460
  216. package/server/lib/ingress/forward-openai-chat.js +305 -305
  217. package/server/lib/ingress/openai-chat.js +491 -491
  218. package/server/lib/ingress/session-notice.js +66 -66
  219. package/server/lib/ingress/session-routes.js +68 -68
  220. package/server/lib/ingress/slot-probe.js +130 -130
  221. package/server/lib/ingress/upstream-billing.js +52 -52
  222. package/server/lib/ingress/upstream-fail-streak.js +72 -72
  223. package/server/lib/ingress/upstream-truncation.js +48 -48
  224. package/server/lib/issues-store.js +182 -182
  225. package/server/lib/kinds/deck.js +72 -72
  226. package/server/lib/kinds/docx.js +283 -283
  227. package/server/lib/kinds/file-kinds.js +113 -113
  228. package/server/lib/kinds/index.js +202 -202
  229. package/server/lib/kinds/site.js +161 -161
  230. package/server/lib/model-ingress.js +543 -543
  231. package/server/lib/moderation.js +255 -255
  232. package/server/lib/notice-store.js +103 -103
  233. package/server/lib/plugin-install.js +104 -104
  234. package/server/lib/plugin-validator.js +721 -721
  235. package/server/lib/publish-store.js +115 -115
  236. package/server/lib/quick-summary.js +52 -52
  237. package/server/lib/quota.js +270 -270
  238. package/server/lib/rate-window.js +29 -29
  239. package/server/lib/reference-assets.js +92 -92
  240. package/server/lib/region-shot.js +135 -135
  241. package/server/lib/safe-path.js +73 -73
  242. package/server/lib/sdk-session.js +33 -33
  243. package/server/lib/showcase-store.js +92 -92
  244. package/server/lib/site-publish.js +465 -465
  245. package/server/lib/ssrf-guard.js +432 -432
  246. package/server/lib/task-scan.js +158 -158
  247. package/server/lib/tavern-json.js +154 -154
  248. package/server/lib/video-variant.js +237 -237
  249. package/server/lib/workspace-path.js +33 -33
  250. package/server/ops/fix-sdk-musl.mjs +40 -40
  251. package/server/ops/install-macos-fonts.sh +114 -114
  252. package/server/ops/macos-fonts.conf +336 -336
  253. package/server/ops/sandbox-shim/bwrap +57 -57
  254. package/server/projects/assets-summary.js +119 -119
  255. package/server/projects/auto-name.js +53 -53
  256. package/server/projects/board-store.js +640 -640
  257. package/server/projects/move-entry.js +103 -103
  258. package/server/projects/store.js +270 -270
  259. package/server/projects/ui-config.js +54 -54
  260. package/server/projects/workspace-templates.js +53 -53
  261. package/server/projects/workspace.js +1025 -1025
  262. package/server/runtime/local-config.js +177 -177
  263. package/server/runtime/profile.js +72 -72
  264. package/server/services/rembg-service.py +334 -334
  265. package/server/shared/README.md +25 -25
  266. package/server/shared/deck.js +54 -54
  267. package/server/shared/time.js +49 -49
  268. package/server/style-pipeline/README.md +53 -53
  269. package/server/ws/broker.js +36 -36
  270. package/server/ws/browse-channel.js +177 -177
  271. package/server/ws/index.js +386 -386
  272. package/web/README.md +89 -89
  273. package/web/dist/assets/BrowserWindow-BYCgizHw.js +11 -0
  274. package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-pNydEM4L.js} +1 -1
  275. package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-PPgzh3Gq.js} +1 -1
  276. package/web/dist/assets/{SiteWindow-D6eS-2gM.js → SiteWindow-DM0LQPCO.js} +1 -1
  277. package/web/dist/assets/{index-Cbl3ATvp.js → index-BHTBJlpY.js} +32 -32
  278. package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-C0I13CbD.js} +1 -1
  279. package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-B1UQ8Hld.js} +1 -1
  280. package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-8lrgqmfv.js} +1 -1
  281. package/web/dist/index.html +17 -17
  282. package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
@@ -1,104 +1,104 @@
1
- /**
2
- * server/lib/async-queue.js — push/pull 解耦的异步队列
3
- *
4
- * 用途:SDK Query streamInput 模式下,runAgent 需要一个 AsyncIterable<SDKUserMessage>
5
- * 持续 yield user message 让 query 一直活着;前端 POST /turn 把 message push 进
6
- * 队列,generator 那头 pull 后 yield 给 SDK。
7
- *
8
- * 行为:
9
- * - push(item) 立即返回;waiter 等着就唤醒一个,否则进 items 数组
10
- * - next() 返回 Promise;items 有就立刻 resolve,否则 Promise 挂着等 push / close
11
- * - close() 终结流;所有 pending waiter 收到 { done: true };后续 push 抛错
12
- * - return() 自动调 close(让 for-await-of 的 break 能正确收尾)
13
- *
14
- * 不是通用线程安全 queue —— 单事件循环内用,order 保证 FIFO。
15
- *
16
- * @template T
17
- */
18
- export class AsyncQueue {
19
- constructor() {
20
- /** @type {T[]} */
21
- this.items = [];
22
- /** @type {Array<(r: IteratorResult<T>) => void>} */
23
- this.waiters = [];
24
- this.closed = false;
25
- }
26
-
27
- /**
28
- * 当前积压 item 数量(已 push 未 pull 的)。
29
- * 给 UI "已排队 N 条" 显示用。
30
- */
31
- get size() {
32
- return this.items.length;
33
- }
34
-
35
- /**
36
- * 是否还有 active waiter 在等(agent 已 pull 完积压,正等下一条)。
37
- * 给"agent idle"判定用。
38
- */
39
- get isPending() {
40
- return this.waiters.length > 0;
41
- }
42
-
43
- /**
44
- * 推一个 item 进队列。
45
- * @param {T} item
46
- * @throws {Error} 已 close 时抛
47
- */
48
- push(item) {
49
- if (this.closed) {
50
- throw new Error('AsyncQueue: push after close');
51
- }
52
- if (this.waiters.length > 0) {
53
- const w = this.waiters.shift();
54
- w({ value: item, done: false });
55
- return;
56
- }
57
- this.items.push(item);
58
- }
59
-
60
- /**
61
- * 终结流。pending waiter 全部收到 done,items 里残留的 item 仍可 pull
62
- * 直到清空,再之后 next() 返回 done。
63
- *
64
- * 幂等:多次调用 noop。
65
- */
66
- close() {
67
- if (this.closed) return;
68
- this.closed = true;
69
- while (this.waiters.length > 0) {
70
- const w = this.waiters.shift();
71
- w({ value: undefined, done: true });
72
- }
73
- }
74
-
75
- // ── AsyncIterator protocol ──
76
-
77
- [Symbol.asyncIterator]() {
78
- return this;
79
- }
80
-
81
- /**
82
- * @returns {Promise<IteratorResult<T>>}
83
- */
84
- next() {
85
- if (this.items.length > 0) {
86
- return Promise.resolve({ value: this.items.shift(), done: false });
87
- }
88
- if (this.closed) {
89
- return Promise.resolve({ value: undefined, done: true });
90
- }
91
- return new Promise((resolve) => {
92
- this.waiters.push(resolve);
93
- });
94
- }
95
-
96
- /**
97
- * for-await-of 提前 break / throw 时被 runtime 调;触发 close。
98
- * @returns {Promise<IteratorResult<T>>}
99
- */
100
- return() {
101
- this.close();
102
- return Promise.resolve({ value: undefined, done: true });
103
- }
104
- }
1
+ /**
2
+ * server/lib/async-queue.js — push/pull 解耦的异步队列
3
+ *
4
+ * 用途:SDK Query streamInput 模式下,runAgent 需要一个 AsyncIterable<SDKUserMessage>
5
+ * 持续 yield user message 让 query 一直活着;前端 POST /turn 把 message push 进
6
+ * 队列,generator 那头 pull 后 yield 给 SDK。
7
+ *
8
+ * 行为:
9
+ * - push(item) 立即返回;waiter 等着就唤醒一个,否则进 items 数组
10
+ * - next() 返回 Promise;items 有就立刻 resolve,否则 Promise 挂着等 push / close
11
+ * - close() 终结流;所有 pending waiter 收到 { done: true };后续 push 抛错
12
+ * - return() 自动调 close(让 for-await-of 的 break 能正确收尾)
13
+ *
14
+ * 不是通用线程安全 queue —— 单事件循环内用,order 保证 FIFO。
15
+ *
16
+ * @template T
17
+ */
18
+ export class AsyncQueue {
19
+ constructor() {
20
+ /** @type {T[]} */
21
+ this.items = [];
22
+ /** @type {Array<(r: IteratorResult<T>) => void>} */
23
+ this.waiters = [];
24
+ this.closed = false;
25
+ }
26
+
27
+ /**
28
+ * 当前积压 item 数量(已 push 未 pull 的)。
29
+ * 给 UI "已排队 N 条" 显示用。
30
+ */
31
+ get size() {
32
+ return this.items.length;
33
+ }
34
+
35
+ /**
36
+ * 是否还有 active waiter 在等(agent 已 pull 完积压,正等下一条)。
37
+ * 给"agent idle"判定用。
38
+ */
39
+ get isPending() {
40
+ return this.waiters.length > 0;
41
+ }
42
+
43
+ /**
44
+ * 推一个 item 进队列。
45
+ * @param {T} item
46
+ * @throws {Error} 已 close 时抛
47
+ */
48
+ push(item) {
49
+ if (this.closed) {
50
+ throw new Error('AsyncQueue: push after close');
51
+ }
52
+ if (this.waiters.length > 0) {
53
+ const w = this.waiters.shift();
54
+ w({ value: item, done: false });
55
+ return;
56
+ }
57
+ this.items.push(item);
58
+ }
59
+
60
+ /**
61
+ * 终结流。pending waiter 全部收到 done,items 里残留的 item 仍可 pull
62
+ * 直到清空,再之后 next() 返回 done。
63
+ *
64
+ * 幂等:多次调用 noop。
65
+ */
66
+ close() {
67
+ if (this.closed) return;
68
+ this.closed = true;
69
+ while (this.waiters.length > 0) {
70
+ const w = this.waiters.shift();
71
+ w({ value: undefined, done: true });
72
+ }
73
+ }
74
+
75
+ // ── AsyncIterator protocol ──
76
+
77
+ [Symbol.asyncIterator]() {
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * @returns {Promise<IteratorResult<T>>}
83
+ */
84
+ next() {
85
+ if (this.items.length > 0) {
86
+ return Promise.resolve({ value: this.items.shift(), done: false });
87
+ }
88
+ if (this.closed) {
89
+ return Promise.resolve({ value: undefined, done: true });
90
+ }
91
+ return new Promise((resolve) => {
92
+ this.waiters.push(resolve);
93
+ });
94
+ }
95
+
96
+ /**
97
+ * for-await-of 提前 break / throw 时被 runtime 调;触发 close。
98
+ * @returns {Promise<IteratorResult<T>>}
99
+ */
100
+ return() {
101
+ this.close();
102
+ return Promise.resolve({ value: undefined, done: true });
103
+ }
104
+ }
@@ -1,130 +1,130 @@
1
- /**
2
- * server/lib/auto-relations.js — 自动落线层(2026-08-14,北极星路线 4)
3
- *
4
- * 有一类关系**不需要人或 agent 判断,机器可证**:html 里 `<img src>` 真实
5
- * 指向工作区里的一张图,就是铁打的「取材」。这层把它们落成 `by:'auto'` 的
6
- * ref 边 —— 关系密度从「靠自觉」变成「有底仓」,用户和 agent 的手画线在
7
- * 底仓之上做增量。
8
- *
9
- * 三条纪律:
10
- * 1. **只动自家的边**。自动边 id 恒为 `b:auto:ref:<hash>`(确定性 → 幂等),
11
- * 对账时新增缺的、删掉失效的,**其余 id 一根手指都不碰** —— 用户/agent
12
- * 画的线不归这层管。
13
- * 2. **引用得真实存在**才落线。悬空 src 是裂图体检的事,不是关系。
14
- * 3. **不画**。BindingLayer 对 auto+ref 不渲染 —— 一个站引三十张图,画出来
15
- * 是蜘蛛网不是版面。它们的消费方是 agent 上下文(digest 收拢成一行)和
16
- * 将来的主角判断(被 ref 指着的 = 素材,不该当 hero)。
17
- *
18
- * 触发:挂在 GET /artifacts 尾巴上 fire-and-forget(那里 manifests 现成),
19
- * 每项目 30s 节流。产物身份的拼法与 BoardCanvas 的物件 id 同构 ——
20
- * `deck:<file>` / `site:<root||''>` / 单页 `site:<entryRel>`。
21
- */
22
-
23
- import fs from 'node:fs/promises';
24
- import path from 'node:path';
25
- import crypto from 'node:crypto';
26
- import { readBoard, patchBoard } from '../projects/board-store.js';
27
-
28
- const MAX_HTML_BYTES = 512 * 1024;
29
- const AUTO_REF_PREFIX = 'b:auto:ref:';
30
- /** 每个项目对账节流(ms)—— /artifacts 是热接口,别把它变成扫盘器 */
31
- const THROTTLE_MS = 30_000;
32
- const lastRun = new Map();
33
-
34
- const hashId = (from, to) =>
35
- AUTO_REF_PREFIX + crypto.createHash('sha1').update(`${from}|${to}`).digest('hex').slice(0, 12);
36
-
37
- /**
38
- * 从一段 html 里抠出本地资源引用(src/href/poster + 内联 url())。
39
- * 返回**相对 html 所在目录**的原始引用串(未归一)。
40
- */
41
- export function extractRefs(html) {
42
- const out = new Set();
43
- const push = (raw) => {
44
- if (!raw) return;
45
- const v = raw.trim().replace(/^['"]|['"]$/g, '').split(/[?#]/)[0];
46
- if (!v || /^(https?:|data:|blob:|mailto:|javascript:|\/\/)/i.test(v)) return;
47
- out.add(v);
48
- };
49
- for (const m of html.matchAll(/<(?:img|video|audio|source|embed)\b[^>]*?\s(?:src|poster)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi)) push(m[1]);
50
- for (const m of html.matchAll(/url\(\s*([^)]+?)\s*\)/gi)) push(m[1]);
51
- return [...out];
52
- }
53
-
54
- /** 引用串 → 工作区相对路径(越界/绝对路径丢弃)。baseDir 是 html 所在目录(工作区相对)。 */
55
- export function resolveRef(raw, baseDir) {
56
- try {
57
- const decoded = decodeURIComponent(raw);
58
- if (decoded.startsWith('/')) return null; // 站内绝对路径按约定不出现,出现也不猜
59
- const joined = path.posix.normalize(path.posix.join(baseDir || '.', decoded));
60
- if (joined.startsWith('..') || path.posix.isAbsolute(joined)) return null;
61
- return joined === '.' ? null : joined;
62
- } catch { return null; }
63
- }
64
-
65
- /**
66
- * 对账一个项目的自动 ref 边。
67
- *
68
- * @param {string} pid
69
- * @param {string} workspaceRoot 绝对路径
70
- * @param {Array} tasks /artifacts 已算好的 tasks(id/artifacts 同构前端)
71
- */
72
- export async function reconcileAutoRefs(pid, workspaceRoot, tasks) {
73
- // 期望集:{ bid: { from, to } }
74
- const desired = new Map();
75
- for (const t of tasks || []) {
76
- for (const a of t.artifacts || []) {
77
- // 产物身份(与 BoardCanvas 的物件 id 同一拼法)
78
- // ⚠️ 卡 id 按**形态**派生,别用「不是 deck 就是 site」的二分(2026-08-18 修)。
79
- // docx 上线之后那个三元把 .docx 产物算成了 `site:` 前缀 —— id 对不上,
80
- // 它的自动关系边落在一个不存在的端点上。规则跟前端 cardIdOf
81
- // (web/src/lib/board-kinds.js)必须一致,两边岔开就是"同一件东西两份实现"。
82
- const fromId = a.kind === 'site'
83
- ? `site:${a.single ? a.entryRel : (a.root || t.id)}`
84
- : `${a.kind || 'deck'}:${a.file}`;
85
- // 这件产物的 html 们(deck 一份;站点 = 各页,路径相对站根)
86
- const pages = a.kind === 'deck'
87
- ? [a.file]
88
- : (a.single ? [a.entryRel] : (a.pages || []).map(p => (a.root ? `${a.root}/${p}` : p)));
89
- for (const rel of pages) {
90
- if (!rel || !/\.html?$/i.test(rel)) continue;
91
- let html;
92
- try {
93
- const abs = path.join(workspaceRoot, rel);
94
- const st = await fs.stat(abs);
95
- if (st.size > MAX_HTML_BYTES) continue;
96
- html = await fs.readFile(abs, 'utf8');
97
- } catch { continue; }
98
- const baseDir = path.posix.dirname(rel.replace(/\\/g, '/'));
99
- for (const raw of extractRefs(html)) {
100
- const to = resolveRef(raw, baseDir === '.' ? '' : baseDir);
101
- if (!to || to === rel) continue;
102
- try { await fs.access(path.join(workspaceRoot, to)); } catch { continue; } // 悬空不落线
103
- desired.set(hashId(fromId, to), { from: fromId, to });
104
- }
105
- }
106
- }
107
- }
108
-
109
- // 现状 → diff。只碰 b:auto:ref:* 的条目。
110
- const board = await readBoard(pid);
111
- const patch = {};
112
- for (const [bid, b] of Object.entries(board.bindings || {})) {
113
- if (bid.startsWith(AUTO_REF_PREFIX) && !desired.has(bid)) patch[bid] = null;
114
- }
115
- for (const [bid, { from, to }] of desired) {
116
- const cur = board.bindings?.[bid];
117
- if (cur && cur.from === from && cur.to === to) continue;
118
- patch[bid] = { type: 'ref', from, to, by: 'auto' };
119
- }
120
- if (Object.keys(patch).length) await patchBoard(pid, { bindings: patch });
121
- return { desired: desired.size, changed: Object.keys(patch).length };
122
- }
123
-
124
- /** 节流版:/artifacts 尾巴上 fire-and-forget 用 */
125
- export function reconcileAutoRefsThrottled(pid, workspaceRoot, tasks) {
126
- const now = Date.now();
127
- if (now - (lastRun.get(pid) || 0) < THROTTLE_MS) return;
128
- lastRun.set(pid, now);
129
- reconcileAutoRefs(pid, workspaceRoot, tasks).catch(() => { /* fail-soft:对账绝不拖累清单接口 */ });
130
- }
1
+ /**
2
+ * server/lib/auto-relations.js — 自动落线层(2026-08-14,北极星路线 4)
3
+ *
4
+ * 有一类关系**不需要人或 agent 判断,机器可证**:html 里 `<img src>` 真实
5
+ * 指向工作区里的一张图,就是铁打的「取材」。这层把它们落成 `by:'auto'` 的
6
+ * ref 边 —— 关系密度从「靠自觉」变成「有底仓」,用户和 agent 的手画线在
7
+ * 底仓之上做增量。
8
+ *
9
+ * 三条纪律:
10
+ * 1. **只动自家的边**。自动边 id 恒为 `b:auto:ref:<hash>`(确定性 → 幂等),
11
+ * 对账时新增缺的、删掉失效的,**其余 id 一根手指都不碰** —— 用户/agent
12
+ * 画的线不归这层管。
13
+ * 2. **引用得真实存在**才落线。悬空 src 是裂图体检的事,不是关系。
14
+ * 3. **不画**。BindingLayer 对 auto+ref 不渲染 —— 一个站引三十张图,画出来
15
+ * 是蜘蛛网不是版面。它们的消费方是 agent 上下文(digest 收拢成一行)和
16
+ * 将来的主角判断(被 ref 指着的 = 素材,不该当 hero)。
17
+ *
18
+ * 触发:挂在 GET /artifacts 尾巴上 fire-and-forget(那里 manifests 现成),
19
+ * 每项目 30s 节流。产物身份的拼法与 BoardCanvas 的物件 id 同构 ——
20
+ * `deck:<file>` / `site:<root||''>` / 单页 `site:<entryRel>`。
21
+ */
22
+
23
+ import fs from 'node:fs/promises';
24
+ import path from 'node:path';
25
+ import crypto from 'node:crypto';
26
+ import { readBoard, patchBoard } from '../projects/board-store.js';
27
+
28
+ const MAX_HTML_BYTES = 512 * 1024;
29
+ const AUTO_REF_PREFIX = 'b:auto:ref:';
30
+ /** 每个项目对账节流(ms)—— /artifacts 是热接口,别把它变成扫盘器 */
31
+ const THROTTLE_MS = 30_000;
32
+ const lastRun = new Map();
33
+
34
+ const hashId = (from, to) =>
35
+ AUTO_REF_PREFIX + crypto.createHash('sha1').update(`${from}|${to}`).digest('hex').slice(0, 12);
36
+
37
+ /**
38
+ * 从一段 html 里抠出本地资源引用(src/href/poster + 内联 url())。
39
+ * 返回**相对 html 所在目录**的原始引用串(未归一)。
40
+ */
41
+ export function extractRefs(html) {
42
+ const out = new Set();
43
+ const push = (raw) => {
44
+ if (!raw) return;
45
+ const v = raw.trim().replace(/^['"]|['"]$/g, '').split(/[?#]/)[0];
46
+ if (!v || /^(https?:|data:|blob:|mailto:|javascript:|\/\/)/i.test(v)) return;
47
+ out.add(v);
48
+ };
49
+ for (const m of html.matchAll(/<(?:img|video|audio|source|embed)\b[^>]*?\s(?:src|poster)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi)) push(m[1]);
50
+ for (const m of html.matchAll(/url\(\s*([^)]+?)\s*\)/gi)) push(m[1]);
51
+ return [...out];
52
+ }
53
+
54
+ /** 引用串 → 工作区相对路径(越界/绝对路径丢弃)。baseDir 是 html 所在目录(工作区相对)。 */
55
+ export function resolveRef(raw, baseDir) {
56
+ try {
57
+ const decoded = decodeURIComponent(raw);
58
+ if (decoded.startsWith('/')) return null; // 站内绝对路径按约定不出现,出现也不猜
59
+ const joined = path.posix.normalize(path.posix.join(baseDir || '.', decoded));
60
+ if (joined.startsWith('..') || path.posix.isAbsolute(joined)) return null;
61
+ return joined === '.' ? null : joined;
62
+ } catch { return null; }
63
+ }
64
+
65
+ /**
66
+ * 对账一个项目的自动 ref 边。
67
+ *
68
+ * @param {string} pid
69
+ * @param {string} workspaceRoot 绝对路径
70
+ * @param {Array} tasks /artifacts 已算好的 tasks(id/artifacts 同构前端)
71
+ */
72
+ export async function reconcileAutoRefs(pid, workspaceRoot, tasks) {
73
+ // 期望集:{ bid: { from, to } }
74
+ const desired = new Map();
75
+ for (const t of tasks || []) {
76
+ for (const a of t.artifacts || []) {
77
+ // 产物身份(与 BoardCanvas 的物件 id 同一拼法)
78
+ // ⚠️ 卡 id 按**形态**派生,别用「不是 deck 就是 site」的二分(2026-08-18 修)。
79
+ // docx 上线之后那个三元把 .docx 产物算成了 `site:` 前缀 —— id 对不上,
80
+ // 它的自动关系边落在一个不存在的端点上。规则跟前端 cardIdOf
81
+ // (web/src/lib/board-kinds.js)必须一致,两边岔开就是"同一件东西两份实现"。
82
+ const fromId = a.kind === 'site'
83
+ ? `site:${a.single ? a.entryRel : (a.root || t.id)}`
84
+ : `${a.kind || 'deck'}:${a.file}`;
85
+ // 这件产物的 html 们(deck 一份;站点 = 各页,路径相对站根)
86
+ const pages = a.kind === 'deck'
87
+ ? [a.file]
88
+ : (a.single ? [a.entryRel] : (a.pages || []).map(p => (a.root ? `${a.root}/${p}` : p)));
89
+ for (const rel of pages) {
90
+ if (!rel || !/\.html?$/i.test(rel)) continue;
91
+ let html;
92
+ try {
93
+ const abs = path.join(workspaceRoot, rel);
94
+ const st = await fs.stat(abs);
95
+ if (st.size > MAX_HTML_BYTES) continue;
96
+ html = await fs.readFile(abs, 'utf8');
97
+ } catch { continue; }
98
+ const baseDir = path.posix.dirname(rel.replace(/\\/g, '/'));
99
+ for (const raw of extractRefs(html)) {
100
+ const to = resolveRef(raw, baseDir === '.' ? '' : baseDir);
101
+ if (!to || to === rel) continue;
102
+ try { await fs.access(path.join(workspaceRoot, to)); } catch { continue; } // 悬空不落线
103
+ desired.set(hashId(fromId, to), { from: fromId, to });
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ // 现状 → diff。只碰 b:auto:ref:* 的条目。
110
+ const board = await readBoard(pid);
111
+ const patch = {};
112
+ for (const [bid, b] of Object.entries(board.bindings || {})) {
113
+ if (bid.startsWith(AUTO_REF_PREFIX) && !desired.has(bid)) patch[bid] = null;
114
+ }
115
+ for (const [bid, { from, to }] of desired) {
116
+ const cur = board.bindings?.[bid];
117
+ if (cur && cur.from === from && cur.to === to) continue;
118
+ patch[bid] = { type: 'ref', from, to, by: 'auto' };
119
+ }
120
+ if (Object.keys(patch).length) await patchBoard(pid, { bindings: patch });
121
+ return { desired: desired.size, changed: Object.keys(patch).length };
122
+ }
123
+
124
+ /** 节流版:/artifacts 尾巴上 fire-and-forget 用 */
125
+ export function reconcileAutoRefsThrottled(pid, workspaceRoot, tasks) {
126
+ const now = Date.now();
127
+ if (now - (lastRun.get(pid) || 0) < THROTTLE_MS) return;
128
+ lastRun.set(pid, now);
129
+ reconcileAutoRefs(pid, workspaceRoot, tasks).catch(() => { /* fail-soft:对账绝不拖累清单接口 */ });
130
+ }
@@ -1,60 +1,60 @@
1
- /**
2
- * 关系线的语义词汇表 —— 画布上一条线「是什么意思」的**唯一真相**。
3
- *
4
- * ## 为什么关系要单独存一张表
5
- *
6
- * 北极星是「任务文件夹能排出登录墙那种版面」。手摆之所以好看,是因为每张纸的
7
- * 位置在回答「它和旁边那张什么关系」。而系统此前只知道每个产物的**尺寸和
8
- * mtime** —— 关系数据不到位,再好的布局算法也只能排出「错落有致的网格」。
9
- *
10
- * 所以关系是 board.json 里的一等公民,跟 objects / zones 平级:
11
- *
12
- * bindings: { "<id>": { type, from, to, label?, by? } }
13
- *
14
- * 端点 `from` / `to` 是 **object id 或 zone id**(用户明确要求文件夹之间也能连线)。
15
- * 线本身不存坐标 —— 端点一移动,线自己跟着走。这是 tldraw `TLBinding` 的思路:
16
- * 关系是独立记录,不是形状的属性,箭头形状本身不存端点坐标。**抄的是思路,
17
- * 不是代码**(tldraw 是专有许可证)。
18
- *
19
- * ## 为什么是这五种
20
- *
21
- * 挑的标准是「这条关系能不能改变布局决定」。能影响摆放的才收,纯装饰的不收。
22
- * 每一种都对应创作过程里一件真实发生的事:
23
- *
24
- * - **改自**:一个产物是另一个的下一版。这是版本谱系,最该被看见的一条 ——
25
- * 用户翻旧版找的就是它。
26
- * - **批注**:一段文字在说某个东西。annotation 形态存在的理由。
27
- * - **顺序**:分镜、章节、流程的先后。它决定读序,也就决定排布方向。
28
- * - **取材**:这张图用在那个 deck 里 / 这份资料喂给了那篇稿。跨任务的引用。
29
- * - **对照**:并列比较(proto-暖调 vs proto-冷调)。无向,且**明确要求并排**,
30
- * 是唯一一条直接给布局下指令的关系。
31
- *
32
- * ## 明确不做成线的:「成组」
33
- *
34
- * 「这几件属于同一批」的正确画法是**圈一个框**,不是两两连线 —— n 个成员
35
- * 两两连线是 n(n-1)/2 条,五件就是十条,画面直接糊掉。成组用已有的 zone
36
- * (工作区 / 文件夹)表达,agent 建文件夹收纳就是在表达成组。
37
- */
38
-
39
- /**
40
- * 词汇表。`directed` 决定渲染时画不画箭头,也决定 from/to 能不能互换。
41
- * `label` 是没写自定义文字时线上显示的默认词。
42
- */
43
- export const BINDING_TYPES = {
44
- 'derives-from': { label: '改自', directed: true },
45
- annotates: { label: '批注', directed: true },
46
- flow: { label: '接着', directed: true },
47
- ref: { label: '取材', directed: true },
48
- contrast: { label: '对照', directed: false },
49
- // 关联(2026-08-14,手动连线上线时加):五种预定义都不合身时的逃生舱。
50
- // 含义由线上的 label 定义(用户写一句,agent 逐字读)。收它不违反
51
- // 「纯装饰不收」——它对布局说「摆近点」(affinity),对 agent 说 label 那句话。
52
- link: { label: '关联', directed: false },
53
- };
54
-
55
- /** 词汇表 id 列表,校验和 parity 断言都用它。 */
56
- export const BINDING_TYPE_IDS = Object.keys(BINDING_TYPES);
57
-
58
- export function isBindingType(t) {
59
- return Object.prototype.hasOwnProperty.call(BINDING_TYPES, t);
60
- }
1
+ /**
2
+ * 关系线的语义词汇表 —— 画布上一条线「是什么意思」的**唯一真相**。
3
+ *
4
+ * ## 为什么关系要单独存一张表
5
+ *
6
+ * 北极星是「任务文件夹能排出登录墙那种版面」。手摆之所以好看,是因为每张纸的
7
+ * 位置在回答「它和旁边那张什么关系」。而系统此前只知道每个产物的**尺寸和
8
+ * mtime** —— 关系数据不到位,再好的布局算法也只能排出「错落有致的网格」。
9
+ *
10
+ * 所以关系是 board.json 里的一等公民,跟 objects / zones 平级:
11
+ *
12
+ * bindings: { "<id>": { type, from, to, label?, by? } }
13
+ *
14
+ * 端点 `from` / `to` 是 **object id 或 zone id**(用户明确要求文件夹之间也能连线)。
15
+ * 线本身不存坐标 —— 端点一移动,线自己跟着走。这是 tldraw `TLBinding` 的思路:
16
+ * 关系是独立记录,不是形状的属性,箭头形状本身不存端点坐标。**抄的是思路,
17
+ * 不是代码**(tldraw 是专有许可证)。
18
+ *
19
+ * ## 为什么是这五种
20
+ *
21
+ * 挑的标准是「这条关系能不能改变布局决定」。能影响摆放的才收,纯装饰的不收。
22
+ * 每一种都对应创作过程里一件真实发生的事:
23
+ *
24
+ * - **改自**:一个产物是另一个的下一版。这是版本谱系,最该被看见的一条 ——
25
+ * 用户翻旧版找的就是它。
26
+ * - **批注**:一段文字在说某个东西。annotation 形态存在的理由。
27
+ * - **顺序**:分镜、章节、流程的先后。它决定读序,也就决定排布方向。
28
+ * - **取材**:这张图用在那个 deck 里 / 这份资料喂给了那篇稿。跨任务的引用。
29
+ * - **对照**:并列比较(proto-暖调 vs proto-冷调)。无向,且**明确要求并排**,
30
+ * 是唯一一条直接给布局下指令的关系。
31
+ *
32
+ * ## 明确不做成线的:「成组」
33
+ *
34
+ * 「这几件属于同一批」的正确画法是**圈一个框**,不是两两连线 —— n 个成员
35
+ * 两两连线是 n(n-1)/2 条,五件就是十条,画面直接糊掉。成组用已有的 zone
36
+ * (工作区 / 文件夹)表达,agent 建文件夹收纳就是在表达成组。
37
+ */
38
+
39
+ /**
40
+ * 词汇表。`directed` 决定渲染时画不画箭头,也决定 from/to 能不能互换。
41
+ * `label` 是没写自定义文字时线上显示的默认词。
42
+ */
43
+ export const BINDING_TYPES = {
44
+ 'derives-from': { label: '改自', directed: true },
45
+ annotates: { label: '批注', directed: true },
46
+ flow: { label: '接着', directed: true },
47
+ ref: { label: '取材', directed: true },
48
+ contrast: { label: '对照', directed: false },
49
+ // 关联(2026-08-14,手动连线上线时加):五种预定义都不合身时的逃生舱。
50
+ // 含义由线上的 label 定义(用户写一句,agent 逐字读)。收它不违反
51
+ // 「纯装饰不收」——它对布局说「摆近点」(affinity),对 agent 说 label 那句话。
52
+ link: { label: '关联', directed: false },
53
+ };
54
+
55
+ /** 词汇表 id 列表,校验和 parity 断言都用它。 */
56
+ export const BINDING_TYPE_IDS = Object.keys(BINDING_TYPES);
57
+
58
+ export function isBindingType(t) {
59
+ return Object.prototype.hasOwnProperty.call(BINDING_TYPES, t);
60
+ }