@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
package/server/api/assets.js
CHANGED
|
@@ -18,17 +18,19 @@ import { guardProject } from './_guard.js';
|
|
|
18
18
|
import {
|
|
19
19
|
getSharedDir, ensureProjectWorkspace, removeSessionWorkspace, commitWorkspace,
|
|
20
20
|
} from '../projects/workspace.js';
|
|
21
|
-
import { patchBoard, readBoard, reconcileBoardRenames, forwardId, forwardPath, renameBoardPaths } from '../projects/board-store.js';
|
|
21
|
+
import { patchBoard, readBoard, reconcileBoardRenames, pruneDanglingBindings, forwardId, forwardPath, renameBoardPaths } from '../projects/board-store.js';
|
|
22
22
|
import { moveEntry, MoveError } from '../projects/move-entry.js';
|
|
23
23
|
import { reconcileAutoRefsThrottled } from '../lib/auto-relations.js';
|
|
24
24
|
import { taskManifest, ENTRY_FILE, KIND_SITE, docxClaimedFiles, isDirArtifact } from '../lib/artifact-target.js';
|
|
25
|
-
import { RESERVED_DIRS, HARD_IGNORE_DIRS, DRAFTS_DIR, isReservedFile } from '../lib/task-scan.js';
|
|
25
|
+
import { RESERVED_DIRS, HARD_IGNORE_DIRS, DRAFTS_DIR, isReservedFile, loadIgnore } from '../lib/task-scan.js';
|
|
26
|
+
import { OUTPUT_DIRS } from '../lib/kinds/site.js';
|
|
26
27
|
import { listReferences } from '../lib/reference-assets.js';
|
|
27
28
|
import { resolveArtifactFile, isServablePath } from '../lib/artifact-file-path.js';
|
|
29
|
+
import { USER_UPLOAD_DIR, ensureUploadDir, uploadRefPath, listUploadedAssets, deleteUploadedAsset } from '../lib/user-uploads.js';
|
|
28
30
|
import { getProjectCover } from '../lib/cover.js';
|
|
29
31
|
import { makeDocxPageHandler, makeDocxPdfHandler } from './assets/docx-page.js';
|
|
30
32
|
import { mountNotesRoutes } from './assets/notes.js';
|
|
31
|
-
import { safeSegment, decorateNoteText } from './assets/helpers.js';
|
|
33
|
+
import { safeSegment, decorateNoteText, decorateFilePreview, PREVIEW_EXTS } from './assets/helpers.js';
|
|
32
34
|
import { CHALK_DIR } from '../lib/chalk.js';
|
|
33
35
|
import {
|
|
34
36
|
sendImage, isThumbPath, findOriginalForThumbnail, imageCacheControl,
|
|
@@ -37,6 +39,7 @@ import {
|
|
|
37
39
|
import { injectSrcset } from '../lib/html-srcset.js';
|
|
38
40
|
import { sendVideo, isVideo } from '../lib/video-variant.js';
|
|
39
41
|
import { IMAGE_EXTS, VIDEO_EXTS, decorateCardKind } from '../lib/kinds/file-kinds.js';
|
|
42
|
+
import { noteBoardDirty } from '../lib/board-dirty.js';
|
|
40
43
|
|
|
41
44
|
const router = express.Router();
|
|
42
45
|
|
|
@@ -95,7 +98,7 @@ router.post('/:pid/assets', upload.single('file'), async (req, res, next) => {
|
|
|
95
98
|
if (!req.file) return res.status(400).json({ error: 'no file (field name: file)' });
|
|
96
99
|
|
|
97
100
|
await ensureProjectWorkspace(req.params.pid);
|
|
98
|
-
const assetsDir =
|
|
101
|
+
const assetsDir = await ensureUploadDir(getSharedDir(req.params.pid));
|
|
99
102
|
|
|
100
103
|
const originalName = decodeUploadName(req.file.originalname);
|
|
101
104
|
let filename = sanitizeFilename(originalName);
|
|
@@ -112,7 +115,7 @@ router.post('/:pid/assets', upload.single('file'), async (req, res, next) => {
|
|
|
112
115
|
asset: {
|
|
113
116
|
// path 给 agent Read 用 — 相对 cwd(sessions/<sid>/)走 ../shared/assets/
|
|
114
117
|
// 或者用 SDK additionalDirectories 拿到的绝对路径前缀;前端展示用 name 即可。
|
|
115
|
-
path:
|
|
118
|
+
path: uploadRefPath(USER_UPLOAD_DIR, filename),
|
|
116
119
|
name: filename,
|
|
117
120
|
originalName,
|
|
118
121
|
size: req.file.size,
|
|
@@ -127,24 +130,7 @@ router.get('/:pid/assets', async (req, res, next) => {
|
|
|
127
130
|
if (!guardProject(req, res)) return;
|
|
128
131
|
|
|
129
132
|
const assetsDir = path.join(getSharedDir(req.params.pid), 'assets');
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
entries = await fs.readdir(assetsDir, { withFileTypes: true });
|
|
133
|
-
} catch (err) {
|
|
134
|
-
if (err.code === 'ENOENT') return res.json({ assets: [] });
|
|
135
|
-
throw err;
|
|
136
|
-
}
|
|
137
|
-
const assets = [];
|
|
138
|
-
for (const e of entries) {
|
|
139
|
-
if (!e.isFile()) continue;
|
|
140
|
-
let stat;
|
|
141
|
-
try { stat = await fs.stat(path.join(assetsDir, e.name)); } catch { continue; }
|
|
142
|
-
assets.push({
|
|
143
|
-
path: `../../shared/assets/${e.name}`, name: e.name,
|
|
144
|
-
size: stat.size, mtime: stat.mtime.toISOString(),
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
assets.sort((a, b) => (a.mtime < b.mtime ? 1 : -1));
|
|
133
|
+
const assets = await listUploadedAssets(getSharedDir(req.params.pid));
|
|
148
134
|
|
|
149
135
|
// 参考素材单独一组,走抽屉不上画布。为什么它们此前完全看不见(以及为什么
|
|
150
136
|
// 修法是加扫描口不是改形态注册表)见 lib/reference-assets.js 文件头。
|
|
@@ -166,17 +152,9 @@ router.delete('/:pid/assets/:filename', async (req, res, next) => {
|
|
|
166
152
|
return res.status(400).json({ error: 'invalid filename' });
|
|
167
153
|
}
|
|
168
154
|
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
return res.status(400).json({ error: 'invalid filename' });
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
await fs.unlink(filePath);
|
|
176
|
-
} catch (err) {
|
|
177
|
-
if (err.code === 'ENOENT') return res.status(404).json({ error: 'asset not found' });
|
|
178
|
-
throw err;
|
|
179
|
-
}
|
|
155
|
+
const outcome = await deleteUploadedAsset(getSharedDir(req.params.pid), filename);
|
|
156
|
+
if (outcome === 'invalid') return res.status(400).json({ error: 'invalid filename' });
|
|
157
|
+
if (outcome === 'not_found') return res.status(404).json({ error: 'asset not found' });
|
|
180
158
|
res.status(204).end();
|
|
181
159
|
} catch (err) { next(err); }
|
|
182
160
|
});
|
|
@@ -254,10 +232,10 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
254
232
|
// 那个接口,而迁移一旦晚于第一次渲染,用户会先看到一个叫 `tasks` 的文件夹
|
|
255
233
|
// 套着他的文件夹。跑过之后是三次 stat 的事(幂等早退),不值得省。
|
|
256
234
|
await ensureProjectWorkspace(req.params.pid);
|
|
257
|
-
// 跟上 agent
|
|
258
|
-
// 是为了让用户看到的第一帧就是对齐过的。没有新 commit 时是一次 rev-parse。
|
|
235
|
+
// 跟上 agent 在画布背后做的改名(挂在这让第一帧就对齐;无新 commit 时只一次 rev-parse)
|
|
259
236
|
await reconcileBoardRenames(req.params.pid).catch(
|
|
260
237
|
(err) => console.warn('[board] 改名对账失败:', err.message));
|
|
238
|
+
await pruneDanglingBindings(req.params.pid).catch(() => {}); // 悬空线 30s 节流清扫(理由见 board-store)
|
|
261
239
|
|
|
262
240
|
const assetsDir = path.join(getSharedDir(req.params.pid), 'assets');
|
|
263
241
|
const artifacts = [];
|
|
@@ -349,8 +327,14 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
349
327
|
item.meta = JSON.parse(metaRaw);
|
|
350
328
|
} catch { /* 无 sidecar(旧图)→ 无 meta */ }
|
|
351
329
|
}
|
|
352
|
-
//
|
|
330
|
+
// 便签/板书带正文、文本文件带 1KB 预览(完整内容都走 artifact-file;见 helpers)
|
|
331
|
+
// ⚠️ upload 也要(2026-08-29):用户上传的角色卡/世界卡就住在 用户内容/,
|
|
332
|
+
// kind='upload' —— 原来只认 task-file,于是上传来的 json/md 卡面永远是空的
|
|
333
|
+
// 细条。json 预览器做完才发现最常见的那一类 json 根本走不到这里。
|
|
353
334
|
if (kind === 'note' && ext === '.md') await decorateNoteText(item, path.join(dir, e.name));
|
|
335
|
+
else if ((kind === 'task-file' || kind === 'upload') && PREVIEW_EXTS.has(ext)) {
|
|
336
|
+
await decorateFilePreview(item, path.join(dir, e.name));
|
|
337
|
+
}
|
|
354
338
|
artifacts.push(item);
|
|
355
339
|
}
|
|
356
340
|
};
|
|
@@ -376,6 +360,7 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
376
360
|
// 磁盘上的位置是同一个字符串 —— 这样 agent `mv` 一个文件之后,git 的改名
|
|
377
361
|
// 检测能直接翻译成画布 id 的改名(见 board-store 的 reconcileBoardRenames)。
|
|
378
362
|
const workspaceRoot = getSharedDir(req.params.pid);
|
|
363
|
+
const rootIgnore = await loadIgnore(workspaceRoot);
|
|
379
364
|
const tasks = [];
|
|
380
365
|
let hasRootSite = false;
|
|
381
366
|
// 根站认领的一级子目录(pages 顶层段)—— 散文件过滤要用同一口径
|
|
@@ -474,6 +459,8 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
474
459
|
if (!e.isDirectory() || e.name.startsWith('.')) continue;
|
|
475
460
|
if (RESERVED_DIRS.has(e.name) || HARD_IGNORE_DIRS.has(e.name)) continue;
|
|
476
461
|
if (e.name === DRAFTS_DIR) continue; // 站点试作,由 site 解析器管
|
|
462
|
+
// 构建目录不当独立站/收纳夹(site:dist 案);递归也吃根 .ndignore(与页面清单同规则)
|
|
463
|
+
if (OUTPUT_DIRS.includes(e.name) || rootIgnore(under(rel, e.name), true)) continue;
|
|
477
464
|
if (claimed.has(e.name)) continue; // 已经是一件产物了
|
|
478
465
|
folders.push(under(rel, e.name));
|
|
479
466
|
await collect(path.join(dir, e.name), under(rel, e.name), depth + 1);
|
|
@@ -493,7 +480,11 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
493
480
|
// 板书住 notes/板书/(不进 notes/ 顶层:便利贴注入只列顶层,板书另有自己的注入)
|
|
494
481
|
await scanDir(path.join(workspaceRoot, CHALK_DIR), 'note', CHALK_DIR);
|
|
495
482
|
for (const rel of folders) {
|
|
496
|
-
|
|
483
|
+
// 用户上传的落点扫成 upload 而不是 task-file:来源轴(board-kinds.sourceOf)
|
|
484
|
+
// 按 kind 分「我放的 / agent 做的」,落回 task-file 的话用户自己拖进来的图
|
|
485
|
+
// 会被标成「agent 做的」。子目录仍按普通文件夹走。
|
|
486
|
+
const kind = rel === USER_UPLOAD_DIR ? 'upload' : 'task-file';
|
|
487
|
+
await scanDir(path.join(workspaceRoot, rel), kind, rel, 0);
|
|
497
488
|
}
|
|
498
489
|
} catch (err) {
|
|
499
490
|
if (err.code !== 'ENOENT') throw err;
|
|
@@ -663,6 +654,8 @@ router.post('/:pid/move', express.json(), async (req, res, next) => {
|
|
|
663
654
|
const out = await moveEntry(req.params.pid, req.body?.from, req.body?.to);
|
|
664
655
|
res.json(out); // ③
|
|
665
656
|
if (out.moved) {
|
|
657
|
+
// 板上动静(2026-08-29 刀 4):用户搬了文件,agent 下次摸板前该知道
|
|
658
|
+
try { noteBoardDirty(req.params.pid, [{ kind: 'mv', id: out.from, to: out.to }]); } catch { /* 记不上不挡搬家 */ }
|
|
666
659
|
// ④ 响应之后再 commit:它只服务于稍后的对账,不该让用户多等一次 git add
|
|
667
660
|
commitWorkspace(req.params.pid, null, `move: ${out.from} → ${out.to}`, { author: 'user' })
|
|
668
661
|
.catch(err => console.warn('[git] move commit failed:', err.message));
|
package/server/api/board.js
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import express from 'express';
|
|
13
|
+
import { listRoleNames } from '../engine/agent/role-card.js';
|
|
14
|
+
import { getWorkspaceRoot } from '../projects/workspace.js';
|
|
13
15
|
import { validateProjectId, getProject } from '../projects/store.js';
|
|
14
16
|
import { guardProject } from './_guard.js';
|
|
15
17
|
import { readBoard, replaceBoard, patchBoard, commitStaging, removeByTag } from '../projects/board-store.js';
|
|
18
|
+
import { noteBoardDirty } from '../lib/board-dirty.js';
|
|
16
19
|
import { setViewpoint, getViewpoint } from '../projects/viewpoint-store.js';
|
|
17
20
|
import { exportGraph, exportGraphZip } from '../lib/board-graph-export.js';
|
|
18
21
|
import { getSharedDir } from '../projects/workspace.js';
|
|
@@ -27,7 +30,12 @@ function guard(req, res) {
|
|
|
27
30
|
router.get('/:pid/board', async (req, res, next) => {
|
|
28
31
|
try {
|
|
29
32
|
if (!guard(req, res)) return;
|
|
30
|
-
|
|
33
|
+
// roles 是**派生态**:每次从 .claude/agents/ 现读,不落 board.json ——
|
|
34
|
+
// 板上存的署名是 slug(权威),展示名住在角色文件里(模型可改),两者不能混成一份数据。
|
|
35
|
+
// 前端只拿它把 slug 渲染得好看,任何判断仍按 slug 走。
|
|
36
|
+
let roles = {};
|
|
37
|
+
try { roles = Object.fromEntries(await listRoleNames(getWorkspaceRoot(req.params.pid))); } catch { /* 没有角色就是空表 */ }
|
|
38
|
+
res.json({ board: await readBoard(req.params.pid), roles });
|
|
31
39
|
} catch (err) { next(err); }
|
|
32
40
|
});
|
|
33
41
|
|
|
@@ -36,7 +44,26 @@ router.patch('/:pid/board', express.json({ limit: '600kb' }), async (req, res, n
|
|
|
36
44
|
if (!guard(req, res)) return;
|
|
37
45
|
const patch = req.body?.patch;
|
|
38
46
|
if (!patch || typeof patch !== 'object') return res.status(400).json({ error: 'patch required' });
|
|
47
|
+
// 板上动静(2026-08-29 刀 4):HTTP PATCH 就是用户的手。**位置真变了才算动静**
|
|
48
|
+
// —— 前端的尺寸回写(useMeasuredSize)也走这条路,只有 w/h 变化不该吵 agent。
|
|
49
|
+
let prev = null;
|
|
50
|
+
if (patch.objects && typeof patch.objects === 'object') {
|
|
51
|
+
try { prev = await readBoard(req.params.pid); } catch { /* 记不上不挡写 */ }
|
|
52
|
+
}
|
|
39
53
|
const board = await patchBoard(req.params.pid, patch);
|
|
54
|
+
if (prev) {
|
|
55
|
+
const events = [];
|
|
56
|
+
for (const [id, o] of Object.entries(patch.objects)) {
|
|
57
|
+
const was = prev.objects?.[id];
|
|
58
|
+
if (o === null) { if (was) events.push({ kind: 'removed', id }); continue; }
|
|
59
|
+
const now = board.objects?.[id];
|
|
60
|
+
if (!was || !now) continue;
|
|
61
|
+
if (Math.round(was.x) !== Math.round(now.x) || Math.round(was.y) !== Math.round(now.y)) {
|
|
62
|
+
events.push({ kind: 'moved', id });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (events.length) noteBoardDirty(req.params.pid, events);
|
|
66
|
+
}
|
|
40
67
|
res.json({ ok: true, board });
|
|
41
68
|
} catch (err) {
|
|
42
69
|
if (err?.status === 400) return res.status(400).json({ error: err.message });
|
|
@@ -94,6 +121,7 @@ router.post('/:pid/board/erase', express.json({ limit: '16kb' }), async (req, re
|
|
|
94
121
|
const tag = typeof req.body?.tag === 'string' ? req.body.tag : '';
|
|
95
122
|
if (!tag) return res.status(400).json({ error: 'tag required' });
|
|
96
123
|
const { board, removed } = await removeByTag(req.params.pid, tag);
|
|
124
|
+
if (removed) noteBoardDirty(req.params.pid, [{ kind: 'erased', id: tag }]);
|
|
97
125
|
res.json({ ok: true, removed, board });
|
|
98
126
|
} catch (err) { next(err); }
|
|
99
127
|
});
|
package/server/api/browse.js
CHANGED
|
@@ -46,6 +46,7 @@ import { pendingHelp } from '../engine/browse/handover.js';
|
|
|
46
46
|
import { readVisit, readFrame, saveFrame, recordVisit, forgetVisit } from '../engine/browse/state.js';
|
|
47
47
|
import { browseCard } from '../engine/browse/card.js';
|
|
48
48
|
import { checkUrl } from '../lib/ssrf-guard.js';
|
|
49
|
+
import { msg } from '../shared/messages.js';
|
|
49
50
|
|
|
50
51
|
const router = express.Router();
|
|
51
52
|
|
|
@@ -96,11 +97,11 @@ router.post('/:pid/browse/open', async (req, res, next) => {
|
|
|
96
97
|
const visit = await readVisit(pid);
|
|
97
98
|
const want = String(req.body?.url || visit?.url || '');
|
|
98
99
|
if (!/^https?:\/\//i.test(want)) {
|
|
99
|
-
return res.status(400).json({ error: '没有可打开的地址(这个项目还没逛过任何站)' });
|
|
100
|
+
return res.status(400).json({ error: msg(req, '没有可打开的地址(这个项目还没逛过任何站)') });
|
|
100
101
|
}
|
|
101
102
|
// 用户点的也过闸 —— 闸是硬边界,不因为"是人点的"就放行
|
|
102
103
|
const pre = await checkUrl(want);
|
|
103
|
-
if (!pre.ok) return res.status(403).json({ error:
|
|
104
|
+
if (!pre.ok) return res.status(403).json({ error: msg(req, '网络闸拒了这个地址:{reason}', { reason: pre.reason }) });
|
|
104
105
|
|
|
105
106
|
const out = await withBrowser(pid, async ({ page }) => {
|
|
106
107
|
if (page.url() !== want) {
|
package/server/api/chatai.js
CHANGED
|
@@ -25,6 +25,7 @@ import path from 'node:path';
|
|
|
25
25
|
import fs from 'node:fs/promises';
|
|
26
26
|
import express from 'express';
|
|
27
27
|
import { guardProject } from './_guard.js';
|
|
28
|
+
import { msg } from '../shared/messages.js';
|
|
28
29
|
import { getWorkspaceRoot } from '../projects/workspace.js';
|
|
29
30
|
import { performTurn } from '../engine/chatai/perform.js';
|
|
30
31
|
import {
|
|
@@ -57,7 +58,7 @@ const playing = new Set();
|
|
|
57
58
|
function gateApproved(req, res) {
|
|
58
59
|
// 档位 + 逐人批准(auth/tier.js localGenApproved):basic 档不开,pro 档要被站主批过
|
|
59
60
|
if (localGenApproved(req.user)) return true;
|
|
60
|
-
res.status(403).json({ error: '演出通路尚未对这个账号开放' });
|
|
61
|
+
res.status(403).json({ error: msg(req, '演出通路尚未对这个账号开放') });
|
|
61
62
|
return false;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -237,30 +238,30 @@ router.post('/:pid/chatai/turn', async (req, res, next) => {
|
|
|
237
238
|
if (!gateApproved(req, res)) return;
|
|
238
239
|
|
|
239
240
|
const input = String(req.body?.input ?? '').trim();
|
|
240
|
-
if (!input) return res.status(422).json({ error: 'input 是空的' });
|
|
241
|
+
if (!input) return res.status(422).json({ error: msg(req, 'input 是空的') });
|
|
241
242
|
if (input.length > MAX_INPUT_CHARS) {
|
|
242
|
-
return res.status(422).json({ error:
|
|
243
|
+
return res.status(422).json({ error: msg(req, 'input 超长(上限 {max} 字符)', { max: MAX_INPUT_CHARS }) });
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
// 08-21 经营态:演出通路烧的是带钥匙的 API 钱,公开注册号(免费档)不开 —— 跟订阅 Claude 同一把资格
|
|
246
247
|
if (!can(req.user, 'subscription')) {
|
|
247
|
-
return res.status(403).json({ error: '演出模式仅限 Pro 档,暂未对外开放;当前档位请用设计会话', code: 'MODEL_LOCKED' });
|
|
248
|
+
return res.status(403).json({ error: msg(req, '演出模式仅限 Pro 档,暂未对外开放;当前档位请用设计会话'), code: 'MODEL_LOCKED' });
|
|
248
249
|
}
|
|
249
250
|
const quota = checkQuota(req.user);
|
|
250
251
|
if (!quota.ok) {
|
|
251
|
-
const word = quota.kind === 'lifetime' ? '试用额度' : '今日额度';
|
|
252
|
-
return res.status(429).json({ error:
|
|
252
|
+
const word = quota.kind === 'lifetime' ? msg(req, '试用额度') : msg(req, '今日额度');
|
|
253
|
+
return res.status(429).json({ error: msg(req, '{word}用完了({used} / {limit})', { word, used: fmtUsd(quota.used), limit: fmtUsd(quota.limit) }) });
|
|
253
254
|
}
|
|
254
255
|
const r = rate.take(req.user?.id ?? 'anon');
|
|
255
256
|
if (!r.ok) {
|
|
256
257
|
res.setHeader('Retry-After', Math.ceil(r.retryAfterMs / 1000));
|
|
257
|
-
return res.status(429).json({ error: '太快了,歇几秒再发' });
|
|
258
|
+
return res.status(429).json({ error: msg(req, '太快了,歇几秒再发') });
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
const dir = await resolvePerformanceDir(req, res);
|
|
261
262
|
if (!dir) return;
|
|
262
263
|
if (playing.has(dir.abs)) {
|
|
263
|
-
return res.status(409).json({ error: '这场演出正有一轮在跑,等它回完' });
|
|
264
|
+
return res.status(409).json({ error: msg(req, '这场演出正有一轮在跑,等它回完') });
|
|
264
265
|
}
|
|
265
266
|
|
|
266
267
|
const stream = req.body?.stream !== false;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { collectCards } from '../../lib/export-collect.js';
|
|
15
15
|
import { packageBundles } from '../../lib/export-package.js';
|
|
16
16
|
|
|
17
|
+
import { msg } from '../../shared/messages.js';
|
|
17
18
|
const FORMATS = ['raw', 'zip', 'md', 'handoff'];
|
|
18
19
|
const MAX_CARDS = 200;
|
|
19
20
|
|
|
@@ -47,22 +48,22 @@ export function makeCardsExportHandler({ guard, rootOf }) {
|
|
|
47
48
|
if (!project) return;
|
|
48
49
|
const { cardIds, format } = req.body || {};
|
|
49
50
|
if (!Array.isArray(cardIds) || !cardIds.length) {
|
|
50
|
-
return res.status(400).json({ error: '要导出哪几张卡(cardIds 不能为空)' });
|
|
51
|
+
return res.status(400).json({ error: msg(req, '要导出哪几张卡(cardIds 不能为空)') });
|
|
51
52
|
}
|
|
52
53
|
if (cardIds.length > MAX_CARDS) {
|
|
53
|
-
return res.status(400).json({ error:
|
|
54
|
+
return res.status(400).json({ error: msg(req, '一次最多导出 {max} 张卡', { max: MAX_CARDS }) });
|
|
54
55
|
}
|
|
55
56
|
if (!FORMATS.includes(format)) {
|
|
56
|
-
return res.status(400).json({ error:
|
|
57
|
+
return res.status(400).json({ error: msg(req, '不认识的导出格式:{format}', { format }) });
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
if (inflight >= MAX_INFLIGHT) {
|
|
60
61
|
res.setHeader('Retry-After', '10');
|
|
61
|
-
return res.status(429).json({ error: '导出排队中(这台机器一次只打两个包),过几秒再点' });
|
|
62
|
+
return res.status(429).json({ error: msg(req, '导出排队中(这台机器一次只打两个包),过几秒再点') });
|
|
62
63
|
}
|
|
63
64
|
if (inflightByProject.get(project.id)) {
|
|
64
65
|
res.setHeader('Retry-After', '10');
|
|
65
|
-
return res.status(429).json({ error: '这个项目已经有一个导出在跑了,等它完事' });
|
|
66
|
+
return res.status(429).json({ error: msg(req, '这个项目已经有一个导出在跑了,等它完事') });
|
|
66
67
|
}
|
|
67
68
|
inflight += 1;
|
|
68
69
|
inflightByProject.set(project.id, true);
|
|
@@ -71,7 +72,7 @@ export function makeCardsExportHandler({ guard, rootOf }) {
|
|
|
71
72
|
const { bundles, skipped } = await collectCards({ workspaceRoot, cardIds });
|
|
72
73
|
if (!bundles.length) {
|
|
73
74
|
return res.status(skipped[0]?.status === 404 ? 404 : 400)
|
|
74
|
-
.json({ error: '一张都没收到', skipped });
|
|
75
|
+
.json({ error: msg(req, '一张都没收到'), skipped });
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
// 格式合法性由 packageBundles 自己把关(raw 要单文件、md 要有 markdown),
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { renderDocx, cleanupRender } from '../../lib/docx/render.js';
|
|
14
14
|
|
|
15
|
+
import { msg } from '../../shared/messages.js';
|
|
15
16
|
/**
|
|
16
17
|
* @param {import('express').Response} res
|
|
17
18
|
* @param {{absPath:string, relPath:string}} target
|
|
@@ -22,7 +23,9 @@ export async function docxToPdfResponse(res, target) {
|
|
|
22
23
|
out = await renderDocx(target.absPath, {}); // 只要 pdf,不出 png
|
|
23
24
|
} catch (err) {
|
|
24
25
|
return res.status(500).json({
|
|
25
|
-
|
|
26
|
+
// 这个函数只收 res 不收 req(签名是给导出管线用的)。express 把请求挂在
|
|
27
|
+
// res.req 上,用它取语言,不为一句报错改所有调用点的签名。
|
|
28
|
+
error: msg(res.req, 'LibreOffice 转换失败'),
|
|
26
29
|
details: String(err.message || err).slice(0, 500),
|
|
27
30
|
});
|
|
28
31
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* server/api/instruction.js — 项目 Instruction 文件读写
|
|
3
3
|
*
|
|
4
|
-
* Instruction = workspace
|
|
4
|
+
* Instruction = workspace/CLAUDE.md(2026-08-24 挪到工作区根:画布可见 + SDK 根目录也原生注入)。SDK 启用 settingSources:
|
|
5
5
|
* ['project'] 后 agent 进每次 session 自动读到 system prompt。S1 已经
|
|
6
6
|
* 在 ensureProjectWorkspace 写入模板 starter,本阶段加 GET/PUT 让前端
|
|
7
7
|
* "项目背景" tab 直接编辑该文件。
|
|
@@ -37,7 +37,7 @@ router.get('/:pid/instruction', async (req, res, next) => {
|
|
|
37
37
|
// ensureProjectWorkspace 幂等补齐(老 project 第一次进 instruction tab 也能读到)
|
|
38
38
|
await ensureProjectWorkspace(req.params.pid);
|
|
39
39
|
|
|
40
|
-
const filePath = path.join(getSharedDir(req.params.pid),
|
|
40
|
+
const filePath = path.join(getSharedDir(req.params.pid), INSTRUCTION_FILE);
|
|
41
41
|
try {
|
|
42
42
|
const content = await fs.readFile(filePath, 'utf8');
|
|
43
43
|
res.json({ content, exists: true });
|
|
@@ -66,7 +66,7 @@ router.put('/:pid/instruction', async (req, res, next) => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
await ensureProjectWorkspace(req.params.pid);
|
|
69
|
-
const filePath = path.join(getSharedDir(req.params.pid),
|
|
69
|
+
const filePath = path.join(getSharedDir(req.params.pid), INSTRUCTION_FILE);
|
|
70
70
|
await fs.writeFile(filePath, content, 'utf8');
|
|
71
71
|
res.json({ ok: true });
|
|
72
72
|
} catch (err) { next(err); }
|
package/server/api/local.js
CHANGED
|
@@ -16,14 +16,15 @@ import { fileURLToPath } from 'node:url';
|
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { platform } from '../runtime/platform.js';
|
|
18
18
|
import { loadLocalConfig, saveLocalConfig, CONFIG_ENUMS } from '../runtime/local-config.js';
|
|
19
|
-
import { MODEL_CONFIG_ERRORS,
|
|
20
|
-
import { UPSTREAMS_BUILTIN } from '../engine/agent/model-table.js';
|
|
19
|
+
import { MODEL_CONFIG_ERRORS, externalModelIds } from '../engine/agent/model-context.js';
|
|
20
|
+
import { UPSTREAMS_BUILTIN, SHARED_SDK_ALIAS } from '../engine/agent/model-table.js';
|
|
21
21
|
import { capabilitySnapshot } from '../runtime/capabilities.js';
|
|
22
22
|
import { TOOL_CAPABILITIES } from '../engine/mcp/capability-gate.js';
|
|
23
23
|
import { probeCapabilities } from '../runtime/capabilities.js';
|
|
24
24
|
import { envView, setEnvValues, envPath } from '../runtime/local-env.js';
|
|
25
25
|
import { probeModel } from '../lib/ingress/slot-probe.js';
|
|
26
26
|
import { selectableModelsFor } from '../engine/agent/model-context.js';
|
|
27
|
+
import { msg } from '../shared/messages.js';
|
|
27
28
|
|
|
28
29
|
export const RESTART_EXIT_CODE = 75;
|
|
29
30
|
|
|
@@ -42,7 +43,8 @@ router.get('/status', (_req, res) => {
|
|
|
42
43
|
modelConfigErrors: MODEL_CONFIG_ERRORS,
|
|
43
44
|
// 本机能力位 + 每个能力位管着哪些工具(配置页那张表)
|
|
44
45
|
capabilities: capabilitySnapshot().map((c) => ({ ...c, tools: Object.entries(TOOL_CAPABILITIES).filter(([, v]) => v.cap === c.id).map(([t]) => t) })),
|
|
45
|
-
externalSdkAlias
|
|
46
|
+
// 外部插槽(和一切不写 sdkAlias 的行)借用的共用 spoof 名。响应字段名保持 externalSdkAlias 不动(对外形状)
|
|
47
|
+
externalSdkAlias: SHARED_SDK_ALIAS,
|
|
46
48
|
// 内置 Claude 行现在能不能选:'api_key' | 'login' | null(设置页「模型」那块的状态行)
|
|
47
49
|
claudeAuth: platform.claudeAuthPresent(),
|
|
48
50
|
// 内置上游(只报名字和是否配了钥匙,不报钥匙):配置页提示「这些名字被占了」
|
|
@@ -60,13 +62,13 @@ router.get('/config', (_req, res) => {
|
|
|
60
62
|
router.put('/config', (req, res) => {
|
|
61
63
|
const raw = req.body;
|
|
62
64
|
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
63
|
-
return res.status(400).json({ error: '配置必须是一个对象 { upstreams, models }' });
|
|
65
|
+
return res.status(400).json({ error: msg(req, '配置必须是一个对象 { upstreams, models }') });
|
|
64
66
|
}
|
|
65
67
|
try {
|
|
66
68
|
const v = saveLocalConfig(raw);
|
|
67
69
|
res.json({ ok: true, path: v.path, errors: v.errors, needsRestart: true });
|
|
68
70
|
} catch (err) {
|
|
69
|
-
res.status(500).json({ error:
|
|
71
|
+
res.status(500).json({ error: msg(req, '写配置失败:{err}', { err: err.message }) });
|
|
70
72
|
}
|
|
71
73
|
});
|
|
72
74
|
|
|
@@ -92,15 +94,15 @@ router.put('/env', async (req, res) => {
|
|
|
92
94
|
const probing = new Set();
|
|
93
95
|
router.post('/models/:id/probe', async (req, res) => {
|
|
94
96
|
const id = req.params.id;
|
|
95
|
-
if (!selectableModelsFor(req.user).some((m) => m.id === id)) return res.status(404).json({ error:
|
|
96
|
-
if (probing.has(id)) return res.status(409).json({ error: '这一行正在体检,等它完' });
|
|
97
|
+
if (!selectableModelsFor(req.user).some((m) => m.id === id)) return res.status(404).json({ error: msg(req, '模型 {id} 不在可选清单里(没配钥匙的行不体检)', { id }) });
|
|
98
|
+
if (probing.has(id)) return res.status(409).json({ error: msg(req, '这一行正在体检,等它完') });
|
|
97
99
|
probing.add(id);
|
|
98
100
|
try {
|
|
99
101
|
const vision = req.query.vision !== '0';
|
|
100
102
|
const timeoutMs = Math.min(120_000, Math.max(5_000, Number(req.query.timeoutMs) || 45_000));
|
|
101
103
|
res.json(await probeModel(id, { vision, timeoutMs }));
|
|
102
104
|
} catch (err) {
|
|
103
|
-
res.status(500).json({ error:
|
|
105
|
+
res.status(500).json({ error: msg(req, '体检出错:{err}', { err: err.message }) });
|
|
104
106
|
} finally {
|
|
105
107
|
probing.delete(id);
|
|
106
108
|
}
|
package/server/api/projects.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
listRunsForProject,
|
|
18
18
|
} from '../projects/store.js';
|
|
19
19
|
import { guardProject } from './_guard.js';
|
|
20
|
+
import { chalkPreview } from '../lib/board-excerpt.js';
|
|
20
21
|
import { taskManifest } from '../lib/artifact-target.js';
|
|
21
22
|
import { countPublishedByUser } from '../lib/publish-store.js';
|
|
22
23
|
import { checkQuota } from '../lib/quota.js';
|
|
@@ -39,6 +40,9 @@ const ownerScope = (req) => req.user?.id ?? null;
|
|
|
39
40
|
|
|
40
41
|
const KIND_VALUES = new Set(['project', 'quick']);
|
|
41
42
|
const KIND_QUERY_VALUES = new Set(['project', 'quick', 'all']);
|
|
43
|
+
// 项目模式(2026-08-27):design=设计工作台(现状默认)/ rp=演出(常驻角色演故事)。
|
|
44
|
+
// 切换下个会话生效(会话启动时读一次),真相源与工具对照表见 engine/mcp/mode-profile.js。
|
|
45
|
+
const MODE_VALUES = new Set(['design', 'rp']);
|
|
42
46
|
|
|
43
47
|
// GET /api/projects 默认行为(2026-05-07):不带 ?kind= 时 **只返 kind='project'**,
|
|
44
48
|
// 把闪聊(kind='quick')从主项目列表里挡掉 —— 避免老 client / 任何漏传 kind 的调用
|
|
@@ -84,7 +88,10 @@ router.get('/stats', async (req, res, next) => {
|
|
|
84
88
|
} catch { /* 目录没了:算 0 件 */ }
|
|
85
89
|
const kinds = {};
|
|
86
90
|
for (const a of artifacts) if (a.kind) kinds[a.kind] = (kinds[a.kind] || 0) + 1;
|
|
87
|
-
|
|
91
|
+
// 板书不进 artifacts(它不是产物),所以卡上"这里躺着什么"必须单独问一句 ——
|
|
92
|
+
// 不问的话整个演出项目会一直写着"还没出东西",而里面躺着一整个故事
|
|
93
|
+
const chalk = await chalkPreview(getSharedDir(p.id)).catch(() => null);
|
|
94
|
+
stats[p.id] = { tasks: artifacts.length, kinds, ...(chalk ? { chalk } : {}) };
|
|
88
95
|
}));
|
|
89
96
|
// dev 模式(不要求登录)没有 req.user,这两笔账就没有主语,整块不下发
|
|
90
97
|
const summary = req.user
|
|
@@ -101,7 +108,7 @@ const DESCRIPTION_MAX = 2000;
|
|
|
101
108
|
|
|
102
109
|
router.post('/', async (req, res, next) => {
|
|
103
110
|
try {
|
|
104
|
-
const { name, skillId, description, kind, autoNamed } = req.body || {};
|
|
111
|
+
const { name, skillId, description, kind, mode, autoNamed } = req.body || {};
|
|
105
112
|
if (!name || typeof name !== 'string' || !name.trim()) {
|
|
106
113
|
return res.status(400).json({ error: 'name required' });
|
|
107
114
|
}
|
|
@@ -114,8 +121,11 @@ router.post('/', async (req, res, next) => {
|
|
|
114
121
|
if (kind != null && !KIND_VALUES.has(kind)) {
|
|
115
122
|
return res.status(400).json({ error: `kind must be project|quick (got ${kind})` });
|
|
116
123
|
}
|
|
124
|
+
if (mode != null && !MODE_VALUES.has(mode)) {
|
|
125
|
+
return res.status(400).json({ error: `mode must be design|rp (got ${mode})` });
|
|
126
|
+
}
|
|
117
127
|
const project = createProject({
|
|
118
|
-
name, skillId, description, kind, autoNamed: !!autoNamed,
|
|
128
|
+
name, skillId, description, kind, mode: mode ?? undefined, autoNamed: !!autoNamed,
|
|
119
129
|
ownerId: req.user?.id ?? null,
|
|
120
130
|
});
|
|
121
131
|
await ensureProjectWorkspace(project.id);
|
|
@@ -154,6 +164,12 @@ router.patch('/:pid', (req, res, next) => {
|
|
|
154
164
|
}
|
|
155
165
|
patch.kind = req.body.kind;
|
|
156
166
|
}
|
|
167
|
+
if ('mode' in (req.body || {})) {
|
|
168
|
+
if (!MODE_VALUES.has(req.body.mode)) {
|
|
169
|
+
return res.status(400).json({ error: `mode must be design|rp (got ${req.body.mode})` });
|
|
170
|
+
}
|
|
171
|
+
patch.mode = req.body.mode;
|
|
172
|
+
}
|
|
157
173
|
// E1a(2026-08-13):会话真相源收敛到 projects.active_session_id 之后,
|
|
158
174
|
// 前端切会话得能直接写这个指针(此前唯一的写入方是 turn.js —— 不发消息、
|
|
159
175
|
// 只是切着看,指针就不动,别的标签页也就永远对不齐)。接受 null(清空)
|
package/server/api/publish.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import express from 'express';
|
|
14
14
|
import { guardProject } from './_guard.js';
|
|
15
15
|
import { publishSite, unpublishSite, validTaskName, lookupPublished } from '../lib/site-publish.js';
|
|
16
|
+
import { msg } from '../shared/messages.js';
|
|
16
17
|
|
|
17
18
|
const router = express.Router();
|
|
18
19
|
|
|
@@ -44,7 +45,7 @@ router.post(['/:pid/publish', '/:pid/publish/:task'], async (req, res) => {
|
|
|
44
45
|
} catch (err) {
|
|
45
46
|
if (err.status) return res.status(err.status).json({ error: err.message });
|
|
46
47
|
console.error('[publish] deploy failed:', err.stderr || err.message);
|
|
47
|
-
res.status(502).json({ error: '发布失败:Cloudflare 部署没成功,稍后再试' });
|
|
48
|
+
res.status(502).json({ error: msg(req, '发布失败:Cloudflare 部署没成功,稍后再试') });
|
|
48
49
|
}
|
|
49
50
|
});
|
|
50
51
|
|
|
@@ -57,7 +58,7 @@ router.delete(['/:pid/publish', '/:pid/publish/:task'], async (req, res) => {
|
|
|
57
58
|
} catch (err) {
|
|
58
59
|
if (err.status) return res.status(err.status).json({ error: err.message });
|
|
59
60
|
console.error('[publish] delete failed:', err.stderr || err.message);
|
|
60
|
-
res.status(502).json({ error: '下线失败,稍后再试' });
|
|
61
|
+
res.status(502).json({ error: msg(req, '下线失败,稍后再试') });
|
|
61
62
|
}
|
|
62
63
|
});
|
|
63
64
|
|