@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.
- package/README.md +156 -106
- package/bin/nodesign.js +0 -0
- package/package.json +1 -1
- package/server/api/README.md +45 -45
- package/server/api/_guard.js +75 -75
- package/server/api/admin.js +187 -187
- package/server/api/assets/docx-page.js +103 -103
- package/server/api/assets/helpers.js +22 -22
- package/server/api/assets/notes.js +144 -144
- package/server/api/assets.js +896 -896
- package/server/api/board.js +57 -57
- package/server/api/browse.js +138 -138
- package/server/api/canvas.js +444 -444
- package/server/api/chatai.js +324 -324
- package/server/api/exports/build-standalone.js +980 -980
- package/server/api/exports/cards.js +118 -118
- package/server/api/exports/docx-pdf.js +46 -46
- package/server/api/exports/export-page.js +142 -142
- package/server/api/exports/handoff.js +147 -147
- package/server/api/exports.js +690 -690
- package/server/api/instruction.js +75 -75
- package/server/api/me.js +116 -116
- package/server/api/memory.js +185 -185
- package/server/api/pending-changes.js +366 -366
- package/server/api/plugins.js +164 -164
- package/server/api/projects.js +220 -220
- package/server/api/publish.js +64 -64
- package/server/api/recent.js +73 -73
- package/server/api/sessions.js +589 -589
- package/server/api/skills.js +77 -77
- package/server/api/standalone-fit.js +168 -168
- package/server/api/turn-compose.js +180 -180
- package/server/api/turn-inflight.js +44 -44
- package/server/auth/README.md +17 -17
- package/server/auth/middleware.js +138 -138
- package/server/auth/origin-guard.js +71 -71
- package/server/auth/session.js +108 -108
- package/server/auth/tier.js +113 -113
- package/server/auth/users-store.js +348 -348
- package/server/edit/README.md +29 -29
- package/server/engine/README.md +113 -113
- package/server/engine/agent/agent-shared.js +550 -550
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
- package/server/engine/agent/auto-mode-rules.js +78 -78
- package/server/engine/agent/context.js +418 -418
- package/server/engine/agent/events.js +464 -464
- package/server/engine/agent/hooks/canvas-validate.js +199 -199
- package/server/engine/agent/hooks/failure.js +120 -120
- package/server/engine/agent/hooks/file-events.js +75 -75
- package/server/engine/agent/hooks/lifecycle.js +233 -233
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
- package/server/engine/agent/hooks/post-guidance.js +206 -206
- package/server/engine/agent/hooks/post-subagent-report.js +104 -104
- package/server/engine/agent/hooks/post-trim.js +58 -58
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
- package/server/engine/agent/hooks/pre-defaults.js +84 -84
- package/server/engine/agent/hooks/pre-injectors.js +277 -277
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
- package/server/engine/agent/hooks/pre-starter-files.js +61 -61
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
- package/server/engine/agent/hooks/site-validate.js +94 -94
- package/server/engine/agent/hooks/tool-prompts.js +31 -31
- package/server/engine/agent/hooks/turn-state-memory.js +56 -56
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -215
- package/server/engine/agent/hooks.js +354 -354
- package/server/engine/agent/init-contract.js +69 -69
- package/server/engine/agent/isolation.js +159 -159
- package/server/engine/agent/model-table.js +292 -292
- package/server/engine/agent/plugin-loader.js +266 -266
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -336
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
- package/server/engine/agent/prompts/tools/site-reference.md +106 -106
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
- package/server/engine/agent/session-loop.js +1024 -1024
- package/server/engine/agent/session-model.js +164 -164
- package/server/engine/agent/skill.js +204 -204
- package/server/engine/agent/task-events.js +78 -78
- package/server/engine/agents/ds-extractor.md +132 -132
- package/server/engine/agents/explorer.md +190 -190
- package/server/engine/agents/index.js +230 -230
- package/server/engine/agents/schemas/design-system.json +156 -156
- package/server/engine/agents/schemas/tweak-schema.json +113 -113
- package/server/engine/agents/tweak-proposer.md +127 -127
- package/server/engine/agents/vision-checker.md +254 -254
- package/server/engine/browse/capture.js +422 -422
- package/server/engine/browse/card.js +137 -137
- package/server/engine/browse/handover.js +59 -59
- package/server/engine/browse/page-digest.js +119 -119
- package/server/engine/browse/refs.js +196 -196
- package/server/engine/browse/registry.js +307 -307
- package/server/engine/browse/screencast.js +170 -170
- package/server/engine/browse/state.js +121 -121
- package/server/engine/chatai/chat-log.js +79 -79
- package/server/engine/chatai/index.js +193 -193
- package/server/engine/chatai/openai-compat.js +152 -152
- package/server/engine/chatai/orchestrate.js +309 -309
- package/server/engine/chatai/perform.js +40 -40
- package/server/engine/chatai/summarize.js +79 -79
- package/server/engine/mcp/capability-gate.js +52 -52
- package/server/engine/mcp/index.js +339 -339
- package/server/engine/mcp/param-sanitizer.js +142 -142
- package/server/engine/mcp/tools/arrange-on-board.js +124 -124
- package/server/engine/mcp/tools/artifact-session.js +278 -278
- package/server/engine/mcp/tools/browse-computer.js +421 -421
- package/server/engine/mcp/tools/browse-find-batch.js +183 -183
- package/server/engine/mcp/tools/browse-screenshot.js +157 -157
- package/server/engine/mcp/tools/browse.js +480 -480
- package/server/engine/mcp/tools/build-docx.js +101 -101
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
- package/server/engine/mcp/tools/create-on-board.js +155 -155
- package/server/engine/mcp/tools/crystallize-skill.js +168 -168
- package/server/engine/mcp/tools/explain-style.js +237 -237
- package/server/engine/mcp/tools/export-handoff.js +133 -133
- package/server/engine/mcp/tools/expose-tweaks.js +149 -149
- package/server/engine/mcp/tools/generate-image.js +842 -842
- package/server/engine/mcp/tools/get-computed-styles.js +164 -164
- package/server/engine/mcp/tools/get-pending-changes.js +205 -205
- package/server/engine/mcp/tools/h3box-ssh.js +84 -84
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
- package/server/engine/mcp/tools/highlight.js +68 -68
- package/server/engine/mcp/tools/list-pages.js +222 -222
- package/server/engine/mcp/tools/lookup-tags.js +59 -59
- package/server/engine/mcp/tools/navigate-to-page.js +60 -60
- package/server/engine/mcp/tools/organize-board.js +64 -64
- package/server/engine/mcp/tools/paint-still.js +434 -434
- package/server/engine/mcp/tools/pin-to-board.js +130 -130
- package/server/engine/mcp/tools/preview-deck.js +130 -130
- package/server/engine/mcp/tools/profile-scroll.js +305 -305
- package/server/engine/mcp/tools/publish-site.js +102 -102
- package/server/engine/mcp/tools/query-elements.js +195 -195
- package/server/engine/mcp/tools/read-board.js +103 -103
- package/server/engine/mcp/tools/read-document.js +93 -93
- package/server/engine/mcp/tools/read-page.js +264 -264
- package/server/engine/mcp/tools/read-tavern-json.js +143 -143
- package/server/engine/mcp/tools/record-decision.js +140 -140
- package/server/engine/mcp/tools/relate-on-board.js +110 -110
- package/server/engine/mcp/tools/report-issue.js +115 -115
- package/server/engine/mcp/tools/roll-film.js +266 -266
- package/server/engine/mcp/tools/screenshot-docx.js +138 -138
- package/server/engine/mcp/tools/screenshot-url.js +195 -195
- package/server/engine/mcp/tools/screenshot.js +583 -583
- package/server/engine/mcp/tools/tier-gate.js +96 -96
- package/server/engine/mcp/tools/trace-motion.js +215 -215
- package/server/engine/mcp/tools/web-search.js +548 -548
- package/server/engine/motion/inventory.js +349 -349
- package/server/engine/perception/session.js +235 -235
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -192
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -162
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -228
- 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
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -323
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
- package/server/engine/runs/active-runs.js +698 -698
- package/server/engine/runs/live-turn.js +252 -252
- package/server/engine/runs/store.js +364 -364
- package/server/engine/runs/turn-relay.js +217 -217
- package/server/engine/runtime/workspace.js +164 -164
- package/server/lib/artifact-file-path.js +80 -80
- package/server/lib/artifact-target.js +284 -284
- package/server/lib/asset-refs.js +239 -239
- package/server/lib/async-queue.js +104 -104
- package/server/lib/auto-relations.js +130 -130
- package/server/lib/binding-types.js +60 -60
- package/server/lib/board-kind-sizes.js +62 -62
- package/server/lib/board-relations.js +133 -133
- package/server/lib/browse-proxy.js +180 -180
- package/server/lib/canvas-id.js +36 -36
- package/server/lib/cover.js +227 -227
- package/server/lib/danbooru-tags.js +291 -291
- package/server/lib/doc-extract.js +156 -156
- package/server/lib/docx/build-from-source.js +260 -260
- package/server/lib/docx/build.js +490 -490
- package/server/lib/docx/dump-styles.js +287 -287
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
- package/server/lib/docx/merge-runs.js +129 -129
- package/server/lib/docx/numbering.js +218 -218
- package/server/lib/docx/order.js +152 -152
- package/server/lib/docx/rawzip.js +172 -172
- package/server/lib/docx/render.js +92 -92
- package/server/lib/docx/text-lint.js +225 -225
- package/server/lib/docx/tokens.js +335 -335
- package/server/lib/docx/units.js +29 -29
- package/server/lib/docx/xml.js +291 -291
- package/server/lib/docx-pages.js +187 -187
- package/server/lib/export-collect.js +289 -289
- package/server/lib/export-package.js +210 -210
- package/server/lib/html-srcset.js +145 -145
- package/server/lib/image-variant.js +460 -460
- package/server/lib/ingress/forward-openai-chat.js +305 -305
- package/server/lib/ingress/openai-chat.js +491 -491
- package/server/lib/ingress/session-notice.js +66 -66
- package/server/lib/ingress/session-routes.js +68 -68
- package/server/lib/ingress/slot-probe.js +130 -130
- package/server/lib/ingress/upstream-billing.js +52 -52
- package/server/lib/ingress/upstream-fail-streak.js +72 -72
- package/server/lib/ingress/upstream-truncation.js +48 -48
- package/server/lib/issues-store.js +182 -182
- package/server/lib/kinds/deck.js +72 -72
- package/server/lib/kinds/docx.js +283 -283
- package/server/lib/kinds/file-kinds.js +113 -113
- package/server/lib/kinds/index.js +202 -202
- package/server/lib/kinds/site.js +161 -161
- package/server/lib/model-ingress.js +543 -543
- package/server/lib/moderation.js +255 -255
- package/server/lib/notice-store.js +103 -103
- package/server/lib/plugin-install.js +104 -104
- package/server/lib/plugin-validator.js +721 -721
- package/server/lib/publish-store.js +115 -115
- package/server/lib/quick-summary.js +52 -52
- package/server/lib/quota.js +270 -270
- package/server/lib/rate-window.js +29 -29
- package/server/lib/reference-assets.js +92 -92
- package/server/lib/region-shot.js +135 -135
- package/server/lib/safe-path.js +73 -73
- package/server/lib/sdk-session.js +33 -33
- package/server/lib/showcase-store.js +92 -92
- package/server/lib/site-publish.js +465 -465
- package/server/lib/ssrf-guard.js +432 -432
- package/server/lib/task-scan.js +158 -158
- package/server/lib/tavern-json.js +154 -154
- package/server/lib/video-variant.js +237 -237
- package/server/lib/workspace-path.js +33 -33
- package/server/ops/fix-sdk-musl.mjs +40 -40
- package/server/ops/install-macos-fonts.sh +114 -114
- package/server/ops/macos-fonts.conf +336 -336
- package/server/ops/sandbox-shim/bwrap +57 -57
- package/server/projects/assets-summary.js +119 -119
- package/server/projects/auto-name.js +53 -53
- package/server/projects/board-store.js +640 -640
- package/server/projects/move-entry.js +103 -103
- package/server/projects/store.js +270 -270
- package/server/projects/ui-config.js +54 -54
- package/server/projects/workspace-templates.js +53 -53
- package/server/projects/workspace.js +1025 -1025
- package/server/runtime/local-config.js +177 -177
- package/server/runtime/profile.js +72 -72
- package/server/services/rembg-service.py +334 -334
- package/server/shared/README.md +25 -25
- package/server/shared/deck.js +54 -54
- package/server/shared/time.js +49 -49
- package/server/style-pipeline/README.md +53 -53
- package/server/ws/broker.js +36 -36
- package/server/ws/browse-channel.js +177 -177
- package/server/ws/index.js +386 -386
- package/web/README.md +89 -89
- package/web/dist/assets/BrowserWindow-BYCgizHw.js +11 -0
- package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-pNydEM4L.js} +1 -1
- package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-PPgzh3Gq.js} +1 -1
- package/web/dist/assets/{SiteWindow-D6eS-2gM.js → SiteWindow-DM0LQPCO.js} +1 -1
- package/web/dist/assets/{index-Cbl3ATvp.js → index-BHTBJlpY.js} +32 -32
- package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-C0I13CbD.js} +1 -1
- package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-B1UQ8Hld.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-8lrgqmfv.js} +1 -1
- package/web/dist/index.html +17 -17
- 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
|
+
}
|