@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
|
@@ -16,43 +16,57 @@ import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
import { readBoard } from '../../../projects/board-store.js';
|
|
18
18
|
import { estimateSizeOn } from '../../../lib/board-kind-sizes.js';
|
|
19
|
-
import { layerOf } from '../../../lib/canvas-id.js';
|
|
19
|
+
import { layerOf, bareTag } from '../../../lib/canvas-id.js';
|
|
20
20
|
import { relationsDigest, bindingLine } from '../../../lib/board-relations.js';
|
|
21
21
|
import { groupObjects, asciiMinimap, bboxOfRects, relationOf, columnsOf, viewportRelation } from '../../../lib/board-groups.js';
|
|
22
|
+
import { laneSummaries } from '../../../lib/board-lanes.js';
|
|
23
|
+
import { capacityOf, DEFAULT_CHALK_W } from '../../../lib/sketch-layout.js';
|
|
24
|
+
import { sheetSummaries, rollCardRect, slotRectOf, nextSpotInSlot, freeColumnsInSheet, latestSheetId } from '../../../lib/board-sheets.js';
|
|
22
25
|
import { getViewpoint } from '../../../projects/viewpoint-store.js';
|
|
23
26
|
import { chalkExcerpts, CHALK_DIR } from '../../../lib/chalk.js';
|
|
24
27
|
import { getSharedDir } from '../../../projects/workspace.js';
|
|
28
|
+
import { promises as fs } from 'node:fs';
|
|
29
|
+
import { byOf, describeBy } from '../actor.js';
|
|
30
|
+
import { listRoleNames } from '../../agent/role-card.js';
|
|
31
|
+
import path from 'node:path';
|
|
25
32
|
|
|
26
33
|
/** 同一"行"的 y 容差:入座算法一行内顶对齐,40 世界像素内视作同行 */
|
|
27
34
|
const ROW_TOLERANCE = 40;
|
|
28
35
|
|
|
29
|
-
function describeEntry(board, id, entry, glyph = null, excerpts = null) {
|
|
36
|
+
function describeEntry(board, id, entry, glyph = null, excerpts = null, staleIds = null, view = null) {
|
|
37
|
+
// view = { by: 当前读者, names: slug→展示名 }。读 read_board 的可能是主 agent,
|
|
38
|
+
// 也可能是常驻角色 —— 同一句「你写的」对两个读者含义相反,所以称呼要带视角。
|
|
39
|
+
const who = (b) => describeBy(b || 'agent', view?.by || 'agent', view?.names);
|
|
40
|
+
const mine = (b) => (b || 'agent') === (view?.by || 'agent');
|
|
30
41
|
const sz = estimateSizeOn(board, id, entry);
|
|
31
42
|
const at = `@(${Math.round(entry.x)},${Math.round(entry.y)}) ${Math.round(sz.w)}x${Math.round(sz.h)}`;
|
|
32
43
|
const g = glyph ? `[${glyph}] ` : '';
|
|
33
44
|
const flags = `${entry.staging ? ' 〔草稿〕' : ''}${entry.tag ? ` #${entry.tag}` : ''}`;
|
|
34
45
|
const ch = excerpts?.get(id);
|
|
35
|
-
if (ch) return `- ${g}[板书·${ch.by
|
|
46
|
+
if (ch) return `- ${g}[板书·${who(ch.by)}写的] 「${ch.first}」 ${at} (path: ${id})${ch.anchor ? ` 关于 ${ch.anchor}` : ''}${ch.replyTo ? ` 回应 ${ch.replyTo}` : ''}${flags}`;
|
|
36
47
|
if (entry.kind === 'text') {
|
|
37
48
|
const t = String(entry.data?.t || '').replace(/\s+/g, ' ').slice(0, entry.data?.format === 'md' ? 60 : 24);
|
|
38
49
|
const md = entry.data?.format === 'md' ? 'md' : '手写';
|
|
39
|
-
return `- ${g}[${md}] 「${t}」 ${at} (id: ${id})${entry.by
|
|
50
|
+
return `- ${g}[${md}] 「${t}」 ${at} (id: ${id})${entry.by ? ` ·${who(entry.by)}写的` : ''}${flags}`;
|
|
40
51
|
}
|
|
41
|
-
if (entry.kind === 'scribble') return `- ${g}[涂鸦] ${at} (id: ${id})${entry.by
|
|
42
|
-
|
|
52
|
+
if (entry.kind === 'scribble') return `- ${g}[涂鸦] ${at} (id: ${id})${entry.by ? ` ·${who(entry.by)}画的` : ''}${flags}`;
|
|
53
|
+
// 过期座位要明说(iss_mt38ucyq:旧路径条目被 agent 当"失效卡"差点建议删素材母版)
|
|
54
|
+
const stale = staleIds?.has(id) ? ' 〔⚠️磁盘上已无此路径 —— 多半被移动/改名了,以磁盘为准,别据此判失效或建议删除〕' : '';
|
|
55
|
+
return `- ${g}${id} ${at}${entry.by ? ` ·${who(entry.by)}摆的` : ''}${flags}${stale}`;
|
|
56
|
+
// eslint-disable-next-line no-unused-vars -- mine 留给后续按视角过滤用
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
export function makeReadBoardTool({ projectId }) {
|
|
59
|
+
export function makeReadBoardTool({ projectId, sharedRoot = null }) {
|
|
46
60
|
return tool(
|
|
47
61
|
'read_board',
|
|
48
62
|
`Read the workbench canvas: an ASCII minimap, then GROUPS (things linked by lines or
|
|
49
63
|
sharing a #tag), then loose items row by row, then relation lines.
|
|
50
64
|
|
|
51
|
-
Use this BEFORE
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
Use this BEFORE moving things (edit_board) or writing/sketching (write_on_board) —
|
|
66
|
+
placement without looking is guessing. Coordinates are world pixels. Only seated items
|
|
67
|
+
appear (files you just wrote are seated automatically within a couple of seconds).
|
|
54
68
|
Items marked 〔草稿〕 are still staging (yours from this turn, half-transparent until
|
|
55
|
-
|
|
69
|
+
edit_board commit / end of turn). The user's current viewport (if known) is drawn as a box
|
|
56
70
|
on the minimap and listed with what is inside it.`,
|
|
57
71
|
{
|
|
58
72
|
layer: z.string().max(300).optional()
|
|
@@ -61,7 +75,11 @@ on the minimap and listed with what is inside it.`,
|
|
|
61
75
|
.describe('Only list items/lines carrying this #tag (one group, e.g. a sketch you made)'),
|
|
62
76
|
minimap: z.boolean().optional().describe('Also print an ASCII minimap (off by default — the relative-position summary is usually enough)'),
|
|
63
77
|
},
|
|
64
|
-
async ({ layer, tag, minimap }) => {
|
|
78
|
+
async ({ layer, tag: rawTag, minimap }, extra) => {
|
|
79
|
+
const tag = rawTag ? bareTag(rawTag) : rawTag; // #状态板 也认(查询侧统一剥 #)
|
|
80
|
+
// 视角:谁在读这块板。常驻角色读到自己写的板书才该显示「你写的」,
|
|
81
|
+
// 读到别人的显示那个人的名字(展示名只是渲染,判断一律用 slug)。
|
|
82
|
+
const view = { by: byOf(extra), names: await listRoleNames(sharedRoot) };
|
|
65
83
|
if (!projectId) {
|
|
66
84
|
return { content: [{ type: 'text', text: 'No project bound.' }], isError: true };
|
|
67
85
|
}
|
|
@@ -82,10 +100,23 @@ on the minimap and listed with what is inside it.`,
|
|
|
82
100
|
const excerpts = await chalkExcerpts(getSharedDir(projectId), (byLayer.get(want) || []).map(it => it.id));
|
|
83
101
|
const items = (byLayer.get(want) || [])
|
|
84
102
|
.filter(({ entry }) => !tag || entry.tag === tag)
|
|
103
|
+
// 收卷(2026-08-27 收纳器):收着的组不逐件列 —— 版图里压成一行,这是 agent
|
|
104
|
+
// 上下文的收纳(跟画布收纳同一刀)。显式 tag= 点名看某组时照常展开列。
|
|
105
|
+
.filter(({ entry }) => tag || !entry.tag || !board.rolls?.[entry.tag])
|
|
85
106
|
// 板书条目但文件已经没了 = 幽灵座位,别列给 agent(删文件那条路会清座位,这是兜底)
|
|
86
107
|
.filter(({ id }) => !id.startsWith(`${CHALK_DIR}/`) || excerpts.has(id))
|
|
87
108
|
.sort((a, b) => (a.entry.y - b.entry.y) || (a.entry.x - b.entry.x));
|
|
88
109
|
const entryOf = new Map(items.map(it => [it.id, it.entry]));
|
|
110
|
+
// 座位 vs 磁盘对账(iss_mt38ucyq):文件挪走后旧座位可能还挂几十秒
|
|
111
|
+
// (改名对账/前端回写有时差)。查一遍真身,过期的在条目上点名。
|
|
112
|
+
const root = getSharedDir(projectId);
|
|
113
|
+
const staleIds = new Set();
|
|
114
|
+
await Promise.all(items.map(async ({ id, entry }) => {
|
|
115
|
+
if (entry.kind === 'text' || entry.kind === 'scribble' || id === 'browse') return;
|
|
116
|
+
const bare = String(id).replace(/^(deck|site|docx):/, '');
|
|
117
|
+
if (!bare || bare.includes('..') || /^(text|scribble|b):/.test(bare)) return;
|
|
118
|
+
try { await fs.access(path.resolve(root, bare)); } catch { staleIds.add(id); }
|
|
119
|
+
}));
|
|
89
120
|
|
|
90
121
|
// 小地图(用户视口画框)
|
|
91
122
|
const vp = getViewpoint(projectId);
|
|
@@ -129,7 +160,7 @@ on the minimap and listed with what is inside it.`,
|
|
|
129
160
|
lines.push('', `组 ${i + 1}${tags ? ` ${tags}` : ''}(${g.members.length} 件 ${g.edges.length} 线${staging ? ',草稿' : ''}):`);
|
|
130
161
|
const sorted = g.members.map(id => ({ id, entry: entryOf.get(id) }))
|
|
131
162
|
.sort((a, b) => (a.entry.y - b.entry.y) || (a.entry.x - b.entry.x));
|
|
132
|
-
for (const { id, entry } of sorted) lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts));
|
|
163
|
+
for (const { id, entry } of sorted) lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts, staleIds, view));
|
|
133
164
|
for (const bid of g.edges.slice(0, 12)) lines.push(` ${bindingLine(board.bindings[bid], board)} (line id: ${bid})`);
|
|
134
165
|
if (g.edges.length > 12) lines.push(` …还有 ${g.edges.length - 12} 条线`);
|
|
135
166
|
});
|
|
@@ -147,7 +178,7 @@ on the minimap and listed with what is inside it.`,
|
|
|
147
178
|
rowY = entry.y;
|
|
148
179
|
lines.push(`— 行 y≈${Math.round(rowY)} —`);
|
|
149
180
|
}
|
|
150
|
-
lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts));
|
|
181
|
+
lines.push(describeEntry(board, id, entry, glyphOf.get(id), excerpts, staleIds, view));
|
|
151
182
|
}
|
|
152
183
|
}
|
|
153
184
|
}
|
|
@@ -160,7 +191,82 @@ on the minimap and listed with what is inside it.`,
|
|
|
160
191
|
}).join('、')}`);
|
|
161
192
|
}
|
|
162
193
|
}
|
|
163
|
-
if (board.hero && !tag) lines.push('', `★ 显式主角:${board.hero}(
|
|
194
|
+
if (board.hero && !tag) lines.push('', `★ 显式主角:${board.hero}(edit_board 的 feature/unfeature 管它)`);
|
|
195
|
+
|
|
196
|
+
// 纸的清单(2026-08-29 纸范式):agent 的空间账本 —— 每张纸的名字/位置/件数/
|
|
197
|
+
// 剩余空地。at 坐标以当前纸版心左上为原点,这一节就是坐标系的地图。
|
|
198
|
+
if (!want && !tag) {
|
|
199
|
+
try {
|
|
200
|
+
const ss = sheetSummaries(board);
|
|
201
|
+
if (ss.length) {
|
|
202
|
+
const latest = latestSheetId(board);
|
|
203
|
+
lines.push('', '纸(sheet):at:{x,y} 写的是纸内像素(版心左上为原点)。'
|
|
204
|
+
+ '**写满不会自动翻页** —— 排不下时写入被拒收,下一页由你自己 open_sheet 规划:');
|
|
205
|
+
for (const s of ss) {
|
|
206
|
+
// 剩多少地方按**字**报(08-29 刀 D):agent 手里的东西是字,
|
|
207
|
+
// 只给像素等于让它每次落笔前做一道做不准的算术
|
|
208
|
+
const cap = capacityOf(DEFAULT_CHALK_W, s.freeH);
|
|
209
|
+
lines.push(` ${s.id}${s.id === latest ? '(当前)' : ''}${s.title ? `(${s.title})` : ''}:世界 (${s.x},${s.y}) ${s.w}x${s.h},${s.count} 件,剩 ~${s.freeH}px 高(≈${cap.lines} 行 / ${cap.cjk} 字)${s.lastId ? `,最新 ${s.lastId}` : ''}`);
|
|
210
|
+
// 规划过的块各剩多少(08-29 刀 E):填到一半要知道还有哪块地是空的,
|
|
211
|
+
// 不然只能靠猜 —— 猜错的下场是写入被拒收,白跑一趟
|
|
212
|
+
// 这张纸还剩哪些空地(08-29 刀 F):只报"最后一件下面剩多少"是不够的 ——
|
|
213
|
+
// 那一列到底了不等于这张纸满了,右边可能整片空着,而 agent 要靠这个
|
|
214
|
+
// 判断"还放不放得下、要不要开新一页"
|
|
215
|
+
const free = freeColumnsInSheet(board, s.id);
|
|
216
|
+
if (free.length > 1) {
|
|
217
|
+
const cells = free.map((f) => `x=${f.x} 剩 ${capacityOf(DEFAULT_CHALK_W, f.freeH).lines} 行`);
|
|
218
|
+
lines.push(` 空地:${cells.join(' / ')}`);
|
|
219
|
+
}
|
|
220
|
+
const sheet = board.sheets?.[s.id];
|
|
221
|
+
for (const [nm, sl] of Object.entries(sheet?.slots || {})) {
|
|
222
|
+
const r = slotRectOf({ ...sheet, id: s.id }, nm);
|
|
223
|
+
const spot = nextSpotInSlot(board, r, { w: 1, h: 1 });
|
|
224
|
+
const freeH = spot.full ? 0 : Math.max(0, r.y + r.h - spot.y);
|
|
225
|
+
const c = capacityOf(r.w, freeH);
|
|
226
|
+
lines.push(` · ${nm}${sl.about ? `(${sl.about})` : ''}${sl.for === 'artifacts' ? '【收产物】' : ''}:块内 (${sl.x},${sl.y}) ${sl.w}x${sl.h},剩 ≈${c.lines} 行 / ${c.cjk} 字`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// 暂存架(2026-08-30):机器到货的默认座 —— 上了墙但还没进版面,等 agent 安置
|
|
231
|
+
const shelf = Object.entries(board.objects || {})
|
|
232
|
+
.filter(([, e]) => e?.seat === 'shelf' && !e.zone && Number.isFinite(e?.x))
|
|
233
|
+
.map(([id]) => id);
|
|
234
|
+
const pend = Array.isArray(board.pending) ? board.pending : [];
|
|
235
|
+
const unplaced = [...shelf, ...pend];
|
|
236
|
+
if (unplaced.length) {
|
|
237
|
+
lines.push('', `📦 暂存架上 ${unplaced.length} 件等安置(机器只码在架上,版面归你):`);
|
|
238
|
+
for (const r of unplaced.slice(0, 8)) lines.push(` ${r}`);
|
|
239
|
+
if (unplaced.length > 8) lines.push(` …还有 ${unplaced.length - 8} 件`);
|
|
240
|
+
lines.push(' 安置的手:open_sheet{plan:[{slot:"图",at:{…},w,h,for:"artifacts"}…]} 规划产物地,'
|
|
241
|
+
+ '或逐件 pin_to_board{path,slot} / edit_board{ops:[{op:"move",…}]}。');
|
|
242
|
+
}
|
|
243
|
+
} catch { /* 纸读不出不挡座次 */ }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 版图(2026-08-27 空间规划):线 = 同 tag 的纵列。这是 agent 的符号地图 ——
|
|
247
|
+
// 摆放按关系(续哪条线/岔自哪条)声明,几何机器排,别按坐标猜。
|
|
248
|
+
if (!tag) {
|
|
249
|
+
const laneList = laneSummaries(board);
|
|
250
|
+
if (laneList.length) {
|
|
251
|
+
lines.push('', '线的清单(一条线 = 同一个 tag 的一纵列;{tag,chain:true} 接着写,open_lane 开新的一条):');
|
|
252
|
+
for (const l of laneList) {
|
|
253
|
+
// 收着的线一行带过:细节不进上下文(要看就 read_board tag= 点名,或 unroll)
|
|
254
|
+
const roll = board.rolls?.[l.tag];
|
|
255
|
+
if (roll) {
|
|
256
|
+
const rc = rollCardRect(board, l.tag);
|
|
257
|
+
lines.push(` #${l.tag}:已收卷${roll.label ? `(「${roll.label}」)` : ''},${l.count} 件收在卷里`
|
|
258
|
+
+ `${rc ? `,卷卡占位约 @(${rc.x},${rc.y}) ${rc.w}x${rc.h}` : ''}`
|
|
259
|
+
+ ` —— 座位和文件都在(Read 照常),edit_board unroll 展开;别往收着的线里接新话`);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const dirTxt = '';
|
|
263
|
+
lines.push(l.registered
|
|
264
|
+
? ` #${l.tag}:${l.count} 节${l.parent ? `,岔自 ${l.parent}` : ''},列头 (${l.x},${l.y})`
|
|
265
|
+
+ `${l.frontier ? `,接着写会落 (${l.frontier.x},${l.frontier.y}) 附近` : ''}${l.lastId ? `,最新 ${l.lastId}` : ''}${dirTxt}`
|
|
266
|
+
: ` #${l.tag}:${l.count} 件(未登记的野线 —— chain:true 照样能续)${dirTxt}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
164
270
|
|
|
165
271
|
// 用户视点(有上报才有)
|
|
166
272
|
if (vp && !tag) {
|
|
@@ -183,7 +289,8 @@ on the minimap and listed with what is inside it.`,
|
|
|
183
289
|
} catch { /* 关系读不到不挡座次 */ }
|
|
184
290
|
}
|
|
185
291
|
|
|
186
|
-
lines.push('', '
|
|
292
|
+
lines.push('', '(口径:稀疏表只列摆过的;层归属为服务端近似;尺寸=存档真值优先、缺了按形态估;'
|
|
293
|
+
+ '角色精灵贴着该角色最新一条板书(那条四周留了 60px 身位);带⚠️的条目=座位与磁盘对不上账)');
|
|
187
294
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
188
295
|
},
|
|
189
296
|
);
|
|
@@ -13,10 +13,38 @@ import path from 'node:path';
|
|
|
13
13
|
import fs from 'node:fs/promises';
|
|
14
14
|
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
-
import { detectKind, digest, fetchEntries } from '../../../lib/tavern-json.js';
|
|
16
|
+
import { detectKind, digest, fetchEntries, extractCardFromPng, listBookEntries } from '../../../lib/tavern-json.js';
|
|
17
17
|
|
|
18
18
|
const MAX_FETCH_CHARS = 24_000;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 世界书条目表。四方世界卡这种 570 条的怪物逐条带引子会刷出上千行 ——
|
|
22
|
+
* 超过 80 条就转紧凑档:常驻的全列(那是要人工挑进 CLAUDE.md 的),
|
|
23
|
+
* 触发的只报名字+触发词不带引子,并指路 export_book 一步落盘。
|
|
24
|
+
*/
|
|
25
|
+
function 渲染世界书(条目, 标题) {
|
|
26
|
+
const L = [标题 + ':'];
|
|
27
|
+
const compact = 条目.length > 80;
|
|
28
|
+
const 常驻 = 条目.filter(e => e.常驻 && !e.停用);
|
|
29
|
+
const 触发 = 条目.filter(e => !e.常驻 && !e.停用);
|
|
30
|
+
const 停用数 = 条目.filter(e => e.停用).length;
|
|
31
|
+
if (compact) {
|
|
32
|
+
L.push(`启用 ${常驻.length + 触发.length}(常驻 ${常驻.length} / 触发 ${触发.length})· 停用 ${停用数}`);
|
|
33
|
+
L.push('');
|
|
34
|
+
L.push(`常驻条目(每轮在场的候选 —— 逐条人工过:内容进 CLAUDE.md,引擎件不搬):`);
|
|
35
|
+
for (const e of 常驻) L.push(`- ${e.名字} ${e.字数}字`);
|
|
36
|
+
L.push('');
|
|
37
|
+
L.push(`触发条目 ${触发.length} 条(名字|触发词)——别逐条 fetch,用 mode="export_book" 一步落成 世界书/ 文件:`);
|
|
38
|
+
for (const e of 触发) L.push(`- ${e.名字} | ${e.触发.slice(0, 4).join(' ')}`);
|
|
39
|
+
} else {
|
|
40
|
+
for (const e of 条目) {
|
|
41
|
+
L.push(`- ${e.名字} ${e.常驻 ? '常驻' : `触发[${e.触发.join(' ')}]`} ${e.字数}字${e.停用 ? ' (停用)' : ''}`);
|
|
42
|
+
if (e.引子) L.push(` ${e.引子}…`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return L.join('\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
20
48
|
function 渲染摘要(d, 文件名) {
|
|
21
49
|
const L = [];
|
|
22
50
|
if (d.形态 === 'preset') {
|
|
@@ -46,17 +74,11 @@ function 渲染摘要(d, 文件名) {
|
|
|
46
74
|
if (d.开场白备选) L.push(`- alternate_greetings ${d.开场白备选} 条备选开场白`);
|
|
47
75
|
if (d.世界书.length) {
|
|
48
76
|
L.push('');
|
|
49
|
-
L.push(`内嵌世界书 ${d.世界书.length}
|
|
50
|
-
for (const e of d.世界书) {
|
|
51
|
-
L.push(`- ${e.名字} ${e.常驻 ? '常驻' : `触发[${e.触发.join(' ')}]`} ${e.字数}字${e.停用 ? ' (停用)' : ''}`);
|
|
52
|
-
}
|
|
77
|
+
L.push(渲染世界书(d.世界书, `内嵌世界书 ${d.世界书.length} 条`));
|
|
53
78
|
}
|
|
54
79
|
} else {
|
|
55
|
-
L.push(`酒馆世界书:${文件名}
|
|
56
|
-
|
|
57
|
-
L.push(`- ${e.名字} ${e.常驻 ? '常驻' : `触发[${e.触发.join(' ')}]`} ${e.字数}字${e.停用 ? ' (停用)' : ''}`);
|
|
58
|
-
if (e.引子) L.push(` ${e.引子}…`);
|
|
59
|
-
}
|
|
80
|
+
L.push(`酒馆世界书:${文件名}`);
|
|
81
|
+
L.push(渲染世界书(d.条目, `${d.条目.length} 条`));
|
|
60
82
|
}
|
|
61
83
|
L.push('');
|
|
62
84
|
L.push('要正文就再调一次本工具:mode="fetch",entries=["名字或名字的一部分", …]。');
|
|
@@ -80,10 +102,18 @@ Two steps:
|
|
|
80
102
|
2. mode "fetch" with entries[] — full text of just the ones you picked (name,
|
|
81
103
|
partial name, or id). Capped at ${MAX_FETCH_CHARS} chars per call.
|
|
82
104
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
Also reads **PNG character cards** directly (V2 chara / V3 ccv3 embedded data) —
|
|
106
|
+
point path at the .png, no conversion needed.
|
|
107
|
+
|
|
108
|
+
mode "export_book" is the one writing mode: dumps every enabled lorebook entry to
|
|
109
|
+
workspace files in one call (triggered entries one file each with trigger keys in
|
|
110
|
+
frontmatter → grep them before writing a chapter; constant entries under 常驻/ for
|
|
111
|
+
you to curate into CLAUDE.md). Use it for big world cards — hundreds of entries
|
|
112
|
+
must not flow through your context. Judgment stays yours: what goes to CLAUDE.md,
|
|
113
|
+
and dropping 酒馆 engine machinery (MVU variables / HTML status bars / CoT
|
|
114
|
+
frameworks / regex_scripts — this platform has its own 状态板/明骰/记忆). Markers
|
|
115
|
+
are filled by 酒馆 at runtime and have no place here; jailbreak sections are
|
|
116
|
+
pointless on this platform.`,
|
|
87
117
|
{
|
|
88
118
|
path: z.string().describe(
|
|
89
119
|
'Path to the .json. Relative paths resolve against the session workspace, '
|
|
@@ -91,12 +121,14 @@ pointless on this platform).`,
|
|
|
91
121
|
),
|
|
92
122
|
// ⚠️ 参数名和枚举值一律 ASCII —— 工具 schema 是模型要照着填的东西,
|
|
93
123
|
// 中文键名在这条路上不可靠(全仓其他工具也都是 ASCII,别开这个头)
|
|
94
|
-
mode: z.enum(['digest', 'fetch']).optional()
|
|
95
|
-
.describe('digest = structure only (default); fetch = full text of the
|
|
124
|
+
mode: z.enum(['digest', 'fetch', 'export_book']).optional()
|
|
125
|
+
.describe('digest = structure only (default); fetch = full text of picked entries; export_book = write every enabled lorebook entry to workspace files (triggered ones one file each with keys in frontmatter, constant ones under 常驻/) — the mechanical half of an import, judgment stays yours'),
|
|
96
126
|
entries: z.array(z.string()).optional()
|
|
97
127
|
.describe('For mode="fetch": entry names (partial match ok) or ids'),
|
|
128
|
+
out: z.string().max(120).optional()
|
|
129
|
+
.describe('For mode="export_book": target folder, workspace-relative (default 世界书)'),
|
|
98
130
|
},
|
|
99
|
-
async ({ path: rel, mode = 'digest', entries = [] }) => {
|
|
131
|
+
async ({ path: rel, mode = 'digest', entries = [], out }) => {
|
|
100
132
|
const fail = (text) => ({ content: [{ type: 'text', text }], isError: true });
|
|
101
133
|
try {
|
|
102
134
|
const raw = String(rel || '').trim();
|
|
@@ -112,8 +144,15 @@ pointless on this platform).`,
|
|
|
112
144
|
if (!abs) return fail(`File not found: ${raw}\nLooked in:\n${candidates.map(c => ` ${c}`).join('\n')}`);
|
|
113
145
|
|
|
114
146
|
let doc;
|
|
115
|
-
|
|
116
|
-
|
|
147
|
+
const buf = await fs.readFile(abs);
|
|
148
|
+
if (buf.length > 3 && buf.readUInt32BE(0) === 0x89504e47) {
|
|
149
|
+
// 酒馆的卡就是一张 PNG(V3 ccv3 / V2 chara 藏在 tEXt 块里)
|
|
150
|
+
doc = extractCardFromPng(buf);
|
|
151
|
+
if (!doc) return fail('这张 PNG 里没有角色卡数据(tEXt 块无 ccv3/chara)——它可能只是普通图片。');
|
|
152
|
+
} else {
|
|
153
|
+
try { doc = JSON.parse(buf.toString('utf8')); } catch (e) {
|
|
154
|
+
return fail(`这个文件不是合法 JSON:${e.message}`);
|
|
155
|
+
}
|
|
117
156
|
}
|
|
118
157
|
const kind = detectKind(doc);
|
|
119
158
|
if (!kind) {
|
|
@@ -134,6 +173,35 @@ pointless on this platform).`,
|
|
|
134
173
|
return { content: [{ type: 'text', text: 块.join('\n\n') }] };
|
|
135
174
|
}
|
|
136
175
|
|
|
176
|
+
if (mode === 'export_book') {
|
|
177
|
+
const all = listBookEntries(doc);
|
|
178
|
+
const live = all.filter(e => !e.停用 && e.正文.trim());
|
|
179
|
+
if (!live.length) return fail('这份文件里没有启用且有正文的世界书条目。');
|
|
180
|
+
const root = sharedRoot || workspaceRoot;
|
|
181
|
+
const dirRel = String(out || '世界书').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
182
|
+
if (!dirRel || dirRel.split('/').some(s2 => s2 === '..' || s2.startsWith('.'))) return fail('out 目录不合法。');
|
|
183
|
+
const slug = (t) => String(t).replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '').slice(0, 32) || 'entry';
|
|
184
|
+
const used = new Set();
|
|
185
|
+
let 触发数 = 0; let 常驻数 = 0;
|
|
186
|
+
for (const e of live) {
|
|
187
|
+
const sub = e.常驻 ? `${dirRel}/常驻` : dirRel;
|
|
188
|
+
await fs.mkdir(path.join(root, sub), { recursive: true });
|
|
189
|
+
let name = slug(e.名字);
|
|
190
|
+
for (let i = 2; used.has(`${sub}/${name}`); i += 1) name = `${slug(e.名字)}-${i}`;
|
|
191
|
+
used.add(`${sub}/${name}`);
|
|
192
|
+
const fm = ['---', 'nd: lore',
|
|
193
|
+
`keys: ${JSON.stringify(e.触发)}`,
|
|
194
|
+
`constant: ${e.常驻}`, `source: ${path.basename(abs)}`, '---', '', e.正文.trim(), ''];
|
|
195
|
+
await fs.writeFile(path.join(root, sub, `${name}.md`), fm.join('\n'), 'utf8');
|
|
196
|
+
if (e.常驻) 常驻数 += 1; else 触发数 += 1;
|
|
197
|
+
}
|
|
198
|
+
return { content: [{ type: 'text', text:
|
|
199
|
+
`Exported ${live.length} entries → ${dirRel}/:触发 ${触发数} 条(一条一文件,frontmatter 带 keys —— `
|
|
200
|
+
+ `每章动笔前拿本章的人名/地名/物件 grep 这个目录,命中就 Read);常驻 ${常驻数} 条在 ${dirRel}/常驻/`
|
|
201
|
+
+ `(逐条人工过:世界观内容挑十条以内进 CLAUDE.md,MVU/状态栏/CoT/回复格式这类酒馆引擎件不搬 —— `
|
|
202
|
+
+ `平台有自己的状态板/明骰/记忆)。停用条目已跳过。` }] };
|
|
203
|
+
}
|
|
204
|
+
|
|
137
205
|
return { content: [{ type: 'text', text: 渲染摘要(digest(doc), path.basename(abs)) }] };
|
|
138
206
|
} catch (err) {
|
|
139
207
|
return fail(`read_tavern_json failed: ${err?.message || String(err)}`);
|
|
@@ -31,18 +31,24 @@ export function makeReportIssueTool({ projectId, sessionId, ctx }) {
|
|
|
31
31
|
return tool(
|
|
32
32
|
'report_issue',
|
|
33
33
|
`Write to the maintainer of this HARNESS — the tools and environment you work
|
|
34
|
-
in. This goes to their issue list, not to the user.
|
|
34
|
+
in. This goes to their issue list, not to the user.
|
|
35
|
+
|
|
36
|
+
This harness is in BETA and you are one of its primary testers: the maintainer
|
|
37
|
+
actively wants these reports and reads every one. When you hesitate between
|
|
38
|
+
reporting and not reporting — report. Three kinds:
|
|
35
39
|
|
|
36
40
|
- "bug": something in the harness behaved WRONGLY — a tool crashed on valid
|
|
37
41
|
input, returned corrupt output, or contradicted its own description.
|
|
38
42
|
- "friction": it worked, but the path was painful — a missing parameter, a
|
|
39
|
-
return shape you had to fight, a workaround
|
|
40
|
-
|
|
43
|
+
return shape you had to fight, a workaround (one detour is enough to report;
|
|
44
|
+
the workaround IS the report).
|
|
41
45
|
- "idea": nothing is broken, but while working you saw something the harness
|
|
42
46
|
could do better or do at all — a tool that should exist, a flag that would
|
|
43
|
-
collapse three steps into one, a default that fights real usage
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
collapse three steps into one, a default that fights real usage, or a
|
|
48
|
+
METHODOLOGY you worked out on the job (a technique, a judgment rule, a
|
|
49
|
+
sequence that reliably works) that the harness should teach every session,
|
|
50
|
+
bake into a skill, or automate. Good ones get built — report it rather than
|
|
51
|
+
let it evaporate.
|
|
46
52
|
|
|
47
53
|
This is NOT a substitute for doing the work. Finish the task the way you would
|
|
48
54
|
have anyway — route around the problem, deliver the result — and log this on
|
|
@@ -50,7 +56,8 @@ the side. Never report and then hand back less than you could have.
|
|
|
50
56
|
|
|
51
57
|
Do NOT report:
|
|
52
58
|
- Your own mistakes (wrong path, malformed edit) — not harness issues
|
|
53
|
-
-
|
|
59
|
+
- A one-off flake that a single retry fixed (the automatic layer counts those);
|
|
60
|
+
a flake that RECURS is friction — report it
|
|
54
61
|
- Anything about the design work itself (that belongs in the conversation)
|
|
55
62
|
- Praise or vague sentiment ("the tools are great", "X could be better
|
|
56
63
|
somehow") — a report with no actionable content is noise
|
|
@@ -94,6 +101,7 @@ every time; a viewportOnly flag would remove the whole detour" is actionable.`,
|
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
try {
|
|
104
|
+
// ℹ️ 遥测事件:前端刻意不消费 —— 摩擦上报的读者是站主(信箱/审计),不是当场的用户
|
|
97
105
|
ctx?.emit?.({ type: 'run.friction_reported', kind, summary, toolName: toolName || null, count: rec.count });
|
|
98
106
|
} catch { /* emit fail-safe */ }
|
|
99
107
|
return {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/role-memory.js —— jot_memory:角色往自己家里记一笔(2026-08-28)
|
|
3
|
+
*
|
|
4
|
+
* 角色文件夹范式(用户拍板):`角色/<名>/` 是一个角色的家 —— 角色卡.md 住这,
|
|
5
|
+
* 记忆.md 也住这,**用户随时能改**。角色没有 Write/Edit(白名单如此,不放开),
|
|
6
|
+
* 所以「角色自己记记忆」只能是一件 MCP 工具:身份从 byOf 盖章来(不是自称),
|
|
7
|
+
* 落点由 harness 拼(角色写不了别人家)。
|
|
8
|
+
*
|
|
9
|
+
* 记忆是**追加式日志**:一次一条、带时间戳小节。改写/整理归用户和 GM(他们有真编辑
|
|
10
|
+
* 权),角色只增不删 —— 「角色能抹掉自己说过的话」不是我们要的性质。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import fs from 'node:fs/promises';
|
|
15
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { byOf } from '../actor.js';
|
|
18
|
+
import { isResidentRole, isSlotType } from '../../agent/cast.js';
|
|
19
|
+
import { readCastRegistry } from '../../agent/role-card.js';
|
|
20
|
+
|
|
21
|
+
const ROLES_DIR = '角色';
|
|
22
|
+
const MEMORY_FILE = '记忆.md';
|
|
23
|
+
const MAX_NOTE = 2000;
|
|
24
|
+
|
|
25
|
+
/** 这个角色的家在哪:登记表里卡的目录优先,没登记就按 slug 开家 */
|
|
26
|
+
export async function roleHomeDir(workspaceRoot, slug) {
|
|
27
|
+
const reg = await readCastRegistry(workspaceRoot);
|
|
28
|
+
const card = reg.roles?.[slug]?.card;
|
|
29
|
+
if (typeof card === 'string' && card.startsWith(`${ROLES_DIR}/`)) {
|
|
30
|
+
const dir = path.dirname(card);
|
|
31
|
+
// 登记表模型可写 —— 家必须还在 角色/ 里,逃出去就按 slug 兜底
|
|
32
|
+
if (!dir.includes('..')) return dir;
|
|
33
|
+
}
|
|
34
|
+
return `${ROLES_DIR}/${slug}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function makeJotMemoryTool({ workspaceRoot }) {
|
|
38
|
+
return tool(
|
|
39
|
+
'jot_memory',
|
|
40
|
+
`Jot one note into YOUR OWN memory file (角色/<你>/${MEMORY_FILE}).
|
|
41
|
+
|
|
42
|
+
For resident roles only. Use it for things future-you must remember across scenes:
|
|
43
|
+
promises made, secrets learned, how you feel about someone now, where an item went.
|
|
44
|
+
One event per call, a few lines. Append-only — the user and the GM curate the file,
|
|
45
|
+
you never rewrite history. When you wake up lost, Read your card and this file.`,
|
|
46
|
+
{
|
|
47
|
+
text: z.string().min(1).max(MAX_NOTE).describe('The note. A few lines, first person, concrete.'),
|
|
48
|
+
},
|
|
49
|
+
async (args, extra) => {
|
|
50
|
+
const fail = (msg) => ({ content: [{ type: 'text', text: msg }], isError: true });
|
|
51
|
+
if (!workspaceRoot) return fail('没有工作区。');
|
|
52
|
+
const me = byOf(extra);
|
|
53
|
+
// ⛔ 演员位(rp-actor/rp-narrator)单独说:它**是**角色,只是实例名还没解析出来
|
|
54
|
+
// (别名桥要到派发结果或第一次 SendMessage 才学到)。08-28 真会话实录:角色刚上场
|
|
55
|
+
// 就 jot,连吃两次「你是主控」,于是它以为自己不是角色 —— 而它没有 Write 权限,
|
|
56
|
+
// 那句指引把它引向一条走不通的路。话术必须说实话,并给一条走得通的出路。
|
|
57
|
+
if (isSlotType(me)) {
|
|
58
|
+
return fail('你的实例名这会儿还没解析出来(刚上场、还没被点过名),记不进你的记忆文件。'
|
|
59
|
+
+ '先照常演,下一拍再记;要是一直记不上,让主控用 SendMessage 点你一次。');
|
|
60
|
+
}
|
|
61
|
+
if (!isResidentRole(me)) {
|
|
62
|
+
return fail('只有常驻角色有记忆文件。你是主控 —— 项目记忆走 记忆/ 目录(Write/Edit)。');
|
|
63
|
+
}
|
|
64
|
+
const text = String(args.text || '').trim();
|
|
65
|
+
if (!text) return fail('空的记不了。');
|
|
66
|
+
|
|
67
|
+
const homeRel = await roleHomeDir(workspaceRoot, me);
|
|
68
|
+
const dir = path.join(workspaceRoot, homeRel);
|
|
69
|
+
const file = path.join(dir, MEMORY_FILE);
|
|
70
|
+
if (!path.resolve(file).startsWith(path.resolve(workspaceRoot, ROLES_DIR) + path.sep)) {
|
|
71
|
+
return fail('记忆文件路径异常,拒绝写入。');
|
|
72
|
+
}
|
|
73
|
+
await fs.mkdir(dir, { recursive: true });
|
|
74
|
+
let head = '';
|
|
75
|
+
try { await fs.access(file); } catch {
|
|
76
|
+
head = `# 记忆\n\n<!-- ${me} 的记忆:角色自己 jot_memory 追加,用户和 GM 可整理改写。 -->\n`;
|
|
77
|
+
}
|
|
78
|
+
const stamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
79
|
+
await fs.appendFile(file, `${head}\n## ${stamp}\n\n${text}\n`, 'utf8');
|
|
80
|
+
return { content: [{ type: 'text', text: `记下了 → ${homeRel}/${MEMORY_FILE}` }] };
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/roll-dice.js —— roll_dice:服务端真随机骰(2026-08-28,沉浸感机制刀①)
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么必须是工具
|
|
5
|
+
*
|
|
6
|
+
* 「明骰」此前是教义(GM 手写骰表),骰子本身却只能由模型**编一个数** ——
|
|
7
|
+
* 玩家一旦意识到骰运是编的,跑团的信任根基当场塌掉。这里用 crypto 真随机投,
|
|
8
|
+
* 结果同时走两条路:工具返回给 GM(写进正文与骰表),`run.dice` 事件直达前端
|
|
9
|
+
* (用户看到与 GM 笔无关的第一手骰面 —— 明骰才真的"明")。
|
|
10
|
+
*
|
|
11
|
+
* 只注册给 GM(不进角色工具白名单):判定归场务的笔,角色要检定就在戏里喊。
|
|
12
|
+
* 参数 ASCII(feedback-ascii-tool-params)。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import crypto from 'node:crypto';
|
|
18
|
+
|
|
19
|
+
/** 判定结果一句话(纯函数可断言)。nd:rp-prompt */
|
|
20
|
+
export function describeRoll({ label, n, sides, modifier, advantage, rolls, kept, total, dc }) {
|
|
21
|
+
const adv = advantage === 'adv' ? '(优势取高)' : advantage === 'dis' ? '(劣势取低)' : '';
|
|
22
|
+
const mod = modifier ? (modifier > 0 ? `+${modifier}` : `${modifier}`) : '';
|
|
23
|
+
const faces = rolls.length > 1 ? `[${rolls.join(', ')}]` : `[${rolls[0]}]`;
|
|
24
|
+
const crit = sides === 20 && n === 1
|
|
25
|
+
? (kept === 20 ? ' —— **自然 20**,给它一个镜头' : kept === 1 ? ' —— **自然 1**,给它一个镜头' : '')
|
|
26
|
+
: '';
|
|
27
|
+
const vs = dc != null ? ` vs DC ${dc} —— ${total >= dc ? '**成功**' : '**失败**'}` : '';
|
|
28
|
+
return `🎲 ${label}:${n}d${sides}${mod}${adv} → ${faces}${mod ? ` ${mod}` : ''} = **${total}**${vs}${crit}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function makeRollDiceTool({ projectId, ctx = null, rngInt = crypto.randomInt } = {}) {
|
|
32
|
+
return tool(
|
|
33
|
+
'roll_dice',
|
|
34
|
+
`Roll real server-side dice (cryptographic RNG) for a check. The result also fires a
|
|
35
|
+
run.dice event straight to the user's screen — open dice the GM's pen cannot fake, which
|
|
36
|
+
is the whole point: never make up a roll yourself. After rolling: write the outcome into
|
|
37
|
+
the beat (a TRPG-style hint beats reporting raw numbers) and log it in the 明骰表.
|
|
38
|
+
Failure buys information or a cost; success buys a new problem — never a flat nothing.`,
|
|
39
|
+
{
|
|
40
|
+
label: z.string().min(1).max(60)
|
|
41
|
+
.describe('What this check is, shown to the user (e.g. "斥候·侦查" or "哥布林·突袭")'),
|
|
42
|
+
sides: z.number().int().min(2).max(1000).default(20).optional().describe('Die faces (default 20)'),
|
|
43
|
+
n: z.number().int().min(1).max(20).default(1).optional().describe('How many dice (default 1)'),
|
|
44
|
+
modifier: z.number().int().min(-100).max(100).default(0).optional().describe('Flat bonus/malus added to the total'),
|
|
45
|
+
advantage: z.enum(['none', 'adv', 'dis']).default('none').optional()
|
|
46
|
+
.describe('adv = roll twice keep high, dis = keep low (n must be 1)'),
|
|
47
|
+
dc: z.number().int().min(1).max(1000).optional().describe('Difficulty class; result line says success/failure'),
|
|
48
|
+
},
|
|
49
|
+
async (args, _extra) => {
|
|
50
|
+
if (!projectId) return { content: [{ type: 'text', text: 'No project bound.' }], isError: true };
|
|
51
|
+
const sides = args.sides ?? 20;
|
|
52
|
+
const n = args.n ?? 1;
|
|
53
|
+
const modifier = args.modifier ?? 0;
|
|
54
|
+
const advantage = args.advantage ?? 'none';
|
|
55
|
+
if (advantage !== 'none' && n !== 1) {
|
|
56
|
+
return { content: [{ type: 'text', text: 'advantage/disadvantage 只对单骰(n=1)有意义。' }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
const rolls = [];
|
|
59
|
+
const times = advantage === 'none' ? n : 2;
|
|
60
|
+
for (let i = 0; i < times; i += 1) rolls.push(rngInt(1, sides + 1));
|
|
61
|
+
const kept = advantage === 'adv' ? Math.max(...rolls)
|
|
62
|
+
: advantage === 'dis' ? Math.min(...rolls)
|
|
63
|
+
: null;
|
|
64
|
+
const total = (advantage === 'none' ? rolls.reduce((a, b) => a + b, 0) : kept) + modifier;
|
|
65
|
+
const outcome = args.dc != null ? (total >= args.dc ? 'success' : 'failure') : null;
|
|
66
|
+
const line = describeRoll({
|
|
67
|
+
label: args.label, n, sides, modifier, advantage,
|
|
68
|
+
rolls, kept: kept ?? rolls[0], total, dc: args.dc ?? null,
|
|
69
|
+
});
|
|
70
|
+
// 明骰直达用户(fail-soft:事件坏了不拦骰)—— 这条是"骰子可信"的另一半
|
|
71
|
+
try {
|
|
72
|
+
ctx?.emit?.({
|
|
73
|
+
type: 'run.dice', sessionId: null,
|
|
74
|
+
label: args.label, sides, n, modifier, advantage,
|
|
75
|
+
rolls, total, ...(args.dc != null ? { dc: args.dc, outcome } : {}),
|
|
76
|
+
});
|
|
77
|
+
} catch { /* fail-soft */ }
|
|
78
|
+
return { content: [{ type: 'text', text:
|
|
79
|
+
`${line}\n(用户已在屏幕上看到这一骰 —— 正文按结果写后果,骰表记一行。)` }] };
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -254,7 +254,7 @@ and move on.`,
|
|
|
254
254
|
shots: z.array(z.object({
|
|
255
255
|
prompt: z.string().describe('H3 three-field English prompt'),
|
|
256
256
|
duration: z.number().min(5.2).max(12.25).default(8),
|
|
257
|
-
name: z.string().regex(/^[\w
|
|
257
|
+
name: z.string().regex(/^[\w一-鿿-ヿ-]{1,40}$/).describe('shot slug, unique in batch'),
|
|
258
258
|
first_frame: z.string().optional(),
|
|
259
259
|
last_frame: z.string().optional(),
|
|
260
260
|
steps: z.number().int().min(4).max(8).optional().describe('default 8; 4 = fast draft'),
|