@xiaobuyu/nodesign 0.0.7 → 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 +8 -0
- package/server/api/assets.js +21 -33
- 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/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 +9 -2
- package/server/engine/agent/hooks/beat-gate.js +73 -0
- package/server/engine/agent/hooks/lifecycle.js +6 -2
- 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 +197 -7
- package/server/engine/agent/hooks.js +76 -6
- package/server/engine/agent/isolation.js +25 -2
- package/server/engine/agent/memory-config.js +27 -1
- 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 +566 -59
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +2 -2
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
- package/server/engine/agent/prompts/tools/site-reference.md +2 -1
- 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 +26 -14
- 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/mcp/actor.js +59 -0
- package/server/engine/mcp/capability-gate.js +21 -2
- package/server/engine/mcp/index.js +116 -24
- 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-find-batch.js +76 -14
- package/server/engine/mcp/tools/browse.js +1 -1
- package/server/engine/mcp/tools/cast-role.js +167 -0
- 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/look-at-board.js +4 -3
- package/server/engine/mcp/tools/open-sheet.js +242 -0
- package/server/engine/mcp/tools/organize-board.js +19 -5
- package/server/engine/mcp/tools/paint-still.js +1 -1
- package/server/engine/mcp/tools/pin-to-board.js +78 -3
- package/server/engine/mcp/tools/read-board.js +109 -16
- package/server/engine/mcp/tools/read-tavern-json.js +87 -19
- package/server/engine/mcp/tools/report-issue.js +1 -0
- 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 +2 -2
- 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 +1 -1
- 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 +567 -107
- package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +11 -3
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +34 -4
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +20 -4
- 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 -0
- 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 +36 -0
- 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 +18 -1
- 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/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/json-preview.js +58 -0
- 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/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/state-table.js +237 -0
- package/server/lib/state-trend.js +94 -0
- package/server/lib/state-triggers.js +199 -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 +95 -6
- package/server/projects/session-jsonl.js +110 -0
- package/server/projects/store.js +56 -3
- package/server/projects/viewpoint-store.js +35 -3
- package/server/projects/workspace-slots.js +30 -0
- package/server/projects/workspace-templates.js +29 -0
- package/server/projects/workspace.js +16 -17
- package/server/runtime/capabilities.js +6 -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-BoOBaIyz.js → BrowserWindow-CcaGRWz5.js} +1 -1
- package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
- package/web/dist/assets/{DocxWindow-B_u6aX9o.js → DocxWindow-B9KZmU5z.js} +2 -2
- package/web/dist/assets/{MermaidBlock-BO-46Du5.js → MermaidBlock-DrfRnz8E.js} +43 -43
- package/web/dist/assets/{SiteWindow-ConPmzdM.js → SiteWindow-CJOjCF7d.js} +6 -6
- package/web/dist/assets/{abnfDiagram-VCTEODGH-FCO9yf-n.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
- package/web/dist/assets/{arc-BLcEOSRX.js → arc-C1-HSUny.js} +1 -1
- package/web/dist/assets/{architectureDiagram-5GKGNRK7-kxiX4TeN.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
- package/web/dist/assets/{blockDiagram-NRAW4CY4-CptFksix.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
- package/web/dist/assets/{c4Diagram-UCG6FXSJ-Dxmxj34F.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
- package/web/dist/assets/channel-BxpWNOGU.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-D9inx5oo.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-DydfHlEc.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
- package/web/dist/assets/{chunk-F27PBJKO-BO7GxhSv.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
- package/web/dist/assets/{chunk-G27WJ6UU-BzP6YiQ_.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-CHcgcd0s.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
- package/web/dist/assets/{chunk-LCL6LL3I-D2Wg5JtX.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
- package/web/dist/assets/{chunk-POPQ4Y6H-B2fCRfpM.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
- package/web/dist/assets/{chunk-SVP7TREG-JSLcsRlZ.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BCjvaeHm.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-0uKv6oJ7.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
- package/web/dist/assets/{cynefin-OW5HDTMX-Bvt-gEmA.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-C9Ht02qC.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
- package/web/dist/assets/{dagre-3AP2YEHR-BfxZny4s.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
- package/web/dist/assets/{diagram-S7CK7UJ4-CrE0ZETE.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
- package/web/dist/assets/{diagram-UQ7AKVKN-BO8VQ4Fx.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
- package/web/dist/assets/{diagram-VSXAHHWV-CHUZnR9F.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
- package/web/dist/assets/{diagram-VX7I27RA-DYQAz1Vu.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
- package/web/dist/assets/{diagram-Z3DM3KII-BDjivgLr.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-PWID7BFC-Bpe_QVbH.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
- package/web/dist/assets/{erDiagram-SSCWMZ5O-BxhwuAu1.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
- package/web/dist/assets/{flowDiagram-A5DVABFB-CCw2H08-.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
- package/web/dist/assets/{ganttDiagram-EL5Y4UJY-DtTc7MyI.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
- package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-BvORxMd2.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-DMcrkypD.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CZkTIbe-.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
- package/web/dist/assets/{journeyDiagram-EYS64GPL-DR0XazdE.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
- package/web/dist/assets/{kanban-definition-3QL26DDD-DN8VbwVu.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
- package/web/dist/assets/{layout-9_paCbyt.js → layout-Dhgck-Zo.js} +1 -1
- package/web/dist/assets/{linear-g6Ct012e.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-BGt4omYC.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
- package/web/dist/assets/{pegDiagram-XKGWAZYB-uSfS-PBE.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
- package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
- package/web/dist/assets/{pieDiagram-E7YTZNPT-DpeI935H.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-AXDQQJYC-B7IuNvWa.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
- package/web/dist/assets/{railroadDiagram-O6MQD6OU-ARzYpPQI.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EFPCY7ZU-Cj4upkAo.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
- package/web/dist/assets/{rotate-cw-CXJcyHWk.js → rotate-cw-62liz7LX.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-P5KCCOFB-OaVwSnLP.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-1yrUbq4t.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-nYH5p9D6.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CTtTVKoH.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
- package/web/dist/assets/{stateDiagram-HBIQ2CUA-Dj5HvyNq.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
- package/web/dist/assets/{swimlanes-XN3QIQJK-umeT0HpB.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
- package/web/dist/assets/{timeline-definition-24CTP7MA-MqnQfHKB.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
- package/web/dist/assets/{vennDiagram-4TSXK5OY-b2liyZra.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-VM6X3IG4-QMK-zNh1.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
- package/web/dist/assets/{xychartDiagram-S5SC5T6Z-D5Mc6tfA.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
- package/web/dist/index.html +2 -2
- package/server/engine/mcp/tools/arrange-on-board.js +0 -126
- 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/relate-on-board.js +0 -150
- package/server/engine/mcp/tools/sketch-on-board.js +0 -345
- 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-Bg3rgkvi.js +0 -11
- package/web/dist/assets/channel-DFyiuGL0.js +0 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +0 -1
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +0 -1
- package/web/dist/assets/index-C0KUf6Wd.css +0 -1
- package/web/dist/assets/index-DGG5wIZs.js +0 -2118
- 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-uP4uvrOD.js +0 -31
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +0 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +0 -8
|
@@ -20,15 +20,17 @@
|
|
|
20
20
|
import { promises as fs } from 'fs';
|
|
21
21
|
import path from 'path';
|
|
22
22
|
import { getSharedDir, ensureProjectWorkspace, gitRenamesSince } from './workspace.js';
|
|
23
|
-
import { CHALK_DIR } from '../lib/chalk.js';
|
|
23
|
+
import { CHALK_DIR, trashChalkFile } from '../lib/chalk.js';
|
|
24
|
+
import { estimateSizeOn } from '../lib/board-kind-sizes.js';
|
|
24
25
|
|
|
25
26
|
export {
|
|
26
27
|
DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS,
|
|
27
28
|
} from './board-limits.js';
|
|
28
29
|
export { TEXT_FONTS } from './board-sanitize.js';
|
|
29
|
-
import { DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS } from './board-limits.js';
|
|
30
|
+
import { DEFAULT_BOARD_SIZE, MAX_BOARD_BYTES, MAX_OBJECTS, MAX_ZONES, MAX_BINDINGS, MAX_LANES, MAX_SHEETS } from './board-limits.js';
|
|
30
31
|
import {
|
|
31
|
-
clampNum, sanitizeSize, sanitizeTag, sanitizeObject, sanitizeBinding, sanitizeZone, sanitizeBoard,
|
|
32
|
+
clampNum, sanitizeSize, sanitizeTag, sanitizeObject, sanitizeBinding, sanitizeZone, sanitizeBoard, sanitizeLane, sanitizeRoll, sanitizeSheet,
|
|
33
|
+
isSafeCanvasId,
|
|
32
34
|
} from './board-sanitize.js';
|
|
33
35
|
|
|
34
36
|
// 分区自动铺位常数 —— 与前端 BoardCanvas 的 ZONE_* 保持一致(数值约定,非共享代码)
|
|
@@ -107,7 +109,14 @@ export function patchBoard(pid, patch) {
|
|
|
107
109
|
if (!isSafeCanvasId(rawId)) continue;
|
|
108
110
|
const id = fwd(rawId);
|
|
109
111
|
if (o === null) { delete board.objects[id]; removed.add(id); continue; }
|
|
110
|
-
|
|
112
|
+
// **合并语义**(2026-08-25):patch 条目盖在已有条目上,缺席字段保留。
|
|
113
|
+
// 病根:前端本地 layout 会落后于服务端(board.json 一次性加载闸门),
|
|
114
|
+
// 入座把 agent 刚写的条目当新客回写一条 {x,y,z} —— 整条替换语义下
|
|
115
|
+
// by/seat/w/h/tag/staging 全被抹掉且零报错(08-25 体检三陷阱之②)。
|
|
116
|
+
// 合并后瘦条目只更新它带来的字段。要删整条传 null;单字段清除走
|
|
117
|
+
// 各自的专用路(commitStaging 清 staging、removeByTag 摘 tag)。
|
|
118
|
+
const merged = board.objects[id] ? { ...board.objects[id], ...o } : o;
|
|
119
|
+
const s = sanitizeObject(merged, board.size);
|
|
111
120
|
if (s && (board.objects[id] || Object.keys(board.objects).length < MAX_OBJECTS)) board.objects[id] = s;
|
|
112
121
|
}
|
|
113
122
|
}
|
|
@@ -115,13 +124,81 @@ export function patchBoard(pid, patch) {
|
|
|
115
124
|
for (const [id, b] of Object.entries(patch.bindings)) {
|
|
116
125
|
if (typeof id !== 'string' || id.length > 300) continue;
|
|
117
126
|
if (b === null) { delete board.bindings[id]; continue; }
|
|
118
|
-
|
|
127
|
+
// 合并语义同 objects(08-25):改材质/label 的瘦 patch 别抹掉 follow 等字段
|
|
128
|
+
const s = sanitizeBinding(board.bindings[id] ? { ...board.bindings[id], ...b } : b);
|
|
119
129
|
if (!s) continue;
|
|
120
130
|
// 端点也要转发:agent 本轮拿旧路径连的线,落下来必须连到新名字上
|
|
121
131
|
const fixed = { ...s, from: fwd(s.from), to: fwd(s.to) };
|
|
122
132
|
if (board.bindings[id] || Object.keys(board.bindings).length < MAX_BINDINGS) board.bindings[id] = fixed;
|
|
123
133
|
}
|
|
124
134
|
}
|
|
135
|
+
// 线注册表(08-27 空间规划):合并语义同 objects;parent 也走改名转发
|
|
136
|
+
if (patch?.lanes && typeof patch.lanes === 'object') {
|
|
137
|
+
board.lanes = board.lanes || {};
|
|
138
|
+
for (const [name, l] of Object.entries(patch.lanes)) {
|
|
139
|
+
const tag = sanitizeTag(name);
|
|
140
|
+
if (!tag) continue;
|
|
141
|
+
if (l === null) { delete board.lanes[tag]; continue; }
|
|
142
|
+
const s = sanitizeLane(board.lanes[tag] ? { ...board.lanes[tag], ...l } : l);
|
|
143
|
+
if (!s) continue;
|
|
144
|
+
const fixed = s.parent ? { ...s, parent: fwd(s.parent) } : s;
|
|
145
|
+
if (board.lanes[tag] || Object.keys(board.lanes).length < MAX_LANES) board.lanes[tag] = fixed;
|
|
146
|
+
}
|
|
147
|
+
if (!Object.keys(board.lanes).length) delete board.lanes;
|
|
148
|
+
}
|
|
149
|
+
// 卷(2026-08-27 收纳器):合并语义同 lanes;null = 展开(删条目)。
|
|
150
|
+
// 只动状态位不动成员座位 —— 展开即归位靠的就是这里什么都不搬。
|
|
151
|
+
if (patch?.rolls && typeof patch.rolls === 'object') {
|
|
152
|
+
board.rolls = board.rolls || {};
|
|
153
|
+
for (const [name, r] of Object.entries(patch.rolls)) {
|
|
154
|
+
const tag = sanitizeTag(name);
|
|
155
|
+
if (!tag) continue;
|
|
156
|
+
if (r === null) { delete board.rolls[tag]; continue; }
|
|
157
|
+
const s = sanitizeRoll(board.rolls[tag] ? { ...board.rolls[tag], ...r } : r);
|
|
158
|
+
if (!s) continue;
|
|
159
|
+
if (board.rolls[tag] || Object.keys(board.rolls).length < MAX_LANES) board.rolls[tag] = s;
|
|
160
|
+
}
|
|
161
|
+
if (!Object.keys(board.rolls).length) delete board.rolls;
|
|
162
|
+
}
|
|
163
|
+
// 纸(2026-08-29 纸范式):合并语义同 lanes;null = 撕掉登记(成员座位不动)。
|
|
164
|
+
// 纸名不是路径,不走改名转发。
|
|
165
|
+
if (patch?.sheets && typeof patch.sheets === 'object') {
|
|
166
|
+
board.sheets = board.sheets || {};
|
|
167
|
+
for (const [name, s0] of Object.entries(patch.sheets)) {
|
|
168
|
+
const nm = sanitizeTag(name);
|
|
169
|
+
if (!nm) continue;
|
|
170
|
+
if (s0 === null) { delete board.sheets[nm]; continue; }
|
|
171
|
+
const s = sanitizeSheet(board.sheets[nm] ? { ...board.sheets[nm], ...s0 } : s0);
|
|
172
|
+
if (!s) continue;
|
|
173
|
+
if (board.sheets[nm] || Object.keys(board.sheets).length < MAX_SHEETS) board.sheets[nm] = s;
|
|
174
|
+
}
|
|
175
|
+
if (!Object.keys(board.sheets).length) delete board.sheets;
|
|
176
|
+
}
|
|
177
|
+
// 跟随规则(2026-08-30):`{ 组tag: {target,side?,label?} | null }`。规则和线分开存
|
|
178
|
+
// —— 线要求两端此刻都在板上,规则不要求,所以「开场先立规则再写第一章」这条
|
|
179
|
+
// skill 教了、模型也一直照做的顺序,第一次真的走得通。
|
|
180
|
+
if (patch?.follows && typeof patch.follows === 'object') {
|
|
181
|
+
board.follows = board.follows || {};
|
|
182
|
+
for (const [g, r] of Object.entries(patch.follows)) {
|
|
183
|
+
const gt = sanitizeTag(g);
|
|
184
|
+
if (!gt) continue;
|
|
185
|
+
if (r === null) { delete board.follows[gt]; continue; }
|
|
186
|
+
board.follows[gt] = r;
|
|
187
|
+
}
|
|
188
|
+
const clean = sanitizeBoard({ ...board, follows: board.follows }).follows;
|
|
189
|
+
if (clean) board.follows = clean; else delete board.follows;
|
|
190
|
+
}
|
|
191
|
+
// 待摆产物队列(刀 G):整表替换(写方只有 board-seater 一个,diff 没意义)
|
|
192
|
+
if (patch?.pending !== undefined) {
|
|
193
|
+
const clean = sanitizeBoard({ ...board, pending: patch.pending }).pending;
|
|
194
|
+
if (clean?.length) board.pending = clean; else delete board.pending;
|
|
195
|
+
}
|
|
196
|
+
// 暂存架原点(2026-08-30):整值替换,写方只有 board-seater / board-tasklist;
|
|
197
|
+
// null = 撤架(下批到货重立)
|
|
198
|
+
if (patch?.shelf !== undefined) {
|
|
199
|
+
const clean = sanitizeBoard({ ...board, shelf: patch.shelf }).shelf;
|
|
200
|
+
if (clean) board.shelf = clean; else delete board.shelf;
|
|
201
|
+
}
|
|
125
202
|
// 主角覆盖:null = 撤销(回到 pickHero 自动推断),字符串 = 显式立主角
|
|
126
203
|
if (patch?.hero !== undefined) {
|
|
127
204
|
if (patch.hero === null) delete board.hero;
|
|
@@ -205,7 +282,8 @@ export function removeByTag(pid, tag) {
|
|
|
205
282
|
// 以进程身份删任意文件;sanitize 现在也拒 `..`,这里是第二道闸)
|
|
206
283
|
if (id.startsWith(`${CHALK_DIR}/`)) {
|
|
207
284
|
const abs = chalkAbsPath(pid, id);
|
|
208
|
-
|
|
285
|
+
// 软删进 .nd/trash/(08-25:擦掉的板书要捞得回来)
|
|
286
|
+
if (abs) await trashChalkFile(getSharedDir(pid), abs);
|
|
209
287
|
delete board.objects[id]; gone.add(id); removed += 1; continue;
|
|
210
288
|
}
|
|
211
289
|
delete o.tag;
|
|
@@ -214,6 +292,8 @@ export function removeByTag(pid, tag) {
|
|
|
214
292
|
if (b.tag === t || gone.has(b.from) || gone.has(b.to)) { delete board.bindings[id]; removed += 1; }
|
|
215
293
|
}
|
|
216
294
|
if (board.hero && gone.has(board.hero)) delete board.hero;
|
|
295
|
+
// 擦组连卷的状态位一起清(收着的组被 erase_group 后不该留一张空卷卡)
|
|
296
|
+
if (board.rolls?.[t]) { delete board.rolls[t]; if (!Object.keys(board.rolls).length) delete board.rolls; }
|
|
217
297
|
await writeBoard(pid, board);
|
|
218
298
|
return { board, removed };
|
|
219
299
|
});
|
|
@@ -451,6 +531,8 @@ export async function reconcileBoardRenames(pid) {
|
|
|
451
531
|
* 文件夹没坐标(还没被摆过)时就摆到桌面上,前端下一轮会给它安排位置。
|
|
452
532
|
*/
|
|
453
533
|
export function pinToZone(pid, { objectId, zoneId = '' }) {
|
|
534
|
+
// 08-27 审计修:这里是 patchBoard 之外唯一的 objects 写入口,原先没有数量闸 ——
|
|
535
|
+
// 反复 pin 新 id 可把 board.objects 撑过 MAX_OBJECTS(只剩 2MB 字节兜底)。
|
|
454
536
|
return withBoardLock(pid, async () => {
|
|
455
537
|
const board = await readBoard(pid);
|
|
456
538
|
const now = Date.now();
|
|
@@ -494,12 +576,19 @@ export function pinToZone(pid, { objectId, zoneId = '' }) {
|
|
|
494
576
|
}
|
|
495
577
|
if (!slot) slot = { x: PAD, y: PAD };
|
|
496
578
|
|
|
579
|
+
if (!board.objects[oid] && Object.keys(board.objects).length >= MAX_OBJECTS) {
|
|
580
|
+
throw new Error(`board 已有 ${MAX_OBJECTS} 件,pin 不进新条目(失控兜底,正常用不该撞到)`);
|
|
581
|
+
}
|
|
497
582
|
const zMax = Math.max(10, ...Object.values(board.objects).map(o => o.z || 0));
|
|
583
|
+
// 落座即写估算 w/h(2026-08-29 纸范式刀 3):此前 pin 出来的条目只有坐标,
|
|
584
|
+
// read_board 只能报形态表猜值。估算先落盘、前端渲染后按真值回写(useMeasuredSize)。
|
|
585
|
+
const sz = estimateSizeOn(board, oid, board.objects[oid] || null);
|
|
498
586
|
board.objects[oid] = {
|
|
499
587
|
...(board.objects[oid] || {}),
|
|
500
588
|
x: clampNum(slot.x, 0, board.size.w, PAD),
|
|
501
589
|
y: clampNum(slot.y, 0, board.size.h, PAD),
|
|
502
590
|
z: zMax + 1,
|
|
591
|
+
...(Number.isFinite(board.objects[oid]?.w) ? {} : { w: Math.round(sz.w), h: Math.round(sz.h) }),
|
|
503
592
|
};
|
|
504
593
|
await writeBoard(pid, board);
|
|
505
594
|
return { board, zone: zid ? { id: zid } : null, placed: board.objects[oid] };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/projects/session-jsonl.js — SDK 会话转录(jsonl)的文件层(2026-08-30 拆出)。
|
|
3
|
+
*
|
|
4
|
+
* 这里只有"那个文件在哪、在不在、怎么安全地截短"这三件事,跟任何业务无关。
|
|
5
|
+
* 分开住是因为读它的人不止一个:回退要截断它,模型切换要靠它判断"这个会话跑过没有"
|
|
6
|
+
* (没跑过的会话没有历史,跨通路闸不该拦),以后想做分支浏览的也得从它读。
|
|
7
|
+
*
|
|
8
|
+
* 转录的位置由 SDK 定:`<CLAUDE_CONFIG_DIR>/projects/<encodeCwdForSDK(cwd)>/<sid>.jsonl`。
|
|
9
|
+
* 扁平化之后同一个项目所有会话共用一个 cwd,所以它们的 jsonl 都在同一个目录下、按 sid 分文件。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { promises as fs } from 'fs';
|
|
14
|
+
import { encodeCwdForSDK } from './workspace.js';
|
|
15
|
+
import { platform } from '../runtime/platform.js';
|
|
16
|
+
|
|
17
|
+
const GLOBAL_CLAUDE_CONFIG_DIR = platform.claudeConfigDir;
|
|
18
|
+
|
|
19
|
+
/** 这个会话的转录文件路径。 */
|
|
20
|
+
export function sessionJsonlPath(sessionRoot, sid) {
|
|
21
|
+
return path.join(GLOBAL_CLAUDE_CONFIG_DIR, 'projects', encodeCwdForSDK(sessionRoot), `${sid}.jsonl`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 转录在不在 —— 等价于"这个会话跑过至少一轮"。 */
|
|
25
|
+
export async function jsonlExistsForSession(sessionRoot, sid) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(sessionJsonlPath(sessionRoot, sid));
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 对话层回滚(2026-08-08):把 jsonl 截断到 userMessageId 那条之前(含它与其后全部)。
|
|
36
|
+
* jsonl 是追加式日志,截到 prefix = 它历史上真实存在过的状态,resume 天然自洽;
|
|
37
|
+
* 之后的 file-history-snapshot 属于被撤销的编辑,一并丢弃是正确语义。
|
|
38
|
+
* 原子写(tmp+rename)。找不到该 uuid 返 null(fail-soft:文件回滚仍算成功)。
|
|
39
|
+
*/
|
|
40
|
+
export async function truncateJsonlAtMessage(sessionRoot, sid, userMessageId) {
|
|
41
|
+
const jsonlPath = sessionJsonlPath(sessionRoot, sid);
|
|
42
|
+
try {
|
|
43
|
+
const raw = await fs.readFile(jsonlPath, 'utf8');
|
|
44
|
+
const lines = raw.split('\n');
|
|
45
|
+
let cut = -1;
|
|
46
|
+
for (let i = 0; i < lines.length; i++) {
|
|
47
|
+
if (!lines[i] || !lines[i].includes(userMessageId)) continue;
|
|
48
|
+
try { if (JSON.parse(lines[i]).uuid === userMessageId) { cut = i; break; } } catch { /* 非 JSON 行跳过 */ }
|
|
49
|
+
}
|
|
50
|
+
if (cut < 0) {
|
|
51
|
+
console.warn(`[sessions.rewind] uuid ${userMessageId.slice(0, 8)} 不在 jsonl 里,跳过对话截断`);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const kept = lines.slice(0, cut).join('\n');
|
|
55
|
+
const tmp = `${jsonlPath}.tmp-rewind`;
|
|
56
|
+
await fs.writeFile(tmp, kept ? `${kept}\n` : '');
|
|
57
|
+
await fs.rename(tmp, jsonlPath);
|
|
58
|
+
return lines.filter(Boolean).length - lines.slice(0, cut).filter(Boolean).length;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.warn(`[sessions.rewind] jsonl 截断失败(不影响文件回滚):${err.message}`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 砍掉转录末尾的那条用户消息(连同它之后的一切)。
|
|
67
|
+
*
|
|
68
|
+
* 专给 fork 用。SDK 的 `forkSession(upToMessageId)` 是**含**那条的(08-30 探针实测:
|
|
69
|
+
* fork 到第二条用户消息,新转录里那条还在),而「从这里分叉」用户点的是**自己那句
|
|
70
|
+
* 说错的话** —— 含着它 fork 出来等于什么都没改。所以 fork 完再补这一刀。
|
|
71
|
+
*
|
|
72
|
+
* 为什么不能拿原来那个 uuid 去截:fork 会 **remap 每条消息的 uuid**(SDK 文档原话
|
|
73
|
+
* "remapping every message UUID"),原 id 在新文件里根本不存在。而 fork 既然截止在
|
|
74
|
+
* 那条消息,它必然就是新转录里最后一条**真**用户消息 —— tool_result 也顶着
|
|
75
|
+
* type='user' 的壳,所以要把那种排掉,认的是带 text block(或纯字符串正文)的那条。
|
|
76
|
+
*
|
|
77
|
+
* @returns {Promise<number|null>} 删掉的行数;没找到用户消息返回 null(fail-soft)
|
|
78
|
+
*/
|
|
79
|
+
export async function truncateJsonlAtLastUserMessage(sessionRoot, sid) {
|
|
80
|
+
const jsonlPath = sessionJsonlPath(sessionRoot, sid);
|
|
81
|
+
try {
|
|
82
|
+
const raw = await fs.readFile(jsonlPath, 'utf8');
|
|
83
|
+
const lines = raw.split('\n');
|
|
84
|
+
let cut = -1;
|
|
85
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
86
|
+
if (!lines[i]) continue;
|
|
87
|
+
let o;
|
|
88
|
+
try { o = JSON.parse(lines[i]); } catch { continue; }
|
|
89
|
+
if (o.type !== 'user') continue;
|
|
90
|
+
const c = o.message?.content;
|
|
91
|
+
const isRealUserText = typeof c === 'string'
|
|
92
|
+
|| (Array.isArray(c) && c.some((b) => b?.type === 'text'));
|
|
93
|
+
if (!isRealUserText) continue; // tool_result 也是 type='user'
|
|
94
|
+
cut = i;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
if (cut < 0) {
|
|
98
|
+
console.warn(`[fork] ${sid.slice(0, 8)} 的转录里找不到用户消息,跳过补刀`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const kept = lines.slice(0, cut).join('\n');
|
|
102
|
+
const tmp = `${jsonlPath}.tmp-fork`;
|
|
103
|
+
await fs.writeFile(tmp, kept ? `${kept}\n` : '');
|
|
104
|
+
await fs.rename(tmp, jsonlPath);
|
|
105
|
+
return lines.filter(Boolean).length - lines.slice(0, cut).filter(Boolean).length;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.warn(`[fork] 转录补刀失败:${err.message}`);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
package/server/projects/store.js
CHANGED
|
@@ -82,6 +82,15 @@ if (!projectsColNames.has('auto_named')) {
|
|
|
82
82
|
console.log('[projects/store] projects.auto_named column added');
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// 模式分离(2026-08-27):design(设计工作台,现状)vs rp(演出:常驻角色演故事)。
|
|
86
|
+
// 两种模式共用全部基础设施(画布/黑板/会话/精灵),差异只在提示词分区与工具面
|
|
87
|
+
// (对照表在 engine/mcp/mode-profile.js 一份)。默认 'design' 让全部存量项目零行为变化。
|
|
88
|
+
// 会话启动时读一次 —— 切模式**下个会话生效**,跟"改人设下次上场生效"同一节奏。
|
|
89
|
+
if (!projectsColNames.has('mode')) {
|
|
90
|
+
db.exec("ALTER TABLE projects ADD COLUMN mode TEXT NOT NULL DEFAULT 'design'");
|
|
91
|
+
console.log('[projects/store] projects.mode column added (default design)');
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
// 多用户内测(2026-07-30):项目归属。NULL 的存量行由 bootstrapAuth() 回填 admin
|
|
86
95
|
if (!projectsColNames.has('owner_id')) {
|
|
87
96
|
db.exec('ALTER TABLE projects ADD COLUMN owner_id TEXT');
|
|
@@ -151,6 +160,7 @@ function rowToProject(row) {
|
|
|
151
160
|
skillId: row.skill_id,
|
|
152
161
|
description: row.description || null,
|
|
153
162
|
kind: row.kind || 'project',
|
|
163
|
+
mode: row.mode || 'design',
|
|
154
164
|
autoNamed: !!row.auto_named,
|
|
155
165
|
ownerId: row.owner_id || null,
|
|
156
166
|
activeSessionId: row.active_session_id,
|
|
@@ -204,6 +214,7 @@ export function createProject({
|
|
|
204
214
|
skillId = 'deskskill-engine-mini',
|
|
205
215
|
description = null,
|
|
206
216
|
kind = 'project',
|
|
217
|
+
mode = 'design',
|
|
207
218
|
autoNamed = false,
|
|
208
219
|
ownerId = null,
|
|
209
220
|
}) {
|
|
@@ -211,15 +222,18 @@ export function createProject({
|
|
|
211
222
|
if (kind !== 'project' && kind !== 'quick') {
|
|
212
223
|
throw new Error(`createProject: kind 非法 (${kind})`);
|
|
213
224
|
}
|
|
225
|
+
if (mode !== 'design' && mode !== 'rp') {
|
|
226
|
+
throw new Error(`createProject: mode 非法 (${mode})`);
|
|
227
|
+
}
|
|
214
228
|
const id = newProjectId();
|
|
215
229
|
const desc = (typeof description === 'string' && description.trim()) ? description.trim() : null;
|
|
216
230
|
db.prepare(
|
|
217
|
-
`INSERT INTO projects (id, name, skill_id, description, kind, auto_named, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
218
|
-
).run(id, name.trim(), skillId, desc, kind, autoNamed ? 1 : 0, ownerId);
|
|
231
|
+
`INSERT INTO projects (id, name, skill_id, description, kind, mode, auto_named, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
232
|
+
).run(id, name.trim(), skillId, desc, kind, mode, autoNamed ? 1 : 0, ownerId);
|
|
219
233
|
return getProject(id);
|
|
220
234
|
}
|
|
221
235
|
|
|
222
|
-
/** 更新(仅允许 name / skill_id / description / active_session_id / kind / autoNamed) */
|
|
236
|
+
/** 更新(仅允许 name / skill_id / description / active_session_id / kind / mode / autoNamed) */
|
|
223
237
|
export function updateProject(id, patch) {
|
|
224
238
|
validateProjectId(id);
|
|
225
239
|
// 用户显式改名 = 这名字他自己定了,系统不再拿摘要覆盖
|
|
@@ -228,12 +242,16 @@ export function updateProject(id, patch) {
|
|
|
228
242
|
if ('kind' in patch && patch.kind !== 'project' && patch.kind !== 'quick') {
|
|
229
243
|
throw new Error(`updateProject: kind 非法 (${patch.kind})`);
|
|
230
244
|
}
|
|
245
|
+
if ('mode' in patch && patch.mode !== 'design' && patch.mode !== 'rp') {
|
|
246
|
+
throw new Error(`updateProject: mode 非法 (${patch.mode})`);
|
|
247
|
+
}
|
|
231
248
|
const map = {
|
|
232
249
|
name: 'name',
|
|
233
250
|
skillId: 'skill_id',
|
|
234
251
|
description: 'description',
|
|
235
252
|
activeSessionId: 'active_session_id',
|
|
236
253
|
kind: 'kind',
|
|
254
|
+
mode: 'mode',
|
|
237
255
|
autoNamed: 'auto_named',
|
|
238
256
|
};
|
|
239
257
|
const sets = [];
|
|
@@ -264,6 +282,41 @@ export function setActiveSession(projectId, sessionId) {
|
|
|
264
282
|
}
|
|
265
283
|
|
|
266
284
|
/** 列 project 关联的 runs(用于级联清理) */
|
|
285
|
+
/**
|
|
286
|
+
* 两个模式各自的用量(控制台那两张卡)。
|
|
287
|
+
*
|
|
288
|
+
* ⛔ **排掉站主自己**:他一个人的设计项目就比所有用户加起来还多,混进去这两个数
|
|
289
|
+
* 就只是在量他自己。同仓口径,见 feedback-creative-tool-metrics。
|
|
290
|
+
*
|
|
291
|
+
* ⚠️ 两条查询分开跑不合并:runs 要 join run_model_usage 才拿得到钱,而那张表
|
|
292
|
+
* 一个 run 有多行(一轮里换过几个模型就几行),并到项目那条里会把 projects
|
|
293
|
+
* 乘出好几份。COUNT(DISTINCT) 能救回来,但读的人得先看出这里有个陷阱。
|
|
294
|
+
*
|
|
295
|
+
* @returns {Array<{mode, projects, users, runs, costUsd, firstAt}>}
|
|
296
|
+
*/
|
|
297
|
+
export function modeStats() {
|
|
298
|
+
const HUMANS = "p.owner_id IN (SELECT id FROM users WHERE role <> 'admin')";
|
|
299
|
+
const out = new Map();
|
|
300
|
+
for (const r of db.prepare(
|
|
301
|
+
`SELECT p.mode AS mode, COUNT(*) AS projects, COUNT(DISTINCT p.owner_id) AS users,
|
|
302
|
+
MIN(p.created_at) AS firstAt
|
|
303
|
+
FROM projects p WHERE ${HUMANS} GROUP BY p.mode`,
|
|
304
|
+
).all()) out.set(r.mode, { ...r, runs: 0, costUsd: 0 });
|
|
305
|
+
|
|
306
|
+
for (const r of db.prepare(
|
|
307
|
+
`SELECT p.mode AS mode, COUNT(DISTINCT r.id) AS runs,
|
|
308
|
+
COALESCE(SUM(m.cost_usd), 0) AS costUsd
|
|
309
|
+
FROM runs r
|
|
310
|
+
JOIN projects p ON p.id = r.project_id
|
|
311
|
+
LEFT JOIN run_model_usage m ON m.run_id = r.id
|
|
312
|
+
WHERE ${HUMANS} GROUP BY p.mode`,
|
|
313
|
+
).all()) {
|
|
314
|
+
const e = out.get(r.mode) || { mode: r.mode, projects: 0, users: 0, firstAt: null };
|
|
315
|
+
out.set(r.mode, { ...e, runs: r.runs, costUsd: r.costUsd });
|
|
316
|
+
}
|
|
317
|
+
return [...out.values()];
|
|
318
|
+
}
|
|
319
|
+
|
|
267
320
|
export function listRunsForProject(projectId) {
|
|
268
321
|
validateProjectId(projectId);
|
|
269
322
|
return db.prepare('SELECT id FROM runs WHERE project_id = ? ORDER BY created_at DESC').all(projectId);
|
|
@@ -30,9 +30,27 @@ function sanitizeViewpoint(raw, userId) {
|
|
|
30
30
|
} : null;
|
|
31
31
|
if (camera && Object.values(camera).some(v => v === null)) return null;
|
|
32
32
|
const str = (v, n) => (typeof v === 'string' && v.length <= n ? v : null);
|
|
33
|
+
/**
|
|
34
|
+
* 设备档(2026-08-28 移动端第二轮)。**浏览器判好了报上来**,这边只验不算 ——
|
|
35
|
+
* 真屏幕多大、是不是手指,只有浏览器知道;服务端手里只有相机矩形和缩放,
|
|
36
|
+
* 反推屏幕要除以缩放,而且分不清「390 宽的手机」和「拖窄了的桌面窗口」。
|
|
37
|
+
* 认不出的档一律当桌面:宁可给手机用户一份桌面版式(能用,只是要缩放),
|
|
38
|
+
* 也别给桌面用户一份 342 宽的窄条(那是坏的)。
|
|
39
|
+
*/
|
|
40
|
+
const dc = raw.device && typeof raw.device === 'object' ? raw.device : null;
|
|
41
|
+
const dw = num(dc?.w, 200, 8000); const dh = num(dc?.h, 200, 8000);
|
|
42
|
+
// ⚠️ 夹持在这儿是错的判法:一个夹出来的 8000 会变成 8000 宽的版式建议。
|
|
43
|
+
// 尺寸不在真实屏幕的范围里就**整个丢掉 device**,退回相机×缩放那条老路。
|
|
44
|
+
const device = (dc && dw !== null && dh !== null && dw === Number(dc.w) && dh === Number(dc.h)) ? {
|
|
45
|
+
class: (dc.class === 'phone' || dc.class === 'tablet') ? dc.class : 'desktop',
|
|
46
|
+
w: dw, h: dh,
|
|
47
|
+
dpr: num(dc.dpr, 0.5, 8),
|
|
48
|
+
coarse: !!dc.coarse,
|
|
49
|
+
} : null;
|
|
33
50
|
return {
|
|
34
51
|
userId: userId || null,
|
|
35
52
|
camera,
|
|
53
|
+
device,
|
|
36
54
|
zoom: num(raw.zoom, 0.01, 100),
|
|
37
55
|
layer: str(raw.layer, 300) || '',
|
|
38
56
|
openWindow: str(raw.openWindow, 300),
|
|
@@ -56,18 +74,32 @@ export function getViewpoint(pid) {
|
|
|
56
74
|
return v;
|
|
57
75
|
}
|
|
58
76
|
|
|
77
|
+
/** 档位的人话(注入和工具共用一份措辞;桌面不说 —— 那是默认情况,说了是噪音) */
|
|
78
|
+
export function describeDevice(v) {
|
|
79
|
+
const d = v?.device;
|
|
80
|
+
if (!d || d.class === 'desktop') return null;
|
|
81
|
+
const name = d.class === 'phone' ? '手机' : '平板';
|
|
82
|
+
return `${name}(屏幕 ${d.w}x${d.h}px,触屏)`;
|
|
83
|
+
}
|
|
84
|
+
|
|
59
85
|
/** 一行人话(注入/工具共用一份措辞)。rects 可选:给了就报视口里有什么 */
|
|
60
86
|
export function describeViewpoint(v, rects = null) {
|
|
61
87
|
if (!v) return null;
|
|
62
88
|
const bits = [];
|
|
89
|
+
const dev = describeDevice(v);
|
|
90
|
+
if (dev) bits.push(`用的是${dev}`);
|
|
63
91
|
if (v.layer) bits.push(`在文件夹「${v.layer}」里`);
|
|
64
92
|
if (v.camera) {
|
|
65
93
|
bits.push(`视口 (${Math.round(v.camera.x)},${Math.round(v.camera.y)}) ${Math.round(v.camera.w)}x${Math.round(v.camera.h)}${v.zoom ? ` 缩放 ${Number(v.zoom).toFixed(2)}` : ''}`);
|
|
66
94
|
if (rects) {
|
|
67
95
|
const c = v.camera;
|
|
68
|
-
const inside = rects.filter(r => !(r.x + r.w < c.x || r.x > c.x + c.w || r.y + r.h < c.y || r.y > c.y + c.h))
|
|
69
|
-
|
|
70
|
-
|
|
96
|
+
const inside = rects.filter(r => !(r.x + r.w < c.x || r.x > c.x + c.w || r.y + r.h < c.y || r.y > c.y + c.h));
|
|
97
|
+
// 带坐标和占位(08-27 用户提):agent 摆放要知道视口里谁占了哪,不该再专门调工具去问
|
|
98
|
+
if (inside.length) {
|
|
99
|
+
bits.push(`视口里 ${inside.length} 件(id@(x,y)宽x高):${inside.slice(0, 12)
|
|
100
|
+
.map(r => `${r.id}@(${Math.round(r.x)},${Math.round(r.y)})${Math.round(r.w)}x${Math.round(r.h)}`)
|
|
101
|
+
.join('、')}${inside.length > 12 ? ' 等' : ''}`);
|
|
102
|
+
} else bits.push('视口里是空地');
|
|
71
103
|
}
|
|
72
104
|
}
|
|
73
105
|
if (v.openWindow) bits.push(`开着窗:${v.openWindow}${v.openPage ? `(${v.openPage})` : ''}`);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* projects/workspace-slots.js —— 角色位铺装(2026-08-28 建;08-29 收成一个位)
|
|
3
|
+
*
|
|
4
|
+
* 预注册的角色子代理定义(rp-role)**必须在会话启动前就在盘上**:会话中途写入的
|
|
5
|
+
* agent 定义只有当时活着的那个 CLI 进程认得,换进程后永久 not found(08-28 三场
|
|
6
|
+
* 真会话勘定,机制见 engine/agent/cast.js)。所以铺装挂在 ensureProjectWorkspace 里,
|
|
7
|
+
* 跟着每次 ensure 走。
|
|
8
|
+
*
|
|
9
|
+
* 内容以代码为准:教义升级后重启即生效,也顺手把模型对定义体的手改抹平
|
|
10
|
+
* (判据不建在模型可写的东西上 —— 派发闸照读文件核 tools,这里保证文件是正版)。
|
|
11
|
+
*
|
|
12
|
+
* 退役的旧位(rp-actor / rp-narrator)在这里**删文件**:留着的话老名字仍然派得出去,
|
|
13
|
+
* 派出来的还是旧分工(写场面的那支笔已经收回主持人手里了)。
|
|
14
|
+
*/
|
|
15
|
+
import { promises as fs } from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { ROLE_SLOT, RETIRED_SLOTS, slotAgentFile } from '../engine/agent/cast.js';
|
|
18
|
+
import { MCP_SERVER_NAME } from '../engine/mcp/server-name.js';
|
|
19
|
+
|
|
20
|
+
export async function ensureActorSlots(root) {
|
|
21
|
+
const dir = path.join(root, '.claude', 'agents');
|
|
22
|
+
const file = path.join(dir, `${ROLE_SLOT}.md`);
|
|
23
|
+
const want = slotAgentFile(MCP_SERVER_NAME);
|
|
24
|
+
let have = null;
|
|
25
|
+
try { have = await fs.readFile(file, 'utf8'); } catch { /* 还没有 */ }
|
|
26
|
+
if (have !== want) await fs.writeFile(file, want, 'utf8');
|
|
27
|
+
for (const old of RETIRED_SLOTS) {
|
|
28
|
+
try { await fs.unlink(path.join(dir, `${old}.md`)); } catch { /* 本来就没有 */ }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -54,3 +54,32 @@ export const DEFAULT_CLAUDE_MD = `# 项目档案
|
|
|
54
54
|
## 用户习惯
|
|
55
55
|
(称呼、语气、工作方式偏好。)
|
|
56
56
|
`;
|
|
57
|
+
|
|
58
|
+
// rp(演出)项目的档案模板(2026-08-28;08-30 随角色子代理停用改口径)。
|
|
59
|
+
// ⚠️ 原注:这份文件**每个角色子代理也强制吃**(项目 CLAUDE.md 进所有子代理上下文,
|
|
60
|
+
// omitClaudeMd 透不过去 —— 08-26 实测)。08-30 起主 agent 代演全场,暂时没有子代理
|
|
61
|
+
// 在读它,但这条约束等角色回来还成立,别按"只有一个读者"重排栏目。
|
|
62
|
+
// 详细设定别塞 —— 放世界书文件按需 grep,这份是每轮都重发的常驻开销。
|
|
63
|
+
export const DEFAULT_CLAUDE_MD_RP = `# 演出档案
|
|
64
|
+
|
|
65
|
+
这份文件每次会话都完整进入 agent 的上下文。放**定了就不常变**的东西;
|
|
66
|
+
会生长的记忆 agent 自己记在 记忆/ 里。详细设定放世界书文件(用到时 grep),
|
|
67
|
+
别塞进这里 —— 这份是每轮都重发的常驻开销。改硬约束要经用户点头。
|
|
68
|
+
|
|
69
|
+
## 世界观锚
|
|
70
|
+
(一句话定调:什么世界、什么时代、什么基调。例:低魔剑与魔法,冒险者公会体系。
|
|
71
|
+
从酒馆世界书搬来的常驻条目也放这,十条以内 —— 其余降级成触发文件。)
|
|
72
|
+
|
|
73
|
+
## 台面规矩
|
|
74
|
+
(这一场怎么玩:想跟谁演(一对一 / 一群人)/ 难度档(爽·写实·严酷)/
|
|
75
|
+
代笔档(全代入·大纲)/ 骰子规则 / 文风与叙事禁令 / 禁区题材。)
|
|
76
|
+
|
|
77
|
+
## 人物
|
|
78
|
+
(在场人物一览:每人三行核心 —— 是谁、什么关系、绝不做什么。这里是名录;
|
|
79
|
+
每个人完整的人设在 角色/<名>/角色卡.md(cast_role 写的那张),写他的话之前先读。)
|
|
80
|
+
|
|
81
|
+
## 称呼与习惯
|
|
82
|
+
(用户扮演谁、怎么称呼;语气与节奏偏好。)
|
|
83
|
+
`;
|
|
84
|
+
// ⚠️ 上面四节的节名是「演出档案骨架」的唯一真相 —— blackboard-rp SKILL 的酒馆导入
|
|
85
|
+
// 落点表按同一套节名指路(08-28 对账:此前两处各写一套骨架,字段对不上)。改节名两处同改。
|
|
@@ -42,8 +42,9 @@ import { spawn } from 'child_process';
|
|
|
42
42
|
import path from 'path';
|
|
43
43
|
import { fileURLToPath } from 'url';
|
|
44
44
|
import { mutex } from 'async-mutex-lite';
|
|
45
|
-
import { validateProjectId } from './store.js';
|
|
45
|
+
import { validateProjectId, getProject } from './store.js';
|
|
46
46
|
import { resolveModelContextWindow } from '../engine/agent/model-context.js';
|
|
47
|
+
import { ensureActorSlots } from './workspace-slots.js';
|
|
47
48
|
|
|
48
49
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
49
50
|
|
|
@@ -87,7 +88,7 @@ export function validateSessionId(sid) {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
import {
|
|
90
|
-
DEFAULT_GITIGNORE, DEFAULT_CLAUDE_MD,
|
|
91
|
+
DEFAULT_GITIGNORE, DEFAULT_CLAUDE_MD, DEFAULT_CLAUDE_MD_RP,
|
|
91
92
|
} from './workspace-templates.js';
|
|
92
93
|
import { migrateMemoryLayout } from './memory-migration.js';
|
|
93
94
|
|
|
@@ -219,16 +220,11 @@ export function getSessionMetaDir(projectId, sessionId) {
|
|
|
219
220
|
// ── ensure ──
|
|
220
221
|
|
|
221
222
|
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* - .git 存在(项目级历史;扁平化前是 per-session 的)
|
|
228
|
-
*
|
|
229
|
-
* 扁平化跟着 ensure 走而不是单独一次性脚本:跟 removeRootLegacyArtifacts 同一个
|
|
230
|
-
* 范式 —— 幂等、按项目惰性触发、跑过一次之后是 no-op。这样线上不需要停机窗口,
|
|
231
|
-
* 也不存在"迁移脚本漏了哪个项目"。
|
|
223
|
+
* 创建项目工作区(幂等)。完成后保证:.claude/{CLAUDE.md, settings.json} 模板
|
|
224
|
+
* (仅不存在时)、.claude/{skills, agents, agent-memory} 与 assets/ 目录、演员位
|
|
225
|
+
* 定义、.gitignore、旧三层结构已扁平化、.git 存在(项目级历史)。
|
|
226
|
+
* 扁平化跟着 ensure 走而不是一次性脚本:幂等、按项目惰性触发、跑过即 no-op ——
|
|
227
|
+
* 线上不需要停机窗口,也不存在"迁移脚本漏了哪个项目"。
|
|
232
228
|
*/
|
|
233
229
|
export async function ensureProjectWorkspace(projectId) {
|
|
234
230
|
await removeRootLegacyArtifacts(projectId);
|
|
@@ -239,16 +235,19 @@ export async function ensureProjectWorkspace(projectId) {
|
|
|
239
235
|
await fs.mkdir(path.join(root, '.claude', 'agent-memory'), { recursive: true });
|
|
240
236
|
await fs.mkdir(path.join(root, 'assets'), { recursive: true });
|
|
241
237
|
|
|
238
|
+
// 演员位(2026-08-28 重构):预注册 RP 子代理定义,必须先于任何会话在盘上(拆件见文件)
|
|
239
|
+
await ensureActorSlots(root);
|
|
240
|
+
|
|
242
241
|
// .gitignore 每次比对而不是"不存在才写":扁平化新增了 .claude/projects/ 和
|
|
243
242
|
// .nd/ 两条,老项目的文件里没有,不补的话 SDK 转录会被 commit 进项目历史。
|
|
244
243
|
await ensureGitignore(path.join(root, '.gitignore'));
|
|
245
|
-
// 记忆体系改版迁移(2026-08-24
|
|
246
|
-
// CLAUDE.md
|
|
247
|
-
// 老的偏好/风格档案并进去,SDK auto-memory 的存量从 .claude/agent-memory/auto
|
|
248
|
-
// 搬到画布可见的 记忆/。三步全是"搬走后源删除",跑几遍结果一样。
|
|
244
|
+
// 记忆体系改版迁移(2026-08-24,幂等:源不在了就什么都不做;三步全是"搬走后源删除")
|
|
245
|
+
// —— CLAUDE.md 挪工作区根、旧偏好/风格档案并入、auto-memory 存量搬 记忆/。细节见函数文档。
|
|
249
246
|
await migrateMemoryLayout(root, { fileExists });
|
|
250
247
|
if (!(await fileExists(path.join(root, 'CLAUDE.md')))) {
|
|
251
|
-
|
|
248
|
+
// rp 项目的档案按"戏"设栏(templates 注释);已有 CLAUDE.md 的一字不动,用户内容优先于模板换代
|
|
249
|
+
const isRp = (getProject(projectId)?.mode || 'design') === 'rp';
|
|
250
|
+
await fs.writeFile(path.join(root, 'CLAUDE.md'), isRp ? DEFAULT_CLAUDE_MD_RP : DEFAULT_CLAUDE_MD, 'utf8');
|
|
252
251
|
}
|
|
253
252
|
// settings.json:每次 merge defaults 让代码层 default 升级时现存 project 自动跟上
|
|
254
253
|
// (用户字段优先,缺失的 NoDesign default 字段补进去)
|
|
@@ -17,6 +17,9 @@ import fs from 'node:fs';
|
|
|
17
17
|
import os from 'node:os';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { isAvailable as rembgAvailable, REMBG_SETUP_HINT } from '../engine/mcp/tools/helpers/rembg.js';
|
|
20
|
+
// 盒子开没开机只有一个判据 —— h3box-ssh.localBoxEnabled。这里曾经自己写了一遍
|
|
21
|
+
// `=== 'on'`,跟那边 `!== 'off'`(默认开)的默认值正好相反,同一个变量两套读法。
|
|
22
|
+
import { localBoxEnabled } from '../engine/mcp/tools/h3box-ssh.js';
|
|
20
23
|
|
|
21
24
|
const isWin = process.platform === 'win32';
|
|
22
25
|
|
|
@@ -96,8 +99,9 @@ export const CAPABILITY_DEFS = Object.freeze([
|
|
|
96
99
|
} },
|
|
97
100
|
{ id: 'localBox', kind: 'service', level: 'feature', label: '本地 GPU 盒子(paint_still / roll_film)', uses: '自部署生图 / 文生视频',
|
|
98
101
|
fix: 'NODESIGN_LOCAL_BOX=on + NODESIGN_H3BOX_SSH(站主自己的 5090 盒子;一般用户用不上)',
|
|
99
|
-
probe: () => (
|
|
100
|
-
? { available: true, detail: process.env.NODESIGN_H3BOX_SSH }
|
|
102
|
+
probe: () => (localBoxEnabled() && process.env.NODESIGN_H3BOX_SSH
|
|
103
|
+
? { available: true, detail: process.env.NODESIGN_H3BOX_SSH }
|
|
104
|
+
: { available: false, detail: !localBoxEnabled() ? '站主标记为关机(NODESIGN_LOCAL_BOX=off)' : 'NODESIGN_H3BOX_SSH 为空' }) },
|
|
101
105
|
]);
|
|
102
106
|
|
|
103
107
|
function bin(name, extra = []) {
|