@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,284 +1,284 @@
1
- /**
2
- * artifact-target.js — 产物寻址(2026-07-28 由 canvas-target.js 泛化;
3
- * 2026-07-29 形态判定与解析下沉到 kinds/ 注册表;2026-08-07 任务层退役)
4
- *
5
- * 定死三件事:
6
- *
7
- * **① 产物有形态(kind),文件就是真相。** 判定规则在 kinds/ 注册表:
8
- * canvas.html → deck;index.html(工作区根 / 声明的产物根 / 约定构建目录)
9
- * → site。
10
- *
11
- * **② 寻址永远带着 kind 一起返回。** 下游(截图 / 分页 / 导出 / fit 注入)必须
12
- * 能看见"这是 deck 还是 site",才可能给出对的行为。kind 每次 resolve 按工作区
13
- * 现状重算,不缓存。
14
- *
15
- * **③ 源和产物可以分开。** 构建型站点的源在工作区根、产物在 dist/ 之类的产物根。
16
- * resolve 返回 workspaceRoot(源,agent 的地盘)和 artifactDir(被预览 / 导出 /
17
- * 发布的根),deck 和手写站点两者相同。消费方要"看"产物就用 artifactDir,
18
- * 别再自己 dirname(entryPath)。
19
- *
20
- * 寻址顺序(越靠前越明确):
21
- * ① 调用方显式给的 path
22
- * ② 本会话的"当前产物"——最近写过 / 截过 / 预览过哪份就是哪份
23
- * ③ 工作区只有一份产物就是它;多份就报错列出来让调用方指定
24
- *
25
- * ## 2026-08-07:第 ③ 条曾经是第 ④ 条
26
- *
27
- * 中间夹着一条「认领绑在本会话名下那个任务」——`tasks/<任务>/.nd-task.json`
28
- * 里记着 sessionId,寻址时按当前会话去反查它的任务。那条规则存在的前提是
29
- * **一个项目里有多个任务、而每个任务属于一个会话**;线上数据说这个前提从来
30
- * 没成立过(13 个有产物的项目,每个都恰好一个任务)。
31
- *
32
- * 任务层拆掉之后,"这个会话的产物"这个问题本身就没有意义了 —— 产物属于项目,
33
- * 谁开的对话都看得见同一批。剩下的歧义(工作区里有多份产物、调用方没说哪份)
34
- * 由 ② 的最近使用和 ③ 的显式报错解决,这两条本来就在,而且不依赖任何绑定。
35
- */
36
-
37
- import path from 'node:path';
38
- import fs from 'node:fs/promises';
39
- import {
40
- KINDS, kindDef, readTaskMarker, taskManifest, artifactOfPath, can,
41
- } from './kinds/index.js';
42
-
43
- export const KIND_DECK = 'deck';
44
- export const KIND_SITE = 'site';
45
-
46
- /** 每种形态的入口文件名(从注册表派生;改形态契约去 kinds/) */
47
- export const ENTRY_FILE = Object.freeze(
48
- Object.fromEntries(Object.values(KINDS).map(k => [k.id, k.entryFile])),
49
- );
50
-
51
- /**
52
- * 产物入口**长什么样**(扩展名集合),同样从注册表派生。
53
- * `.html` / `.htm` 是 deck+site 的形状,`.docx` 是 word 的 —— 加形态时这里
54
- * 自动跟上,不用再回来补一条正则。
55
- */
56
- const ENTRY_EXTS = new Set(
57
- Object.values(KINDS)
58
- .map(k => (k.entryFile.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase())
59
- .filter(Boolean)
60
- .concat('.htm'), // html 的老写法,注册表里只写了 .html
61
- );
62
-
63
- // 形态判定与解析的权威在 kinds/,这里转发老名字(消费方 import 不用改两次)
64
- export { readTaskMarker, taskManifest, kindDef, artifactOfPath };
65
- export { formatAllowed, docxClaimedFiles, isDirArtifact } from './kinds/index.js';
66
- export { can };
67
-
68
- /** sessionId → { path, kind }。会话结束不清也无妨(几个短字符串) */
69
- const activeArtifact = new Map();
70
-
71
- /**
72
- * 记住这个会话正在做哪份产物(写文件 / 截图 / 预览时调)。
73
- *
74
- * 只认**产物入口的形状**(ENTRY_EXTS,从注册表派生)。以前还认"任务内的非
75
- * html"(改 style.css 也算在这个任务上干活,寻址时走任务入口),任务层没了之后
76
- * 那一支失去了落点:改 `style.css` 说明不了在做哪一份 deck,工作区根上可能并排
77
- * 放着好几份。认不出来就不认,让 resolve 走"只有一份就是它 / 多份报错"那条 ——
78
- * 比猜一个错的默认目标强。
79
- */
80
- export function setActiveArtifact(sessionId, relPath, kind) {
81
- if (!sessionId || typeof relPath !== 'string') return;
82
- const p = normalizeRel(relPath);
83
- if (/\.template\.(html?|css)$/i.test(p)) return; // 模板不是产物
84
- const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
85
- if (!ENTRY_EXTS.has(ext)) return;
86
- activeArtifact.set(sessionId, { path: p, kind: kind || null });
87
- }
88
-
89
- export function getActiveArtifact(sessionId) {
90
- return (sessionId && activeArtifact.get(sessionId)) || null;
91
- }
92
-
93
- export function clearActiveArtifact(sessionId) {
94
- activeArtifact.delete(sessionId);
95
- }
96
-
97
- function normalizeRel(p) {
98
- return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
99
- }
100
-
101
- async function exists(p) {
102
- try { await fs.access(p); return true; } catch { return false; }
103
- }
104
-
105
- /** workspace 内才算数(resolve 不出根) */
106
- function insideWorkspace(workspaceRoot, absPath) {
107
- const root = path.resolve(workspaceRoot);
108
- return absPath === root || absPath.startsWith(root + path.sep);
109
- }
110
-
111
- /**
112
- * 站点的页面清单(相对产物根)。
113
- */
114
- export async function listSitePages(workspaceRoot) {
115
- const m = await KINDS[KIND_SITE].manifest(workspaceRoot, null);
116
- return m.pages;
117
- }
118
-
119
- /**
120
- * 推断某个 html 路径的产物形态。
121
- *
122
- * 多产物平权:canvas.html 跟一个根站并存时仍然是 deck、about.html 是站点子页 ——
123
- * 同名文件形态可以不同,问 artifacts 清单而不是猜文件名。清单里找不到(文件
124
- * 还没写出来)才退回文件名约定。
125
- */
126
- export async function kindOfPath(workspaceRoot, relPath) {
127
- const p = normalizeRel(relPath);
128
- const base = path.posix.basename(p);
129
- const m = await taskManifest(workspaceRoot);
130
- if (m) {
131
- const art = artifactOfPath(m, p);
132
- if (art) return art.kind;
133
- if (m.kind) return m.kind;
134
- }
135
- if (base === ENTRY_FILE[KIND_DECK]) return KIND_DECK;
136
- if (base === ENTRY_FILE[KIND_SITE]) return KIND_SITE;
137
- // 清单里没有(文件还没写出来)时按扩展名回退。.docx 只可能是 word 形态,
138
- // 让它落进 deck 会让感知层拿 playwright 去开一个二进制包。
139
- const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
140
- const byExt = Object.values(KINDS).find(k => k.entryFile.toLowerCase().endsWith(ext) && ext !== '.html');
141
- if (byExt) return byExt.id;
142
- return KIND_DECK; // 认不出的散装 .html 一律按 deck
143
- }
144
-
145
- /** 这个工作区现有的产物入口(平权,没有主次) */
146
- export async function listWorkspaceArtifacts(workspaceRoot) {
147
- const m = await taskManifest(workspaceRoot);
148
- return (m?.artifacts || []).map(a => ({ rel: a.entryRel, kind: a.kind }));
149
- }
150
-
151
- /** 兼容旧名:只要 deck 那些 */
152
- export async function listWorkspaceDecks(workspaceRoot) {
153
- return (await listWorkspaceArtifacts(workspaceRoot))
154
- .filter(a => a.kind === KIND_DECK)
155
- .map(a => a.rel);
156
- }
157
-
158
- /**
159
- * 解析产物目标。
160
- *
161
- * @param {string} workspaceRoot
162
- * @param {string|null} relPath 调用方显式给的路径(优先级最高)
163
- * @param {string|null} sessionId
164
- * @returns {Promise<{ ok: true, absPath: string, relPath: string, kind: 'deck'|'site',
165
- * task: string|null, taskDir: string|null,
166
- * artifactDir: string|null, artifactRel: string|null }
167
- * | { ok: false, message: string }>}
168
- * taskDir 源(任务根,agent 的地盘)
169
- * artifactDir 产物根(被预览 / 导出 / 发布的目录);deck 和手写站点 = taskDir
170
- */
171
- export async function resolveArtifactTarget(workspaceRoot, relPath, sessionId) {
172
- const decorate = async (rel) => {
173
- const p = normalizeRel(rel);
174
- // 按所属产物定 kind 和产物根(多产物平权:canvas.html 是 deck、
175
- // v2/index.html 是另一个站,root 各归各)
176
- let kind = null;
177
- let root = '';
178
- let artifact = null;
179
- const m = await taskManifest(workspaceRoot);
180
- if (m) {
181
- artifact = artifactOfPath(m, p);
182
- if (artifact) { kind = artifact.kind; root = artifact.root || ''; }
183
- else if (m.kind) { kind = m.kind; root = m.root || ''; }
184
- }
185
- if (!kind) kind = await kindOfPath(workspaceRoot, p);
186
- return {
187
- ok: true,
188
- absPath: path.resolve(workspaceRoot, p),
189
- relPath: p,
190
- kind,
191
- // task / taskDir 保留成兼容字段:任务层没了,"源目录"永远是工作区根。
192
- // 下游有十几处读 taskDir 当"agent 的地盘",给它对的值比改十几处安全。
193
- task: null,
194
- taskDir: workspaceRoot,
195
- artifact, // 所属产物条目(manifest.artifacts 里那条;散文件为 null)
196
- artifactDir: root ? path.join(workspaceRoot, root) : workspaceRoot,
197
- artifactRel: root || '.',
198
- };
199
- };
200
-
201
- const tryPath = async (rel) => {
202
- if (!rel) return null;
203
- const abs = path.resolve(workspaceRoot, rel);
204
- if (!insideWorkspace(workspaceRoot, abs)) return null;
205
- return (await exists(abs)) ? decorate(rel) : null;
206
- };
207
-
208
- if (relPath) {
209
- const hit = await tryPath(relPath);
210
- if (hit) { setActiveArtifact(sessionId, hit.relPath, hit.kind); return hit; }
211
- const abs = path.resolve(workspaceRoot, relPath);
212
- return {
213
- ok: false,
214
- message: insideWorkspace(workspaceRoot, abs)
215
- ? `${relPath} not found. Write it first, or check the path.`
216
- : 'path escapes workspace',
217
- };
218
- }
219
-
220
- const active = getActiveArtifact(sessionId);
221
- if (active?.path) {
222
- const activeHit = await tryPath(active.path);
223
- if (activeHit) return activeHit;
224
- }
225
-
226
- const all = await listWorkspaceArtifacts(workspaceRoot);
227
- if (all.length === 1) {
228
- setActiveArtifact(sessionId, all[0].rel, all[0].kind);
229
- return decorate(all[0].rel);
230
- }
231
- if (all.length > 1) {
232
- return {
233
- ok: false,
234
- message: 'Multiple artifacts in this workspace — pass path explicitly:\n'
235
- + all.map(a => `- ${a.rel} (${a.kind})`).join('\n'),
236
- };
237
- }
238
- return {
239
- ok: false,
240
- message: 'No artifact found. Deck = canvas.html, site = index.html — '
241
- + 'write one first, or pass path explicitly.',
242
- };
243
- }
244
-
245
- /**
246
- * 浏览器类工具的统一闸门:目标形态不能用浏览器打开就别往下走。
247
- *
248
- * 用法是 resolveCanvasTarget 之后立刻过一道:
249
- * const target = await resolveCanvasTarget(...);
250
- * if (!target.ok) return err(target.message);
251
- * const gate = requireBrowsable(target);
252
- * if (gate) return err(gate);
253
- *
254
- * 不把它塞进 resolveArtifactTarget 内部,是因为导出、寻址这些**正当**需要拿到
255
- * 不可浏览目标的调用方也走那个函数,在源头拦会误伤。
256
- *
257
- * @returns {string|null} 不可浏览时返回给 agent 的说明,可浏览返回 null
258
- */
259
- export function requireBrowsable(target) {
260
- if (!target?.kind || can(target.kind, 'browsable')) return null;
261
- const who = target.relPath || target.kind;
262
- // 可渲染形态(docx)要给对路的替代方案。说"直接 Read 它的文件"是**错的建议**
263
- // —— 那是个二进制 zip,Read 出来是乱码,而它其实是**看得见**的,只是要先渲染。
264
- if (can(target.kind, 'renderable')) {
265
- return `${who} 是 ${target.kind} 形态,没有 DOM,读页面 / 查元素 / 取计算样式这几个工具对它无效。`
266
- + '看长相用 screenshot(它会渲染成页图,可以带 pages 参数指定范围);'
267
- + '看结构读它的 token 源(同名 .json),别去 Read 那个 .docx 本身,它是二进制包。';
268
- }
269
- return `${who} 是 ${target.kind} 形态,没有可以用浏览器打开的入口,`
270
- + '截图 / 读页面 / 查元素这类工具对它没有意义。直接 Read 它的文件。';
271
- }
272
-
273
- /** 给各工具复用的 path 参数描述(保持措辞一致) */
274
- export const ARTIFACT_PATH_DESC =
275
- 'Relative path of the artifact entry (deck: "canvas.html", site: "index.html" '
276
- + 'or its built output like "dist/index.html", word: "文档.docx"). '
277
- + 'Omit to use the artifact you are currently working on.';
278
-
279
- // ── 兼容层:老名字继续可用,内部全部走上面的实现 ────────────────────────────
280
- export const CANVAS_PATH_DESC = ARTIFACT_PATH_DESC;
281
- export const resolveCanvasTarget = resolveArtifactTarget;
282
- export const setActiveDeck = (sessionId, relPath) => setActiveArtifact(sessionId, relPath);
283
- export const getActiveDeck = (sessionId) => getActiveArtifact(sessionId)?.path || null;
284
- export const clearActiveDeck = clearActiveArtifact;
1
+ /**
2
+ * artifact-target.js — 产物寻址(2026-07-28 由 canvas-target.js 泛化;
3
+ * 2026-07-29 形态判定与解析下沉到 kinds/ 注册表;2026-08-07 任务层退役)
4
+ *
5
+ * 定死三件事:
6
+ *
7
+ * **① 产物有形态(kind),文件就是真相。** 判定规则在 kinds/ 注册表:
8
+ * canvas.html → deck;index.html(工作区根 / 声明的产物根 / 约定构建目录)
9
+ * → site。
10
+ *
11
+ * **② 寻址永远带着 kind 一起返回。** 下游(截图 / 分页 / 导出 / fit 注入)必须
12
+ * 能看见"这是 deck 还是 site",才可能给出对的行为。kind 每次 resolve 按工作区
13
+ * 现状重算,不缓存。
14
+ *
15
+ * **③ 源和产物可以分开。** 构建型站点的源在工作区根、产物在 dist/ 之类的产物根。
16
+ * resolve 返回 workspaceRoot(源,agent 的地盘)和 artifactDir(被预览 / 导出 /
17
+ * 发布的根),deck 和手写站点两者相同。消费方要"看"产物就用 artifactDir,
18
+ * 别再自己 dirname(entryPath)。
19
+ *
20
+ * 寻址顺序(越靠前越明确):
21
+ * ① 调用方显式给的 path
22
+ * ② 本会话的"当前产物"——最近写过 / 截过 / 预览过哪份就是哪份
23
+ * ③ 工作区只有一份产物就是它;多份就报错列出来让调用方指定
24
+ *
25
+ * ## 2026-08-07:第 ③ 条曾经是第 ④ 条
26
+ *
27
+ * 中间夹着一条「认领绑在本会话名下那个任务」——`tasks/<任务>/.nd-task.json`
28
+ * 里记着 sessionId,寻址时按当前会话去反查它的任务。那条规则存在的前提是
29
+ * **一个项目里有多个任务、而每个任务属于一个会话**;线上数据说这个前提从来
30
+ * 没成立过(13 个有产物的项目,每个都恰好一个任务)。
31
+ *
32
+ * 任务层拆掉之后,"这个会话的产物"这个问题本身就没有意义了 —— 产物属于项目,
33
+ * 谁开的对话都看得见同一批。剩下的歧义(工作区里有多份产物、调用方没说哪份)
34
+ * 由 ② 的最近使用和 ③ 的显式报错解决,这两条本来就在,而且不依赖任何绑定。
35
+ */
36
+
37
+ import path from 'node:path';
38
+ import fs from 'node:fs/promises';
39
+ import {
40
+ KINDS, kindDef, readTaskMarker, taskManifest, artifactOfPath, can,
41
+ } from './kinds/index.js';
42
+
43
+ export const KIND_DECK = 'deck';
44
+ export const KIND_SITE = 'site';
45
+
46
+ /** 每种形态的入口文件名(从注册表派生;改形态契约去 kinds/) */
47
+ export const ENTRY_FILE = Object.freeze(
48
+ Object.fromEntries(Object.values(KINDS).map(k => [k.id, k.entryFile])),
49
+ );
50
+
51
+ /**
52
+ * 产物入口**长什么样**(扩展名集合),同样从注册表派生。
53
+ * `.html` / `.htm` 是 deck+site 的形状,`.docx` 是 word 的 —— 加形态时这里
54
+ * 自动跟上,不用再回来补一条正则。
55
+ */
56
+ const ENTRY_EXTS = new Set(
57
+ Object.values(KINDS)
58
+ .map(k => (k.entryFile.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase())
59
+ .filter(Boolean)
60
+ .concat('.htm'), // html 的老写法,注册表里只写了 .html
61
+ );
62
+
63
+ // 形态判定与解析的权威在 kinds/,这里转发老名字(消费方 import 不用改两次)
64
+ export { readTaskMarker, taskManifest, kindDef, artifactOfPath };
65
+ export { formatAllowed, docxClaimedFiles, isDirArtifact } from './kinds/index.js';
66
+ export { can };
67
+
68
+ /** sessionId → { path, kind }。会话结束不清也无妨(几个短字符串) */
69
+ const activeArtifact = new Map();
70
+
71
+ /**
72
+ * 记住这个会话正在做哪份产物(写文件 / 截图 / 预览时调)。
73
+ *
74
+ * 只认**产物入口的形状**(ENTRY_EXTS,从注册表派生)。以前还认"任务内的非
75
+ * html"(改 style.css 也算在这个任务上干活,寻址时走任务入口),任务层没了之后
76
+ * 那一支失去了落点:改 `style.css` 说明不了在做哪一份 deck,工作区根上可能并排
77
+ * 放着好几份。认不出来就不认,让 resolve 走"只有一份就是它 / 多份报错"那条 ——
78
+ * 比猜一个错的默认目标强。
79
+ */
80
+ export function setActiveArtifact(sessionId, relPath, kind) {
81
+ if (!sessionId || typeof relPath !== 'string') return;
82
+ const p = normalizeRel(relPath);
83
+ if (/\.template\.(html?|css)$/i.test(p)) return; // 模板不是产物
84
+ const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
85
+ if (!ENTRY_EXTS.has(ext)) return;
86
+ activeArtifact.set(sessionId, { path: p, kind: kind || null });
87
+ }
88
+
89
+ export function getActiveArtifact(sessionId) {
90
+ return (sessionId && activeArtifact.get(sessionId)) || null;
91
+ }
92
+
93
+ export function clearActiveArtifact(sessionId) {
94
+ activeArtifact.delete(sessionId);
95
+ }
96
+
97
+ function normalizeRel(p) {
98
+ return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
99
+ }
100
+
101
+ async function exists(p) {
102
+ try { await fs.access(p); return true; } catch { return false; }
103
+ }
104
+
105
+ /** workspace 内才算数(resolve 不出根) */
106
+ function insideWorkspace(workspaceRoot, absPath) {
107
+ const root = path.resolve(workspaceRoot);
108
+ return absPath === root || absPath.startsWith(root + path.sep);
109
+ }
110
+
111
+ /**
112
+ * 站点的页面清单(相对产物根)。
113
+ */
114
+ export async function listSitePages(workspaceRoot) {
115
+ const m = await KINDS[KIND_SITE].manifest(workspaceRoot, null);
116
+ return m.pages;
117
+ }
118
+
119
+ /**
120
+ * 推断某个 html 路径的产物形态。
121
+ *
122
+ * 多产物平权:canvas.html 跟一个根站并存时仍然是 deck、about.html 是站点子页 ——
123
+ * 同名文件形态可以不同,问 artifacts 清单而不是猜文件名。清单里找不到(文件
124
+ * 还没写出来)才退回文件名约定。
125
+ */
126
+ export async function kindOfPath(workspaceRoot, relPath) {
127
+ const p = normalizeRel(relPath);
128
+ const base = path.posix.basename(p);
129
+ const m = await taskManifest(workspaceRoot);
130
+ if (m) {
131
+ const art = artifactOfPath(m, p);
132
+ if (art) return art.kind;
133
+ if (m.kind) return m.kind;
134
+ }
135
+ if (base === ENTRY_FILE[KIND_DECK]) return KIND_DECK;
136
+ if (base === ENTRY_FILE[KIND_SITE]) return KIND_SITE;
137
+ // 清单里没有(文件还没写出来)时按扩展名回退。.docx 只可能是 word 形态,
138
+ // 让它落进 deck 会让感知层拿 playwright 去开一个二进制包。
139
+ const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
140
+ const byExt = Object.values(KINDS).find(k => k.entryFile.toLowerCase().endsWith(ext) && ext !== '.html');
141
+ if (byExt) return byExt.id;
142
+ return KIND_DECK; // 认不出的散装 .html 一律按 deck
143
+ }
144
+
145
+ /** 这个工作区现有的产物入口(平权,没有主次) */
146
+ export async function listWorkspaceArtifacts(workspaceRoot) {
147
+ const m = await taskManifest(workspaceRoot);
148
+ return (m?.artifacts || []).map(a => ({ rel: a.entryRel, kind: a.kind }));
149
+ }
150
+
151
+ /** 兼容旧名:只要 deck 那些 */
152
+ export async function listWorkspaceDecks(workspaceRoot) {
153
+ return (await listWorkspaceArtifacts(workspaceRoot))
154
+ .filter(a => a.kind === KIND_DECK)
155
+ .map(a => a.rel);
156
+ }
157
+
158
+ /**
159
+ * 解析产物目标。
160
+ *
161
+ * @param {string} workspaceRoot
162
+ * @param {string|null} relPath 调用方显式给的路径(优先级最高)
163
+ * @param {string|null} sessionId
164
+ * @returns {Promise<{ ok: true, absPath: string, relPath: string, kind: 'deck'|'site',
165
+ * task: string|null, taskDir: string|null,
166
+ * artifactDir: string|null, artifactRel: string|null }
167
+ * | { ok: false, message: string }>}
168
+ * taskDir 源(任务根,agent 的地盘)
169
+ * artifactDir 产物根(被预览 / 导出 / 发布的目录);deck 和手写站点 = taskDir
170
+ */
171
+ export async function resolveArtifactTarget(workspaceRoot, relPath, sessionId) {
172
+ const decorate = async (rel) => {
173
+ const p = normalizeRel(rel);
174
+ // 按所属产物定 kind 和产物根(多产物平权:canvas.html 是 deck、
175
+ // v2/index.html 是另一个站,root 各归各)
176
+ let kind = null;
177
+ let root = '';
178
+ let artifact = null;
179
+ const m = await taskManifest(workspaceRoot);
180
+ if (m) {
181
+ artifact = artifactOfPath(m, p);
182
+ if (artifact) { kind = artifact.kind; root = artifact.root || ''; }
183
+ else if (m.kind) { kind = m.kind; root = m.root || ''; }
184
+ }
185
+ if (!kind) kind = await kindOfPath(workspaceRoot, p);
186
+ return {
187
+ ok: true,
188
+ absPath: path.resolve(workspaceRoot, p),
189
+ relPath: p,
190
+ kind,
191
+ // task / taskDir 保留成兼容字段:任务层没了,"源目录"永远是工作区根。
192
+ // 下游有十几处读 taskDir 当"agent 的地盘",给它对的值比改十几处安全。
193
+ task: null,
194
+ taskDir: workspaceRoot,
195
+ artifact, // 所属产物条目(manifest.artifacts 里那条;散文件为 null)
196
+ artifactDir: root ? path.join(workspaceRoot, root) : workspaceRoot,
197
+ artifactRel: root || '.',
198
+ };
199
+ };
200
+
201
+ const tryPath = async (rel) => {
202
+ if (!rel) return null;
203
+ const abs = path.resolve(workspaceRoot, rel);
204
+ if (!insideWorkspace(workspaceRoot, abs)) return null;
205
+ return (await exists(abs)) ? decorate(rel) : null;
206
+ };
207
+
208
+ if (relPath) {
209
+ const hit = await tryPath(relPath);
210
+ if (hit) { setActiveArtifact(sessionId, hit.relPath, hit.kind); return hit; }
211
+ const abs = path.resolve(workspaceRoot, relPath);
212
+ return {
213
+ ok: false,
214
+ message: insideWorkspace(workspaceRoot, abs)
215
+ ? `${relPath} not found. Write it first, or check the path.`
216
+ : 'path escapes workspace',
217
+ };
218
+ }
219
+
220
+ const active = getActiveArtifact(sessionId);
221
+ if (active?.path) {
222
+ const activeHit = await tryPath(active.path);
223
+ if (activeHit) return activeHit;
224
+ }
225
+
226
+ const all = await listWorkspaceArtifacts(workspaceRoot);
227
+ if (all.length === 1) {
228
+ setActiveArtifact(sessionId, all[0].rel, all[0].kind);
229
+ return decorate(all[0].rel);
230
+ }
231
+ if (all.length > 1) {
232
+ return {
233
+ ok: false,
234
+ message: 'Multiple artifacts in this workspace — pass path explicitly:\n'
235
+ + all.map(a => `- ${a.rel} (${a.kind})`).join('\n'),
236
+ };
237
+ }
238
+ return {
239
+ ok: false,
240
+ message: 'No artifact found. Deck = canvas.html, site = index.html — '
241
+ + 'write one first, or pass path explicitly.',
242
+ };
243
+ }
244
+
245
+ /**
246
+ * 浏览器类工具的统一闸门:目标形态不能用浏览器打开就别往下走。
247
+ *
248
+ * 用法是 resolveCanvasTarget 之后立刻过一道:
249
+ * const target = await resolveCanvasTarget(...);
250
+ * if (!target.ok) return err(target.message);
251
+ * const gate = requireBrowsable(target);
252
+ * if (gate) return err(gate);
253
+ *
254
+ * 不把它塞进 resolveArtifactTarget 内部,是因为导出、寻址这些**正当**需要拿到
255
+ * 不可浏览目标的调用方也走那个函数,在源头拦会误伤。
256
+ *
257
+ * @returns {string|null} 不可浏览时返回给 agent 的说明,可浏览返回 null
258
+ */
259
+ export function requireBrowsable(target) {
260
+ if (!target?.kind || can(target.kind, 'browsable')) return null;
261
+ const who = target.relPath || target.kind;
262
+ // 可渲染形态(docx)要给对路的替代方案。说"直接 Read 它的文件"是**错的建议**
263
+ // —— 那是个二进制 zip,Read 出来是乱码,而它其实是**看得见**的,只是要先渲染。
264
+ if (can(target.kind, 'renderable')) {
265
+ return `${who} 是 ${target.kind} 形态,没有 DOM,读页面 / 查元素 / 取计算样式这几个工具对它无效。`
266
+ + '看长相用 screenshot(它会渲染成页图,可以带 pages 参数指定范围);'
267
+ + '看结构读它的 token 源(同名 .json),别去 Read 那个 .docx 本身,它是二进制包。';
268
+ }
269
+ return `${who} 是 ${target.kind} 形态,没有可以用浏览器打开的入口,`
270
+ + '截图 / 读页面 / 查元素这类工具对它没有意义。直接 Read 它的文件。';
271
+ }
272
+
273
+ /** 给各工具复用的 path 参数描述(保持措辞一致) */
274
+ export const ARTIFACT_PATH_DESC =
275
+ 'Relative path of the artifact entry (deck: "canvas.html", site: "index.html" '
276
+ + 'or its built output like "dist/index.html", word: "文档.docx"). '
277
+ + 'Omit to use the artifact you are currently working on.';
278
+
279
+ // ── 兼容层:老名字继续可用,内部全部走上面的实现 ────────────────────────────
280
+ export const CANVAS_PATH_DESC = ARTIFACT_PATH_DESC;
281
+ export const resolveCanvasTarget = resolveArtifactTarget;
282
+ export const setActiveDeck = (sessionId, relPath) => setActiveArtifact(sessionId, relPath);
283
+ export const getActiveDeck = (sessionId) => getActiveArtifact(sessionId)?.path || null;
284
+ export const clearActiveDeck = clearActiveArtifact;