@xiaobuyu/nodesign 0.0.6 → 0.0.8
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 +266 -79
- package/README.zh-CN.md +254 -67
- package/package.json +2 -2
- package/server/api/admin.js +7 -0
- package/server/api/assets/docx-page.js +5 -4
- package/server/api/assets/helpers.js +26 -0
- package/server/api/assets/notes.js +50 -0
- package/server/api/assets.js +31 -38
- package/server/api/board.js +29 -1
- package/server/api/browse.js +3 -2
- package/server/api/chatai.js +9 -8
- package/server/api/exports/cards.js +7 -6
- package/server/api/exports/docx-pdf.js +4 -1
- package/server/api/instruction.js +3 -3
- package/server/api/local.js +10 -8
- package/server/api/projects.js +19 -3
- package/server/api/publish.js +3 -2
- package/server/api/sessions-rewind.js +220 -0
- package/server/api/sessions.js +58 -194
- package/server/api/stage.js +35 -0
- package/server/api/turn-model-switch.js +109 -0
- package/server/api/turn.js +52 -88
- package/server/auth/middleware.js +29 -6
- package/server/auth/tier.js +27 -7
- package/server/auth/users-store.js +14 -1
- package/server/engine/agent/actor-trail.js +110 -0
- package/server/engine/agent/agent-shared.js +22 -52
- package/server/engine/agent/beat-state.js +61 -0
- package/server/engine/agent/cast.js +322 -0
- package/server/engine/agent/events.js +18 -1
- package/server/engine/agent/hooks/beat-gate.js +73 -0
- package/server/engine/agent/hooks/failure.js +11 -1
- package/server/engine/agent/hooks/lifecycle.js +16 -41
- package/server/engine/agent/hooks/post-guidance.js +4 -38
- package/server/engine/agent/hooks/post-trim.js +1 -0
- package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
- package/server/engine/agent/hooks/pre-defaults.js +206 -2
- package/server/engine/agent/hooks/pre-injectors.js +19 -2
- package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
- package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
- package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
- package/server/engine/agent/hooks/slot-alias.js +30 -0
- package/server/engine/agent/hooks/user-prompt-submit.js +208 -47
- package/server/engine/agent/hooks.js +80 -14
- package/server/engine/agent/isolation.js +25 -2
- package/server/engine/agent/memory-config.js +87 -0
- package/server/engine/agent/model-context.js +102 -18
- package/server/engine/agent/model-table.js +361 -58
- package/server/engine/agent/prompts/nodesign-prelude.md +579 -70
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +26 -9
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
- package/server/engine/agent/prompts/tools/site-reference.md +15 -4
- package/server/engine/agent/role-card.js +175 -0
- package/server/engine/agent/rp-mode.js +17 -0
- package/server/engine/agent/session-loop.js +55 -32
- package/server/engine/agent/stage-status.js +73 -0
- package/server/engine/agent/subagent-flight.js +73 -0
- package/server/engine/agent/system-prompts.js +41 -0
- package/server/engine/agent/tool-input-stream.js +165 -0
- package/server/engine/agents/explorer.md +1 -1
- package/server/engine/agents/index.js +1 -1
- package/server/engine/mcp/actor.js +59 -0
- package/server/engine/mcp/capability-gate.js +21 -2
- package/server/engine/mcp/index.js +118 -33
- package/server/engine/mcp/mode-profile.js +139 -0
- package/server/engine/mcp/server-name.js +19 -0
- package/server/engine/mcp/tools/artifact-session.js +2 -1
- package/server/engine/mcp/tools/browse-computer.js +2 -1
- package/server/engine/mcp/tools/browse-find-batch.js +95 -16
- package/server/engine/mcp/tools/browse.js +25 -3
- package/server/engine/mcp/tools/cast-role.js +167 -0
- package/server/engine/mcp/tools/crystallize-skill.js +1 -1
- package/server/engine/mcp/tools/draw-trend.js +104 -0
- package/server/engine/mcp/tools/edit-board-schema.js +56 -0
- package/server/engine/mcp/tools/edit-board.js +569 -0
- 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/look-at-board.js +4 -3
- package/server/engine/mcp/tools/open-sheet.js +242 -0
- package/server/engine/mcp/tools/organize-board.js +45 -6
- package/server/engine/mcp/tools/paint-still.js +1 -1
- package/server/engine/mcp/tools/pin-to-board.js +103 -19
- package/server/engine/mcp/tools/read-board.js +123 -16
- package/server/engine/mcp/tools/read-tavern-json.js +87 -19
- package/server/engine/mcp/tools/report-issue.js +15 -7
- package/server/engine/mcp/tools/role-memory.js +83 -0
- package/server/engine/mcp/tools/roll-dice.js +82 -0
- package/server/engine/mcp/tools/roll-film.js +1 -1
- package/server/engine/mcp/tools/screenshot.js +19 -9
- package/server/engine/mcp/tools/set-vars.js +150 -0
- package/server/engine/mcp/tools/sheet-replan.js +46 -0
- package/server/engine/mcp/tools/web-search.js +12 -16
- package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
- package/server/engine/mcp/tools/write-on-board-place.js +211 -0
- package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
- package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
- package/server/engine/mcp/tools/write-on-board.js +568 -99
- package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +14 -7
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +44 -5
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +24 -8
- package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
- package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
- package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
- package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
- package/server/engine/runs/board-seater.js +272 -0
- package/server/engine/runs/board-tasklist.js +90 -48
- package/server/engine/runs/turn-relay.js +24 -2
- package/server/engine/runs/user-chalk-echo.js +44 -0
- package/server/index.js +2 -2
- package/server/lib/artifact-target.js +31 -14
- package/server/lib/board-anchor.js +49 -0
- package/server/lib/board-dirty.js +93 -0
- package/server/lib/board-excerpt.js +80 -0
- package/server/lib/board-follow.js +111 -0
- package/server/lib/board-groups.js +1 -5
- package/server/lib/board-hero.js +3 -1
- package/server/lib/board-kind-sizes.js +38 -3
- package/server/lib/board-lanes.js +58 -0
- package/server/lib/board-obstacles.js +80 -0
- package/server/lib/board-relations.js +5 -1
- package/server/lib/board-sheets.js +467 -0
- package/server/lib/board-shelf.js +86 -0
- package/server/lib/board-transform.js +62 -0
- package/server/lib/canvas-id.js +46 -4
- package/server/lib/chalk-flow.js +88 -0
- package/server/lib/chalk-rewrite.js +63 -0
- package/server/lib/chalk-size-pref.js +48 -0
- package/server/lib/chalk.js +42 -3
- package/server/lib/export-collect.js +3 -1
- package/server/lib/ingress/forward-openai-chat.js +16 -4
- package/server/lib/ingress/openai-chat.js +23 -12
- package/server/lib/ingress/session-routes.js +3 -3
- package/server/lib/ingress/stream-watchdog.js +53 -0
- package/server/lib/ingress/upstream-billing.js +15 -0
- package/server/lib/ingress/upstream-error-hints.js +36 -0
- package/server/lib/issues-store.js +3 -1
- package/server/lib/json-preview.js +58 -0
- 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/model-ingress.js +26 -1
- package/server/lib/moderation.js +10 -5
- package/server/lib/quota.js +26 -6
- package/server/lib/rect.js +50 -0
- package/server/lib/rewrite-refs.js +110 -0
- package/server/lib/screen.js +36 -0
- package/server/lib/sheet-state.js +19 -0
- package/server/lib/sketch-array.js +118 -0
- package/server/lib/sketch-hatch.js +99 -0
- package/server/lib/sketch-layout.js +269 -69
- package/server/lib/sketch-shapes.js +152 -0
- package/server/lib/sketch-stencils.js +170 -0
- package/server/lib/ssrf-guard.js +9 -0
- package/server/lib/state-table.js +237 -0
- package/server/lib/state-trend.js +94 -0
- package/server/lib/state-triggers.js +199 -0
- package/server/lib/task-scan.js +18 -0
- package/server/lib/tavern-json.js +48 -0
- package/server/lib/user-uploads.js +88 -0
- package/server/projects/board-limits.js +5 -3
- package/server/projects/board-sanitize.js +220 -10
- package/server/projects/board-store.js +137 -7
- package/server/projects/memory-migration.js +85 -0
- package/server/projects/move-entry.js +11 -1
- package/server/projects/session-jsonl.js +110 -0
- package/server/projects/store.js +56 -3
- package/server/projects/ui-config.js +2 -1
- package/server/projects/viewpoint-store.js +35 -3
- package/server/projects/workspace-slots.js +30 -0
- package/server/projects/workspace-templates.js +42 -10
- package/server/projects/workspace.js +25 -26
- package/server/runtime/capabilities.js +6 -2
- package/server/services/rembg-launcher.js +5 -2
- package/server/shared/locales.js +70 -0
- package/server/shared/messages-en.js +69 -0
- package/server/shared/messages.js +65 -0
- package/server/ws/broker.js +3 -0
- package/server/ws/index.js +43 -3
- package/web/README.md +1 -1
- package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-CcaGRWz5.js} +1 -1
- package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
- package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B9KZmU5z.js} +2 -2
- package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-DrfRnz8E.js} +43 -43
- package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-CJOjCF7d.js} +6 -6
- package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
- package/web/dist/assets/{arc-D_khH_xN.js → arc-C1-HSUny.js} +1 -1
- package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
- package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
- package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
- package/web/dist/assets/channel-BxpWNOGU.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
- package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
- package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
- package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
- package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
- package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
- package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
- package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
- package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
- package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
- package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
- package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
- package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
- package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
- package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
- package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
- package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
- package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
- package/web/dist/assets/index-BYKfMnmv.js +2759 -0
- package/web/dist/assets/index-CW7VsrVg.css +1 -0
- package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
- package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
- package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
- package/web/dist/assets/{layout-CnXiF_NB.js → layout-Dhgck-Zo.js} +1 -1
- package/web/dist/assets/{linear-BMW0u6G6.js → linear-DMSQ-aUw.js} +1 -1
- package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
- package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
- package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
- package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
- package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
- package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
- package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-62liz7LX.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
- package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
- package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
- package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
- package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
- package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
- package/web/dist/index.html +2 -2
- 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/arrange-on-board.js +0 -124
- package/server/engine/mcp/tools/create-on-board.js +0 -152
- package/server/engine/mcp/tools/edit-sketch.js +0 -167
- package/server/engine/mcp/tools/record-decision.js +0 -140
- package/server/engine/mcp/tools/relate-on-board.js +0 -112
- package/server/engine/mcp/tools/sketch-on-board.js +0 -337
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
- 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 +0 -78
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
- 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-C0KUf6Wd.css +0 -1
- package/web/dist/assets/index-GAamh1p4.js +0 -2152
- package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
- package/web/dist/assets/pending-edit-apply-AcdOQ9Ij.js +0 -31
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
|
@@ -6,12 +6,29 @@
|
|
|
6
6
|
* 行数棘轮,语义一个字没改。
|
|
7
7
|
*
|
|
8
8
|
* schema 见 board-store 头注释。黑板字段(2026-08-23):
|
|
9
|
-
* object.tag / object.staging / text.data.format
|
|
9
|
+
* object.tag / object.staging / text.data.format / text.data.lid
|
|
10
10
|
* binding.material / binding.tag / binding.staging
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { isBindingType, isBindingMaterial } from '../lib/binding-types.js';
|
|
14
|
-
import {
|
|
14
|
+
import { ROLE_SLUG_RE } from '../engine/agent/cast.js';
|
|
15
|
+
import { DEFAULT_BOARD_SIZE, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS, MAX_LANES, MAX_SHEETS } from './board-limits.js';
|
|
16
|
+
import { ONE_SCREEN } from '../lib/screen.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 板上署名的白名单:'user' / 'agent' / 常驻角色 slug(`rp-*`,见 engine/agent/cast.js)。
|
|
20
|
+
* 认不出的一律丢掉(落回无归属),**不透传** —— 这个值由模型写,而它的读者有一串:
|
|
21
|
+
* 前端标题与线标签、read_board 的分段、板书注入、将来的收件箱路由。
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ **有东西寄生在这个值域上**:`lib/board-hero.js` 与 `web/src/lib/hero.js`(parity 对)
|
|
24
|
+
* 把「手画的线」判成 `by && by !== 'auto'` —— 那是个黑名单,只在「值域被这里钉死」的
|
|
25
|
+
* 前提下才与白名单等价。放宽这里的值域前,先去看那两处。
|
|
26
|
+
*/
|
|
27
|
+
function sanitizeBy(v) {
|
|
28
|
+
const s = typeof v === 'string' ? v.trim() : '';
|
|
29
|
+
if (s === 'user' || s === 'agent') return s;
|
|
30
|
+
return ROLE_SLUG_RE.test(s) ? s : null;
|
|
31
|
+
}
|
|
15
32
|
|
|
16
33
|
export function clampNum(v, min, max, fallback) {
|
|
17
34
|
const n = Number(v);
|
|
@@ -48,19 +65,19 @@ const CANVAS_NATIVE_KINDS = new Set(['scribble', 'text']);
|
|
|
48
65
|
const COORD_LIMIT = 1e6;
|
|
49
66
|
|
|
50
67
|
/** 涂鸦路径串上限。一条随手画的线约 300~800 字符,8000 够长且撑不爆 board.json */
|
|
51
|
-
const MAX_SCRIBBLE_PATH =
|
|
68
|
+
const MAX_SCRIBBLE_PATH = 24000;
|
|
52
69
|
/**
|
|
53
70
|
* 画布文字的字数上限。
|
|
54
71
|
*
|
|
55
72
|
* 它是"写在白板上的一句话",不是文档 —— 长东西该写成 .md(那是便利贴,
|
|
56
73
|
* agent 读得到)。2000 字够写一段说明,也撑不爆 board.json。
|
|
57
74
|
*/
|
|
58
|
-
const MAX_TEXT_LEN =
|
|
75
|
+
const MAX_TEXT_LEN = 8000;
|
|
59
76
|
/**
|
|
60
77
|
* md 档文字的上限(2026-08-23 黑板)。画布上的 markdown/KaTeX/mermaid 节点比手写
|
|
61
78
|
* 一句话长,但它仍是"黑板上的一块"不是文档 —— 想写长的走 .md。
|
|
62
79
|
*/
|
|
63
|
-
const MAX_MD_TEXT_LEN =
|
|
80
|
+
const MAX_MD_TEXT_LEN = 8000;
|
|
64
81
|
/** 文字的排版格式:plain = 原样手写;md = Markdown(含 KaTeX 与 mermaid 围栏) */
|
|
65
82
|
const TEXT_FORMATS = ['plain', 'md'];
|
|
66
83
|
/**
|
|
@@ -83,6 +100,7 @@ export function isSafeCanvasId(id) {
|
|
|
83
100
|
return !p.split('/').some(seg => seg === '..');
|
|
84
101
|
}
|
|
85
102
|
|
|
103
|
+
export { TAG_RE };
|
|
86
104
|
export function sanitizeTag(v) {
|
|
87
105
|
return typeof v === 'string' && TAG_RE.test(v) ? v : null;
|
|
88
106
|
}
|
|
@@ -117,6 +135,10 @@ function sanitizeCanvasData(kind, data) {
|
|
|
117
135
|
font: TEXT_FONTS.includes(data?.font) ? data.font : 'kai',
|
|
118
136
|
size: TEXT_SIZES.includes(data?.size) ? data.size : 'md',
|
|
119
137
|
color: ['ink', 'red', 'pencil', 'brass'].includes(data?.color) ? data.color : 'ink',
|
|
138
|
+
// lid = sketch_on_board 里那个局部 id(linfan / zhangwei…)。留着它,
|
|
139
|
+
// edit_sketch 才能按 agent 当初起的名字找回这个节点 —— 否则局部 id 一落定
|
|
140
|
+
// 就作废,加条线得先去翻上一次调用的返回。这层是白名单重建,不列就丢。
|
|
141
|
+
...(typeof data?.lid === 'string' && /^[A-Za-z0-9_-]{1,24}$/.test(data.lid) ? { lid: data.lid } : {}),
|
|
120
142
|
...sanitizeTransform(data),
|
|
121
143
|
};
|
|
122
144
|
}
|
|
@@ -151,9 +173,31 @@ export function sanitizeObject(o, size) {
|
|
|
151
173
|
...(o.expanded ? { expanded: true } : {}),
|
|
152
174
|
// 显式归属:'' = 明确无归属(覆盖 sid 派生),非空 = 所属工作区 id
|
|
153
175
|
...(typeof o.zone === 'string' && o.zone.length <= 300 ? { zone: o.zone } : {}),
|
|
154
|
-
// 出处(2026-08-14 agent
|
|
155
|
-
//
|
|
156
|
-
|
|
176
|
+
// 出处(2026-08-14 agent 摆位/建元素):谁**造**的这件东西。08-25 起也收
|
|
177
|
+
// 'user'(原来只认 agent,跟 bindings 三值不对称是口径病);08-26 起再收
|
|
178
|
+
// 常驻角色的 slug(rp-*)—— RP 场里板上大半东西是角色写的,全落回 'agent'
|
|
179
|
+
// 就丢了归属。⚠️ 这里是白名单,不是透传:写这个值的是模型,而 by 有一串读者
|
|
180
|
+
// (前端显示 / read_board 分段 / 板书注入 / 将来的收件箱路由)。
|
|
181
|
+
...(sanitizeBy(o.by) ? { by: sanitizeBy(o.by) } : {}),
|
|
182
|
+
// 座位出处(2026-08-25 范式重做):谁**摆**的这个座。三值:
|
|
183
|
+
// user = 用户亲手拖的 —— 出处记号+学习票源(inferFlowDir 认它学摆放方向)。
|
|
184
|
+
// 08-28 起**不再是禁令**(用户拍板全放开):move/reflow/组操作都挪得动,
|
|
185
|
+
// 但工具返回必须注明"原是用户亲手摆的",且他拖回去就是定案。
|
|
186
|
+
// auto = 入座算法排的 —— 可以被重排
|
|
187
|
+
// agent = agent 显式摆的 —— 用户可拖走(拖走后变 user)
|
|
188
|
+
// 没有这个字段,用户拖的和自动排的在数据上分不清
|
|
189
|
+
// (08-25 体检结论:老数据 by 被前端回写抹掉)。
|
|
190
|
+
// shelf = 暂存架上等安置(2026-08-30)—— 机器到货默认座。pin_to_board /
|
|
191
|
+
// edit_board move / 用户拖拽一改写 seat 就自然离架(lib/board-shelf.js)
|
|
192
|
+
...(o.seat === 'user' || o.seat === 'auto' || o.seat === 'agent' || o.seat === 'shelf' ? { seat: o.seat } : {}),
|
|
193
|
+
// 尺寸出处(2026-08-28):'user' = 这块板书的宽高是用户亲手拖出来的。
|
|
194
|
+
// 两个用途:① 重排/估算别拿内容宽度盖掉他调过的宽 ② 写入端拿它当**学习票源**
|
|
195
|
+
// 推断"他喜欢多宽的板书"(lib/chalk-size-pref.js)。跟 seat 是两件事 ——
|
|
196
|
+
// seat 说"谁摆的位置",sized 说"谁定的大小",用户可以只调一个。
|
|
197
|
+
...(o.sized === 'user' ? { sized: 'user' } : {}),
|
|
198
|
+
// 贴身跟随(2026-08-27 shapes 编辑面):这个涂鸦是"圈住 hug 那件东西"的记号,
|
|
199
|
+
// 挪那件东西时它跟着走(edit_board move/move_group/reflow;前端拖拽同口径)
|
|
200
|
+
...(typeof o.hug === 'string' && o.hug.length <= 300 ? { hug: o.hug } : {}),
|
|
157
201
|
// 分组标签 + 草稿位(2026-08-23 黑板)。staging = agent 这一轮还在打草稿:
|
|
158
202
|
// 入座不看它、read_board 默认不列它、画面上半透明;落定(commitStaging)清位。
|
|
159
203
|
...(sanitizeTag(o.tag) ? { tag: sanitizeTag(o.tag) } : {}),
|
|
@@ -188,12 +232,17 @@ export function sanitizeBinding(b) {
|
|
|
188
232
|
: {}),
|
|
189
233
|
// 谁画的。用户画的线 agent 不该擅自删,反过来也一样;auto = 机器可证的
|
|
190
234
|
// 引用关系(auto-relations.js 对账层专属,只有它增删自家 b:auto:* 条目)。
|
|
191
|
-
...(b.by === '
|
|
235
|
+
...(b.by === 'auto' ? { by: 'auto' } : sanitizeBy(b.by) ? { by: sanitizeBy(b.by) } : {}),
|
|
192
236
|
// 材质(2026-08-23 黑板):语义之外的第二个轴 —— 墨线/手绘/丝线。不落默认值,
|
|
193
237
|
// 渲染侧按语义给缺省材质;存了默认值以后改缺省就改不动存量。
|
|
194
238
|
...(isBindingMaterial(b.material) && b.material !== 'ink' ? { material: b.material } : {}),
|
|
195
239
|
...(sanitizeTag(b.tag) ? { tag: sanitizeTag(b.tag) } : {}),
|
|
196
240
|
...(b.staging === true ? { staging: true } : {}),
|
|
241
|
+
// 跟随线(2026-08-25 范式重做,RP「状态板重锚」案):follow = 目标 tag ——
|
|
242
|
+
// 这条线的 to 端永远指向该 tag 最新落板的那件,服务端在新件落板时自动重指
|
|
243
|
+
// 并把 from 端所在的组挪过去(board-follow.js)。followSide = 挪到哪一侧。
|
|
244
|
+
...(sanitizeTag(b.follow) ? { follow: sanitizeTag(b.follow) } : {}),
|
|
245
|
+
...(['right', 'left', 'above', 'below'].includes(b.followSide) ? { followSide: b.followSide } : {}),
|
|
197
246
|
};
|
|
198
247
|
}
|
|
199
248
|
|
|
@@ -218,6 +267,94 @@ export function sanitizeZone(z) {
|
|
|
218
267
|
};
|
|
219
268
|
}
|
|
220
269
|
|
|
270
|
+
/**
|
|
271
|
+
* 线(lane)注册表条目(2026-08-27 空间规划):{x,y,w,parent}。
|
|
272
|
+
* 键 = tag(线就是 tag,见 lib/board-lanes.js 头注);frontier 不存 —— 从成员现算。
|
|
273
|
+
*/
|
|
274
|
+
export function sanitizeLane(l) {
|
|
275
|
+
if (!l || typeof l !== 'object') return null;
|
|
276
|
+
const x = Number(l.x); const y = Number(l.y);
|
|
277
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
278
|
+
const parent = typeof l.parent === 'string' && l.parent.length <= 300 ? l.parent : null;
|
|
279
|
+
return {
|
|
280
|
+
x: Math.round(x), y: Math.round(y),
|
|
281
|
+
w: clampNum(l.w, 96, 2400, 480),
|
|
282
|
+
...(parent ? { parent } : {}),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 卷(2026-08-27 收纳器):{ at, by, label? }。有条目 = 这个 tag 收着。
|
|
288
|
+
* 只是视觉收纳的状态位 —— 成员对象的座位原样留在 objects 里(展开即归位),
|
|
289
|
+
* 落位引擎照旧把它们当障碍。
|
|
290
|
+
*/
|
|
291
|
+
export function sanitizeRoll(r) {
|
|
292
|
+
if (!r || typeof r !== 'object') return null;
|
|
293
|
+
const at = typeof r.at === 'string' && r.at.length <= 40 ? r.at : new Date().toISOString();
|
|
294
|
+
const by = typeof r.by === 'string' && r.by.length <= 40 ? r.by : 'user';
|
|
295
|
+
const label = typeof r.label === 'string' && r.label.trim() ? r.label.trim().slice(0, 60) : null;
|
|
296
|
+
return { at, by, ...(label ? { label } : {}) };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 纸(sheet 注册表,2026-08-29 纸范式):{ x, y, w, h, by?, at?, title? }。
|
|
301
|
+
* 键 = 纸名(TAG_RE 字符集)。纸是**分配纪律不是本体容器**:成员按几何派生
|
|
302
|
+
* (中心点落在纸内),这里只登记「这块地是谁在什么时候铺的、多大」。
|
|
303
|
+
* w/h 显式存 —— 纸尺寸取决于铺纸那一刻的设备档(手机纸小、桌面纸大),
|
|
304
|
+
* 不能全局常量化。
|
|
305
|
+
*/
|
|
306
|
+
/** 一张纸上最多规划几块地(版面切太碎就不是版面了) */
|
|
307
|
+
export const MAX_SLOTS = 24;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 版位(slot,2026-08-29 占位契约刀 E):开工时先把这一屏切成几块地,各自起名。
|
|
311
|
+
*
|
|
312
|
+
* ⚠️ 坐标**跟 at 同一套**:纸内局部像素、原点在版心左上角。不用格数 —— 同一组
|
|
313
|
+
* 字段两种单位是站主点过名的老坑("同字段按数值切单位=中间那段必错且不出声")。
|
|
314
|
+
*/
|
|
315
|
+
export function sanitizeSlot(s) {
|
|
316
|
+
if (!s || typeof s !== 'object') return null;
|
|
317
|
+
const x = Number(s.x); const y = Number(s.y);
|
|
318
|
+
const w = Number(s.w); const h = Number(s.h);
|
|
319
|
+
if (![x, y, w, h].every(Number.isFinite)) return null;
|
|
320
|
+
if (w < 48 || h < 24) return null; // 比这还小的地放不下一行字
|
|
321
|
+
return {
|
|
322
|
+
x: Math.round(Math.min(Math.max(0, x), 12000)),
|
|
323
|
+
y: Math.round(Math.min(Math.max(0, y), 12000)),
|
|
324
|
+
w: Math.round(Math.min(w, 12000)),
|
|
325
|
+
h: Math.round(Math.min(h, 12000)),
|
|
326
|
+
...(typeof s.about === 'string' && s.about.trim() ? { about: s.about.trim().slice(0, 60) } : {}),
|
|
327
|
+
// 收产物的那块地(2026-08-30 刀 G):`for:'artifacts'` 一张纸上最多一块。
|
|
328
|
+
// 产物(生成的图、写出来的文件、目录型产物)是 agent 干活的**副产品**,
|
|
329
|
+
// 它没法在写之前一件件点名落位 —— 但它可以提前说「这一页的产物都放这儿」。
|
|
330
|
+
// 没有这块地,机器就只能自己决定,那就又回到「机器定版面」了。
|
|
331
|
+
...(s.for === 'artifacts' ? { for: 'artifacts' } : {}),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function sanitizeSheet(s) {
|
|
336
|
+
if (!s || typeof s !== 'object') return null;
|
|
337
|
+
const x = Number(s.x); const y = Number(s.y);
|
|
338
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
339
|
+
const slots = {};
|
|
340
|
+
let slotN = 0;
|
|
341
|
+
for (const [nm, v] of Object.entries(s?.slots && typeof s.slots === 'object' ? s.slots : {})) {
|
|
342
|
+
if (slotN >= MAX_SLOTS) break;
|
|
343
|
+
if (!TAG_RE.test(nm)) continue;
|
|
344
|
+
const sl = sanitizeSlot(v);
|
|
345
|
+
if (sl) { slots[nm] = sl; slotN += 1; }
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
x: Math.round(x), y: Math.round(y),
|
|
349
|
+
w: clampNum(s.w, 240, 8000, ONE_SCREEN.w),
|
|
350
|
+
h: clampNum(s.h, 240, 12000, ONE_SCREEN.h),
|
|
351
|
+
...(sanitizeBy(s.by) ? { by: sanitizeBy(s.by) } : {}),
|
|
352
|
+
...(typeof s.at === 'string' && s.at.length <= 40 ? { at: s.at } : {}),
|
|
353
|
+
...(typeof s.title === 'string' && s.title.trim() ? { title: s.title.trim().slice(0, 60) } : {}),
|
|
354
|
+
...(slotN ? { slots } : {}),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
221
358
|
export function sanitizeBoard(raw) {
|
|
222
359
|
const size = sanitizeSize(raw?.size);
|
|
223
360
|
const objects = {};
|
|
@@ -247,6 +384,79 @@ export function sanitizeBoard(raw) {
|
|
|
247
384
|
// 主角覆盖(2026-08-14 agent 摆位):显式立的主角压过前端 pickHero 的推断。
|
|
248
385
|
// 存 id 不存理由 —— 理由在会话里,画布只要知道谁站 C 位
|
|
249
386
|
const hero = typeof raw?.hero === 'string' && raw.hero.length <= 300 ? raw.hero : null;
|
|
250
|
-
|
|
387
|
+
const lanes = {};
|
|
388
|
+
let lCount = 0;
|
|
389
|
+
for (const [name, l] of Object.entries(raw?.lanes && typeof raw.lanes === 'object' ? raw.lanes : {})) {
|
|
390
|
+
if (lCount >= MAX_LANES) break;
|
|
391
|
+
const tag = sanitizeTag(name);
|
|
392
|
+
if (!tag) continue;
|
|
393
|
+
const s = sanitizeLane(l);
|
|
394
|
+
if (s) { lanes[tag] = s; lCount += 1; }
|
|
395
|
+
}
|
|
396
|
+
const rolls = {};
|
|
397
|
+
let rCount = 0;
|
|
398
|
+
for (const [name, r] of Object.entries(raw?.rolls && typeof raw.rolls === 'object' ? raw.rolls : {})) {
|
|
399
|
+
if (rCount >= MAX_LANES) break; // 卷跟线同量级:一线一卷
|
|
400
|
+
const tag = sanitizeTag(name);
|
|
401
|
+
if (!tag) continue;
|
|
402
|
+
const s = sanitizeRoll(r);
|
|
403
|
+
if (s) { rolls[tag] = s; rCount += 1; }
|
|
404
|
+
}
|
|
405
|
+
const sheets = {};
|
|
406
|
+
let sCount = 0;
|
|
407
|
+
for (const [name, s0] of Object.entries(raw?.sheets && typeof raw.sheets === 'object' ? raw.sheets : {})) {
|
|
408
|
+
if (sCount >= MAX_SHEETS) break;
|
|
409
|
+
const nm = sanitizeTag(name);
|
|
410
|
+
if (!nm) continue;
|
|
411
|
+
const s = sanitizeSheet(s0);
|
|
412
|
+
if (s) { sheets[nm] = s; sCount += 1; }
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* 跟随规则(2026-08-30):`{ 组tag: { target, side?, label? } }`。
|
|
416
|
+
*
|
|
417
|
+
* 以前 follow 只有「线」这一种形态,而线要求两端**此刻都在板上**。真会话里最常见的
|
|
418
|
+
* 写法是开场先立规则再写第一章(skill 的原话就是「开场画一次状态板,然后立一条跟随
|
|
419
|
+
* 规则」)—— 那一刻目标 tag 还是空的,于是必炸。全库 5 次、跨 4 个项目,形态一模一样。
|
|
420
|
+
* 规则和线分开存之后,规则可以先立着,目标一出现 applyFollows 顺手把线接上。
|
|
421
|
+
*/
|
|
422
|
+
const follows = {};
|
|
423
|
+
let fCount = 0;
|
|
424
|
+
for (const [g, r] of Object.entries(raw?.follows && typeof raw.follows === 'object' ? raw.follows : {})) {
|
|
425
|
+
if (fCount >= 12) break;
|
|
426
|
+
const gt = sanitizeTag(g);
|
|
427
|
+
const target = sanitizeTag(r?.target);
|
|
428
|
+
if (!gt || !target || gt === target) continue;
|
|
429
|
+
follows[gt] = {
|
|
430
|
+
target,
|
|
431
|
+
...(['right', 'left', 'above', 'below'].includes(r?.side) ? { side: r.side } : {}),
|
|
432
|
+
...(typeof r?.label === 'string' && r.label.trim() ? { label: r.label.trim().slice(0, 60) } : {}),
|
|
433
|
+
};
|
|
434
|
+
fCount += 1;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* 待摆产物(2026-08-30 刀 G):磁盘上有、但板上还没地方放的工作区相对路径。
|
|
438
|
+
* 入座不再自己铺纸(那是「机器替 agent 定版面」),排不下就进这条队列,
|
|
439
|
+
* 每回合状态块点名,等 agent 规划出地方再落座。
|
|
440
|
+
*/
|
|
441
|
+
const pending = [];
|
|
442
|
+
for (const v of Array.isArray(raw?.pending) ? raw.pending : []) {
|
|
443
|
+
if (pending.length >= 50) break;
|
|
444
|
+
if (typeof v !== 'string') continue;
|
|
445
|
+
const t = v.trim();
|
|
446
|
+
if (!t || t.length > 300 || t.includes('..') || t.startsWith('/')) continue;
|
|
447
|
+
if (!pending.includes(t)) pending.push(t);
|
|
448
|
+
}
|
|
449
|
+
// 暂存架原点(2026-08-30,lib/board-shelf.js):机器到货码放的竖带左上角
|
|
450
|
+
const shelf = (raw?.shelf && Number.isFinite(Number(raw.shelf.x)) && Number.isFinite(Number(raw.shelf.y)))
|
|
451
|
+
? { x: clampNum(raw.shelf.x, -COORD_LIMIT, COORD_LIMIT, 0), y: clampNum(raw.shelf.y, -COORD_LIMIT, COORD_LIMIT, 0) }
|
|
452
|
+
: null;
|
|
453
|
+
return {
|
|
454
|
+
size, zones, objects, bindings,
|
|
455
|
+
...(hero ? { hero } : {}), ...(lCount ? { lanes } : {}), ...(rCount ? { rolls } : {}),
|
|
456
|
+
...(sCount ? { sheets } : {}),
|
|
457
|
+
...(fCount ? { follows } : {}),
|
|
458
|
+
...(pending.length ? { pending } : {}),
|
|
459
|
+
...(shelf ? { shelf } : {}),
|
|
460
|
+
};
|
|
251
461
|
}
|
|
252
462
|
|
|
@@ -20,15 +20,17 @@
|
|
|
20
20
|
import { promises as fs } from 'fs';
|
|
21
21
|
import path from 'path';
|
|
22
22
|
import { getSharedDir, ensureProjectWorkspace, gitRenamesSince } from './workspace.js';
|
|
23
|
-
import { CHALK_DIR } from '../lib/chalk.js';
|
|
23
|
+
import { CHALK_DIR, trashChalkFile } from '../lib/chalk.js';
|
|
24
|
+
import { estimateSizeOn } from '../lib/board-kind-sizes.js';
|
|
24
25
|
|
|
25
26
|
export {
|
|
26
27
|
DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS,
|
|
27
28
|
} from './board-limits.js';
|
|
28
29
|
export { TEXT_FONTS } from './board-sanitize.js';
|
|
29
|
-
import { DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS } from './board-limits.js';
|
|
30
|
+
import { DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS, MAX_LANES, MAX_SHEETS } from './board-limits.js';
|
|
30
31
|
import {
|
|
31
|
-
clampNum, sanitizeSize, sanitizeTag, sanitizeObject, sanitizeBinding, sanitizeZone, sanitizeBoard,
|
|
32
|
+
clampNum, sanitizeSize, sanitizeTag, sanitizeObject, sanitizeBinding, sanitizeZone, sanitizeBoard, sanitizeLane, sanitizeRoll, sanitizeSheet,
|
|
33
|
+
isSafeCanvasId,
|
|
32
34
|
} from './board-sanitize.js';
|
|
33
35
|
|
|
34
36
|
// 分区自动铺位常数 —— 与前端 BoardCanvas 的 ZONE_* 保持一致(数值约定,非共享代码)
|
|
@@ -107,7 +109,14 @@ export function patchBoard(pid, patch) {
|
|
|
107
109
|
if (!isSafeCanvasId(rawId)) continue;
|
|
108
110
|
const id = fwd(rawId);
|
|
109
111
|
if (o === null) { delete board.objects[id]; removed.add(id); continue; }
|
|
110
|
-
|
|
112
|
+
// **合并语义**(2026-08-25):patch 条目盖在已有条目上,缺席字段保留。
|
|
113
|
+
// 病根:前端本地 layout 会落后于服务端(board.json 一次性加载闸门),
|
|
114
|
+
// 入座把 agent 刚写的条目当新客回写一条 {x,y,z} —— 整条替换语义下
|
|
115
|
+
// by/seat/w/h/tag/staging 全被抹掉且零报错(08-25 体检三陷阱之②)。
|
|
116
|
+
// 合并后瘦条目只更新它带来的字段。要删整条传 null;单字段清除走
|
|
117
|
+
// 各自的专用路(commitStaging 清 staging、removeByTag 摘 tag)。
|
|
118
|
+
const merged = board.objects[id] ? { ...board.objects[id], ...o } : o;
|
|
119
|
+
const s = sanitizeObject(merged, board.size);
|
|
111
120
|
if (s && (board.objects[id] || Object.keys(board.objects).length < MAX_OBJECTS)) board.objects[id] = s;
|
|
112
121
|
}
|
|
113
122
|
}
|
|
@@ -115,13 +124,81 @@ export function patchBoard(pid, patch) {
|
|
|
115
124
|
for (const [id, b] of Object.entries(patch.bindings)) {
|
|
116
125
|
if (typeof id !== 'string' || id.length > 300) continue;
|
|
117
126
|
if (b === null) { delete board.bindings[id]; continue; }
|
|
118
|
-
|
|
127
|
+
// 合并语义同 objects(08-25):改材质/label 的瘦 patch 别抹掉 follow 等字段
|
|
128
|
+
const s = sanitizeBinding(board.bindings[id] ? { ...board.bindings[id], ...b } : b);
|
|
119
129
|
if (!s) continue;
|
|
120
130
|
// 端点也要转发:agent 本轮拿旧路径连的线,落下来必须连到新名字上
|
|
121
131
|
const fixed = { ...s, from: fwd(s.from), to: fwd(s.to) };
|
|
122
132
|
if (board.bindings[id] || Object.keys(board.bindings).length < MAX_BINDINGS) board.bindings[id] = fixed;
|
|
123
133
|
}
|
|
124
134
|
}
|
|
135
|
+
// 线注册表(08-27 空间规划):合并语义同 objects;parent 也走改名转发
|
|
136
|
+
if (patch?.lanes && typeof patch.lanes === 'object') {
|
|
137
|
+
board.lanes = board.lanes || {};
|
|
138
|
+
for (const [name, l] of Object.entries(patch.lanes)) {
|
|
139
|
+
const tag = sanitizeTag(name);
|
|
140
|
+
if (!tag) continue;
|
|
141
|
+
if (l === null) { delete board.lanes[tag]; continue; }
|
|
142
|
+
const s = sanitizeLane(board.lanes[tag] ? { ...board.lanes[tag], ...l } : l);
|
|
143
|
+
if (!s) continue;
|
|
144
|
+
const fixed = s.parent ? { ...s, parent: fwd(s.parent) } : s;
|
|
145
|
+
if (board.lanes[tag] || Object.keys(board.lanes).length < MAX_LANES) board.lanes[tag] = fixed;
|
|
146
|
+
}
|
|
147
|
+
if (!Object.keys(board.lanes).length) delete board.lanes;
|
|
148
|
+
}
|
|
149
|
+
// 卷(2026-08-27 收纳器):合并语义同 lanes;null = 展开(删条目)。
|
|
150
|
+
// 只动状态位不动成员座位 —— 展开即归位靠的就是这里什么都不搬。
|
|
151
|
+
if (patch?.rolls && typeof patch.rolls === 'object') {
|
|
152
|
+
board.rolls = board.rolls || {};
|
|
153
|
+
for (const [name, r] of Object.entries(patch.rolls)) {
|
|
154
|
+
const tag = sanitizeTag(name);
|
|
155
|
+
if (!tag) continue;
|
|
156
|
+
if (r === null) { delete board.rolls[tag]; continue; }
|
|
157
|
+
const s = sanitizeRoll(board.rolls[tag] ? { ...board.rolls[tag], ...r } : r);
|
|
158
|
+
if (!s) continue;
|
|
159
|
+
if (board.rolls[tag] || Object.keys(board.rolls).length < MAX_LANES) board.rolls[tag] = s;
|
|
160
|
+
}
|
|
161
|
+
if (!Object.keys(board.rolls).length) delete board.rolls;
|
|
162
|
+
}
|
|
163
|
+
// 纸(2026-08-29 纸范式):合并语义同 lanes;null = 撕掉登记(成员座位不动)。
|
|
164
|
+
// 纸名不是路径,不走改名转发。
|
|
165
|
+
if (patch?.sheets && typeof patch.sheets === 'object') {
|
|
166
|
+
board.sheets = board.sheets || {};
|
|
167
|
+
for (const [name, s0] of Object.entries(patch.sheets)) {
|
|
168
|
+
const nm = sanitizeTag(name);
|
|
169
|
+
if (!nm) continue;
|
|
170
|
+
if (s0 === null) { delete board.sheets[nm]; continue; }
|
|
171
|
+
const s = sanitizeSheet(board.sheets[nm] ? { ...board.sheets[nm], ...s0 } : s0);
|
|
172
|
+
if (!s) continue;
|
|
173
|
+
if (board.sheets[nm] || Object.keys(board.sheets).length < MAX_SHEETS) board.sheets[nm] = s;
|
|
174
|
+
}
|
|
175
|
+
if (!Object.keys(board.sheets).length) delete board.sheets;
|
|
176
|
+
}
|
|
177
|
+
// 跟随规则(2026-08-30):`{ 组tag: {target,side?,label?} | null }`。规则和线分开存
|
|
178
|
+
// —— 线要求两端此刻都在板上,规则不要求,所以「开场先立规则再写第一章」这条
|
|
179
|
+
// skill 教了、模型也一直照做的顺序,第一次真的走得通。
|
|
180
|
+
if (patch?.follows && typeof patch.follows === 'object') {
|
|
181
|
+
board.follows = board.follows || {};
|
|
182
|
+
for (const [g, r] of Object.entries(patch.follows)) {
|
|
183
|
+
const gt = sanitizeTag(g);
|
|
184
|
+
if (!gt) continue;
|
|
185
|
+
if (r === null) { delete board.follows[gt]; continue; }
|
|
186
|
+
board.follows[gt] = r;
|
|
187
|
+
}
|
|
188
|
+
const clean = sanitizeBoard({ ...board, follows: board.follows }).follows;
|
|
189
|
+
if (clean) board.follows = clean; else delete board.follows;
|
|
190
|
+
}
|
|
191
|
+
// 待摆产物队列(刀 G):整表替换(写方只有 board-seater 一个,diff 没意义)
|
|
192
|
+
if (patch?.pending !== undefined) {
|
|
193
|
+
const clean = sanitizeBoard({ ...board, pending: patch.pending }).pending;
|
|
194
|
+
if (clean?.length) board.pending = clean; else delete board.pending;
|
|
195
|
+
}
|
|
196
|
+
// 暂存架原点(2026-08-30):整值替换,写方只有 board-seater / board-tasklist;
|
|
197
|
+
// null = 撤架(下批到货重立)
|
|
198
|
+
if (patch?.shelf !== undefined) {
|
|
199
|
+
const clean = sanitizeBoard({ ...board, shelf: patch.shelf }).shelf;
|
|
200
|
+
if (clean) board.shelf = clean; else delete board.shelf;
|
|
201
|
+
}
|
|
125
202
|
// 主角覆盖:null = 撤销(回到 pickHero 自动推断),字符串 = 显式立主角
|
|
126
203
|
if (patch?.hero !== undefined) {
|
|
127
204
|
if (patch.hero === null) delete board.hero;
|
|
@@ -205,7 +282,8 @@ export function removeByTag(pid, tag) {
|
|
|
205
282
|
// 以进程身份删任意文件;sanitize 现在也拒 `..`,这里是第二道闸)
|
|
206
283
|
if (id.startsWith(`${CHALK_DIR}/`)) {
|
|
207
284
|
const abs = chalkAbsPath(pid, id);
|
|
208
|
-
|
|
285
|
+
// 软删进 .nd/trash/(08-25:擦掉的板书要捞得回来)
|
|
286
|
+
if (abs) await trashChalkFile(getSharedDir(pid), abs);
|
|
209
287
|
delete board.objects[id]; gone.add(id); removed += 1; continue;
|
|
210
288
|
}
|
|
211
289
|
delete o.tag;
|
|
@@ -214,6 +292,8 @@ export function removeByTag(pid, tag) {
|
|
|
214
292
|
if (b.tag === t || gone.has(b.from) || gone.has(b.to)) { delete board.bindings[id]; removed += 1; }
|
|
215
293
|
}
|
|
216
294
|
if (board.hero && gone.has(board.hero)) delete board.hero;
|
|
295
|
+
// 擦组连卷的状态位一起清(收着的组被 erase_group 后不该留一张空卷卡)
|
|
296
|
+
if (board.rolls?.[t]) { delete board.rolls[t]; if (!Object.keys(board.rolls).length) delete board.rolls; }
|
|
217
297
|
await writeBoard(pid, board);
|
|
218
298
|
return { board, removed };
|
|
219
299
|
});
|
|
@@ -451,6 +531,8 @@ export async function reconcileBoardRenames(pid) {
|
|
|
451
531
|
* 文件夹没坐标(还没被摆过)时就摆到桌面上,前端下一轮会给它安排位置。
|
|
452
532
|
*/
|
|
453
533
|
export function pinToZone(pid, { objectId, zoneId = '' }) {
|
|
534
|
+
// 08-27 审计修:这里是 patchBoard 之外唯一的 objects 写入口,原先没有数量闸 ——
|
|
535
|
+
// 反复 pin 新 id 可把 board.objects 撑过 MAX_OBJECTS(只剩 2MB 字节兜底)。
|
|
454
536
|
return withBoardLock(pid, async () => {
|
|
455
537
|
const board = await readBoard(pid);
|
|
456
538
|
const now = Date.now();
|
|
@@ -473,7 +555,7 @@ export function pinToZone(pid, { objectId, zoneId = '' }) {
|
|
|
473
555
|
*/
|
|
474
556
|
const CELL_W = 320; const CELL_H = 220; const PAD = 10;
|
|
475
557
|
const dirOf = (id) => {
|
|
476
|
-
if (typeof id !== 'string'
|
|
558
|
+
if (typeof id !== 'string') return '';
|
|
477
559
|
const c = id.indexOf(':');
|
|
478
560
|
const path = (c > 0 && /^[a-z]+$/.test(id.slice(0, c))) ? id.slice(c + 1) : id;
|
|
479
561
|
const i = path.lastIndexOf('/');
|
|
@@ -494,14 +576,62 @@ export function pinToZone(pid, { objectId, zoneId = '' }) {
|
|
|
494
576
|
}
|
|
495
577
|
if (!slot) slot = { x: PAD, y: PAD };
|
|
496
578
|
|
|
579
|
+
if (!board.objects[oid] && Object.keys(board.objects).length >= MAX_OBJECTS) {
|
|
580
|
+
throw new Error(`board 已有 ${MAX_OBJECTS} 件,pin 不进新条目(失控兜底,正常用不该撞到)`);
|
|
581
|
+
}
|
|
497
582
|
const zMax = Math.max(10, ...Object.values(board.objects).map(o => o.z || 0));
|
|
583
|
+
// 落座即写估算 w/h(2026-08-29 纸范式刀 3):此前 pin 出来的条目只有坐标,
|
|
584
|
+
// read_board 只能报形态表猜值。估算先落盘、前端渲染后按真值回写(useMeasuredSize)。
|
|
585
|
+
const sz = estimateSizeOn(board, oid, board.objects[oid] || null);
|
|
498
586
|
board.objects[oid] = {
|
|
499
587
|
...(board.objects[oid] || {}),
|
|
500
588
|
x: clampNum(slot.x, 0, board.size.w, PAD),
|
|
501
589
|
y: clampNum(slot.y, 0, board.size.h, PAD),
|
|
502
590
|
z: zMax + 1,
|
|
591
|
+
...(Number.isFinite(board.objects[oid]?.w) ? {} : { w: Math.round(sz.w), h: Math.round(sz.h) }),
|
|
503
592
|
};
|
|
504
593
|
await writeBoard(pid, board);
|
|
505
594
|
return { board, zone: zid ? { id: zid } : null, placed: board.objects[oid] };
|
|
506
595
|
});
|
|
507
596
|
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* 悬空关系线清扫(2026-08-24,iss_mszz20zn):删掉产物后 board.json 里会留下
|
|
600
|
+
* 端点指向已不存在文件的 binding —— 关系线只能画不能删,agent 收尾时"我制造的
|
|
601
|
+
* 东西我清不掉"。关系线依附于产物存在:产物没了线自然该没。
|
|
602
|
+
*
|
|
603
|
+
* 端点合法 = 板上有座位(board.objects)或磁盘上真有这个路径(kind 前缀剥掉后)。
|
|
604
|
+
* 两条都不满足才剪 —— 宁可留一条可疑的,不错杀一条活的。30s 节流:挂在
|
|
605
|
+
* /artifacts 热路径上,每张板最多半分钟对账一次。
|
|
606
|
+
*/
|
|
607
|
+
const pruneStamp = new Map(); // pid → last run ms
|
|
608
|
+
const PRUNE_INTERVAL_MS = 30_000;
|
|
609
|
+
|
|
610
|
+
export function pruneDanglingBindings(pid) {
|
|
611
|
+
const now = Date.now();
|
|
612
|
+
if ((pruneStamp.get(pid) || 0) + PRUNE_INTERVAL_MS > now) return Promise.resolve({ pruned: 0 });
|
|
613
|
+
pruneStamp.set(pid, now);
|
|
614
|
+
return withBoardLock(pid, async () => {
|
|
615
|
+
const board = await readBoard(pid);
|
|
616
|
+
const entries = Object.entries(board.bindings || {});
|
|
617
|
+
if (!entries.length) return { pruned: 0 };
|
|
618
|
+
const root = getSharedDir(pid);
|
|
619
|
+
const alive = async (ep) => {
|
|
620
|
+
const id = String(ep || '');
|
|
621
|
+
if (!id) return false;
|
|
622
|
+
if (board.objects?.[id]) return true;
|
|
623
|
+
const bare = id.replace(/^(deck|site|docx|text|scribble):/, '');
|
|
624
|
+
if (!bare || bare.includes('..')) return false;
|
|
625
|
+
try { await fs.access(path.resolve(root, bare)); return true; } catch { return false; }
|
|
626
|
+
};
|
|
627
|
+
const dead = [];
|
|
628
|
+
for (const [bid, b] of entries) {
|
|
629
|
+
if (!(await alive(b?.from)) || !(await alive(b?.to))) dead.push(bid);
|
|
630
|
+
}
|
|
631
|
+
if (!dead.length) return { pruned: 0 };
|
|
632
|
+
for (const bid of dead) delete board.bindings[bid];
|
|
633
|
+
await writeBoard(pid, board);
|
|
634
|
+
console.log(`[board] ${pid} 清掉 ${dead.length} 条悬空关系线`);
|
|
635
|
+
return { pruned: dead.length };
|
|
636
|
+
});
|
|
637
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* projects/memory-migration.js — 记忆体系改版迁移(2026-08-24,幂等)
|
|
3
|
+
*
|
|
4
|
+
* ensureProjectWorkspace 每次调用(源不在了就什么都不做):
|
|
5
|
+
* ① .claude/CLAUDE.md → <root>/CLAUDE.md(根上已有就把旧文件并进末尾再删)
|
|
6
|
+
* ② .claude/agent-memory/memory.md、brand/memory.md → 并进根 CLAUDE.md 的
|
|
7
|
+
* 「用户习惯」/「风格档案」节末尾,源删除
|
|
8
|
+
* ③ .claude/agent-memory/auto/* → 记忆/(SDK auto-memory 新家,画布可见)
|
|
9
|
+
* 三步全是"搬走后源删除",跑几遍结果一样。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { promises as fs } from 'node:fs';
|
|
14
|
+
import { MEMORY_DIR_NAME } from '../engine/agent/memory-config.js';
|
|
15
|
+
import { DEFAULT_CLAUDE_MD } from './workspace-templates.js';
|
|
16
|
+
|
|
17
|
+
/** 改版前的默认模板原文(迁移判据用:全文相等 = 用户没写过一个字,不搬) */
|
|
18
|
+
const OLD_DEFAULT_CLAUDE_MD = `# Project Instructions
|
|
19
|
+
|
|
20
|
+
This file is read by the AI agent at the start of every session as part of its
|
|
21
|
+
system prompt. Write project-specific guidance here — design intent,
|
|
22
|
+
constraints, vocabulary, must-do / must-not-do.
|
|
23
|
+
|
|
24
|
+
The agent will see this verbatim. Keep it concise and actionable.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
- Design tone: minimal, editorial, generous whitespace
|
|
28
|
+
- Hard constraints: never use red as a primary color
|
|
29
|
+
- Vocabulary: refer to the user as "the team"
|
|
30
|
+
|
|
31
|
+
(Edit this file from the NoDesign UI — the agent picks up changes on next session.)
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
export async function migrateMemoryLayout(root, { fileExists }) {
|
|
35
|
+
const rootMd = path.join(root, 'CLAUDE.md');
|
|
36
|
+
const appendTo = async (heading, text, label) => {
|
|
37
|
+
const body = String(text || '').trim();
|
|
38
|
+
if (!body) return;
|
|
39
|
+
let cur = '';
|
|
40
|
+
try { cur = await fs.readFile(rootMd, 'utf8'); } catch { /* 还没有 */ }
|
|
41
|
+
if (!cur.trim()) cur = DEFAULT_CLAUDE_MD; // 起新文件时以「项目档案」模板打底
|
|
42
|
+
await fs.writeFile(rootMd, `${cur ? `${cur.replace(/\n+$/, '')}\n\n` : ''}${heading}\n(${label},2026-08-24 自动迁入)\n\n${body}\n`, 'utf8');
|
|
43
|
+
};
|
|
44
|
+
const oldMd = path.join(root, '.claude', 'CLAUDE.md');
|
|
45
|
+
try {
|
|
46
|
+
const t = await fs.readFile(oldMd, 'utf8');
|
|
47
|
+
// 旧英文默认模板(用户一个字没改,**全文精确比对**才算)没有搬运价值 ——
|
|
48
|
+
// 删掉让 ensure 落新版「项目档案」模板。差一个字都按用户内容原样搬,宁可
|
|
49
|
+
// 多搬不可误删(判据宽了会吃掉用户在模板上的增改)。
|
|
50
|
+
if (t.trim() === OLD_DEFAULT_CLAUDE_MD.trim()) {
|
|
51
|
+
await fs.rm(oldMd, { force: true });
|
|
52
|
+
} else {
|
|
53
|
+
if (await fileExists(rootMd)) await appendTo('## 项目指引', t, '原 .claude/CLAUDE.md');
|
|
54
|
+
else await fs.writeFile(rootMd, t, 'utf8');
|
|
55
|
+
await fs.rm(oldMd, { force: true });
|
|
56
|
+
}
|
|
57
|
+
} catch { /* 没有旧文件:新项目 */ }
|
|
58
|
+
const memRoot = path.join(root, '.claude', 'agent-memory');
|
|
59
|
+
try {
|
|
60
|
+
const t = await fs.readFile(path.join(memRoot, 'memory.md'), 'utf8');
|
|
61
|
+
await appendTo('## 用户习惯', t, '原用户偏好记忆');
|
|
62
|
+
await fs.rm(path.join(memRoot, 'memory.md'), { force: true });
|
|
63
|
+
} catch { /* */ }
|
|
64
|
+
try {
|
|
65
|
+
const t = await fs.readFile(path.join(memRoot, 'brand', 'memory.md'), 'utf8');
|
|
66
|
+
await appendTo('## 风格档案', t, '原风格档案');
|
|
67
|
+
await fs.rm(path.join(memRoot, 'brand'), { recursive: true, force: true });
|
|
68
|
+
} catch { /* */ }
|
|
69
|
+
// auto/ 的存量记忆搬去 记忆/(同名不覆盖:目标已有的跳过,宁可留双份可见)
|
|
70
|
+
const autoDir = path.join(memRoot, 'auto');
|
|
71
|
+
try {
|
|
72
|
+
const entries = await fs.readdir(autoDir, { withFileTypes: true });
|
|
73
|
+
const dest = path.join(root, MEMORY_DIR_NAME);
|
|
74
|
+
await fs.mkdir(dest, { recursive: true });
|
|
75
|
+
for (const e of entries) {
|
|
76
|
+
const from = path.join(autoDir, e.name);
|
|
77
|
+
const to = path.join(dest, e.name);
|
|
78
|
+
if (await fileExists(to)) continue;
|
|
79
|
+
await fs.rename(from, to).catch(() => {});
|
|
80
|
+
}
|
|
81
|
+
const left = await fs.readdir(autoDir).catch(() => []);
|
|
82
|
+
if (left.length === 0) await fs.rm(autoDir, { recursive: true, force: true });
|
|
83
|
+
} catch { /* 没有 auto/:多数项目 */ }
|
|
84
|
+
}
|
|
85
|
+
|
|
@@ -19,6 +19,7 @@ import { getSharedDir } from './workspace.js';
|
|
|
19
19
|
import { renameBoardPaths } from './board-store.js';
|
|
20
20
|
import { taskManifest, KIND_SITE } from '../lib/artifact-target.js';
|
|
21
21
|
import { RESERVED_DIRS } from '../lib/task-scan.js';
|
|
22
|
+
import { CHALK_DIR } from '../lib/chalk.js';
|
|
22
23
|
|
|
23
24
|
export class MoveError extends Error {
|
|
24
25
|
constructor(status, message) { super(message); this.status = status; }
|
|
@@ -62,8 +63,17 @@ export async function moveEntry(pid, fromRaw, toRaw, { createFolder = false } =
|
|
|
62
63
|
if (NO_MOVE_OUT.has(guardSeg(from)) || guardSeg(from).startsWith('.')) {
|
|
63
64
|
throw new MoveError(400, '这个位置的东西不参与搬家');
|
|
64
65
|
}
|
|
66
|
+
// 板书不参与搬家(08-24,上报 iss_mt5qujy1):它是画布上的**话**,不是文件产物。
|
|
67
|
+
// 拖拽误触把它挪出 notes/板书/ 会丢 chalk 身份渲染成普通细条卡,且没有正规
|
|
68
|
+
// 归位通道。搬出一律拒;搬回(下面目标守卫的例外)留给误逃文件的恢复。
|
|
69
|
+
if (from === CHALK_DIR || from.startsWith(`${CHALK_DIR}/`)) {
|
|
70
|
+
throw new MoveError(400, '板书是画布上的话,不参与搬家');
|
|
71
|
+
}
|
|
65
72
|
if (to && (RESERVED_DIRS.has(guardSeg(to)) || guardSeg(to).startsWith('.'))) {
|
|
66
|
-
|
|
73
|
+
// 唯一例外:把误逃的 .md 送**回**板书目录(恢复通道;organize_board 同享。
|
|
74
|
+
// 不限根层 —— groupInto 误触会把板书埋进"新建文件夹/"里,也得捞得回来)
|
|
75
|
+
const isChalkReturn = to === CHALK_DIR && /\.md$/i.test(from);
|
|
76
|
+
if (!isChalkReturn) throw new MoveError(400, '不能搬进这个目录');
|
|
67
77
|
}
|
|
68
78
|
// 搬进自己肚子里(文件夹拖到它自己的子文件夹上)—— fs.rename 会报
|
|
69
79
|
// EINVAL,但那时目录树已经没法自洽了,提前拦住
|