@xiaobuyu/nodesign 0.0.5 → 0.0.7
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/server/api/assets/helpers.js +18 -0
- package/server/api/assets/notes.js +50 -0
- package/server/api/assets.js +11 -6
- package/server/api/instruction.js +3 -3
- package/server/engine/agent/events.js +11 -1
- package/server/engine/agent/hooks/failure.js +11 -1
- package/server/engine/agent/hooks/lifecycle.js +10 -39
- package/server/engine/agent/hooks/post-guidance.js +4 -38
- package/server/engine/agent/hooks/user-prompt-submit.js +11 -40
- package/server/engine/agent/hooks.js +4 -8
- package/server/engine/agent/memory-config.js +61 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +14 -12
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +24 -7
- package/server/engine/agent/prompts/tools/site-reference.md +13 -3
- package/server/engine/agent/session-loop.js +29 -18
- package/server/engine/agents/explorer.md +1 -1
- package/server/engine/agents/index.js +1 -1
- package/server/engine/mcp/index.js +4 -11
- package/server/engine/mcp/tools/arrange-on-board.js +3 -1
- package/server/engine/mcp/tools/browse-computer.js +2 -1
- package/server/engine/mcp/tools/browse-find-batch.js +20 -3
- package/server/engine/mcp/tools/browse.js +24 -2
- package/server/engine/mcp/tools/crystallize-skill.js +1 -1
- package/server/engine/mcp/tools/generate-image.js +50 -70
- package/server/engine/mcp/tools/helpers/codex-imagegen.js +139 -0
- package/server/engine/mcp/tools/helpers/rembg.js +7 -2
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +109 -0
- package/server/engine/mcp/tools/list-pages.js +11 -5
- package/server/engine/mcp/tools/organize-board.js +26 -1
- package/server/engine/mcp/tools/pin-to-board.js +26 -17
- package/server/engine/mcp/tools/read-board.js +19 -5
- package/server/engine/mcp/tools/relate-on-board.js +42 -4
- package/server/engine/mcp/tools/report-issue.js +14 -7
- package/server/engine/mcp/tools/screenshot.js +17 -7
- package/server/engine/mcp/tools/sketch-on-board.js +13 -5
- package/server/engine/mcp/tools/web-search.js +11 -15
- package/server/engine/mcp/tools/write-on-board.js +15 -6
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +3 -4
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +10 -1
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +4 -4
- package/server/index.js +0 -2
- package/server/lib/artifact-target.js +31 -14
- package/server/lib/board-kind-sizes.js +2 -3
- package/server/lib/canvas-id.js +29 -4
- package/server/lib/export-collect.js +3 -1
- package/server/lib/issues-store.js +3 -1
- package/server/lib/kinds/docx.js +11 -4
- package/server/lib/kinds/index.js +6 -16
- package/server/lib/kinds/site.js +40 -7
- package/server/lib/rewrite-refs.js +110 -0
- package/server/lib/ssrf-guard.js +9 -0
- package/server/lib/task-scan.js +18 -0
- package/server/projects/board-store.js +42 -1
- package/server/projects/memory-migration.js +85 -0
- package/server/projects/move-entry.js +11 -1
- package/server/projects/ui-config.js +2 -1
- package/server/projects/workspace-templates.js +13 -10
- package/server/projects/workspace.js +15 -15
- package/server/services/rembg-launcher.js +5 -2
- package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-BoOBaIyz.js} +1 -1
- package/web/dist/assets/DeckWindow-Bg3rgkvi.js +11 -0
- package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B_u6aX9o.js} +1 -1
- package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-BO-46Du5.js} +6 -6
- package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-ConPmzdM.js} +1 -1
- package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-FCO9yf-n.js} +1 -1
- package/web/dist/assets/{arc-D_khH_xN.js → arc-BLcEOSRX.js} +1 -1
- package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-kxiX4TeN.js} +1 -1
- package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-CptFksix.js} +1 -1
- package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-Dxmxj34F.js} +1 -1
- package/web/dist/assets/channel-DFyiuGL0.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-D9inx5oo.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DydfHlEc.js} +1 -1
- package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-BO7GxhSv.js} +1 -1
- package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BzP6YiQ_.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-CHcgcd0s.js} +1 -1
- package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-D2Wg5JtX.js} +1 -1
- package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-B2fCRfpM.js} +1 -1
- package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-JSLcsRlZ.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BCjvaeHm.js} +1 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-0uKv6oJ7.js} +1 -1
- package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-Bvt-gEmA.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-C9Ht02qC.js} +1 -1
- package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BfxZny4s.js} +1 -1
- package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-CrE0ZETE.js} +1 -1
- package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-BO8VQ4Fx.js} +1 -1
- package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-CHUZnR9F.js} +1 -1
- package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-DYQAz1Vu.js} +1 -1
- package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII-BDjivgLr.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Bpe_QVbH.js} +1 -1
- package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-BxhwuAu1.js} +1 -1
- package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-CCw2H08-.js} +1 -1
- package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-DtTc7MyI.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-BvORxMd2.js} +1 -1
- package/web/dist/assets/index-DGG5wIZs.js +2118 -0
- package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-DMcrkypD.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CZkTIbe-.js} +1 -1
- package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-DR0XazdE.js} +1 -1
- package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DN8VbwVu.js} +1 -1
- package/web/dist/assets/{layout-CnXiF_NB.js → layout-9_paCbyt.js} +1 -1
- package/web/dist/assets/{linear-BMW0u6G6.js → linear-g6Ct012e.js} +1 -1
- package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-BGt4omYC.js} +1 -1
- package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-uSfS-PBE.js} +1 -1
- package/web/dist/assets/{pending-edit-apply-AcdOQ9Ij.js → pending-edit-apply-uP4uvrOD.js} +1 -1
- package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DpeI935H.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-B7IuNvWa.js} +1 -1
- package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-ARzYpPQI.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-Cj4upkAo.js} +1 -1
- package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-CXJcyHWk.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-OaVwSnLP.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-1yrUbq4t.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-nYH5p9D6.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-CTtTVKoH.js} +1 -1
- package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-Dj5HvyNq.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +1 -0
- package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-umeT0HpB.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +8 -0
- package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-MqnQfHKB.js} +1 -1
- package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-b2liyZra.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-QMK-zNh1.js} +1 -1
- package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D5Mc6tfA.js} +1 -1
- package/web/dist/index.html +1 -1
- package/server/api/memory.js +0 -185
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +0 -583
- package/server/engine/mcp/tools/record-decision.js +0 -140
- package/web/dist/assets/DeckWindow-BpLv5082.js +0 -16
- package/web/dist/assets/channel-DGkccMO1.js +0 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +0 -1
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +0 -1
- package/web/dist/assets/index-GAamh1p4.js +0 -2152
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
|
@@ -31,18 +31,24 @@ export function makeReportIssueTool({ projectId, sessionId, ctx }) {
|
|
|
31
31
|
return tool(
|
|
32
32
|
'report_issue',
|
|
33
33
|
`Write to the maintainer of this HARNESS — the tools and environment you work
|
|
34
|
-
in. This goes to their issue list, not to the user.
|
|
34
|
+
in. This goes to their issue list, not to the user.
|
|
35
|
+
|
|
36
|
+
This harness is in BETA and you are one of its primary testers: the maintainer
|
|
37
|
+
actively wants these reports and reads every one. When you hesitate between
|
|
38
|
+
reporting and not reporting — report. Three kinds:
|
|
35
39
|
|
|
36
40
|
- "bug": something in the harness behaved WRONGLY — a tool crashed on valid
|
|
37
41
|
input, returned corrupt output, or contradicted its own description.
|
|
38
42
|
- "friction": it worked, but the path was painful — a missing parameter, a
|
|
39
|
-
return shape you had to fight, a workaround
|
|
40
|
-
|
|
43
|
+
return shape you had to fight, a workaround (one detour is enough to report;
|
|
44
|
+
the workaround IS the report).
|
|
41
45
|
- "idea": nothing is broken, but while working you saw something the harness
|
|
42
46
|
could do better or do at all — a tool that should exist, a flag that would
|
|
43
|
-
collapse three steps into one, a default that fights real usage
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
collapse three steps into one, a default that fights real usage, or a
|
|
48
|
+
METHODOLOGY you worked out on the job (a technique, a judgment rule, a
|
|
49
|
+
sequence that reliably works) that the harness should teach every session,
|
|
50
|
+
bake into a skill, or automate. Good ones get built — report it rather than
|
|
51
|
+
let it evaporate.
|
|
46
52
|
|
|
47
53
|
This is NOT a substitute for doing the work. Finish the task the way you would
|
|
48
54
|
have anyway — route around the problem, deliver the result — and log this on
|
|
@@ -50,7 +56,8 @@ the side. Never report and then hand back less than you could have.
|
|
|
50
56
|
|
|
51
57
|
Do NOT report:
|
|
52
58
|
- Your own mistakes (wrong path, malformed edit) — not harness issues
|
|
53
|
-
-
|
|
59
|
+
- A one-off flake that a single retry fixed (the automatic layer counts those);
|
|
60
|
+
a flake that RECURS is friction — report it
|
|
54
61
|
- Anything about the design work itself (that belongs in the conversation)
|
|
55
62
|
- Praise or vague sentiment ("the tools are great", "X could be better
|
|
56
63
|
somehow") — a report with no actionable content is noise
|
|
@@ -19,7 +19,7 @@ import { can } from '../../../lib/kinds/index.js';
|
|
|
19
19
|
import { screenshotDocx } from './screenshot-docx.js';
|
|
20
20
|
import { SITE_DEVICE_W } from './helpers/perception-page.js';
|
|
21
21
|
import { acquireArtifactPage, LIVE_PARAM_DESC } from './helpers/acquire-page.js';
|
|
22
|
-
import { normalizeShot, detectPaintTransform, attachPageDiagnostics, runWaitFor, runBeforeShot } from './helpers/shot-pipeline.js';
|
|
22
|
+
import { normalizeShot, detectPaintTransform, attachPageDiagnostics, runWaitFor, runBeforeShot, shotWithFallback, longPageSheet, clipShotWithFallback } from './helpers/shot-pipeline.js';
|
|
23
23
|
import { recordMotion, pickNearestFrames, composeSheet, encodeWebm, motionCaptionLines } from './helpers/motion-lab.js';
|
|
24
24
|
import { wheelScroll, elementMotionReport, elementMotionLines } from './helpers/motion-scroll.js';
|
|
25
25
|
|
|
@@ -428,6 +428,8 @@ Do NOT use this tool when:
|
|
|
428
428
|
// (系统 fit script 包 frame + scroll-snap),locator.screenshot 自动
|
|
429
429
|
// 拿目标 section 的 bbox,不需要再操作 DOM。
|
|
430
430
|
let buf;
|
|
431
|
+
let shotDegraded = false;
|
|
432
|
+
let longPageNote = null;
|
|
431
433
|
let captureMode;
|
|
432
434
|
const targetSelector = selector
|
|
433
435
|
|| (pageIndex ? `section[data-page="${pageIndex}"]` : null);
|
|
@@ -476,13 +478,19 @@ Do NOT use this tool when:
|
|
|
476
478
|
};
|
|
477
479
|
clip.width = Math.max(1, Math.min(rect.width, rect.docW - clip.x));
|
|
478
480
|
clip.height = Math.max(1, Math.min(rect.height, rect.docH - clip.y));
|
|
479
|
-
buf = await page
|
|
481
|
+
({ buf, degraded: shotDegraded } = await clipShotWithFallback(page, { clip, selector: targetSelector }));
|
|
480
482
|
captureMode = `selector="${targetSelector}"`;
|
|
481
483
|
} else {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
484
|
+
// 超长页 fullPage → 滚动联络表(阈值与理由见 shot-pipeline.longPageSheet)
|
|
485
|
+
const sheet = fp ? await longPageSheet(page).catch(() => null) : null;
|
|
486
|
+
if (sheet) {
|
|
487
|
+
buf = sheet.buf; captureMode = sheet.mode; longPageNote = sheet.note;
|
|
488
|
+
} else {
|
|
489
|
+
({ buf, degraded: shotDegraded } = await shotWithFallback(page, { fullPage: fp, type: 'png' }));
|
|
490
|
+
captureMode = isSite
|
|
491
|
+
? `site ${device || 'desktop'} ${vp.width}px, fullPage=${fp}`
|
|
492
|
+
: `fullPage=${fp}`;
|
|
493
|
+
}
|
|
486
494
|
}
|
|
487
495
|
// live 页的 DPR 是会话的(1),detail:'normal' 的 0.6 光栅在这里事后缩
|
|
488
496
|
if (acq.live && rasterScale < 1) {
|
|
@@ -537,7 +545,9 @@ Do NOT use this tool when:
|
|
|
537
545
|
const shot = await normalizeShot(buf);
|
|
538
546
|
|
|
539
547
|
const captionParts = [
|
|
540
|
-
`Screenshot of ${target.relPath} (layout ${vp.width}x${vp.height} @${rasterScale}x raster, ${captureMode})
|
|
548
|
+
`Screenshot of ${target.relPath} (layout ${vp.width}x${vp.height} @${rasterScale}x raster, ${captureMode})`
|
|
549
|
+
+ (shotDegraded ? '(常规截图等稳定帧超时——页面在持续动画(WebGL/rAF),已改抓当前帧:画面是真实的某一瞬间,动画中间态属正常)' : '')
|
|
550
|
+
+ (longPageNote ? `(${longPageNote})` : ''),
|
|
541
551
|
];
|
|
542
552
|
// 加载通道写进 caption:agent 不用再靠"把 location.protocol 写进 DOM 再截一张"
|
|
543
553
|
// 去反推自己被什么方式打开了(问题库 iss_msxk2oci_0v0v 就是这么查了四轮)
|
|
@@ -19,10 +19,11 @@ import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
import { readBoard, patchBoard, commitStaging, removeByTag, TEXT_FONTS } from '../../../projects/board-store.js';
|
|
21
21
|
import { estimateSizeOn } from '../../../lib/board-kind-sizes.js';
|
|
22
|
-
import { layerOf, normalizeCanvasId } from '../../../lib/canvas-id.js';
|
|
22
|
+
import { layerOf, normalizeCanvasId, tagEnvelope } from '../../../lib/canvas-id.js';
|
|
23
23
|
import { BINDING_TYPE_IDS, BINDING_MATERIALS } from '../../../lib/binding-types.js';
|
|
24
24
|
import { UNIT, SKETCH_FIT, SKETCH_MAX, textBox, shapePath, layoutNodes, bboxOf, findSpot, fitFor } from '../../../lib/sketch-layout.js';
|
|
25
25
|
import { getViewpoint } from '../../../projects/viewpoint-store.js';
|
|
26
|
+
import { Events } from '../../agent/events.js';
|
|
26
27
|
|
|
27
28
|
const MAX_NODES = 40;
|
|
28
29
|
const MAX_SHAPES = 30;
|
|
@@ -248,9 +249,16 @@ Per sketch: ≤${MAX_NODES} nodes, ${MAX_SHAPES} shapes, ${MAX_EDGES} edges (spl
|
|
|
248
249
|
if (args.near) {
|
|
249
250
|
const nid = normalizeCanvasId(args.near);
|
|
250
251
|
const e = nid ? board.objects?.[nid] : null;
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
if (e && Number.isFinite(e.x)) {
|
|
253
|
+
zone = layerOf(nid, e, known);
|
|
254
|
+
anchor = { x: e.x, y: e.y, ...estimateSizeOn(after, nid, e) };
|
|
255
|
+
} else {
|
|
256
|
+
// near 也认 tag(08-23 案,同 write_on_board)
|
|
257
|
+
const env = tagEnvelope(board, args.near, (id2, e2) => estimateSizeOn(after, id2, e2));
|
|
258
|
+
if (!env) return err(`锚点 ${args.near} 还没有座位,也不是板上任何 tag(read_board 里看不到就锚不上)。`);
|
|
259
|
+
zone = layerOf(env.anchorId, board.objects[env.anchorId], known);
|
|
260
|
+
anchor = { x: env.x, y: env.y, w: env.w, h: env.h };
|
|
261
|
+
}
|
|
254
262
|
}
|
|
255
263
|
const obstacles = [];
|
|
256
264
|
let contentBottom = 0;
|
|
@@ -293,7 +301,7 @@ Per sketch: ≤${MAX_NODES} nodes, ${MAX_SHAPES} shapes, ${MAX_EDGES} edges (spl
|
|
|
293
301
|
try {
|
|
294
302
|
ctx?.emit?.({ type: 'board.updated', sessionId: null, summary: `画了一张草图 #${tag}` });
|
|
295
303
|
// 镜头提示(前端只在黑板模式跟随;不是劫持,是"黑板是主窗口"的约定)
|
|
296
|
-
ctx?.emit?.(
|
|
304
|
+
ctx?.emit?.(Events.boardFocus(world, { tag, layer: zone }));
|
|
297
305
|
} catch { /* fail-soft */ }
|
|
298
306
|
const lines = [
|
|
299
307
|
`Sketch #${tag} landed${staging ? ' as STAGING (半透明)' : ''}: ${Object.keys(objects).length - shapes.length} nodes, ${shapes.length} shapes, ${Object.keys(bindings).length} lines; layout ${tpl}; at world (${world.x},${world.y}) ${world.w}x${world.h}${anchor ? ` (${spot.side} of ${args.near})` : ' (below current content)'}.`,
|
|
@@ -393,6 +393,7 @@ Suggested flow for image-led pages:
|
|
|
393
393
|
// - 普通模式:tavily/exa/baidu/zhipu auto-route by language(CJK→baidu, EN→tavily)
|
|
394
394
|
// - include_images:zhipu 不支持图,从候选中剔除;其余按语言 auto-route
|
|
395
395
|
let providerId;
|
|
396
|
+
let providerNote = '';
|
|
396
397
|
if (include_images) {
|
|
397
398
|
if (provider === 'zhipu') {
|
|
398
399
|
return {
|
|
@@ -418,15 +419,13 @@ Suggested flow for image-led pages:
|
|
|
418
419
|
};
|
|
419
420
|
}
|
|
420
421
|
} else {
|
|
422
|
+
// 点名的 provider 没配 key:换配了的顶上别硬失败(结果>供应商身份,iss_mt5z4s8j)
|
|
421
423
|
providerId = provider;
|
|
422
424
|
if (!getKey(providerId)) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}],
|
|
428
|
-
isError: true,
|
|
429
|
-
};
|
|
425
|
+
const alt = ((looksChinese(query) ? PRIORITY_CJK : PRIORITY_NON_CJK).filter(id => id !== 'zhipu')).find(id => getKey(id)) || null;
|
|
426
|
+
if (!alt) return { content: [{ type: 'text', text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set, and no other image-capable provider configured.` }], isError: true };
|
|
427
|
+
providerNote = `(provider "${provider}" 没配 key,已换用 ${alt})`;
|
|
428
|
+
providerId = alt;
|
|
430
429
|
}
|
|
431
430
|
}
|
|
432
431
|
} else {
|
|
@@ -443,13 +442,10 @@ Suggested flow for image-led pages:
|
|
|
443
442
|
};
|
|
444
443
|
}
|
|
445
444
|
if (!getKey(providerId)) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}],
|
|
451
|
-
isError: true,
|
|
452
|
-
};
|
|
445
|
+
const alt = autoSelectProvider(query); // 只挑配了 key 的
|
|
446
|
+
if (!alt || !getKey(alt)) return { content: [{ type: 'text', text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set, and no other provider configured.` }], isError: true };
|
|
447
|
+
providerNote = `(provider "${providerId}" 没配 key,已换用 ${alt})`;
|
|
448
|
+
providerId = alt;
|
|
453
449
|
}
|
|
454
450
|
}
|
|
455
451
|
|
|
@@ -518,7 +514,7 @@ Suggested flow for image-led pages:
|
|
|
518
514
|
// count 张 + base64 体积。已在上一步通过 count 截断控量。
|
|
519
515
|
const out = [{
|
|
520
516
|
type: 'text',
|
|
521
|
-
text: formatMarkdown(query, providerId, hits, { images: downloadedImages })
|
|
517
|
+
text: `${providerNote ? `${providerNote}\n` : ''}${formatMarkdown(query, providerId, hits, { images: downloadedImages })}`,
|
|
522
518
|
}];
|
|
523
519
|
for (const img of downloadedImages) {
|
|
524
520
|
if (img.base64) {
|
|
@@ -17,10 +17,11 @@ import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
|
17
17
|
import { z } from 'zod';
|
|
18
18
|
import { readBoard, patchBoard } from '../../../projects/board-store.js';
|
|
19
19
|
import { estimateSizeOn } from '../../../lib/board-kind-sizes.js';
|
|
20
|
-
import { layerOf, normalizeCanvasId } from '../../../lib/canvas-id.js';
|
|
20
|
+
import { layerOf, normalizeCanvasId, tagEnvelope } from '../../../lib/canvas-id.js';
|
|
21
21
|
import { textBox, findSpot, SKETCH_FIT } from '../../../lib/sketch-layout.js';
|
|
22
22
|
import { getViewpoint } from '../../../projects/viewpoint-store.js';
|
|
23
23
|
import { renderChalk, chalkFileName, writeChalkFile, CHALK_DIR } from '../../../lib/chalk.js';
|
|
24
|
+
import { Events } from '../../agent/events.js';
|
|
24
25
|
|
|
25
26
|
let seq = 0;
|
|
26
27
|
const stamp = () => `${Date.now().toString(36)}${(seq++ % 1000).toString(36)}`;
|
|
@@ -91,10 +92,17 @@ Keep the chat reply short — a line pointing at the board is enough.`,
|
|
|
91
92
|
} else if (near) {
|
|
92
93
|
const nid = normalizeCanvasId(near);
|
|
93
94
|
const e = nid ? board.objects?.[nid] : null;
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
if (e && Number.isFinite(e.x)) {
|
|
96
|
+
anchorId = nid; zone = layerOf(nid, e, known);
|
|
97
|
+
const as = estimateSizeOn(board, nid, e);
|
|
98
|
+
pos = avoid({ x: e.x + as.w + 24, y: e.y }, obstaclesOf(zone), box.w, box.h);
|
|
99
|
+
} else {
|
|
100
|
+
// near 也认 tag(08-23 案):落到那片东西的包络右侧,锚线连最右那个
|
|
101
|
+
const env = tagEnvelope(board, near, (id2, e2) => estimateSizeOn(board, id2, e2));
|
|
102
|
+
if (!env) return err(`锚点 ${near} 还没有座位,也不是板上任何 tag(read_board 里看不到就锚不上)。`);
|
|
103
|
+
anchorId = env.anchorId; zone = layerOf(env.anchorId, board.objects[env.anchorId], known);
|
|
104
|
+
pos = avoid({ x: env.x + env.w + 24, y: env.y }, obstaclesOf(zone), box.w, box.h);
|
|
105
|
+
}
|
|
98
106
|
} else {
|
|
99
107
|
const obstacles = obstaclesOf('');
|
|
100
108
|
let bottom = 0; for (const o of obstacles) bottom = Math.max(bottom, o.y + o.h);
|
|
@@ -118,7 +126,8 @@ Keep the chat reply short — a line pointing at the board is enough.`,
|
|
|
118
126
|
const rect = { x: Math.round(pos.x), y: Math.round(pos.y), w: box.w, h: box.h };
|
|
119
127
|
try {
|
|
120
128
|
ctx?.emit?.({ type: 'board.updated', sessionId: null, summary: parentId ? '回了一条板书' : '写了一条板书' });
|
|
121
|
-
|
|
129
|
+
// chalk 字段进精灵追踪链(08-24):形状钉在 Events.boardFocus,别再内联对象
|
|
130
|
+
ctx?.emit?.(Events.boardFocus(rect, { tag: tag || null, layer: zone, soft: true, chalk: rel }));
|
|
122
131
|
} catch { /* */ }
|
|
123
132
|
const big = box.h > SKETCH_FIT.h * 0.6;
|
|
124
133
|
return { content: [{ type: 'text', text:
|
|
@@ -116,10 +116,9 @@ IP、角色、真人、某个具体产品型号、小众品牌 —— 那种情
|
|
|
116
116
|
用户点头之后,把这一份固化下来,它就是后面所有页的基准:
|
|
117
117
|
|
|
118
118
|
- `<style id="design-tokens">` 的 `:root` 整组写死(色号、字号、字体链)
|
|
119
|
-
- `
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
- 风格锚定之后顺手更新 `agent-memory/brand/memory.md`(风格档案),参照图留在
|
|
119
|
+
- 落锚进长期记忆:写一条 `type: project` 记忆(名字打头,如「风格锚 = 拼贴」,
|
|
120
|
+
取值写进正文:"纸纹底 + 微旋转 + 白边 + 手写体标注 + 一点樱桃红");项目级
|
|
121
|
+
定案还该进根 `CLAUDE.md` 的「风格档案」节(用户点头后)。参照图留在
|
|
123
122
|
`assets/references/` 别删 —— 后面几页跑偏时靠它回锚
|
|
124
123
|
|
|
125
124
|
锚说不出名字,说明还没定下来,回去再试一轮。
|
|
@@ -38,6 +38,13 @@ docx 的版式谱系跟网页不是一套,常用的这些:
|
|
|
38
38
|
| 简历 / 投递 | **单页履历体** | 窄边距、姓名区大字号、条目左对齐悬挂缩进、零装饰 |
|
|
39
39
|
| 手册 / 说明书 | **技术文档体** | 编号标题、等宽代码块、表格密排 |
|
|
40
40
|
|
|
41
|
+
⚠️ 公文体的坑(实测反馈,08-21 案):严格国标的三级标题**同缩进、纯换字体**
|
|
42
|
+
(黑/楷/仿宋),而预览的替身字体会吃掉楷体与仿宋的对比度 —— 三级在预览里几乎
|
|
43
|
+
不可辨,用户大概率抱怨"层级看不出来"。两条路先问一句:a) 严格国标(对方单位
|
|
44
|
+
真按 9704 收文时用);b) **层级强化变体**(用户没点名国标就默认它)——
|
|
45
|
+
Heading1 加 `spacing.beforePt: 8`、Heading2 加 `indent.leftChars: 200` + `bold: true`,
|
|
46
|
+
层级立刻一眼可辨且不破坏 exact 行距的页数稳定性。
|
|
47
|
+
|
|
41
48
|
内置词典条目有三个(`build_docx` 的 `preset`):`办公标准` / `公文` / `学术论文`。
|
|
42
49
|
**它们是起点不是菜单** —— 上表其余的用 preset 起手再覆写 tokens,或者干脆不写
|
|
43
50
|
preset 给完整 token 表。自造词作废("科技感文档"不是版式名),但凡是真实存在、
|
|
@@ -60,7 +67,9 @@ preset 给完整 token 表。自造词作废("科技感文档"不是版式名
|
|
|
60
67
|
新旧并排),⛔ 别覆盖旧版 —— 版本是给用户看的,不是给你腾地方的。
|
|
61
68
|
|
|
62
69
|
起手文件 `文档.template.json` 已经拷进工作区了 —— 照着它改,别从空白开始。
|
|
63
|
-
**字段全集 Read `references/token-schema.md
|
|
70
|
+
**字段全集 Read `references/token-schema.md`**(⚠️ 相对**本 SKILL 文件所在目录**,
|
|
71
|
+
不相对工作区 —— 用加载本 skill 时给出的 SKILL.md 路径拼出绝对路径去 Read,
|
|
72
|
+
别在工作区里找、更别 find / 全盘扫)—— 键名、值域、单位、块的形状、
|
|
64
73
|
常见错法对照全在那里,别靠猜,也别去读引擎源码。
|
|
65
74
|
|
|
66
75
|
**schema 是闭合的**:写了没登记过的字段名,`build_docx` 直接报错。这是故意的 ——
|
|
@@ -262,6 +262,22 @@
|
|
|
262
262
|
写成 `"para": {...}` **会被拒**(2026-08-18 起 content 和页眉页脚都过闭合校验)。
|
|
263
263
|
在那之前它是"不报错也不生效"——最难发现的一类错,起手模板自己教错过一次。
|
|
264
264
|
|
|
265
|
+
⭐ **对象型键(`spacing` / `indent` / `borders`)与样式里的同名键是属性级深合并**,
|
|
266
|
+
跟 tokens 对 preset 的合并规则一致:引擎只把你写的属性写进这一段的 pPr,
|
|
267
|
+
没写的属性照常从样式继承(OOXML 的属性级联就是这个粒度;2026-08-24 真渲染
|
|
268
|
+
实测证实——样式 `line:10/lineRule:"exact"` 的段,块级只写 `beforePt:30`,
|
|
269
|
+
行距不掉):
|
|
270
|
+
|
|
271
|
+
```jsonc
|
|
272
|
+
{ "t": "p", "style": "要点", "spacing": { "beforePt": 3 } }
|
|
273
|
+
// ✅ 只多 3pt 段前距;样式里的 line/lineRule 原样保留,不必重抄
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
唯一的例外是 `indent`:`firstLine*` 和 `hanging*` 互斥(见上),而深合并抹不掉
|
|
277
|
+
从样式继承来的属性——所以"样式带首行缩进的段想改成悬挂"**不要**靠块级开小灶
|
|
278
|
+
(继承的 firstLine 还在,两个属性并存后哪个生效随渲染器),正路是给这类段落
|
|
279
|
+
单独立一个不带首行缩进的样式。
|
|
280
|
+
|
|
265
281
|
### `{"t": "table"}` 表格
|
|
266
282
|
|
|
267
283
|
```jsonc
|
|
@@ -125,10 +125,10 @@ IP、角色、真人、某个具体产品型号、小众品牌 —— 那种情
|
|
|
125
125
|
2-4 个带小样的候选,不是一张风格大全。摊开菜单就退回模板思维了。
|
|
126
126
|
|
|
127
127
|
他已经点了名字("我要拼贴风")的,跳过前两步直接进试作;直接甩了参考图的,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
铺到第五页跑偏时靠它回锚。
|
|
128
|
+
读图读出那是什么风格、跟他确认一下名字,再进试作。名字定下来之后落锚进长期
|
|
129
|
+
记忆(一条 `type: project` 记忆,名字打头如「风格锚 = 拼贴」,取值写进正文);
|
|
130
|
+
项目级定案经用户点头后进根 `CLAUDE.md` 的「风格档案」节。参照图留在
|
|
131
|
+
`assets/references/` 别删 —— 铺到第五页跑偏时靠它回锚。
|
|
132
132
|
|
|
133
133
|
### 翻译词典 —— 你的词汇量,不是给用户的菜单
|
|
134
134
|
|
package/server/index.js
CHANGED
|
@@ -33,7 +33,6 @@ import turnRouter from './api/turn.js';
|
|
|
33
33
|
import exportsRouter from './api/exports.js';
|
|
34
34
|
import sessionsRouter from './api/sessions.js';
|
|
35
35
|
import instructionRouter from './api/instruction.js';
|
|
36
|
-
import memoryRouter from './api/memory.js';
|
|
37
36
|
import boardRouter from './api/board.js';
|
|
38
37
|
import pendingChangesRouter from './api/pending-changes.js';
|
|
39
38
|
import browseRouter from './api/browse.js';
|
|
@@ -118,7 +117,6 @@ app.use('/api/projects', turnRouter);
|
|
|
118
117
|
app.use('/api/projects', exportsRouter);
|
|
119
118
|
app.use('/api/projects', sessionsRouter);
|
|
120
119
|
app.use('/api/projects', instructionRouter);
|
|
121
|
-
app.use('/api/projects', memoryRouter);
|
|
122
120
|
app.use('/api/projects', boardRouter);
|
|
123
121
|
app.use('/api/projects', pendingChangesRouter); // C4: 用户直接编辑 + 评论 buffer
|
|
124
122
|
app.use('/api/projects', browseRouter); // 刷新后拿回浏览器窗/求助状态
|
|
@@ -95,7 +95,10 @@ export function clearActiveArtifact(sessionId) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
function normalizeRel(p) {
|
|
98
|
-
|
|
98
|
+
// 尾部 ?query / #hash 一并剥掉:agent 常把预览 URL 原样拿来当 path
|
|
99
|
+
//('dist/index.html?t=18&panel=0' 落 not found,08-24 清账两案)。
|
|
100
|
+
// 磁盘文件名里合法出现 ? # 的场景在这套产物约定里不存在。
|
|
101
|
+
return String(p).replace(/\\/g, '/').replace(/^\.\//, '').replace(/[?#].*$/, '');
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
async function exists(p) {
|
|
@@ -108,13 +111,9 @@ function insideWorkspace(workspaceRoot, absPath) {
|
|
|
108
111
|
return absPath === root || absPath.startsWith(root + path.sep);
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
export async function listSitePages(workspaceRoot) {
|
|
115
|
-
const m = await KINDS[KIND_SITE].manifest(workspaceRoot, null);
|
|
116
|
-
return m.pages;
|
|
117
|
-
}
|
|
114
|
+
// (listSitePages 2026-08-24 拆除:调用 KINDS.site.manifest 这个不存在的方法,
|
|
115
|
+
// 一调必 TypeError,全仓无调用方 —— 死代码假装公开 API。页面清单读
|
|
116
|
+
// taskManifest().artifacts[].pages。)
|
|
118
117
|
|
|
119
118
|
/**
|
|
120
119
|
* 推断某个 html 路径的产物形态。
|
|
@@ -209,11 +208,28 @@ export async function resolveArtifactTarget(workspaceRoot, relPath, sessionId) {
|
|
|
209
208
|
const hit = await tryPath(relPath);
|
|
210
209
|
if (hit) { setActiveArtifact(sessionId, hit.relPath, hit.kind); return hit; }
|
|
211
210
|
const abs = path.resolve(workspaceRoot, relPath);
|
|
211
|
+
if (!insideWorkspace(workspaceRoot, abs)) return { ok: false, message: 'path escapes workspace' };
|
|
212
|
+
// 回退:agent 常拿**产物根相对**或**源目录相对**路径来喂('index.html'、
|
|
213
|
+
// 'dist/index.html'),而这里收的是工作区相对('jet-engine/dist/index.html')。
|
|
214
|
+
// 没命中时按各站点实例的 root / srcRoot 补前缀再试一轮,省一次注定失败的
|
|
215
|
+
// 往返(08-24 案:三个工具三种写法全 not found)。
|
|
216
|
+
const m = await taskManifest(workspaceRoot);
|
|
217
|
+
const rel = normalizeRel(relPath);
|
|
218
|
+
const prefixes = new Set();
|
|
219
|
+
for (const a of (m?.artifacts || [])) {
|
|
220
|
+
if (a.kind !== KIND_SITE || a.single) continue;
|
|
221
|
+
if (a.root) prefixes.add(a.root);
|
|
222
|
+
if (a.srcRoot && a.srcRoot !== a.root) prefixes.add(a.srcRoot);
|
|
223
|
+
}
|
|
224
|
+
for (const pre of prefixes) {
|
|
225
|
+
const hit2 = await tryPath(`${pre}/${rel}`);
|
|
226
|
+
if (hit2) { setActiveArtifact(sessionId, hit2.relPath, hit2.kind); return hit2; }
|
|
227
|
+
}
|
|
228
|
+
const candidates = (m?.artifacts || []).map(a => a.entryRel).filter(Boolean);
|
|
212
229
|
return {
|
|
213
230
|
ok: false,
|
|
214
|
-
message:
|
|
215
|
-
?
|
|
216
|
-
: 'path escapes workspace',
|
|
231
|
+
message: `${relPath} not found. Write it first, or check the path (workspace-relative).`
|
|
232
|
+
+ (candidates.length ? `\nExisting artifact entries:\n${candidates.map(c => `- ${c}`).join('\n')}` : ''),
|
|
217
233
|
};
|
|
218
234
|
}
|
|
219
235
|
|
|
@@ -272,9 +288,10 @@ export function requireBrowsable(target) {
|
|
|
272
288
|
|
|
273
289
|
/** 给各工具复用的 path 参数描述(保持措辞一致) */
|
|
274
290
|
export const ARTIFACT_PATH_DESC =
|
|
275
|
-
'
|
|
276
|
-
+ '
|
|
277
|
-
+ '
|
|
291
|
+
'WORKSPACE-relative path of the artifact entry (deck: "canvas.html", '
|
|
292
|
+
+ 'root site: "index.html" or "dist/index.html", site in a folder: full path '
|
|
293
|
+
+ 'like "my-site/dist/index.html", word: "文档.docx"). All artifact tools share '
|
|
294
|
+
+ 'this one convention. Omit to use the artifact you are currently working on.';
|
|
278
295
|
|
|
279
296
|
// ── 兼容层:老名字继续可用,内部全部走上面的实现 ────────────────────────────
|
|
280
297
|
export const CANVAS_PATH_DESC = ARTIFACT_PATH_DESC;
|
|
@@ -19,7 +19,6 @@ export const ARTIFACT_PREVIEW_H = { deck: 360, site: 400, docx: 420, browse: 360
|
|
|
19
19
|
// file 是 224x32 的细条卡(parity 测试上岗第一天就逮住我猜成 160x120 ——
|
|
20
20
|
// 那是涂鸦的默认身位。别猜,抄表)
|
|
21
21
|
export const KIND_SIZES = {
|
|
22
|
-
doc: { w: 200, h: 96 },
|
|
23
22
|
image: { w: 200, h: 176 },
|
|
24
23
|
video: { w: 240, h: 160 },
|
|
25
24
|
note: { w: 200, h: 148 },
|
|
@@ -44,7 +43,6 @@ export function estimateSize(id, entry) {
|
|
|
44
43
|
return { w: entry.w, h: entry.h };
|
|
45
44
|
}
|
|
46
45
|
const s = String(id || '');
|
|
47
|
-
if (s.startsWith('doc:')) return KIND_SIZES.doc;
|
|
48
46
|
// 浏览器卡是**单例**,id 就是 'browse'(没有路径可挂 —— 它背后不是文件)。
|
|
49
47
|
// 不给这条分支的话它会掉到最后的 file 兜底(224×32 的细条),agent 摆位时
|
|
50
48
|
// 就会拿一个错的矩形去算"挨着谁摆、会不会压到谁"。
|
|
@@ -58,7 +56,8 @@ export function estimateSize(id, entry) {
|
|
|
58
56
|
}
|
|
59
57
|
if (IMG_EXT.test(s)) return KIND_SIZES.image;
|
|
60
58
|
if (VIDEO_EXT.test(s)) return KIND_SIZES.video;
|
|
61
|
-
|
|
59
|
+
// 文本类文件卡带内容预览(08-24:md/json 等升级出预览体),身位=note
|
|
60
|
+
if (/\.(md|markdown|txt|json|csv|ya?ml)$/i.test(s)) return KIND_SIZES.note;
|
|
62
61
|
return KIND_SIZES.file;
|
|
63
62
|
}
|
|
64
63
|
|
package/server/lib/canvas-id.js
CHANGED
|
@@ -10,21 +10,46 @@
|
|
|
10
10
|
export function normalizeCanvasId(raw) {
|
|
11
11
|
let id = String(raw || '').trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+|\/+$/g, '');
|
|
12
12
|
if (!id || id.includes('..')) return null;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// (doc:brand / doc:_root 映射 2026-08-24 拆除:项目文档并进根 CLAUDE.md,
|
|
14
|
+
// 记忆住 记忆/,都是普通画布文件,没有特殊 id 了)
|
|
15
15
|
if (!/^(deck|site|doc|text|scribble):/.test(id) && /\.html?$/i.test(id)) return `deck:${id}`;
|
|
16
16
|
return id;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 一个 tag 下所有座位的包络(08-23 案:near 只认节点 id 不认 tag,可 tag 本来
|
|
21
|
+
* 就是"一片东西的名字"——刚用 tag 画完草图,把 tag 传给 near 是自然写法)。
|
|
22
|
+
* 返回 { x, y, w, h, anchorId, ids } 或 null;anchorId = 最右那个(贴右侧摆用)。
|
|
23
|
+
* sizeOf(id, entry) 由调用方给(estimateSizeOn 绑着 board,不在这层 import)。
|
|
24
|
+
*/
|
|
25
|
+
export function tagEnvelope(board, tag, sizeOf) {
|
|
26
|
+
const hits = Object.entries(board?.objects || {})
|
|
27
|
+
.filter(([, e]) => e?.tag === tag && Number.isFinite(e?.x));
|
|
28
|
+
if (!hits.length) return null;
|
|
29
|
+
let x1 = Infinity; let y1 = Infinity; let x2 = -Infinity; let y2 = -Infinity;
|
|
30
|
+
let anchorId = null; let rightEdge = -Infinity;
|
|
31
|
+
for (const [id, e] of hits) {
|
|
32
|
+
const s = sizeOf(id, e) || { w: 0, h: 0 };
|
|
33
|
+
x1 = Math.min(x1, e.x); y1 = Math.min(y1, e.y);
|
|
34
|
+
x2 = Math.max(x2, e.x + s.w); y2 = Math.max(y2, e.y + s.h);
|
|
35
|
+
if (e.x + s.w > rightEdge) { rightEdge = e.x + s.w; anchorId = id; }
|
|
36
|
+
}
|
|
37
|
+
return { x: x1, y: y1, w: x2 - x1, h: y2 - y1, anchorId, ids: hits.map(([id]) => id) };
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
/**
|
|
20
41
|
* 它住在哪一层(服务端近似版):显式 zone 字段优先,其次沿路径往上找第一个
|
|
21
42
|
* 已知文件夹。已知集 = board.zones 的 key —— 比前端少了"服务端扫出来的任务
|
|
22
43
|
* 目录"这一路,没摆过的深层目录会归到根。read_board 的输出里写明这是近似。
|
|
23
44
|
*/
|
|
24
45
|
export function layerOf(id, entry, knownFolders) {
|
|
25
|
-
|
|
46
|
+
// 显式 zone 只认**真实存在的层**('' 或 zones 里有的文件夹)。历史上
|
|
47
|
+
// pin_to_board 往 zone 里写过 'assets/generated' 这类前端根本不当层渲染的
|
|
48
|
+
// 路径(iss_mt5t487g:pin 上来的图被判在"assets 层",arrange 跟根层的草图
|
|
49
|
+
// 节点一比就拒)——错标签直接落回按路径推,存量脏数据顺带自愈。
|
|
50
|
+
if (entry && typeof entry.zone === 'string'
|
|
51
|
+
&& (entry.zone === '' || knownFolders?.has?.(entry.zone))) return entry.zone;
|
|
26
52
|
const s = String(id);
|
|
27
|
-
if (s.startsWith('doc:')) return '';
|
|
28
53
|
const c = s.indexOf(':');
|
|
29
54
|
const p = (c > 0 && /^[a-z]+$/.test(s.slice(0, c))) ? s.slice(c + 1) : s;
|
|
30
55
|
let d = p.includes('/') ? p.slice(0, p.lastIndexOf('/')) : '';
|
|
@@ -145,7 +145,9 @@ export async function collectCard({ workspaceRoot, cardId }) {
|
|
|
145
145
|
const m = await taskManifest(workspaceRoot).catch(() => null);
|
|
146
146
|
const rootSite = (m?.artifacts || []).find(a => a.kind === 'site' && !a.single && !a.srcRoot);
|
|
147
147
|
for (const pg of (rootSite?.pages || [])) {
|
|
148
|
-
|
|
148
|
+
// ⚠️ pages 是**产物根相对**(构建型根站 root='dist' 时是 'index.html' 不是
|
|
149
|
+
// 'dist/index.html')——对着工作区根解析会抓到源文件或落空(08-24 对齐)
|
|
150
|
+
const pRel = (rootSite.root ? `${rootSite.root}/` : '') + String(pg).replace(/\\/g, '/');
|
|
149
151
|
const pAbs = await safeResolve(workspaceRoot, pRel);
|
|
150
152
|
if (!pAbs) continue;
|
|
151
153
|
try { if ((await fs.stat(pAbs)).isFile()) files.push({ abs: pAbs, rel: pRel }); } catch { /* 页面刚被删 */ }
|
|
@@ -59,7 +59,9 @@ db.exec(`
|
|
|
59
59
|
*/
|
|
60
60
|
export function signatureOf(text) {
|
|
61
61
|
const norm = String(text || '')
|
|
62
|
-
|
|
62
|
+
// 字符类里不能有空格:不然一个孤立的 "/" 会贪婪吞掉后面一整串词(08-24 案,
|
|
63
|
+
// artifact_find 输出里的 "→ https://…" 之后的文字被不定长吃掉,指纹漂移不聚合)
|
|
64
|
+
.replace(/[/~][\w.\-/@一-鿿]+/g, '<path>') // 路径
|
|
63
65
|
.replace(/\b[0-9a-f]{8,}\b/gi, '<hash>') // id / hash
|
|
64
66
|
.replace(/\d+/g, 'N') // 行号、字节数、耗时
|
|
65
67
|
.replace(/\s+/g, ' ')
|
package/server/lib/kinds/docx.js
CHANGED
|
@@ -264,16 +264,23 @@ export default {
|
|
|
264
264
|
async describe(taskDir, artifact) {
|
|
265
265
|
const label = `word ${artifact.entryRel}`;
|
|
266
266
|
const src = artifact.sourceFile;
|
|
267
|
+
// 文件夹里的**全部**成员都要点名(08-19 案:目录里 v3/v4 两份,清单只报
|
|
268
|
+
// 默认那份,agent 对着过时的 v3 干了一整轮 —— 注入的清单让人默认它是全的,
|
|
269
|
+
// 半份状态比没状态更误导)
|
|
270
|
+
const others = (artifact.members || [])
|
|
271
|
+
.map(m => m.file)
|
|
272
|
+
.filter(f => f && f !== artifact.entryRel);
|
|
273
|
+
const memberNote = others.length ? ` · 同夹还有:${others.join(' / ')}` : '';
|
|
267
274
|
try {
|
|
268
275
|
const stat = await fs.stat(path.join(taskDir, artifact.entryRel));
|
|
269
276
|
const kb = (stat.size / 1024).toFixed(0);
|
|
270
277
|
return src
|
|
271
|
-
? `${label} · ${kb}KB · 源 ${src}(改源重建,别直接改 .docx
|
|
272
|
-
: `${label} · ${kb}KB · 外来文档,没有 token
|
|
278
|
+
? `${label} · ${kb}KB · 源 ${src}(改源重建,别直接改 .docx)${memberNote}`
|
|
279
|
+
: `${label} · ${kb}KB · 外来文档,没有 token 源${memberNote}`;
|
|
273
280
|
} catch {
|
|
274
281
|
return src
|
|
275
|
-
? `${label} · 还没构建(源 ${src}
|
|
276
|
-
: `${label} ·
|
|
282
|
+
? `${label} · 还没构建(源 ${src} 已就位)${memberNote}`
|
|
283
|
+
: `${label} · 入口读不到${memberNote}`;
|
|
277
284
|
}
|
|
278
285
|
},
|
|
279
286
|
|
|
@@ -30,7 +30,9 @@ import docx from './docx.js';
|
|
|
30
30
|
|
|
31
31
|
export const KINDS = Object.freeze({ [deck.id]: deck, [site.id]: site, [docx.id]: docx });
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
import { readTaskMarker } from '../task-scan.js';
|
|
34
|
+
|
|
35
|
+
export { RESERVED_DIRS, isReservedFile, readTaskMarker } from '../task-scan.js';
|
|
34
36
|
|
|
35
37
|
export function kindDef(kind) {
|
|
36
38
|
return KINDS[kind] || null;
|
|
@@ -57,21 +59,9 @@ export function can(kind, cap) {
|
|
|
57
59
|
return !!KINDS[kind]?.capabilities?.includes(cap);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* 2026-08-07 从 `.nd-task.json` 改名。旧文件里有两个字段:`sessionId`(这个
|
|
64
|
-
* 任务属于哪次对话 —— 正是要废的那条绑定,迁移时直接删)和 `root`(构建型
|
|
65
|
-
* 站点显式声明产物根,agent 刚写完源还没 build 的窗口期靠它认出"站已经在了")。
|
|
66
|
-
* 后者还有用,所以标记本身留着,只是不再承载归属。
|
|
67
|
-
*/
|
|
68
|
-
export async function readTaskMarker(root) {
|
|
69
|
-
try {
|
|
70
|
-
const raw = await fs.readFile(path.join(root, '.nd-project.json'), 'utf8');
|
|
71
|
-
const parsed = JSON.parse(raw);
|
|
72
|
-
return (parsed && typeof parsed === 'object') ? parsed : null;
|
|
73
|
-
} catch { return null; }
|
|
74
|
-
}
|
|
62
|
+
// readTaskMarker 本体 2026-08-24 迁去 task-scan.js(site.js 要按子目录读 marker,
|
|
63
|
+
// 住这里会循环 import)。上面 re-export 保住老调用方。历史注释一并搬走:
|
|
64
|
+
// `.nd-project.json` 2026-08-07 从 `.nd-task.json` 改名,只剩 `root` 字段有用。
|
|
75
65
|
|
|
76
66
|
/**
|
|
77
67
|
* 任务的完整 manifest —— 服务端唯一的解析器,前端、感知工具、导出都吃这份。
|