@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,103 +1,103 @@
1
- /**
2
- * mcp/tools/read-board.js —— read_board(2026-08-14,agent 摆位批·读侧)
3
- *
4
- * 让 agent **看得见版面**。在这之前它对画布的了解只有关系线摘要 —— 每件东西
5
- * 坐哪、挨着谁、谁是主角,全是盲区,"摆放"无从谈起。这个工具把 board.json
6
- * 翻译成一张按层分组、按行排读的座次表。
7
- *
8
- * 口径说明(都写进输出,agent 不用猜):
9
- * - 只列**摆过的**:board.json 是稀疏表,刚产出还没排座的产物没有条目
10
- * (前端首排后几百毫秒内落盘,通常都在)
11
- * - 层归属是服务端近似(zone 字段优先,其次沿路径找已知文件夹)
12
- * - 尺寸是形态估算(文字/涂鸦用存档实测值)
13
- */
14
-
15
- import { tool } from '@anthropic-ai/claude-agent-sdk';
16
- import { z } from 'zod';
17
- import { readBoard } from '../../../projects/board-store.js';
18
- import { estimateSize } from '../../../lib/board-kind-sizes.js';
19
- import { layerOf } from '../../../lib/canvas-id.js';
20
- import { relationsDigest } from '../../../lib/board-relations.js';
21
-
22
- /** 同一"行"的 y 容差:入座算法一行内顶对齐,40 世界像素内视作同行 */
23
- const ROW_TOLERANCE = 40;
24
-
25
- function describeEntry(id, entry) {
26
- const sz = estimateSize(id, entry);
27
- const at = `@(${Math.round(entry.x)},${Math.round(entry.y)}) ${Math.round(sz.w)}x${Math.round(sz.h)}`;
28
- if (entry.kind === 'text') {
29
- const t = String(entry.data?.t || '').replace(/\s+/g, ' ').slice(0, 24);
30
- return `- [手写] 「${t}」 ${at} (id: ${id})${entry.by === 'agent' ? ' ·你写的' : ''}`;
31
- }
32
- if (entry.kind === 'scribble') return `- [涂鸦] ${at} (id: ${id})`;
33
- return `- ${id} ${at}${entry.by === 'agent' ? ' ·你摆的' : ''}`;
34
- }
35
-
36
- export function makeReadBoardTool({ projectId }) {
37
- return tool(
38
- 'read_board',
39
- `Read the workbench canvas seating chart: what sits where, row by row, per folder layer.
40
-
41
- Use this BEFORE arranging anything (arrange_on_board) or writing notes near things
42
- (create_on_board) — placement without looking is guessing. Coordinates are world
43
- pixels; items listed top-to-bottom, left-to-right per row. Only items that have
44
- been seated appear (freshly produced artifacts get their seat within a second).`,
45
- {
46
- layer: z.string().max(300).optional()
47
- .describe("Folder path to read ('' or omitted = the root desktop plus a folder list)"),
48
- },
49
- async ({ layer }) => {
50
- if (!projectId) {
51
- return { content: [{ type: 'text', text: 'No project bound.' }], isError: true };
52
- }
53
- const board = await readBoard(projectId);
54
- const known = new Set(Object.keys(board.zones || {}));
55
- const want = typeof layer === 'string' ? layer : '';
56
-
57
- // 分层
58
- const byLayer = new Map();
59
- for (const [id, entry] of Object.entries(board.objects || {})) {
60
- if (!Number.isFinite(entry?.x) || !Number.isFinite(entry?.y)) continue;
61
- const l = layerOf(id, entry, known);
62
- if (!byLayer.has(l)) byLayer.set(l, []);
63
- byLayer.get(l).push({ id, entry });
64
- }
65
-
66
- const lines = [];
67
- const renderLayer = (l) => {
68
- const items = (byLayer.get(l) || []).sort((a, b) =>
69
- (a.entry.y - b.entry.y) || (a.entry.x - b.entry.x));
70
- if (!items.length) { lines.push('(这一层还没有摆过的东西)'); return; }
71
- let rowY = null;
72
- for (const { id, entry } of items) {
73
- if (rowY === null || Math.abs(entry.y - rowY) > ROW_TOLERANCE) {
74
- rowY = entry.y;
75
- lines.push(`— 行 y≈${Math.round(rowY)} —`);
76
- }
77
- lines.push(describeEntry(id, entry));
78
- }
79
- };
80
-
81
- lines.push(want ? `文件夹「${want}」的座次:` : '桌面(根层)的座次:');
82
- renderLayer(want);
83
- if (!want) {
84
- const folders = Object.keys(board.zones || {}).sort();
85
- if (folders.length) {
86
- lines.push('', `文件夹卡:${folders.map(f => {
87
- const zz = board.zones[f];
88
- return `${f}@(${Math.round(zz.x)},${Math.round(zz.y)})`;
89
- }).join('、')}`);
90
- }
91
- }
92
- if (board.hero) lines.push('', `★ 显式主角:${board.hero}(arrange_on_board 的 feature/unfeature 管它)`);
93
-
94
- try {
95
- const digest = await relationsDigest(projectId, { limit: 16 });
96
- if (digest) lines.push('', '关系线:', digest);
97
- } catch { /* 关系读不到不挡座次 */ }
98
-
99
- lines.push('', '(口径:稀疏表只列摆过的;层归属为服务端近似;尺寸为形态估算)');
100
- return { content: [{ type: 'text', text: lines.join('\n') }] };
101
- },
102
- );
103
- }
1
+ /**
2
+ * mcp/tools/read-board.js —— read_board(2026-08-14,agent 摆位批·读侧)
3
+ *
4
+ * 让 agent **看得见版面**。在这之前它对画布的了解只有关系线摘要 —— 每件东西
5
+ * 坐哪、挨着谁、谁是主角,全是盲区,"摆放"无从谈起。这个工具把 board.json
6
+ * 翻译成一张按层分组、按行排读的座次表。
7
+ *
8
+ * 口径说明(都写进输出,agent 不用猜):
9
+ * - 只列**摆过的**:board.json 是稀疏表,刚产出还没排座的产物没有条目
10
+ * (前端首排后几百毫秒内落盘,通常都在)
11
+ * - 层归属是服务端近似(zone 字段优先,其次沿路径找已知文件夹)
12
+ * - 尺寸是形态估算(文字/涂鸦用存档实测值)
13
+ */
14
+
15
+ import { tool } from '@anthropic-ai/claude-agent-sdk';
16
+ import { z } from 'zod';
17
+ import { readBoard } from '../../../projects/board-store.js';
18
+ import { estimateSize } from '../../../lib/board-kind-sizes.js';
19
+ import { layerOf } from '../../../lib/canvas-id.js';
20
+ import { relationsDigest } from '../../../lib/board-relations.js';
21
+
22
+ /** 同一"行"的 y 容差:入座算法一行内顶对齐,40 世界像素内视作同行 */
23
+ const ROW_TOLERANCE = 40;
24
+
25
+ function describeEntry(id, entry) {
26
+ const sz = estimateSize(id, entry);
27
+ const at = `@(${Math.round(entry.x)},${Math.round(entry.y)}) ${Math.round(sz.w)}x${Math.round(sz.h)}`;
28
+ if (entry.kind === 'text') {
29
+ const t = String(entry.data?.t || '').replace(/\s+/g, ' ').slice(0, 24);
30
+ return `- [手写] 「${t}」 ${at} (id: ${id})${entry.by === 'agent' ? ' ·你写的' : ''}`;
31
+ }
32
+ if (entry.kind === 'scribble') return `- [涂鸦] ${at} (id: ${id})`;
33
+ return `- ${id} ${at}${entry.by === 'agent' ? ' ·你摆的' : ''}`;
34
+ }
35
+
36
+ export function makeReadBoardTool({ projectId }) {
37
+ return tool(
38
+ 'read_board',
39
+ `Read the workbench canvas seating chart: what sits where, row by row, per folder layer.
40
+
41
+ Use this BEFORE arranging anything (arrange_on_board) or writing notes near things
42
+ (create_on_board) — placement without looking is guessing. Coordinates are world
43
+ pixels; items listed top-to-bottom, left-to-right per row. Only items that have
44
+ been seated appear (freshly produced artifacts get their seat within a second).`,
45
+ {
46
+ layer: z.string().max(300).optional()
47
+ .describe("Folder path to read ('' or omitted = the root desktop plus a folder list)"),
48
+ },
49
+ async ({ layer }) => {
50
+ if (!projectId) {
51
+ return { content: [{ type: 'text', text: 'No project bound.' }], isError: true };
52
+ }
53
+ const board = await readBoard(projectId);
54
+ const known = new Set(Object.keys(board.zones || {}));
55
+ const want = typeof layer === 'string' ? layer : '';
56
+
57
+ // 分层
58
+ const byLayer = new Map();
59
+ for (const [id, entry] of Object.entries(board.objects || {})) {
60
+ if (!Number.isFinite(entry?.x) || !Number.isFinite(entry?.y)) continue;
61
+ const l = layerOf(id, entry, known);
62
+ if (!byLayer.has(l)) byLayer.set(l, []);
63
+ byLayer.get(l).push({ id, entry });
64
+ }
65
+
66
+ const lines = [];
67
+ const renderLayer = (l) => {
68
+ const items = (byLayer.get(l) || []).sort((a, b) =>
69
+ (a.entry.y - b.entry.y) || (a.entry.x - b.entry.x));
70
+ if (!items.length) { lines.push('(这一层还没有摆过的东西)'); return; }
71
+ let rowY = null;
72
+ for (const { id, entry } of items) {
73
+ if (rowY === null || Math.abs(entry.y - rowY) > ROW_TOLERANCE) {
74
+ rowY = entry.y;
75
+ lines.push(`— 行 y≈${Math.round(rowY)} —`);
76
+ }
77
+ lines.push(describeEntry(id, entry));
78
+ }
79
+ };
80
+
81
+ lines.push(want ? `文件夹「${want}」的座次:` : '桌面(根层)的座次:');
82
+ renderLayer(want);
83
+ if (!want) {
84
+ const folders = Object.keys(board.zones || {}).sort();
85
+ if (folders.length) {
86
+ lines.push('', `文件夹卡:${folders.map(f => {
87
+ const zz = board.zones[f];
88
+ return `${f}@(${Math.round(zz.x)},${Math.round(zz.y)})`;
89
+ }).join('、')}`);
90
+ }
91
+ }
92
+ if (board.hero) lines.push('', `★ 显式主角:${board.hero}(arrange_on_board 的 feature/unfeature 管它)`);
93
+
94
+ try {
95
+ const digest = await relationsDigest(projectId, { limit: 16 });
96
+ if (digest) lines.push('', '关系线:', digest);
97
+ } catch { /* 关系读不到不挡座次 */ }
98
+
99
+ lines.push('', '(口径:稀疏表只列摆过的;层归属为服务端近似;尺寸为形态估算)');
100
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
101
+ },
102
+ );
103
+ }
@@ -1,93 +1,93 @@
1
- /**
2
- * mcp/tools/read-document.js — read_document MCP tool(2026-08-07)
3
- *
4
- * Word / Excel / PowerPoint 三种格式的读取口。
5
- *
6
- * 为什么需要它:用户往上下文托盘里拖一份 .docx("照这份需求做一版海报"),
7
- * agent 拿 Read 去读得到的是二进制乱码 —— **而且不报错**,它会当成一份读不懂
8
- * 的文件继续往下干,交出一个跟需求无关的东西。这是最坏的一种失败:安静的。
9
- *
10
- * PDF 不走这里:SDK 的 Read 原生支持,2026-08-07 真跑验过。
11
- */
12
-
13
- import path from 'node:path';
14
- import fs from 'node:fs/promises';
15
- import { tool } from '@anthropic-ai/claude-agent-sdk';
16
- import { z } from 'zod';
17
- import { extractDocument, isExtractable, DOC_KINDS, MAX_CHARS } from '../../../lib/doc-extract.js';
18
-
19
- const EXT_LIST = Object.keys(DOC_KINDS).join(' / ');
20
-
21
- export function makeReadDocumentTool({ workspaceRoot, sharedRoot }) {
22
- return tool(
23
- 'read_document',
24
- `Read the text out of a Word / Excel / PowerPoint file (${EXT_LIST}).
25
-
26
- The plain Read tool CANNOT read these — they are zip archives, so Read returns
27
- binary garbage without failing, and you end up working from nothing. Whenever a
28
- user attaches or points at one of these files, use this tool instead.
29
-
30
- (PDF is different: the normal Read tool handles PDFs natively — use Read for those.)
31
-
32
- What you get back is text with structure markers, not layout:
33
- - .docx — the document text, headings/lists/tables flattened in reading order
34
- - .xlsx — one section per worksheet, rows as tab-separated cells
35
- - .pptx — one section per slide, in slide order
36
-
37
- Layout is deliberately not preserved. You are being asked what the document
38
- *says*; how it should look is your job to decide.
39
-
40
- Long files are cut at ${MAX_CHARS} characters and the result says so.`,
41
- {
42
- path: z.string().describe(
43
- 'Path to the file. Relative paths resolve against the session workspace, '
44
- + 'then against the shared project dir (e.g. "assets/需求.docx").',
45
- ),
46
- },
47
- async ({ path: rel }) => {
48
- const fail = (text) => ({ content: [{ type: 'text', text }], isError: true });
49
- try {
50
- const raw = String(rel || '').trim();
51
- if (!raw) return fail('read_document needs a path.');
52
-
53
- // 绝对路径直接用;相对路径先会话工作区、再项目共享目录
54
- const candidates = path.isAbsolute(raw)
55
- ? [raw]
56
- : [workspaceRoot && path.resolve(workspaceRoot, raw),
57
- sharedRoot && path.resolve(sharedRoot, raw)].filter(Boolean);
58
-
59
- let absPath = null;
60
- for (const c of candidates) {
61
- try { await fs.access(c); absPath = c; break; } catch { /* 下一个 */ }
62
- }
63
- if (!absPath) {
64
- return fail(`File not found: ${raw}\nLooked in:\n${candidates.map(c => ` ${c}`).join('\n')}`);
65
- }
66
-
67
- if (!isExtractable(absPath)) {
68
- const ext = path.extname(absPath).toLowerCase();
69
- return fail(
70
- ext === '.pdf'
71
- ? 'PDFs are handled by the normal Read tool — call Read on this path instead.'
72
- : `read_document only handles ${EXT_LIST}. For "${ext}" try the normal Read tool.`,
73
- );
74
- }
75
-
76
- const { kind, text, chars, truncated, note } = await extractDocument(absPath);
77
- if (!text) {
78
- return {
79
- content: [{ type: 'text', text: `${kind} ${path.basename(absPath)} 里没有可读的文字${note ? `(${note})` : ''}。` }],
80
- };
81
- }
82
- const head = [
83
- `${kind}:${path.basename(absPath)}`,
84
- `${chars} 字${truncated ? `,只给前 ${MAX_CHARS} 字` : ''}`,
85
- note,
86
- ].filter(Boolean).join(' · ');
87
- return { content: [{ type: 'text', text: `${head}\n\n${text}` }] };
88
- } catch (err) {
89
- return fail(`read_document failed: ${err?.message || String(err)}`);
90
- }
91
- },
92
- );
93
- }
1
+ /**
2
+ * mcp/tools/read-document.js — read_document MCP tool(2026-08-07)
3
+ *
4
+ * Word / Excel / PowerPoint 三种格式的读取口。
5
+ *
6
+ * 为什么需要它:用户往上下文托盘里拖一份 .docx("照这份需求做一版海报"),
7
+ * agent 拿 Read 去读得到的是二进制乱码 —— **而且不报错**,它会当成一份读不懂
8
+ * 的文件继续往下干,交出一个跟需求无关的东西。这是最坏的一种失败:安静的。
9
+ *
10
+ * PDF 不走这里:SDK 的 Read 原生支持,2026-08-07 真跑验过。
11
+ */
12
+
13
+ import path from 'node:path';
14
+ import fs from 'node:fs/promises';
15
+ import { tool } from '@anthropic-ai/claude-agent-sdk';
16
+ import { z } from 'zod';
17
+ import { extractDocument, isExtractable, DOC_KINDS, MAX_CHARS } from '../../../lib/doc-extract.js';
18
+
19
+ const EXT_LIST = Object.keys(DOC_KINDS).join(' / ');
20
+
21
+ export function makeReadDocumentTool({ workspaceRoot, sharedRoot }) {
22
+ return tool(
23
+ 'read_document',
24
+ `Read the text out of a Word / Excel / PowerPoint file (${EXT_LIST}).
25
+
26
+ The plain Read tool CANNOT read these — they are zip archives, so Read returns
27
+ binary garbage without failing, and you end up working from nothing. Whenever a
28
+ user attaches or points at one of these files, use this tool instead.
29
+
30
+ (PDF is different: the normal Read tool handles PDFs natively — use Read for those.)
31
+
32
+ What you get back is text with structure markers, not layout:
33
+ - .docx — the document text, headings/lists/tables flattened in reading order
34
+ - .xlsx — one section per worksheet, rows as tab-separated cells
35
+ - .pptx — one section per slide, in slide order
36
+
37
+ Layout is deliberately not preserved. You are being asked what the document
38
+ *says*; how it should look is your job to decide.
39
+
40
+ Long files are cut at ${MAX_CHARS} characters and the result says so.`,
41
+ {
42
+ path: z.string().describe(
43
+ 'Path to the file. Relative paths resolve against the session workspace, '
44
+ + 'then against the shared project dir (e.g. "assets/需求.docx").',
45
+ ),
46
+ },
47
+ async ({ path: rel }) => {
48
+ const fail = (text) => ({ content: [{ type: 'text', text }], isError: true });
49
+ try {
50
+ const raw = String(rel || '').trim();
51
+ if (!raw) return fail('read_document needs a path.');
52
+
53
+ // 绝对路径直接用;相对路径先会话工作区、再项目共享目录
54
+ const candidates = path.isAbsolute(raw)
55
+ ? [raw]
56
+ : [workspaceRoot && path.resolve(workspaceRoot, raw),
57
+ sharedRoot && path.resolve(sharedRoot, raw)].filter(Boolean);
58
+
59
+ let absPath = null;
60
+ for (const c of candidates) {
61
+ try { await fs.access(c); absPath = c; break; } catch { /* 下一个 */ }
62
+ }
63
+ if (!absPath) {
64
+ return fail(`File not found: ${raw}\nLooked in:\n${candidates.map(c => ` ${c}`).join('\n')}`);
65
+ }
66
+
67
+ if (!isExtractable(absPath)) {
68
+ const ext = path.extname(absPath).toLowerCase();
69
+ return fail(
70
+ ext === '.pdf'
71
+ ? 'PDFs are handled by the normal Read tool — call Read on this path instead.'
72
+ : `read_document only handles ${EXT_LIST}. For "${ext}" try the normal Read tool.`,
73
+ );
74
+ }
75
+
76
+ const { kind, text, chars, truncated, note } = await extractDocument(absPath);
77
+ if (!text) {
78
+ return {
79
+ content: [{ type: 'text', text: `${kind} ${path.basename(absPath)} 里没有可读的文字${note ? `(${note})` : ''}。` }],
80
+ };
81
+ }
82
+ const head = [
83
+ `${kind}:${path.basename(absPath)}`,
84
+ `${chars} 字${truncated ? `,只给前 ${MAX_CHARS} 字` : ''}`,
85
+ note,
86
+ ].filter(Boolean).join(' · ');
87
+ return { content: [{ type: 'text', text: `${head}\n\n${text}` }] };
88
+ } catch (err) {
89
+ return fail(`read_document failed: ${err?.message || String(err)}`);
90
+ }
91
+ },
92
+ );
93
+ }