@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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上游 4xx 的人话翻译(08-30 建)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要:上游的错是**给开发者看的英文**,可它会原样穿过 SDK 落到用户的聊天框里。
|
|
5
|
+
* 用户看到的是「API Error: 400 GLM requests accept at most 8 inline PNG, JPEG, WEBP…」——
|
|
6
|
+
* 这句话没有一个字告诉他该怎么办,而这里恰好有个一句话就能给出的出路(换一条线)。
|
|
7
|
+
*
|
|
8
|
+
* ⭐ 只翻译**用户自己能处理**的那几条。上游挂了、限流、鉴权坏了这些他做不了什么,
|
|
9
|
+
* 翻译过去只是把英文换成中文,没有增益,反而把原文这个排查线索藏了 —— 所以不收。
|
|
10
|
+
* ⚠️ 原文一律保留在括号里:翻译是加一句,不是换一句。看日志和看聊天框的是同一个人。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** 一条上游原文里的图片张数上限(particle 是 8)。这条闸只有 particle 有,zai 到 20 张都收 */
|
|
14
|
+
const INLINE_IMAGE_CAP = /accept at most (\d+) inline/i;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} raw 上游回的错误正文
|
|
18
|
+
* @param {object} [wire] 这一发用的行(要 bodyExtra.vendors 判是不是"只走 particle"那条)
|
|
19
|
+
* @returns {string|null} 加了人话的完整文案;没有可翻译的就回 null(调用方原样透传)
|
|
20
|
+
*/
|
|
21
|
+
export function upstreamErrorHint(raw, wire) {
|
|
22
|
+
const text = String(raw || '');
|
|
23
|
+
const cap = text.match(INLINE_IMAGE_CAP);
|
|
24
|
+
if (cap) {
|
|
25
|
+
const n = cap[1];
|
|
26
|
+
// 只有"演出"那条线(点死 particle)才该建议换线;默认那条线不该撞到这条闸,
|
|
27
|
+
// 真撞到了说明是别的原因,别把人往一条同样会挂的线上引。
|
|
28
|
+
const onlyParticle = Array.isArray(wire?.bodyExtra?.vendors)
|
|
29
|
+
&& wire.bodyExtra.vendors.length === 1 && wire.bodyExtra.vendors[0] === 'particle';
|
|
30
|
+
const hint = onlyParticle
|
|
31
|
+
? `「演出」这条线整场最多带 ${n} 张图,这一轮超了。在模型菜单里换成「GLM-5.3-Flash · 设计」就没有这个限制 —— 同一个模型、同样的价钱,只是每一步稍慢一点。`
|
|
32
|
+
: `这一轮带的图超过了上游的 ${n} 张上限。少发几张、或者开一个新会话再继续。`;
|
|
33
|
+
return `${hint}(上游原文:${text.slice(0, 200).replace(/\s+/g, ' ')})`;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
@@ -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, ' ')
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/lib/json-preview.js —— json 文件卡的结构预览(2026-08-29 占位契约刀 B)
|
|
3
|
+
*
|
|
4
|
+
* 站主点名「给 json 一个预览器和显示器」。原来 json 跟 txt/csv 走同一条路:
|
|
5
|
+
* 截前 1KB 原样塞进等宽 <pre>,于是卡面上是半行 `{"name":"…` 就没了 —— 既看不出
|
|
6
|
+
* 结构,也**没法在前端画折叠树**(截断的 json parse 不动)。
|
|
7
|
+
*
|
|
8
|
+
* 这里做的是"裁剪"不是"截断":按深度/数组长度/字符串长度各自设限,产出的仍然是
|
|
9
|
+
* **合法 json**,前端 JSON.parse 得动就能画可折叠的键值树。被裁掉的地方留一句人话
|
|
10
|
+
* (`… +42 more`),不假装内容到此为止 —— 跟卡的折叠角标同一条纪律:
|
|
11
|
+
* 省略要看得见。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const MAX_DEPTH = 4;
|
|
15
|
+
const MAX_ITEMS = 12; // 数组/对象每层最多留几项
|
|
16
|
+
const MAX_STR = 120; // 单个字符串值
|
|
17
|
+
const MAX_OUT = 4096; // 产出上限,超了降一档深度重来
|
|
18
|
+
|
|
19
|
+
/** 折叠标记:值是以这个开头的字符串 = 这里被裁过(前端据此画成灰色省略号) */
|
|
20
|
+
export const ELLIPSIS = '…';
|
|
21
|
+
|
|
22
|
+
function shrink(v, depth, maxDepth) {
|
|
23
|
+
if (v === null || typeof v === 'number' || typeof v === 'boolean') return v;
|
|
24
|
+
if (typeof v === 'string') return v.length > MAX_STR ? `${v.slice(0, MAX_STR)}${ELLIPSIS}` : v;
|
|
25
|
+
if (Array.isArray(v)) {
|
|
26
|
+
if (depth >= maxDepth) return [`${ELLIPSIS} ${v.length} items`];
|
|
27
|
+
const out = v.slice(0, MAX_ITEMS).map(x => shrink(x, depth + 1, maxDepth));
|
|
28
|
+
if (v.length > MAX_ITEMS) out.push(`${ELLIPSIS} +${v.length - MAX_ITEMS} more`);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
if (typeof v === 'object') {
|
|
32
|
+
const keys = Object.keys(v);
|
|
33
|
+
if (depth >= maxDepth) return { [ELLIPSIS]: `${keys.length} keys` };
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const k of keys.slice(0, MAX_ITEMS)) out[k] = shrink(v[k], depth + 1, maxDepth);
|
|
36
|
+
if (keys.length > MAX_ITEMS) out[ELLIPSIS] = `+${keys.length - MAX_ITEMS} more keys`;
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
return String(v);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} raw 文件原文
|
|
44
|
+
* @returns {string|null} 裁剪后的合法 json 文本;不是合法 json 时返回 null(调用方退回原样预览)
|
|
45
|
+
*/
|
|
46
|
+
export function jsonPreview(raw) {
|
|
47
|
+
let data;
|
|
48
|
+
try {
|
|
49
|
+
data = JSON.parse(raw);
|
|
50
|
+
} catch {
|
|
51
|
+
return null; // 不是合法 json(或本来就是片段):退回等宽原样,别假装看得懂
|
|
52
|
+
}
|
|
53
|
+
for (let d = MAX_DEPTH; d >= 1; d -= 1) {
|
|
54
|
+
const text = JSON.stringify(shrink(data, 0, d), null, 2);
|
|
55
|
+
if (text.length <= MAX_OUT || d === 1) return text.slice(0, MAX_OUT);
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
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 —— 服务端唯一的解析器,前端、感知工具、导出都吃这份。
|
package/server/lib/kinds/site.js
CHANGED
|
@@ -12,12 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import fs from 'node:fs/promises';
|
|
15
|
-
import { walkTaskFiles, loadIgnore, DRAFTS_DIR, RESERVED_DIRS } from '../task-scan.js';
|
|
15
|
+
import { walkTaskFiles, loadIgnore, readTaskMarker, DRAFTS_DIR, RESERVED_DIRS } from '../task-scan.js';
|
|
16
16
|
|
|
17
17
|
const ENTRY = 'index.html';
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* 构建产物的约定目录,按序探测(谁有 index.html 认谁)。
|
|
21
|
+
* export:assets.js 的文件夹递归要用同一张表把构建目录挡在"独立站点/文件夹"之外
|
|
22
|
+
* (08-24 案:dist/ 被递归成第二张 site 卡)。
|
|
23
|
+
*/
|
|
24
|
+
export const OUTPUT_DIRS = ['dist', 'out', 'build', '_site', 'public'];
|
|
21
25
|
|
|
22
26
|
/** 站点页面扫描深度(产物根之下;子页 / posts/ / pages/ 够用) */
|
|
23
27
|
const PAGE_DEPTH = 4;
|
|
@@ -31,18 +35,36 @@ function normRoot(r) {
|
|
|
31
35
|
return (!s || s === '.' || s.includes('..')) ? '' : s;
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
/**
|
|
39
|
+
* 根 index.html 是不是**构建源**(vite / react / vue 工程的开发入口):
|
|
40
|
+
* module script 引用 src/ 目录或 .tsx/.jsx/.ts/.vue 源文件。构建源不是可浏览的
|
|
41
|
+
* 产物 —— 浏览器直接打开是白屏(脚本路径只有 dev server / 构建器认识)。
|
|
42
|
+
* 只认强证据;读不动 / 不确定一律按"真产物"处理(老行为)。
|
|
43
|
+
*/
|
|
44
|
+
async function isBuildSourceEntry(dirAbs) {
|
|
45
|
+
try {
|
|
46
|
+
const html = await fs.readFile(path.join(dirAbs, ENTRY), 'utf8');
|
|
47
|
+
return /<script[^>]+src=["'](?:\.?\/)?src\/[^"']+["']/i.test(html)
|
|
48
|
+
|| /<script[^>]+src=["'][^"']+\.(?:tsx|jsx|ts|vue)["']/i.test(html);
|
|
49
|
+
} catch { return false; }
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
/**
|
|
35
53
|
* 产物根(相对任务目录,'' = 任务根)。
|
|
36
54
|
* marker.root 是显式声明,最优先 —— 哪怕入口还没构建出来(agent 刚写完源、
|
|
37
55
|
* 还没跑 build 的窗口期),寻址也该指向将要出现的地方而不是源目录。
|
|
56
|
+
* 任务根的 index.html 是构建源且约定目录里已有构建产物时,优先产物
|
|
57
|
+
* (08-24 案:vite 工程源码 index.html 引 /src/main.tsx,预览白屏)。
|
|
38
58
|
*/
|
|
39
59
|
async function artifactRoot(taskDir, marker) {
|
|
40
60
|
const declared = normRoot(marker?.root);
|
|
41
61
|
if (declared) return declared;
|
|
42
|
-
|
|
62
|
+
const hasRootEntry = await exists(path.join(taskDir, ENTRY));
|
|
63
|
+
if (hasRootEntry && !(await isBuildSourceEntry(taskDir))) return '';
|
|
43
64
|
for (const d of OUTPUT_DIRS) {
|
|
44
65
|
if (await exists(path.join(taskDir, d, ENTRY))) return d;
|
|
45
66
|
}
|
|
67
|
+
// 构建源但还没 build 出产物:退回任务根(至少寻址不落空;build 完自动切换)
|
|
46
68
|
return '';
|
|
47
69
|
}
|
|
48
70
|
|
|
@@ -67,7 +89,10 @@ async function discoverInstances(taskDir, marker) {
|
|
|
67
89
|
if (rootSite) {
|
|
68
90
|
out.push({ srcRoot: '', root, single: false });
|
|
69
91
|
} else {
|
|
70
|
-
//
|
|
92
|
+
// 无根站:一级子目录各自为站。每个子目录按自己的 marker + 构建证据算产物根
|
|
93
|
+
// (08-24 案:jet-engine/ 里的 .nd-project.json 以前读不到、dist/ 轮不上,
|
|
94
|
+
// 构建型子目录站没有干净路径)。root 是**任务根相对**(jet-engine/dist),
|
|
95
|
+
// srcRoot 是子目录名 —— 卡片身份挂 srcRoot 那个文件夹,寻址走 root。
|
|
71
96
|
let entries = [];
|
|
72
97
|
try { entries = await fs.readdir(taskDir, { withFileTypes: true }); } catch { /* */ }
|
|
73
98
|
const ignore = await loadIgnore(taskDir);
|
|
@@ -75,8 +100,16 @@ async function discoverInstances(taskDir, marker) {
|
|
|
75
100
|
if (!e.isDirectory() || e.name.startsWith('.') || e.name === DRAFTS_DIR) continue;
|
|
76
101
|
if (RESERVED_DIRS.has(e.name)) continue;
|
|
77
102
|
if (ignore(e.name, true)) continue;
|
|
78
|
-
|
|
79
|
-
|
|
103
|
+
const subDir = path.join(taskDir, e.name);
|
|
104
|
+
const subMarker = await readTaskMarker(subDir);
|
|
105
|
+
const subRoot = await artifactRoot(subDir, subMarker);
|
|
106
|
+
const subDeclared = normRoot(subMarker?.root);
|
|
107
|
+
if (subDeclared || await exists(path.join(subDir, subRoot || '.', ENTRY))) {
|
|
108
|
+
out.push({
|
|
109
|
+
srcRoot: e.name,
|
|
110
|
+
root: subRoot ? `${e.name}/${subRoot}` : e.name,
|
|
111
|
+
single: false,
|
|
112
|
+
});
|
|
80
113
|
}
|
|
81
114
|
}
|
|
82
115
|
}
|
|
@@ -39,6 +39,7 @@ import { resolveWireModel, UPSTREAMS } from '../engine/agent/model-context.js';
|
|
|
39
39
|
import { resolveSessionWire, fallbackLogged } from './ingress/session-routes.js';
|
|
40
40
|
import { forwardOpenAIChat } from './ingress/forward-openai-chat.js';
|
|
41
41
|
import { failStreaks, exhaustedErrorBody } from './ingress/upstream-fail-streak.js';
|
|
42
|
+
import { armIdleWatchdog } from './ingress/stream-watchdog.js';
|
|
42
43
|
import { noteUpstreamBilling } from './ingress/upstream-billing.js';
|
|
43
44
|
import { noteUpstreamTruncation } from './ingress/upstream-truncation.js';
|
|
44
45
|
import { noticeSession } from './ingress/session-notice.js';
|
|
@@ -295,6 +296,15 @@ async function handleRequest(req, res, bodyBuf) {
|
|
|
295
296
|
noteOutcome(true);
|
|
296
297
|
}
|
|
297
298
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
299
|
+
// 断流看门狗(2026-08-29,proj_mtexu1kp 现场):上游 SSE 半路断粮不报错不 EOF,
|
|
300
|
+
// run 会无限挂起。只对流式响应上岗;掐掉后 CLI 收到流错误自己重试。
|
|
301
|
+
if (proxyRes.statusCode < 400 && String(proxyRes.headers['content-type'] || '').includes('event-stream')) {
|
|
302
|
+
armIdleWatchdog(proxyRes, { onIdle: (silentMs) => {
|
|
303
|
+
console.warn(`[model-ingress] sid=${sidShort} upstream=${wire.upstreamId} 流静默 ${Math.round(silentMs / 1000)}s —— 看门狗掐断死流`);
|
|
304
|
+
noteOutcome(false, 'stream idle');
|
|
305
|
+
try { proxyRes.destroy(new Error('ingress stream idle watchdog')); } catch { /* 已经死了就算了 */ }
|
|
306
|
+
} });
|
|
307
|
+
}
|
|
298
308
|
proxyRes.pipe(res);
|
|
299
309
|
});
|
|
300
310
|
|
|
@@ -327,7 +337,12 @@ export async function transformForUpstream(parsed, wire) {
|
|
|
327
337
|
|
|
328
338
|
// thinking:'strip' = 删字段让上游自决(Gemini thinking 关不掉、参数过桥行为
|
|
329
339
|
// 未知);'enabled8k' = adaptive 改写成 enabled+budget(Kimi 实测 adaptive =
|
|
330
|
-
// 0 thinking blocks
|
|
340
|
+
// 0 thinking blocks);'adaptive' = 反过来,enabled+budget 改写成 adaptive。
|
|
341
|
+
//
|
|
342
|
+
// ⚠️ 本站给**每一条 API 行**发的都是 enabled+8192(model-context.pickThinkingConfig),
|
|
343
|
+
// 对"自己会判断该不该想"的模型等于逐轮强制想满预算。MiniMax M3 是这一类(08-25 实测
|
|
344
|
+
// GMI 部署 adaptive/disabled/enabled 三种都收):agent 一轮里大半是读文件调工具,
|
|
345
|
+
// 该不该想交给它自己。
|
|
331
346
|
if (wire.thinking === 'strip') {
|
|
332
347
|
if ('thinking' in parsed) { delete parsed.thinking; mutated = true; }
|
|
333
348
|
} else if (wire.thinking === 'enabled8k') {
|
|
@@ -335,6 +350,12 @@ export async function transformForUpstream(parsed, wire) {
|
|
|
335
350
|
parsed.thinking = { type: 'enabled', budget_tokens: 8192 };
|
|
336
351
|
mutated = true;
|
|
337
352
|
}
|
|
353
|
+
} else if (wire.thinking === 'adaptive') {
|
|
354
|
+
// 'disabled' 不动:那是调用方明确要求别想,行的默认档不该压过它
|
|
355
|
+
if (parsed.thinking && parsed.thinking.type !== 'adaptive' && parsed.thinking.type !== 'disabled') {
|
|
356
|
+
parsed.thinking = { type: 'adaptive' };
|
|
357
|
+
mutated = true;
|
|
358
|
+
}
|
|
338
359
|
}
|
|
339
360
|
|
|
340
361
|
// tool_result 里的图提升到 user message 顶层(丢图桥的必修 shim)
|
|
@@ -349,6 +370,10 @@ export async function transformForUpstream(parsed, wire) {
|
|
|
349
370
|
if (await normalizeImages(parsed.messages, VISION_MAX_DIM, allowed)) mutated = true;
|
|
350
371
|
}
|
|
351
372
|
|
|
373
|
+
// 上游要的额外顶层字段(merge 的 vendor 点名)。放在这里是 **Anthropic 透传腿的读者** ——
|
|
374
|
+
// 那条腿直接 JSON.stringify(parsed)。openai-chat 腿另有一个读者,见 toOpenAIChatRequest
|
|
375
|
+
if (wire.bodyExtra) { Object.assign(parsed, wire.bodyExtra); mutated = true; }
|
|
376
|
+
|
|
352
377
|
return mutated;
|
|
353
378
|
}
|
|
354
379
|
|
package/server/lib/moderation.js
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
* 强度三档,**per-user、per-通路可调**(站主在控制台按人设)。08-20 起按模型通路分成
|
|
21
21
|
* 两个独立旋钮:users.moderation_level 管订阅模型(Sonnet/Opus,跑在站主账号上),
|
|
22
22
|
* users.moderation_level_api 管本地 qwen / 中转站那些走 ingress 的 API 行。哪条通路
|
|
23
|
-
* 由 model-context 的 resolveModelRoute
|
|
23
|
+
* 由 model-context 的 resolveModelRoute 说了算,这里不判模型名。
|
|
24
|
+
* ⭐ **08-30 起两边的默认档也各算各的**(tier.js 的 moderationDefault / moderationDefaultApi):
|
|
25
|
+
* 订阅 strict、非订阅 off。在此之前两边推导同一个值,"订阅严、其他放开"只能靠逐人钉。
|
|
24
26
|
* 同一档位值同时管 GPT 外审和 prelude 的成人句(见 agent/system-prompts.js)。
|
|
25
27
|
* off 不审
|
|
26
28
|
* loose 只拦"换个说法也还是违法"的硬线:未成年人色情 / 恐怖主义 / 武器毒品
|
|
@@ -29,8 +31,10 @@
|
|
|
29
31
|
* 小说 / RP 形态天然要碰这些,误伤创作比漏审更伤产品。
|
|
30
32
|
* strict loose 全部 + 露骨色情 / 美化煽动真实暴力 / 群体仇恨歧视。
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
+
* 默认档(该旋钮为 NULL 时)走 tier.js 的能力表:订阅通路 admin off / pro·basic strict,
|
|
35
|
+
* API 通路一律 off。改默认档不影响已显式钉过档的号 —— 所以改默认时要顺手看一眼库里还有多少
|
|
36
|
+
* 显式值压着(08-30 那次把 41 个显式的 API 档清回 NULL,就是为了让这张表真的说了算)。
|
|
37
|
+
* `NODESIGN_MODERATION=off` 是全站总闸。
|
|
34
38
|
*/
|
|
35
39
|
|
|
36
40
|
import db from '../engine/runs/store.js';
|
|
@@ -94,9 +98,10 @@ export function levelForKnob(user, knob) {
|
|
|
94
98
|
if (!user) return 'off';
|
|
95
99
|
const explicit = knob === 'api' ? user.moderationLevelApi : user.moderationLevel;
|
|
96
100
|
if (LEVELS.includes(explicit)) return explicit;
|
|
97
|
-
//
|
|
101
|
+
// 默认档按账号档位**和通路**(auth/tier.js 能力表):订阅 admin off / pro·basic strict,API 一律 off。
|
|
98
102
|
// 08-21 前这里猜的是 lifetimeCostLimitUsd(试用码 ⇒ strict),开放注册号该字段为 null 被当成正式号拿 loose。
|
|
99
|
-
|
|
103
|
+
// ⚠️ knob 必须往下传:08-30 之前这里不传,于是 tier 表里就算分了两栏也读不到第二栏。
|
|
104
|
+
return defaultModerationLevel(user, knob);
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
export function shouldModerate(user, appModel = null) {
|
package/server/lib/quota.js
CHANGED
|
@@ -222,12 +222,22 @@ export function usedTodayByFamily(userId, now = Date.now()) {
|
|
|
222
222
|
//
|
|
223
223
|
// 08-21 经营态转向后分两档:
|
|
224
224
|
// - 订阅行(花站主的订阅):全局固定数 NODESIGN_MAX_CONCURRENT_RUNS(默认 3)
|
|
225
|
-
// -
|
|
225
|
+
// - 非订阅行(走 ingress 的 API 行):不受全局固定数限制。它的约束只剩机器本身 ——
|
|
226
226
|
// 每个 claude CLI 进程 350MB~1GB、机器 1 vCPU/8G 无 swap,所以改成**内存闸**:
|
|
227
227
|
// MemAvailable 低于 NODESIGN_MIN_FREE_MEM_MB(默认 700)就拒,外加一个防失控的
|
|
228
228
|
// 上限 NODESIGN_FREE_MAX_CONCURRENT_RUNS(默认 12)。内存闸对两档都生效(订阅行
|
|
229
229
|
// 的固定数 3 本来就是按内存拍的,内存先见底时固定数也救不了)
|
|
230
230
|
// - 每用户同时 1 个(admin 免)两档都一样:这是公平性不是资源
|
|
231
|
+
//
|
|
232
|
+
// ⭐⭐ **2026-08-30 判据从「免费/付费」改成「订阅/非订阅」**。起因:全员默认行换成
|
|
233
|
+
// glm-5.3-flash-merge(付费但极便宜,$0.015/M)。按老判据它落进"付费"那一档,站点默认
|
|
234
|
+
// 路径的并发天花板会从 12 掉到 3,而这台盒子实测峰值在飞 turn 是 4 —— 第 4 个人当场
|
|
235
|
+
// 吃「现在有点挤」。
|
|
236
|
+
// 这不是把闸放松,是**把闸对准它本来护的东西**:那个 3 从头到尾护的是站主的 Claude 订阅
|
|
237
|
+
// (同时开太多 query 会烧额度、也会被上游限流),跟"这一轮花不花钱"没有关系。一条
|
|
238
|
+
// $0.015/M 的网关行既不烧订阅、也不烧机器以外的任何东西,它该受的约束就是内存。
|
|
239
|
+
// ⚠️ 于是**付费的 API 行现在也走 12 + 内存闸**(deepseek 视觉行、merge 行)。真要给某一家
|
|
240
|
+
// 单独收并发,那是 per-upstream 的闸,不是把它塞回订阅那一档。
|
|
231
241
|
|
|
232
242
|
export function memAvailableMb() {
|
|
233
243
|
try {
|
|
@@ -236,13 +246,20 @@ export function memAvailableMb() {
|
|
|
236
246
|
} catch { return null; } // 非 Linux / 读不到 → 不以内存拒
|
|
237
247
|
}
|
|
238
248
|
|
|
239
|
-
/**
|
|
240
|
-
|
|
249
|
+
/**
|
|
250
|
+
* 纯决策:输入全是数,方便测。
|
|
251
|
+
* @param {boolean} offSubscription 这一轮**不花站主订阅**(走 ingress 的 API 行)。
|
|
252
|
+
* ⚠️ 08-30 前这个参数叫 `free`、判据是"模型免不免费",改名是因为那个名字骗过人一次:
|
|
253
|
+
* 默认行换成一条付费但极便宜的 API 行时,它被判进订阅那一档、天花板从 12 掉到 3。
|
|
254
|
+
*/
|
|
255
|
+
export function decideConcurrency({ running, mine, isAdmin, offSubscription, free, memMb, env = process.env }) {
|
|
256
|
+
// 老参数名还认(旧调用方 / 测试),但新名优先
|
|
257
|
+
const offSub = offSubscription !== undefined ? offSubscription : !!free;
|
|
241
258
|
const minMem = Number(env.NODESIGN_MIN_FREE_MEM_MB) || 700;
|
|
242
259
|
if (memMb != null && memMb < minMem) {
|
|
243
260
|
return { ok: false, code: 'BUSY', message: `机器内存快满了(${running} 个任务在跑),稍等一会儿再发` };
|
|
244
261
|
}
|
|
245
|
-
const globalMax =
|
|
262
|
+
const globalMax = offSub
|
|
246
263
|
? (Number(env.NODESIGN_FREE_MAX_CONCURRENT_RUNS) || 12)
|
|
247
264
|
: (Number(env.NODESIGN_MAX_CONCURRENT_RUNS) || 3);
|
|
248
265
|
if (running >= globalMax) {
|
|
@@ -257,7 +274,7 @@ export function decideConcurrency({ running, mine, isAdmin, free, memMb, env = p
|
|
|
257
274
|
return { ok: true };
|
|
258
275
|
}
|
|
259
276
|
|
|
260
|
-
export function checkConcurrency(user, { free = false } = {}) {
|
|
277
|
+
export function checkConcurrency(user, { offSubscription = false, free = false } = {}) {
|
|
261
278
|
// running turn → user 归属:runId 查 runs.user_id(不给 session 注册表加
|
|
262
279
|
// userId 字段 —— 正在跑的 turn 就几个,查表成本可忽略)
|
|
263
280
|
let mine = 0;
|
|
@@ -266,5 +283,8 @@ export function checkConcurrency(user, { free = false } = {}) {
|
|
|
266
283
|
if (getRun(rid)?.userId === user?.id) mine += 1;
|
|
267
284
|
}
|
|
268
285
|
}
|
|
269
|
-
return decideConcurrency({
|
|
286
|
+
return decideConcurrency({
|
|
287
|
+
running: countRunningTurns(), mine, isAdmin: user?.role === 'admin',
|
|
288
|
+
offSubscription: offSubscription || free, memMb: memAvailableMb(),
|
|
289
|
+
});
|
|
270
290
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/lib/rect.js —— 矩形原语(2026-08-27 审计收敛)
|
|
3
|
+
*
|
|
4
|
+
* 收敛前的账:AABB 重叠判定在服务端手写了 4 份、包围盒 5 份、UNIT=24 两处
|
|
5
|
+
* export,各自维护、容差和空集语义悄悄分叉(bbox 空集有的返回 null 有的返回
|
|
6
|
+
* 零框)。改判定只改这一份;需要不同空集语义的调用方自己在出口包一层。
|
|
7
|
+
*
|
|
8
|
+
* 前端的对应物是 web/src/lib/board-geometry.js 的 rectsHit —— 语义必须一致
|
|
9
|
+
* (相邻贴边 = 不重叠)。改这边记得看那边。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** 网格步长:全部世界坐标的公倍数。曾在 board-place / sketch-layout 各 export 一份。 */
|
|
13
|
+
export const UNIT = 24;
|
|
14
|
+
|
|
15
|
+
/** AABB 重叠(贴边不算重叠)。pad = 双方各让的身位。 */
|
|
16
|
+
export const overlaps = (a, b, pad = 0) => !(
|
|
17
|
+
a.x + a.w + pad <= b.x || b.x + b.w <= a.x - pad
|
|
18
|
+
|| a.y + a.h + pad <= b.y || b.y + b.h <= a.y - pad
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/** 一组矩形的包围盒;空集返回 null(要零框语义自己在出口兜)。 */
|
|
22
|
+
export function bboxOf(rects) {
|
|
23
|
+
let x0 = Infinity; let y0 = Infinity; let x1 = -Infinity; let y1 = -Infinity;
|
|
24
|
+
for (const r of rects) {
|
|
25
|
+
x0 = Math.min(x0, r.x); y0 = Math.min(y0, r.y);
|
|
26
|
+
x1 = Math.max(x1, r.x + (r.w || 0)); y1 = Math.max(y1, r.y + (r.h || 0));
|
|
27
|
+
}
|
|
28
|
+
return Number.isFinite(x0) ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 点在矩形内(含边)。 */
|
|
32
|
+
export const pointIn = (pt, r) => pt.x >= r.x && pt.x <= r.x + r.w && pt.y >= r.y && pt.y <= r.y + r.h;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 线段 a→b 是否穿过矩形 r(Liang-Barsky 裁剪;端点擦边也算穿)。
|
|
36
|
+
* 落位引擎用它判「连线走廊」:新块和它锚点之间那条线会不会压在第三块身上。
|
|
37
|
+
*/
|
|
38
|
+
export function segHitsRect(a, b, r) {
|
|
39
|
+
const dx = b.x - a.x; const dy = b.y - a.y;
|
|
40
|
+
let t0 = 0; let t1 = 1;
|
|
41
|
+
const clip = (p, q) => {
|
|
42
|
+
if (p === 0) return q >= 0; // 与这条边平行:q<0 = 整段在外
|
|
43
|
+
const t = q / p;
|
|
44
|
+
if (p < 0) { if (t > t1) return false; if (t > t0) t0 = t; }
|
|
45
|
+
else { if (t < t0) return false; if (t < t1) t1 = t; }
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
return clip(-dx, a.x - r.x) && clip(dx, r.x + r.w - a.x)
|
|
49
|
+
&& clip(-dy, a.y - r.y) && clip(dy, r.y + r.h - a.y);
|
|
50
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rewrite-refs.js —— 搬文件后把全工作区的引用改写到新路径(2026-08-24,iss_mt38uih6)
|
|
3
|
+
*
|
|
4
|
+
* 用户的方法论原话:「新建一个文件夹然后改个索引」。organize_board 归纳素材时
|
|
5
|
+
* 磁盘和画布身份跟着走了,但 HTML/MD 里的 src/href 还指旧路径 —— 裂图。这里把
|
|
6
|
+
* "改索引"那半补上:对每个文本文件,把指向被搬条目的引用按**相对该文件目录**
|
|
7
|
+
* 的写法换算后替换。
|
|
8
|
+
*
|
|
9
|
+
* 设计取舍:
|
|
10
|
+
* - 引用是相对路径(站点路径铁律),所以同一个目标在不同文件里的写法不同
|
|
11
|
+
* (根上的 deck 写 `assets/x.png`,子页写 `../assets/x.png`)——逐文件换算。
|
|
12
|
+
* - 被搬的文件**自己**也可能引用别人:它落了新家后自己的相对引用基准变了,
|
|
13
|
+
* 按"旧目录里的写法 → 新目录里的写法"改(哪怕目标没动)。
|
|
14
|
+
* - 边界字符防误伤:只有前后都像"引用上下文"(引号/括号/=/空白/行首尾/`/`
|
|
15
|
+
* 延续)才替换。改了多少处如实报给调用方 —— 自动改写用户内容,必须可核对。
|
|
16
|
+
* - 搬的是文件夹时按前缀匹配(`素材/x.png` 里的 `素材` 段)。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { promises as fs } from 'node:fs';
|
|
21
|
+
import { walkTaskFiles, loadIgnore } from './task-scan.js';
|
|
22
|
+
|
|
23
|
+
const TEXT_EXTS = /\.(html?|css|md|markdown|js|mjs|json|svg|ya?ml|txt)$/i;
|
|
24
|
+
const MAX_BYTES = 2 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
const relFrom = (dirRel, targetRel) => path.posix.relative(dirRel || '.', targetRel);
|
|
27
|
+
const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 纯函数:一段文本里把 pairs([旧工作区相对路径, 新路径])的引用改写掉。
|
|
31
|
+
* @param {string} text
|
|
32
|
+
* @param {string} oldDirRel 这段文本**写下时**所在目录(工作区相对,'' = 根)
|
|
33
|
+
* @param {string} newDirRel 这段文本现在所在目录(自己没被搬则与 oldDirRel 相同)
|
|
34
|
+
* @param {Array<[string,string]>} pairs
|
|
35
|
+
* @returns {{ text: string, hits: number }}
|
|
36
|
+
*/
|
|
37
|
+
export function rewriteTextRefs(text, oldDirRel, newDirRel, pairs) {
|
|
38
|
+
let out = text; let hits = 0;
|
|
39
|
+
const variants = [];
|
|
40
|
+
for (const [oldRel, newRel] of pairs) {
|
|
41
|
+
const oldRef = relFrom(oldDirRel, oldRel);
|
|
42
|
+
const newRef = relFrom(newDirRel, newRel);
|
|
43
|
+
if (!oldRef || oldRef === newRef) continue;
|
|
44
|
+
variants.push([oldRef, newRef]);
|
|
45
|
+
const encOld = encodeURI(oldRef); const encNew = encodeURI(newRef);
|
|
46
|
+
if (encOld !== oldRef) variants.push([encOld, encNew]);
|
|
47
|
+
}
|
|
48
|
+
// 长的先替:`素材` 与 `素材/子` 并存时避免前缀误吞
|
|
49
|
+
variants.sort((a, b) => b[0].length - a[0].length);
|
|
50
|
+
for (const [o, n] of variants) {
|
|
51
|
+
const re = new RegExp(`(^|["'\\(=,\\s])${escRe(o)}(?=["'\\)\\s#?,]|/|$)`, 'gm');
|
|
52
|
+
out = out.replace(re, (m, p1) => { hits += 1; return `${p1}${n}`; });
|
|
53
|
+
}
|
|
54
|
+
return { text: out, hits };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 自己被搬过的文本文件:**所有**相对引用的基准都变了(不只指向被搬条目的)。
|
|
59
|
+
* 把每个像相对路径的 token 按旧目录解析、按新目录重写;目标自己也搬了就再查
|
|
60
|
+
* moves 表。只认引号/括号里带扩展名的 token,http/data//#/绝对路径不碰。
|
|
61
|
+
*/
|
|
62
|
+
export function rebaseSelfRefs(text, oldDirRel, newDirRel, movesMap) {
|
|
63
|
+
let hits = 0;
|
|
64
|
+
const out = text.replace(
|
|
65
|
+
/(["'(=])((?:\.\.\/|\.\/)?[^"'\s()#?]*\.[A-Za-z0-9]{1,8})(?=["')\s#?]|$)/gm,
|
|
66
|
+
(m, pre, ref) => {
|
|
67
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|\/|#)/i.test(ref)) return m;
|
|
68
|
+
const target = path.posix.normalize(path.posix.join(oldDirRel || '.', decodeURI(ref)));
|
|
69
|
+
if (target.startsWith('..')) return m; // 出工作区的不碰
|
|
70
|
+
const finalTarget = movesMap.get(target) || target;
|
|
71
|
+
const next = relFrom(newDirRel, finalTarget);
|
|
72
|
+
if (next === ref) return m;
|
|
73
|
+
hits += 1;
|
|
74
|
+
return `${pre}${next}`;
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
return { text: out, hits };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 扫全工作区文本文件,改写指向 pairs 的引用。
|
|
82
|
+
* @param {string} root 工作区根(绝对路径)
|
|
83
|
+
* @param {Array<{from: string, to: string}>} moves 已完成的搬家(工作区相对)
|
|
84
|
+
* @returns {Promise<{files: number, hits: number, lines: string[]}>}
|
|
85
|
+
*/
|
|
86
|
+
export async function rewriteWorkspaceRefs(root, moves) {
|
|
87
|
+
const pairs = moves.map((m) => [m.from, m.to]);
|
|
88
|
+
const movesMap = new Map(pairs);
|
|
89
|
+
const movedTo = new Map(moves.map((m) => [m.to, m.from]));
|
|
90
|
+
const ignore = await loadIgnore(root);
|
|
91
|
+
const all = await walkTaskFiles(root, { maxDepth: 6, ignore, includeDrafts: true });
|
|
92
|
+
let files = 0; let hits = 0; const lines = [];
|
|
93
|
+
for (const f of all) {
|
|
94
|
+
if (!TEXT_EXTS.test(f.name) || f.size > MAX_BYTES) continue;
|
|
95
|
+
const newDir = path.posix.dirname(f.rel) === '.' ? '' : path.posix.dirname(f.rel);
|
|
96
|
+
const selfOld = movedTo.get(f.rel);
|
|
97
|
+
let raw;
|
|
98
|
+
try { raw = await fs.readFile(f.abs, 'utf8'); } catch { continue; }
|
|
99
|
+
// 自己被搬过 → 整体换基准(含指向没搬目标的引用);没搬 → 只改指向被搬条目的
|
|
100
|
+
const r = selfOld
|
|
101
|
+
? rebaseSelfRefs(raw, path.posix.dirname(selfOld) === '.' ? '' : path.posix.dirname(selfOld), newDir, movesMap)
|
|
102
|
+
: rewriteTextRefs(raw, newDir, newDir, pairs);
|
|
103
|
+
if (r.hits > 0 && r.text !== raw) {
|
|
104
|
+
try { await fs.writeFile(f.abs, r.text, 'utf8'); } catch { continue; }
|
|
105
|
+
files += 1; hits += r.hits;
|
|
106
|
+
lines.push(` ↻ ${f.rel}:${r.hits} 处`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { files, hits, lines };
|
|
110
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/lib/screen.js —— 「一屏」的唯一基准(2026-08-29 纸范式刀 0)
|
|
3
|
+
*
|
|
4
|
+
* 纸范式拍板:一张纸 = **0.75 倍缩放下等于用户一屏**的矩形工作区。
|
|
5
|
+
* 在这之前「一屏」有三个同源近邻值各自散落(fitFor 的 /0.8、board-place 的
|
|
6
|
+
* ONE_SCREEN 1750×1125、sketch-layout 的 SKETCH_FIT 1700×1100),注释里互相
|
|
7
|
+
* 提醒「改缩放基准要几处一起看」—— 现在基准只有这一份,改这里就是改全部。
|
|
8
|
+
*
|
|
9
|
+
* DEFAULT_SCREEN 是没有视点上报时的兜底屏(普通笔记本 1400×900);真实纸尺寸
|
|
10
|
+
* 永远优先按上报的 device 屏幕算(见 sketch-layout.js fitFor)。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const ZOOM_BASIS = 0.75;
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_SCREEN = { w: 1400, h: 900 };
|
|
16
|
+
|
|
17
|
+
/** 兜底「一屏」世界像素(1400×900 ÷ 0.75 = 1867×1200) */
|
|
18
|
+
export const ONE_SCREEN = {
|
|
19
|
+
w: Math.round(DEFAULT_SCREEN.w / ZOOM_BASIS),
|
|
20
|
+
h: Math.round(DEFAULT_SCREEN.h / ZOOM_BASIS),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 一张卡的高度天花板(2026-08-29 占位契约刀 B,站主定「一张纸的 40%」)。
|
|
25
|
+
*
|
|
26
|
+
* 生产真板上最高一条板书 **2471px** —— 312 宽、2471 高,一整章小说写成一根柱子。
|
|
27
|
+
* 卡的高度原来完全由内容决定、没有上限,于是两件坏事同时发生:版面被柱子劈开,
|
|
28
|
+
* 而落位系统按估算高度排好队之后,渲染回写又把卡撑大去压邻居。
|
|
29
|
+
*
|
|
30
|
+
* 定成固定值而不是「按当前纸算」,是因为卡的高度不该随看它的设备变 —— 同一块板
|
|
31
|
+
* 在手机和大屏上看,卡的版面必须是同一个。384 = 16 格,也接近 973 高的纸的 40%。
|
|
32
|
+
*
|
|
33
|
+
* 超出的部分不是丢掉:卡上折叠(可展开,展开是临时的、不改占位),工具如实告诉
|
|
34
|
+
* agent「你这条被折叠了,实际 N px」——「越界钳住但要说」。
|
|
35
|
+
*/
|
|
36
|
+
export const CARD_MAX_H = 384;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/lib/sheet-state.js —— 「当前纸」的会话指针(2026-08-29 纸范式)
|
|
3
|
+
*
|
|
4
|
+
* 每个会话记一张「正写在哪张纸上」。进程内存不落盘:丢了也无害 —— 读侧回落到
|
|
5
|
+
* 登记时间最新的一张(board-sheets.js currentSheet 的兜底),agent 重新铺纸或
|
|
6
|
+
* 点名纸名都能续上。放 board.json 是错的:两个会话同时开工会互相抢指针。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const cur = new Map(); // sessionId → sheet name
|
|
10
|
+
|
|
11
|
+
export function setCurrentSheetId(sessionId, id) {
|
|
12
|
+
if (sessionId && typeof id === 'string') cur.set(sessionId, id);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function currentSheetIdOf(sessionId) {
|
|
16
|
+
return (sessionId && cur.get(sessionId)) || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function _resetSheetState() { cur.clear(); }
|