@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
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { Events } from './events.js';
|
|
16
|
+
import { callerOf } from './actor-trail.js';
|
|
16
17
|
import { handleTaskMessage } from './task-events.js';
|
|
17
18
|
import { listWorkspaceArtifacts } from '../../lib/artifact-target.js';
|
|
18
19
|
import { toWorkspaceRel } from '../../lib/workspace-path.js';
|
|
19
|
-
import {
|
|
20
|
+
import { TOOL_INPUT_STREAM_FIELDS, toolInputStreams, pumpToolInputStream } from './tool-input-stream.js';
|
|
20
21
|
|
|
21
22
|
// 系统提示词(prelude / plan instructions 的加载与渲染)2026-08-19 迁去
|
|
22
23
|
// ./system-prompts.js —— 那一块跟本文件其余部分(SDK 消息翻译层 + options
|
|
@@ -61,6 +62,11 @@ export const DEFAULT_TOOL_ALLOWLIST = [
|
|
|
61
62
|
'WebFetch',
|
|
62
63
|
'Task',
|
|
63
64
|
'TaskOutput',
|
|
65
|
+
// 常驻角色(rp-*,见 cast.js)的唯一叫醒方式。SendMessage 是 **deferred 工具**:
|
|
66
|
+
// 列在这里只是进了可见集,模型还得先 ToolSearch('select:SendMessage') 取 schema
|
|
67
|
+
// 才能调(2026-08-26 探针实测,主代理和子代理都会自己去取,不用教)。
|
|
68
|
+
// 漏挂的后果不是报错而是够不着:主代理派出去的角色再也叫不回来。
|
|
69
|
+
'SendMessage',
|
|
64
70
|
'Skill',
|
|
65
71
|
// deferred MCP 工具的取 schema 入口(ENABLE_TOOL_SEARCH=true 时生效)。
|
|
66
72
|
// 漏挂 = 延迟加载的工具永远调不到(同 Skill 的可见集陷阱)
|
|
@@ -108,7 +114,12 @@ export function handleSDKMessage(ctx, msg) {
|
|
|
108
114
|
// ctx.emit 会解析回 child 自己 → 无限递归(真机爆过 Maximum call stack)
|
|
109
115
|
const base = ctx;
|
|
110
116
|
const child = Object.create(base);
|
|
111
|
-
|
|
117
|
+
// actor:这条事件是谁干的(常驻角色的 slug)。parentToolUseId 是**派发那次
|
|
118
|
+
// Agent 调用**的 id,而派发闸在那一刻按同一个 id 盖过章 —— 查回来就知道
|
|
119
|
+
// 是哪个角色。画布的在场表据此给每个角色立自己的精灵(不然全算主 agent
|
|
120
|
+
// 头上,主精灵会在角色写的东西之间瞬移,那正是 08-18 把子代理精灵拆掉的原因)。
|
|
121
|
+
const actor = callerOf(parent)?.agentType || null;
|
|
122
|
+
child.emit = (evt) => base.emit({ parentToolUseId: parent, ...(actor ? { actor } : {}), ...evt });
|
|
112
123
|
ctx = child;
|
|
113
124
|
}
|
|
114
125
|
|
|
@@ -355,52 +366,6 @@ function handleAssistantBlocks(ctx, content, skipTextThinking = false) {
|
|
|
355
366
|
* 累积 + 节流抽字段(见下方"真流式工具入参"段),其余工具照旧等完整 block。
|
|
356
367
|
*/
|
|
357
368
|
|
|
358
|
-
// ── 真流式工具入参(2026-07-28,工作台舞台层代码直播)──
|
|
359
|
-
// input_json_delta 是半截 JSON 碎片,等拼完再发一次的话,模型逐 token 生成
|
|
360
|
-
// new_string 的几十秒里前端只能干转圈。这里对写代码的工具累积缓冲区,节流用
|
|
361
|
-
// partial-json 容错解析累积串,把目标字段相对上次的纯文本增量推给前端
|
|
362
|
-
// (run.delta.tool_input)。转义符跨块断开由解析器兜住;字段单调增长,
|
|
363
|
-
// 万一局部解析短暂回缩就跳过本拍(append 只在变长时发)。
|
|
364
|
-
const TOOL_INPUT_STREAM_FIELDS = {
|
|
365
|
-
Edit: 'new_string',
|
|
366
|
-
Write: 'content',
|
|
367
|
-
};
|
|
368
|
-
const TOOL_INPUT_THROTTLE_MS = 120;
|
|
369
|
-
|
|
370
|
-
function toolInputStreams(ctx) {
|
|
371
|
-
if (!ctx._toolInputStreams) ctx._toolInputStreams = new Map();
|
|
372
|
-
return ctx._toolInputStreams;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
function pumpToolInputStream(ctx, st, flush) {
|
|
377
|
-
const now = Date.now();
|
|
378
|
-
if (!flush && now - st.lastEmit < TOOL_INPUT_THROTTLE_MS) return;
|
|
379
|
-
let obj;
|
|
380
|
-
try { obj = parsePartialJson(st.buf, PartialAllow.ALL); } catch { return; }
|
|
381
|
-
if (!obj || typeof obj !== 'object') return;
|
|
382
|
-
const text = typeof obj[st.field] === 'string' ? obj[st.field] : '';
|
|
383
|
-
// file_path 只在确定流完后才取:容错解析会把半截字符串也带出来,第一拍常
|
|
384
|
-
// 截在路径中间(e2e 撞过:抽到项目目录名 → 前端物件寻址指错)。目标字段的
|
|
385
|
-
// key 出现(键序在 file_path 之后)或对象已有第二个键 = 路径已闭合。
|
|
386
|
-
const pathComplete = obj[st.field] !== undefined || Object.keys(obj).length >= 2;
|
|
387
|
-
// 发**工作区相对路径**(2026-08-13):前端拿它当画布物件 id 的路径部分,
|
|
388
|
-
// 而 id = 工作区相对路径。以前原样转发绝对路径,前端靠 `tasks/<任务>/`
|
|
389
|
-
// 这个特征段抠相对部分 —— 那一层拆掉后绝对路径里没有可锚定的标志了。
|
|
390
|
-
const rawFilePath = !st.filePathSent && pathComplete && typeof obj.file_path === 'string' ? obj.file_path : null;
|
|
391
|
-
const filePath = rawFilePath ? toWorkspaceRel(rawFilePath, ctx.workspace?.root?.()) : null;
|
|
392
|
-
const append = text.length > st.sent ? text.slice(st.sent) : '';
|
|
393
|
-
if (!append && !filePath && !flush) return;
|
|
394
|
-
st.lastEmit = now;
|
|
395
|
-
if (append) st.sent = text.length;
|
|
396
|
-
if (filePath) st.filePathSent = true;
|
|
397
|
-
ctx.emit(Events.deltaToolInput(ctx.counters.turns, st.id, st.name, {
|
|
398
|
-
...(filePath ? { filePath } : {}),
|
|
399
|
-
...(append ? { append } : {}),
|
|
400
|
-
...(flush ? { done: true } : {}),
|
|
401
|
-
}));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
369
|
function handleStreamEvent(ctx, msg) {
|
|
405
370
|
const evt = msg.event;
|
|
406
371
|
if (!evt) return;
|
|
@@ -425,11 +390,13 @@ function handleStreamEvent(ctx, msg) {
|
|
|
425
390
|
const cb = evt.content_block;
|
|
426
391
|
if (cb && cb.type === 'tool_use' && cb.id && cb.name) {
|
|
427
392
|
ctx.emit(Events.toolUseStarted(ctx.counters.turns, cb.id, cb.name));
|
|
428
|
-
const
|
|
429
|
-
if (
|
|
393
|
+
const spec = TOOL_INPUT_STREAM_FIELDS[cb.name];
|
|
394
|
+
if (spec && Number.isInteger(evt.index)) {
|
|
395
|
+
const conf = typeof spec === 'string' ? { field: spec } : spec;
|
|
430
396
|
toolInputStreams(ctx).set(streamKey, {
|
|
431
|
-
id: cb.id, name: cb.name, field,
|
|
432
|
-
|
|
397
|
+
id: cb.id, name: cb.name, field: conf.field, batch: conf.batch || null,
|
|
398
|
+
spot: conf.spot || null,
|
|
399
|
+
actionIdx: -1, buf: '', sent: 0, lastEmit: 0, filePathSent: false, spotSent: false,
|
|
433
400
|
});
|
|
434
401
|
}
|
|
435
402
|
}
|
|
@@ -548,3 +515,6 @@ export async function detectArtifact(ctx) {
|
|
|
548
515
|
}
|
|
549
516
|
return null;
|
|
550
517
|
}
|
|
518
|
+
|
|
519
|
+
/** 兼容出口(真身在 tool-input-stream.js,2026-08-29 棘轮拆件) */
|
|
520
|
+
export { latestBatchField, pickSpot } from './tool-input-stream.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/agent/beat-state.js —— 「这一拍收完了吗」(2026-08-29)
|
|
3
|
+
*
|
|
4
|
+
* ## 它拦的事故
|
|
5
|
+
*
|
|
6
|
+
* 08-28 真会话(proj_mtcyfdls)诊断出来的头号病:主持人写完两千字正文就收工,
|
|
7
|
+
* 玩家面前没有任何把手,只能自己打出「接下来该怎么办」——那句话本身就是漏拍的信号。
|
|
8
|
+
* 这条教义在提示词里写了整整一节,模型照旧漏。这条线的老账:**写成话术它不听,
|
|
9
|
+
* 写成闸它才听**。
|
|
10
|
+
*
|
|
11
|
+
* ## 判据(保守,宁可漏拦不可错拦)
|
|
12
|
+
*
|
|
13
|
+
* 这一轮里主持人往画布上写过东西,却**既没给按钮、也没把这一拍交给任何角色** ——
|
|
14
|
+
* 那这一拍确实停在半空。三个信号都是服务端亲眼所见的工具调用,不是模型的自述:
|
|
15
|
+
*
|
|
16
|
+
* 写过 write_on_board / board_batch(且调用者是主持人,不是角色)
|
|
17
|
+
* 给了按钮 同一批调用的正文里出现 nd:controls 围栏
|
|
18
|
+
* 交出去了 Agent 派角色 / SendMessage 寄给角色
|
|
19
|
+
*
|
|
20
|
+
* 只拦**第一次** Stop(`stop_hook_active` 为真说明已经拦过一轮,再拦就是死循环)。
|
|
21
|
+
*
|
|
22
|
+
* 状态跟会话走,每轮开头(UserPromptSubmit)清一次 —— 判的是「这一轮」,不是历史。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const beats = new Map(); // sessionId → { wrote, controls, handed }
|
|
26
|
+
|
|
27
|
+
function slot(sessionId) {
|
|
28
|
+
if (!beats.has(sessionId)) beats.set(sessionId, { wrote: false, controls: false, handed: false });
|
|
29
|
+
return beats.get(sessionId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 主持人往画布上写了东西。hasControls = 这次的正文里有 nd:controls 围栏 */
|
|
33
|
+
export function noteBeatWrite(sessionId, hasControls) {
|
|
34
|
+
if (!sessionId) return;
|
|
35
|
+
const s = slot(sessionId);
|
|
36
|
+
s.wrote = true;
|
|
37
|
+
if (hasControls) s.controls = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 这一拍交给角色了(派它上场 / 寄话给它) */
|
|
41
|
+
export function noteBeatHandoff(sessionId) {
|
|
42
|
+
if (!sessionId) return;
|
|
43
|
+
slot(sessionId).handed = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 新的一轮开始,重新记 */
|
|
47
|
+
export function resetBeat(sessionId) {
|
|
48
|
+
if (sessionId) beats.delete(sessionId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 这一拍是不是停在半空(写了正文,既没按钮也没交给谁) */
|
|
52
|
+
export function beatLeftHanging(sessionId) {
|
|
53
|
+
const s = beats.get(sessionId);
|
|
54
|
+
return !!s && s.wrote && !s.controls && !s.handed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 会话收摊 */
|
|
58
|
+
export function clearBeatState(sessionId) { beats.delete(sessionId); }
|
|
59
|
+
|
|
60
|
+
/** 测试用 */
|
|
61
|
+
export function _resetBeatState() { beats.clear(); }
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/agent/cast.js —— 常驻角色(RP 演员)名册的判据层
|
|
3
|
+
*
|
|
4
|
+
* 「常驻角色」= 会话期间一直活着、靠 SendMessage 唤醒的子代理(叙事者 / NPC)。
|
|
5
|
+
* 它跟干活型子代理(vision-checker 那类)只差一条,但这条是结构性的:
|
|
6
|
+
* **它的产出走画布和收件箱,不走 tool_result 报告**,所以不能被强制前台 ——
|
|
7
|
+
* 前台跑等于把主线卡在一个根本不打算结束的角色身上。
|
|
8
|
+
*
|
|
9
|
+
* ## 判据为什么是名字前缀,不是内存注册表
|
|
10
|
+
*
|
|
11
|
+
* 服务器重启 / 会话重开之后,`.claude/agents/*.md` 还躺在工作区里,内存表却是空的。
|
|
12
|
+
* 判据一失效,常驻角色就会被 force-foreground 改回前台 —— 症状是「重启之后叙事者
|
|
13
|
+
* 突然开始阻塞主线」,而且不报错。前缀写在文件名里,跟真相源(那个 md 文件)一起走。
|
|
14
|
+
*
|
|
15
|
+
* ## ASCII 是硬约束,不是风格选择
|
|
16
|
+
*
|
|
17
|
+
* SendMessage 的收件人校验(CLI 2.1.237 实测原文):
|
|
18
|
+
* `name must start with a letter or digit and contain only letters, digits,
|
|
19
|
+
* underscores, or hyphens (max 64 chars)`
|
|
20
|
+
* 也就是说**「墨璃」这种名字寄不出去**。所以寻址名一律是 ASCII slug(rp-molli),
|
|
21
|
+
* 中文展示名另存(见 cast_role 工具与板书的 by 字段)。同族教训见记忆
|
|
22
|
+
* feedback-ascii-tool-params:中文参数名会让 agent 静默结束回合。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** 常驻角色的 subagent_type 前缀。改它等于改判据,两个消费方(前台豁免、归属盖章)一起动。 */
|
|
26
|
+
export const ROLE_PREFIX = 'rp-';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 演员位(2026-08-28 重构):预注册的通用子代理定义,角色身份全在派发 prompt 里。
|
|
30
|
+
*
|
|
31
|
+
* ## 为什么从「一角色一定义文件」改成两个固定演员位
|
|
32
|
+
*
|
|
33
|
+
* 会话中途写进 `.claude/agents/` 的角色文件,只有**写入时活着的那个 CLI 进程**
|
|
34
|
+
* 可靠认得(chokidar 事件 → 下一回合 agent_listing_delta)。生产是 resume 频繁换
|
|
35
|
+
* 进程的架构,三场真会话同一天跑出「12 连败 / 恰好成功 / 永久 not found」三种结局
|
|
36
|
+
* (proj_mtcsei49 / proj_mtcyfdls / proj_mtd1tap1,2026-08-28 全链路勘定)。
|
|
37
|
+
* 演员位在**建项目时**落盘 —— 任何进程出生就在初始快照里,这个故障类整个消失。
|
|
38
|
+
*
|
|
39
|
+
* ## 语义
|
|
40
|
+
*
|
|
41
|
+
* 派发:Agent(subagent_type: 演员位, name: "rp-<角色>", prompt: 角色卡)。
|
|
42
|
+
* 同型多实例、name 各自寻址、转录互不相通(2026-08-28 探针 P0-P3 实测)。
|
|
43
|
+
* 角色卡退化成数据(角色/ 目录 + .nd/cast.json 登记),见 cast-role.js。
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* 角色位:**只有一个**(2026-08-29 用户拍板)。
|
|
47
|
+
*
|
|
48
|
+
* 原来有两个(rp-actor 演一个人 / rp-narrator 写场面),08-29 合成一个 ——
|
|
49
|
+
* 环境和场面的笔收回主持人手里,角色只写「这个人此刻说什么做什么」。少一个位
|
|
50
|
+
* 就少一次选型,而选型本身是会失手的动作(08-28 真会话里免费行模型连撞三次派发闸)。
|
|
51
|
+
*
|
|
52
|
+
* RETIRED_SLOTS 是存量项目盘上那两个旧位的名字:铺装时删掉文件,判据里继续认
|
|
53
|
+
* (不许拿它们当角色名,也不许把它们当成一个角色)。
|
|
54
|
+
*/
|
|
55
|
+
export const ROLE_SLOT = 'rp-role';
|
|
56
|
+
export const RETIRED_SLOTS = Object.freeze(['rp-actor', 'rp-narrator']);
|
|
57
|
+
const SLOT_NAMES = new Set([ROLE_SLOT, ...RETIRED_SLOTS]);
|
|
58
|
+
|
|
59
|
+
/** 这个 subagent_type 是不是角色位(派发闸据此走 name 闸分支) */
|
|
60
|
+
export function isSlotType(t) { return typeof t === 'string' && SLOT_NAMES.has(t); }
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 常驻角色名的**唯一**判据(2026-08-26 收成一份)。
|
|
64
|
+
*
|
|
65
|
+
* 收之前这条正则有三份拷贝:这里、`lib/chalk.js` 的 normalizeBy、
|
|
66
|
+
* `projects/board-sanitize.js` 的 sanitizeBy。三份"同一件事"里裂了一道缝——
|
|
67
|
+
* 这里是「`rp-` 前缀 + 整名字符集」,另两份要求 `rp-` **之后**的首字符是字母数字。
|
|
68
|
+
* 于是 `rp--x` 这种名字:派发闸放行、名册登记、byOf 也认,但落盘时两个白名单
|
|
69
|
+
* 把它剥掉 → 板上条目没有 by、板书 frontmatter 折回 'agent',**静默失名**。
|
|
70
|
+
*
|
|
71
|
+
* 字符集本身抄的是 CLI 对 SendMessage 收件人名的校验(letters/digits/_/-,≤64),
|
|
72
|
+
* 因为这个名字要同时当收件人名和文件名。
|
|
73
|
+
*/
|
|
74
|
+
export const ROLE_SLUG_RE = /^rp-[A-Za-z0-9][A-Za-z0-9_-]{0,60}$/;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 这个 subagent_type 是不是常驻角色。
|
|
78
|
+
* 只认前缀 + 合法 slug —— `rp-` 开头但含中文/斜杠的名字一律不算,
|
|
79
|
+
* 免得后面拿它去拼文件路径(cast_role 会写 .claude/agents/<slug>.md)。
|
|
80
|
+
*/
|
|
81
|
+
export function isResidentRole(subagentType) {
|
|
82
|
+
// 不 trim:判据要对**下游实际使用的那个值**成立。带空白的 slug 派发本来就会失败,
|
|
83
|
+
// 但「判据看 trim 后、下游用 trim 前」是同族病的幼苗(08-26 复审指出)。
|
|
84
|
+
return typeof subagentType === 'string' && ROLE_SLUG_RE.test(subagentType);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 合法的角色 slug(含前缀)。cast_role 写文件前用它守门。 */
|
|
88
|
+
export function isValidRoleSlug(slug) {
|
|
89
|
+
return isResidentRole(slug);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 会话级角色名册 —— H1 的修法(2026-08-26 fable 审出)。
|
|
94
|
+
*
|
|
95
|
+
* ## 为什么形状判据不够
|
|
96
|
+
*
|
|
97
|
+
* SendMessage 的裸名解析范围是**整台机器**(CLI schema 原文:a name that exactly
|
|
98
|
+
* matches one live agent *or session (on this machine)* delivers directly;并且
|
|
99
|
+
* "if the same name also names an in-process agent, the bare name always wins")。
|
|
100
|
+
* 也就是说「本会话有同名 agent」时我们才被 in-process 优先保护,**本会话没有的时候,
|
|
101
|
+
* 裸名会落到同机别的会话上**。
|
|
102
|
+
*
|
|
103
|
+
* 而 Nodesign 是多用户共机、又给每个用户同一套命名教义(rp-narrator 这种名字必然撞车)。
|
|
104
|
+
* 于是有一条**正路**会漏:服务器重启 → 角色进程没了、名字空出 → 主 agent 按教义
|
|
105
|
+
* `SendMessage({to:'rp-narrator'})` 唤醒 → 落到另一个用户会话里那个活着的同名角色。
|
|
106
|
+
* 重启后恰恰是模型最会去试这个名字的时刻,不需要任何人有恶意。
|
|
107
|
+
*
|
|
108
|
+
* 所以判据从「名字长得像角色」改成「**这个会话真的派过它**」。名册是闭包级的
|
|
109
|
+
* (createHooks 里一会话一个 Set),天然按会话隔离,不需要全局表也不会跨会话看见。
|
|
110
|
+
*
|
|
111
|
+
* 附赠:重派同名角色能被硬拦。重派的后果是**静默失忆**(CLI 的 latest wins:
|
|
112
|
+
* 新角色顶掉名字,旧角色连同它演过的全部剧情失联),只靠提示词劝是拦不住的。
|
|
113
|
+
*/
|
|
114
|
+
export function createRoleRoster() {
|
|
115
|
+
const names = new Set();
|
|
116
|
+
// (08-28 演员位重构:castRun/noteCast/castedInRun 整族退役 —— 演员位建项目时就在
|
|
117
|
+
// 初始快照里,「本回合刚造派不动」这个状态不存在了,闸随机制一起拆。)
|
|
118
|
+
return {
|
|
119
|
+
/** 派发时登记。返回 false = 这个名字本会话已经在场(重派会顶掉旧的) */
|
|
120
|
+
claim(name) {
|
|
121
|
+
if (!isResidentRole(name)) return false;
|
|
122
|
+
if (names.has(name)) return false;
|
|
123
|
+
names.add(name);
|
|
124
|
+
return true;
|
|
125
|
+
},
|
|
126
|
+
/** 在场吗(收件人闸的唯一判据) */
|
|
127
|
+
has(name) { return names.has(name); },
|
|
128
|
+
/** 显式退场:将来的「重置角色」路径用(块 2 的 cast_role 会接) */
|
|
129
|
+
release(name) { return names.delete(name); },
|
|
130
|
+
list() { return [...names]; },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 角色能拿的工具白名单(短名,不带 `mcp__nodesign__` 前缀)。
|
|
136
|
+
*
|
|
137
|
+
* 定这张表的原则:**角色只能通过画布表达自己**。
|
|
138
|
+
* - 给:板上写字画画读板看板、跟人说话(SendMessage)、取工具 schema(ToolSearch)、
|
|
139
|
+
* 读工作区里的设定与世界书(Read/Glob/Grep —— 每章动笔前 grep 世界书是酒馆
|
|
140
|
+
* 触发词在我们这儿的原生形态)。
|
|
141
|
+
* - 不给:任何**外发**或**花钱**的东西(publish_site / deliver_files / 生图产线),
|
|
142
|
+
* 任何**改工作区结构**的东西(Write/Edit/Bash),以及 Task(角色不再派角色)。
|
|
143
|
+
*
|
|
144
|
+
* ⚠️ 内置工具那一栏其实是"求个心安":后台子代理本来就被 CLI 剥得只剩
|
|
145
|
+
* Read/Glob/Grep/ToolSearch/SendMessage/Agent(2026-08-26 实测)。写在这里是为了
|
|
146
|
+
* 让"角色能干什么"在一处读得全,而不是散在 CLI 的过滤逻辑里。
|
|
147
|
+
*/
|
|
148
|
+
export const ROLE_TOOL_WHITELIST = Object.freeze([
|
|
149
|
+
// 板上(MCP 短名;旧别名 create/relate/arrange/sketch/edit_sketch/finish 08-28 随注册表一起收摊)
|
|
150
|
+
'write_on_board', 'read_board', 'edit_board',
|
|
151
|
+
// board_batch 2026-08-30 暂停(注册表摘牌,这里的对账闸会点名);恢复时加回
|
|
152
|
+
'look_at_board', 'read_user_view', 'organize_board', 'pin_to_board',
|
|
153
|
+
// 记忆(2026-08-28 角色文件夹范式):角色没有 Write,往自己家记一笔只能走这件
|
|
154
|
+
'jot_memory',
|
|
155
|
+
// 内置
|
|
156
|
+
'SendMessage', 'ToolSearch', 'Read', 'Glob', 'Grep',
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
/** 内置工具(不加 mcp 前缀)——其余按 MCP 短名加前缀 */
|
|
160
|
+
const BUILTIN = new Set(['SendMessage', 'ToolSearch', 'Read', 'Glob', 'Grep']);
|
|
161
|
+
|
|
162
|
+
/** 角色默认拿到的一套:够它在板上演,不多不少 */
|
|
163
|
+
export const ROLE_DEFAULT_TOOLS = Object.freeze([
|
|
164
|
+
'write_on_board', 'read_board', 'look_at_board', 'read_user_view',
|
|
165
|
+
'jot_memory',
|
|
166
|
+
'SendMessage', 'ToolSearch', 'Read', 'Grep',
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
/** 短名 → 写进角色文件 frontmatter 的全名 */
|
|
170
|
+
export function qualifyRoleTool(shortName, mcpServerName) {
|
|
171
|
+
return BUILTIN.has(shortName) ? shortName : `mcp__${mcpServerName}__${shortName}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 角色位的定义体(harness 所有,ensureProjectWorkspace 落盘并保持内容为准)。
|
|
176
|
+
*
|
|
177
|
+
* 定义体刻意空心:角色身份全在派发指令里的角色卡上。这里只放三样**扛得住上下文
|
|
178
|
+
* 压缩**的东西 —— 「你是谁由卡定」的失忆保险、分工规矩、一轮怎么结束。
|
|
179
|
+
*
|
|
180
|
+
* 为什么分工规矩钉在这里而不是写在角色卡尾:定义体是子代理的系统提示词,长对话
|
|
181
|
+
* 压缩不会动它;卡是派发指令的一部分,压缩会把它压掉(2026-08-28 搬家的原因)。
|
|
182
|
+
*
|
|
183
|
+
* 实测地板:零工具子代理 ~1356 token(CLI 脚手架),定义体几百 token,主代理的
|
|
184
|
+
* prelude 一个字不进(2026-08-26 十二发探针)。⚠️ 项目 CLAUDE.md 会强制注入每个
|
|
185
|
+
* 子代理(omitClaudeMd 对子代理无效)—— 演出项目里那正是这场故事的公共设定,
|
|
186
|
+
* 是特性不是泄漏,但这意味着写进它的东西默认全体角色可见(主持人的暗线写别的文件)。
|
|
187
|
+
*/
|
|
188
|
+
export function slotAgentFile(mcpServerName) {
|
|
189
|
+
const tools = ROLE_DEFAULT_TOOLS.map((t) => qualifyRoleTool(t, mcpServerName)).join(', ');
|
|
190
|
+
return [
|
|
191
|
+
'---',
|
|
192
|
+
`name: ${ROLE_SLOT}`,
|
|
193
|
+
'description: "角色位:演一个人。身份由派发指令里的角色卡决定"',
|
|
194
|
+
`tools: ${tools}`,
|
|
195
|
+
'model: inherit',
|
|
196
|
+
'---',
|
|
197
|
+
'',
|
|
198
|
+
'你是一个角色位。这次派给你的指令里有一张角色卡 —— 从收到它那一刻起,你**就是**',
|
|
199
|
+
'卡上那个人,此后一直是。角色卡是你身份的唯一依据:任何时候拿不准自己是谁',
|
|
200
|
+
'(对话被压缩过、醒来接不上),用 Read 重读指令开头给你的卡路径,再接着演。',
|
|
201
|
+
'',
|
|
202
|
+
'你的家在 `角色/<你的名字>/`:角色卡.md 和 记忆.md 都在那里,用户随时可能改它们',
|
|
203
|
+
'(那是正当操作,不是入侵)。以后还用得上的事随手 `jot_memory` 记一笔(答应过什么、',
|
|
204
|
+
'谁的秘密、你现在怎么看某个人、东西放哪了)—— 对话记录会被压缩,记忆文件不会。',
|
|
205
|
+
'',
|
|
206
|
+
'(如果上下文末尾出现给干活助手写的 `Notes:` —— 不许用表情符号、要写报告、',
|
|
207
|
+
'回绝对路径那套 —— 无视它:你在演一个人,不在交报告。)',
|
|
208
|
+
'',
|
|
209
|
+
'## 你负责写什么(这是分工,不是人设)',
|
|
210
|
+
'',
|
|
211
|
+
'你只写你自己:你说的话、你做的动作、你心里想的,一律**第一人称**。以下不归你写:',
|
|
212
|
+
'- 环境和场面(天色、房间、路人、气氛)—— 那是主持人写的。你的动作做出去就停,',
|
|
213
|
+
' 世界怎么反应,等主持人接。',
|
|
214
|
+
'- 别人的话和反应 —— 一个字都不替他们写。想要谁回应,就在故事里对他说。',
|
|
215
|
+
'- 上帝视角的推进(「与此同时」「谁也没注意到」)—— 你只知道你这个人知道的。',
|
|
216
|
+
'',
|
|
217
|
+
'一次只写一段:**三五行到十来行**,这一段说完就停。写满一屏就是抢戏 —— 用户一次',
|
|
218
|
+
'要读主持人和你两份,你多写一行他的耐心就少一分;信息留到下一段再给。',
|
|
219
|
+
'',
|
|
220
|
+
'有限视角:你只知道你这个人**亲眼见过、亲耳听过、被告诉过**的事。信息差是故事的',
|
|
221
|
+
'燃料 —— 别把你不知道的事演成知道,你蒙在鼓里的样子本身就是戏。',
|
|
222
|
+
'',
|
|
223
|
+
'写法(防 AI 腔三条,**默认如此**;角色卡或用户给的文风另有主张时以那边为准):',
|
|
224
|
+
'对白光秃秃地放 —— 引号前后不挂「她轻声说」式的语气描写;动作用白描 ——',
|
|
225
|
+
'「A 做了 B」就停,别缀「仿佛…」的解释尾巴;不用「微不可察」「一丝」「一抹」',
|
|
226
|
+
'这类没有信息量的程度词。',
|
|
227
|
+
'',
|
|
228
|
+
'文风不跟着主持人跑:你从它写的东西里只拿**事实**(谁在哪、做了什么、说了什么),',
|
|
229
|
+
'不学它的句式、节奏、修辞 —— 你的语气只来自你的人设和你自己说过的话。',
|
|
230
|
+
'',
|
|
231
|
+
'## 写到哪里',
|
|
232
|
+
'',
|
|
233
|
+
'用 `write_on_board` 把你这一段写到画布上(不是回给主持人 —— 用户是在画布上看故事的)。',
|
|
234
|
+
'位置:主持人给你开过一条属于你的线时,什么位置参数都不用给,你的话会自动接在',
|
|
235
|
+
'自己那条线上;没有的话,你在回应谁就用 `reply_to` 指他那一条(路径在给你的指令里)。',
|
|
236
|
+
'',
|
|
237
|
+
'## 这一轮什么时候结束',
|
|
238
|
+
'',
|
|
239
|
+
'写完这一段,你这一轮就结束了 —— 不用等人说话,也不用问「还需要我做什么吗」。',
|
|
240
|
+
'用户的下一句和场上的新情况会由主持人再寄给你,那时你带着完整的记忆接着演。',
|
|
241
|
+
'',
|
|
242
|
+
].join('\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 校验一批请求的工具名。返回 { tools, rejected }。
|
|
247
|
+
* 不认识的名字**不静默丢**——丢了的后果是角色少一只手而没人知道,
|
|
248
|
+
* 调用方要把 rejected 如实回给 agent。
|
|
249
|
+
*/
|
|
250
|
+
export function resolveRoleTools(requested, mcpServerName) {
|
|
251
|
+
if (!Array.isArray(requested) || requested.length === 0) {
|
|
252
|
+
return { tools: ROLE_DEFAULT_TOOLS.map((t) => qualifyRoleTool(t, mcpServerName)), rejected: [] };
|
|
253
|
+
}
|
|
254
|
+
const allow = new Set(ROLE_TOOL_WHITELIST);
|
|
255
|
+
const tools = [];
|
|
256
|
+
const rejected = [];
|
|
257
|
+
for (const raw of requested) {
|
|
258
|
+
// 两种写法都收:短名(write_on_board)和全名(mcp__nodesign__write_on_board)
|
|
259
|
+
const short = String(raw).replace(new RegExp(`^mcp__${mcpServerName}__`), '');
|
|
260
|
+
if (allow.has(short)) tools.push(qualifyRoleTool(short, mcpServerName));
|
|
261
|
+
else rejected.push(String(raw));
|
|
262
|
+
}
|
|
263
|
+
// SendMessage 是角色的命脉(它没有别的方式跟主代理说话),漏了就补上
|
|
264
|
+
const sm = 'SendMessage';
|
|
265
|
+
if (!tools.includes(sm)) tools.push(sm);
|
|
266
|
+
if (!tools.includes('ToolSearch')) tools.push('ToolSearch'); // 没它取不到 SendMessage 的 schema
|
|
267
|
+
return { tools, rejected };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 启动期断言:白名单里的 MCP 短名必须真的注册过。
|
|
272
|
+
* 名字写错的后果是**角色少一只手而不报错**(frontmatter 里写个不存在的工具名,
|
|
273
|
+
* CLI 只会当它不存在),这种错只有对着真实注册表才查得出来。
|
|
274
|
+
*/
|
|
275
|
+
export function assertRoleToolsRegistered(registeredShortNames) {
|
|
276
|
+
const missing = ROLE_TOOL_WHITELIST
|
|
277
|
+
.filter((t) => !BUILTIN.has(t))
|
|
278
|
+
.filter((t) => !registeredShortNames.has(t));
|
|
279
|
+
if (missing.length) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`[cast] 角色工具白名单里这些 MCP 工具并不存在:${missing.join(', ')} —— `
|
|
282
|
+
+ '写错名字不会报错,只会让角色少一只手,所以在这里拦住',
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 展示名的保留字闸(2026-08-26;08-26 复审后加固)。
|
|
289
|
+
*
|
|
290
|
+
* 角色的展示名取自它自己的角色文件,**那份文件模型能写** —— 一个角色可以把自己叫
|
|
291
|
+
* 「用户」,渲染进模型上下文之后跟真用户的标签逐字相同,读的人(模型)分不出来。
|
|
292
|
+
* 收件箱上线后「用户说了什么」就是一条决策依据,所以这不是理论风险。
|
|
293
|
+
*
|
|
294
|
+
* 三个真实绕法(都实测过,别删):
|
|
295
|
+
* 'User' / 'USER' —— 精确匹配的 Set 放行大小写变体
|
|
296
|
+
* '用\u200b户' —— 零宽空格,肉眼同形
|
|
297
|
+
* 'агent' —— 西里尔 а,肉眼同形
|
|
298
|
+
* 前两个在这里堵死;同形字收不完(Unicode 太大),靠的是**正门也用同一套规则拒名** +
|
|
299
|
+
* 手写文件绕正门时这一层仍在。
|
|
300
|
+
*
|
|
301
|
+
* ⚠️ 这个函数要在**源头**用(listRoleNames 出口),不是在每个渲染面各用一次 ——
|
|
302
|
+
* 08-26 复审实证:三个渲染面里漏了一个,而漏的那个正是携带用户原话的标注回路。
|
|
303
|
+
*/
|
|
304
|
+
const RESERVED_LABELS = new Set([
|
|
305
|
+
'user', 'agent', 'main', 'system', 'assistant', 'claude', 'human', 'admin', 'root',
|
|
306
|
+
'用户', '主控', '你', '我', '系统', '管理员', '主人', '助手',
|
|
307
|
+
]);
|
|
308
|
+
|
|
309
|
+
/** 比较前的归一:剥不可见字符(Cf:零宽、bidi 控制…)与空白,转小写 */
|
|
310
|
+
function normalizeLabel(v) {
|
|
311
|
+
return String(v || '').replace(/[\p{Cf}\s]/gu, '').toLowerCase();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 安全的展示名:撞保留字或空 → 退回 slug(宁可难看,也不能让署名可冒充)。
|
|
316
|
+
* 返回值里的不可见字符也一并剥掉 —— 渲染出去的字不许藏东西。
|
|
317
|
+
*/
|
|
318
|
+
export function safeRoleLabel(slug, displayName) {
|
|
319
|
+
const cleaned = String(displayName || '').replace(/\p{Cf}/gu, '').trim();
|
|
320
|
+
if (!cleaned) return slug;
|
|
321
|
+
return RESERVED_LABELS.has(normalizeLabel(cleaned)) ? slug : cleaned;
|
|
322
|
+
}
|
|
@@ -277,8 +277,12 @@ export const Events = {
|
|
|
277
277
|
* 认 chalk 字段),形状收进构造器钉住(前端 parity 测试对着这里逐字校)。
|
|
278
278
|
* chalk = 板书文件的工作区相对路径(就是它的画布 id);草图不传。
|
|
279
279
|
*/
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
// actor:这次落定是谁干的(常驻角色的 slug)。**必须带**:board.focus 是板书落定的
|
|
281
|
+
// 唯一信号,画布的在场表靠它给精灵定位。不带的话角色的板书会被记到主 agent 头上
|
|
282
|
+
// —— 主精灵瞬移到角色写的东西上(08-18 拆子代理精灵的病根),而角色自己的精灵
|
|
283
|
+
// 因为永远拿不到 targetId 而从不出现(2026-08-26 审出)。
|
|
284
|
+
boardFocus: (rect, { tag = null, layer = '', soft = false, chalk = null, actor = null } = {}) => (
|
|
285
|
+
{ type: 'board.focus', sessionId: null, rect, tag, layer, soft, chalk, ...(actor ? { actor } : {}) }
|
|
282
286
|
),
|
|
283
287
|
|
|
284
288
|
// ── Phase 1 翻译补全 ──
|
|
@@ -323,6 +327,9 @@ export const Events = {
|
|
|
323
327
|
...(toolUseId ? { toolUseId } : {}),
|
|
324
328
|
}),
|
|
325
329
|
|
|
330
|
+
// (run.role.wait / run.scene 2026-08-29 随收件箱与场声明一起退役:角色写完一段
|
|
331
|
+
// 就结束这一轮,「在写 / 写完了」直接由 subagentStart / subagentStop 表达。)
|
|
332
|
+
|
|
326
333
|
// PostToolUseFailure hook:工具失败,hook 已注入了恢复建议给 agent,
|
|
327
334
|
// 上层只需可见"哪个工具失败了"做监控/告警。
|
|
328
335
|
toolFailure: (toolName, error) => ({
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks/beat-gate.js —— 这一拍没收完就别收工(2026-08-29)
|
|
3
|
+
*
|
|
4
|
+
* 判据与理由见 engine/agent/beat-state.js。这里只做三件接线:
|
|
5
|
+
*
|
|
6
|
+
* PostToolUse(write_on_board|board_batch) 主持人写了 → 记一笔,顺带看有没有按钮
|
|
7
|
+
* PostToolUse(Agent|SendMessage) 这一拍交给角色了 → 记一笔
|
|
8
|
+
* Stop 两样都没有就 block 一次,把话说清楚
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ 只认**主持人自己**的写入:角色写的板书不算「这一拍收完了」——它写完正是主持人
|
|
11
|
+
* 该收尾的时候。判据用 hook input 的 `agent_id`(子代理调用才有,harness 盖的章)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { noteBeatWrite, noteBeatHandoff, beatLeftHanging, resetBeat } from '../beat-state.js';
|
|
15
|
+
import { isRpProject } from '../rp-mode.js';
|
|
16
|
+
import { isResidentRole } from '../cast.js';
|
|
17
|
+
|
|
18
|
+
const CONTROLS_RE = /nd:controls/;
|
|
19
|
+
|
|
20
|
+
/** 从 write_on_board / board_batch 的入参里捞出所有正文 */
|
|
21
|
+
function textsOf(input) {
|
|
22
|
+
const t = input?.tool_input;
|
|
23
|
+
if (!t || typeof t !== 'object') return [];
|
|
24
|
+
const out = [];
|
|
25
|
+
if (typeof t.text === 'string') out.push(t.text);
|
|
26
|
+
for (const op of Array.isArray(t.ops) ? t.ops : []) {
|
|
27
|
+
if (op && typeof op.text === 'string') out.push(op.text);
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function makePostToolUseBeatWrite({ sessionId = null, projectId = null } = {}) {
|
|
33
|
+
return async function beatWrite(input) {
|
|
34
|
+
if (!isRpProject(projectId)) return {};
|
|
35
|
+
if (input?.agent_id) return {}; // 角色写的不算
|
|
36
|
+
const texts = textsOf(input);
|
|
37
|
+
if (!texts.length) return {};
|
|
38
|
+
noteBeatWrite(sessionId, texts.some((x) => CONTROLS_RE.test(x)));
|
|
39
|
+
return {};
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function makePostToolUseBeatHandoff({ sessionId = null, projectId = null } = {}) {
|
|
44
|
+
return async function beatHandoff(input) {
|
|
45
|
+
if (!isRpProject(projectId)) return {};
|
|
46
|
+
const t = input?.tool_input;
|
|
47
|
+
const to = typeof t?.to === 'string' ? t.to : null;
|
|
48
|
+
const name = typeof t?.name === 'string' ? t.name : null;
|
|
49
|
+
// Agent 派角色(name 是实例名)或 SendMessage 寄给角色,都算把这一拍交出去了
|
|
50
|
+
if (isResidentRole(to) || isResidentRole(name) || t?.subagent_type) noteBeatHandoff(sessionId);
|
|
51
|
+
return {};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** nd:rp-prompt —— 拦下来时对主持人说的话 */
|
|
56
|
+
export const BEAT_HANGING_REASON =
|
|
57
|
+
'这一拍还停在半空:你往画布上写了东西,但没给玩家一个可按的下一步。'
|
|
58
|
+
+ '在这一拍下面补一条 ```nd:controls``` 围栏再收工 —— 两到四枚选项'
|
|
59
|
+
+ '(一枚推进主线、一枚人际、一枚剑走偏锋),再留一句「想干别的直接在画布上标注」。\n'
|
|
60
|
+
+ '(这一版角色子代理停用,台上所有人都由你写完 —— 别拿「交给角色」当这一拍的出口。)\n'
|
|
61
|
+
+ '(纯粹的场记维护 —— 只更新状态板、只理版面 —— 不受这条约束:'
|
|
62
|
+
+ '那种情况下你本来也没写新的一拍。)';
|
|
63
|
+
|
|
64
|
+
export function makeStopBeatGate({ sessionId = null, projectId = null } = {}) {
|
|
65
|
+
return async function beatGate(input) {
|
|
66
|
+
if (!isRpProject(projectId)) return {};
|
|
67
|
+
// 已经拦过一轮就放行:再拦是死循环,而且模型此刻正在照着做
|
|
68
|
+
if (input?.stop_hook_active) { resetBeat(sessionId); return {}; }
|
|
69
|
+
if (!beatLeftHanging(sessionId)) { resetBeat(sessionId); return {}; }
|
|
70
|
+
resetBeat(sessionId);
|
|
71
|
+
return { decision: 'block', reason: BEAT_HANGING_REASON };
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* (2026-08-14 可维护性行动:从 hooks.js 原样迁出,语义零改动)
|
|
4
4
|
*/
|
|
5
5
|
import { Events } from '../events.js';
|
|
6
|
+
import { noteSubagentStart, noteSubagentStop } from '../subagent-flight.js';
|
|
6
7
|
import { getQuery } from '../../runs/active-runs.js';
|
|
7
8
|
import { listWorkspaceArtifacts } from '../../../lib/artifact-target.js';
|
|
8
9
|
import { resetTurnMemory } from './turn-state-memory.js';
|
|
@@ -161,9 +162,11 @@ export function makePostCompactHandler({ ctx: _ctx, workspaceRoot: _ws, sessionI
|
|
|
161
162
|
*
|
|
162
163
|
* Phase 2 仅 emit;不注入 additionalContext(子代理刚启动还没产出,注啥都早)。
|
|
163
164
|
*/
|
|
164
|
-
export function makeSubagentStartHandler({ ctx }) {
|
|
165
|
+
export function makeSubagentStartHandler({ ctx, sessionId = null }) {
|
|
165
166
|
return async (input, _toolUseId, _options) => {
|
|
166
167
|
try {
|
|
168
|
+
// 在飞台账:ws 的 grace 闸靠它认「后台角色还在写」,别把人腰斩(subagent-flight.js)
|
|
169
|
+
noteSubagentStart(sessionId, input.agent_id, input.agent_type);
|
|
167
170
|
ctx.emit(Events.subagentStart(input.agent_id, input.agent_type));
|
|
168
171
|
} catch (err) {
|
|
169
172
|
console.warn(`[hooks/SubagentStart] handler threw:`, err.message);
|
|
@@ -186,9 +189,10 @@ export function makeSubagentStartHandler({ ctx }) {
|
|
|
186
189
|
* 只能返回通用 SyncHookJSONOutput(continue/decision/systemMessage)。
|
|
187
190
|
* 这里只 emit 不返 specific 输出,符合规范。
|
|
188
191
|
*/
|
|
189
|
-
export function makeSubagentStopHandler({ ctx }) {
|
|
192
|
+
export function makeSubagentStopHandler({ ctx, sessionId = null }) {
|
|
190
193
|
return async (input, toolUseId, _options) => {
|
|
191
194
|
try {
|
|
195
|
+
noteSubagentStop(sessionId, input.agent_id);
|
|
192
196
|
ctx.emit(Events.subagentStop(
|
|
193
197
|
input.agent_id,
|
|
194
198
|
input.agent_type,
|
|
@@ -36,6 +36,7 @@ export function makePostToolUseEditWriteTrimHandler({ ctx }) {
|
|
|
36
36
|
const trimmed = { ...resp, originalFile: null };
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
|
+
// ℹ️ 遥测事件:前端刻意不消费(截断是常态,弹提示只会变成噪音)——审计/排查用
|
|
39
40
|
ctx.emit({
|
|
40
41
|
type: 'run.tool_response_trimmed',
|
|
41
42
|
tool: input?.tool_name || 'Edit/Write',
|