@xiaobuyu/nodesign 0.0.1 → 0.0.3
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 +156 -106
- package/bin/nodesign.js +0 -0
- package/package.json +1 -1
- package/server/api/README.md +45 -45
- package/server/api/_guard.js +75 -75
- package/server/api/admin.js +187 -187
- package/server/api/assets/docx-page.js +103 -103
- package/server/api/assets/helpers.js +42 -22
- package/server/api/assets/notes.js +194 -144
- package/server/api/assets.js +891 -896
- package/server/api/board.js +134 -57
- package/server/api/browse.js +138 -138
- package/server/api/canvas.js +444 -444
- package/server/api/chatai.js +324 -324
- package/server/api/exports/build-standalone.js +980 -980
- package/server/api/exports/cards.js +118 -118
- package/server/api/exports/docx-pdf.js +46 -46
- package/server/api/exports/export-page.js +142 -142
- package/server/api/exports/handoff.js +147 -147
- package/server/api/exports.js +690 -690
- package/server/api/instruction.js +75 -75
- package/server/api/me.js +116 -116
- package/server/api/memory.js +185 -185
- package/server/api/pending-changes.js +366 -366
- package/server/api/plugins.js +164 -164
- package/server/api/projects.js +220 -220
- package/server/api/publish.js +64 -64
- package/server/api/recent.js +73 -73
- package/server/api/sessions.js +589 -589
- package/server/api/skills.js +77 -77
- package/server/api/standalone-fit.js +168 -168
- package/server/api/turn-compose.js +180 -180
- package/server/api/turn-inflight.js +44 -44
- package/server/auth/README.md +17 -17
- package/server/auth/middleware.js +138 -138
- package/server/auth/origin-guard.js +71 -71
- package/server/auth/session.js +108 -108
- package/server/auth/tier.js +113 -113
- package/server/auth/users-store.js +348 -348
- package/server/edit/README.md +29 -29
- package/server/engine/README.md +113 -113
- package/server/engine/agent/agent-shared.js +550 -550
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
- package/server/engine/agent/auto-mode-rules.js +78 -78
- package/server/engine/agent/context.js +418 -418
- package/server/engine/agent/events.js +465 -464
- package/server/engine/agent/hooks/canvas-validate.js +199 -199
- package/server/engine/agent/hooks/failure.js +120 -120
- package/server/engine/agent/hooks/file-events.js +75 -75
- package/server/engine/agent/hooks/lifecycle.js +233 -233
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
- package/server/engine/agent/hooks/post-guidance.js +206 -206
- package/server/engine/agent/hooks/post-subagent-report.js +104 -104
- package/server/engine/agent/hooks/post-trim.js +58 -58
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
- package/server/engine/agent/hooks/pre-defaults.js +84 -84
- package/server/engine/agent/hooks/pre-injectors.js +277 -277
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
- package/server/engine/agent/hooks/pre-starter-files.js +61 -61
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
- package/server/engine/agent/hooks/site-validate.js +94 -94
- package/server/engine/agent/hooks/tool-prompts.js +31 -31
- package/server/engine/agent/hooks/turn-state-memory.js +56 -56
- package/server/engine/agent/hooks/user-prompt-submit.js +258 -215
- package/server/engine/agent/hooks.js +354 -354
- package/server/engine/agent/init-contract.js +69 -69
- package/server/engine/agent/isolation.js +159 -159
- package/server/engine/agent/model-table.js +292 -292
- package/server/engine/agent/plugin-loader.js +266 -266
- package/server/engine/agent/prompts/nodesign-prelude.md +388 -336
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
- package/server/engine/agent/prompts/tools/site-reference.md +106 -106
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
- package/server/engine/agent/session-loop.js +1035 -1024
- package/server/engine/agent/session-model.js +164 -164
- package/server/engine/agent/skill.js +204 -204
- package/server/engine/agent/task-events.js +78 -78
- package/server/engine/agents/ds-extractor.md +132 -132
- package/server/engine/agents/explorer.md +190 -190
- package/server/engine/agents/index.js +230 -230
- package/server/engine/agents/schemas/design-system.json +156 -156
- package/server/engine/agents/schemas/tweak-schema.json +113 -113
- package/server/engine/agents/tweak-proposer.md +127 -127
- package/server/engine/agents/vision-checker.md +254 -254
- package/server/engine/browse/capture.js +422 -422
- package/server/engine/browse/card.js +137 -137
- package/server/engine/browse/handover.js +59 -59
- package/server/engine/browse/page-digest.js +119 -119
- package/server/engine/browse/refs.js +196 -196
- package/server/engine/browse/registry.js +307 -307
- package/server/engine/browse/screencast.js +170 -170
- package/server/engine/browse/state.js +121 -121
- package/server/engine/chatai/chat-log.js +79 -79
- package/server/engine/chatai/index.js +193 -193
- package/server/engine/chatai/openai-compat.js +152 -152
- package/server/engine/chatai/orchestrate.js +309 -309
- package/server/engine/chatai/perform.js +40 -40
- package/server/engine/chatai/summarize.js +79 -79
- package/server/engine/mcp/capability-gate.js +52 -52
- package/server/engine/mcp/index.js +351 -339
- package/server/engine/mcp/param-sanitizer.js +142 -142
- package/server/engine/mcp/tools/arrange-on-board.js +124 -124
- package/server/engine/mcp/tools/artifact-session.js +278 -278
- package/server/engine/mcp/tools/browse-computer.js +421 -421
- package/server/engine/mcp/tools/browse-find-batch.js +183 -183
- package/server/engine/mcp/tools/browse-screenshot.js +157 -157
- package/server/engine/mcp/tools/browse.js +480 -480
- package/server/engine/mcp/tools/build-docx.js +101 -101
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
- package/server/engine/mcp/tools/create-on-board.js +152 -155
- package/server/engine/mcp/tools/crystallize-skill.js +168 -168
- package/server/engine/mcp/tools/edit-sketch.js +167 -0
- package/server/engine/mcp/tools/explain-style.js +237 -237
- package/server/engine/mcp/tools/export-handoff.js +133 -133
- package/server/engine/mcp/tools/expose-tweaks.js +149 -149
- package/server/engine/mcp/tools/generate-image.js +842 -842
- package/server/engine/mcp/tools/get-computed-styles.js +164 -164
- package/server/engine/mcp/tools/get-pending-changes.js +205 -205
- package/server/engine/mcp/tools/h3box-ssh.js +84 -84
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
- package/server/engine/mcp/tools/highlight.js +68 -68
- package/server/engine/mcp/tools/list-pages.js +222 -222
- package/server/engine/mcp/tools/look-at-board.js +124 -0
- package/server/engine/mcp/tools/lookup-tags.js +59 -59
- package/server/engine/mcp/tools/navigate-to-page.js +60 -60
- package/server/engine/mcp/tools/organize-board.js +64 -64
- package/server/engine/mcp/tools/paint-still.js +434 -434
- package/server/engine/mcp/tools/pin-to-board.js +130 -130
- package/server/engine/mcp/tools/preview-deck.js +130 -130
- package/server/engine/mcp/tools/profile-scroll.js +305 -305
- package/server/engine/mcp/tools/publish-site.js +102 -102
- package/server/engine/mcp/tools/query-elements.js +195 -195
- package/server/engine/mcp/tools/read-board.js +190 -103
- package/server/engine/mcp/tools/read-document.js +93 -93
- package/server/engine/mcp/tools/read-page.js +264 -264
- package/server/engine/mcp/tools/read-tavern-json.js +143 -143
- package/server/engine/mcp/tools/read-user-view.js +59 -0
- package/server/engine/mcp/tools/record-decision.js +140 -140
- package/server/engine/mcp/tools/relate-on-board.js +112 -110
- package/server/engine/mcp/tools/report-issue.js +115 -115
- package/server/engine/mcp/tools/roll-film.js +266 -266
- package/server/engine/mcp/tools/screenshot-docx.js +138 -138
- package/server/engine/mcp/tools/screenshot-url.js +195 -195
- package/server/engine/mcp/tools/screenshot.js +583 -583
- package/server/engine/mcp/tools/sketch-on-board.js +337 -0
- package/server/engine/mcp/tools/tier-gate.js +96 -96
- package/server/engine/mcp/tools/trace-motion.js +215 -215
- package/server/engine/mcp/tools/web-search.js +548 -548
- package/server/engine/mcp/tools/write-on-board.js +130 -0
- package/server/engine/motion/inventory.js +349 -349
- package/server/engine/perception/session.js +235 -235
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +199 -192
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +169 -162
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +235 -228
- 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 +78 -78
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +330 -323
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
- package/server/engine/runs/active-runs.js +698 -698
- package/server/engine/runs/board-tasklist.js +130 -0
- package/server/engine/runs/live-turn.js +252 -252
- package/server/engine/runs/store.js +364 -364
- package/server/engine/runs/turn-relay.js +217 -217
- package/server/engine/runtime/workspace.js +164 -164
- package/server/index.js +3 -0
- package/server/lib/artifact-file-path.js +80 -80
- package/server/lib/artifact-target.js +284 -284
- package/server/lib/asset-refs.js +239 -239
- package/server/lib/async-queue.js +104 -104
- package/server/lib/auto-relations.js +130 -130
- package/server/lib/binding-types.js +72 -60
- package/server/lib/board-graph-export.js +191 -0
- package/server/lib/board-groups.js +167 -0
- package/server/lib/board-hero.js +76 -0
- package/server/lib/board-kind-sizes.js +85 -62
- package/server/lib/board-relations.js +133 -133
- package/server/lib/browse-proxy.js +180 -180
- package/server/lib/canvas-id.js +36 -36
- package/server/lib/chalk.js +121 -0
- package/server/lib/cover.js +227 -227
- package/server/lib/danbooru-tags.js +291 -291
- package/server/lib/doc-extract.js +156 -156
- package/server/lib/docx/build-from-source.js +260 -260
- package/server/lib/docx/build.js +490 -490
- package/server/lib/docx/dump-styles.js +287 -287
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
- package/server/lib/docx/merge-runs.js +129 -129
- package/server/lib/docx/numbering.js +218 -218
- package/server/lib/docx/order.js +152 -152
- package/server/lib/docx/rawzip.js +172 -172
- package/server/lib/docx/render.js +92 -92
- package/server/lib/docx/text-lint.js +225 -225
- package/server/lib/docx/tokens.js +335 -335
- package/server/lib/docx/units.js +29 -29
- package/server/lib/docx/xml.js +291 -291
- package/server/lib/docx-pages.js +187 -187
- package/server/lib/export-collect.js +289 -289
- package/server/lib/export-package.js +210 -210
- package/server/lib/html-srcset.js +145 -145
- package/server/lib/image-variant.js +460 -460
- package/server/lib/ingress/forward-openai-chat.js +305 -305
- package/server/lib/ingress/openai-chat.js +491 -491
- package/server/lib/ingress/session-notice.js +66 -66
- package/server/lib/ingress/session-routes.js +68 -68
- package/server/lib/ingress/slot-probe.js +130 -130
- package/server/lib/ingress/upstream-billing.js +52 -52
- package/server/lib/ingress/upstream-fail-streak.js +72 -72
- package/server/lib/ingress/upstream-truncation.js +48 -48
- package/server/lib/issues-store.js +182 -182
- package/server/lib/kinds/deck.js +72 -72
- package/server/lib/kinds/docx.js +283 -283
- package/server/lib/kinds/file-kinds.js +113 -113
- package/server/lib/kinds/index.js +202 -202
- package/server/lib/kinds/site.js +161 -161
- package/server/lib/model-ingress.js +543 -543
- package/server/lib/moderation.js +255 -255
- package/server/lib/notice-store.js +103 -103
- package/server/lib/plugin-install.js +104 -104
- package/server/lib/plugin-validator.js +721 -721
- package/server/lib/publish-store.js +115 -115
- package/server/lib/quick-summary.js +52 -52
- package/server/lib/quota.js +270 -270
- package/server/lib/rate-window.js +29 -29
- package/server/lib/reference-assets.js +92 -92
- package/server/lib/region-shot.js +135 -135
- package/server/lib/safe-path.js +73 -73
- package/server/lib/sdk-session.js +33 -33
- package/server/lib/showcase-store.js +92 -92
- package/server/lib/site-publish.js +465 -465
- package/server/lib/sketch-layout.js +277 -0
- package/server/lib/ssrf-guard.js +432 -432
- package/server/lib/task-scan.js +158 -158
- package/server/lib/tavern-json.js +154 -154
- package/server/lib/video-variant.js +237 -237
- package/server/lib/workspace-path.js +33 -33
- package/server/ops/fix-sdk-musl.mjs +40 -40
- package/server/ops/install-macos-fonts.sh +114 -114
- package/server/ops/macos-fonts.conf +336 -336
- package/server/ops/sandbox-shim/bwrap +57 -57
- package/server/projects/assets-summary.js +119 -119
- package/server/projects/auto-name.js +53 -53
- package/server/projects/board-limits.js +12 -0
- package/server/projects/board-sanitize.js +252 -0
- package/server/projects/board-store.js +507 -640
- package/server/projects/move-entry.js +103 -103
- package/server/projects/store.js +270 -270
- package/server/projects/ui-config.js +60 -54
- package/server/projects/viewpoint-store.js +78 -0
- package/server/projects/workspace-templates.js +53 -53
- package/server/projects/workspace.js +1025 -1025
- package/server/runtime/local-config.js +177 -177
- package/server/runtime/profile.js +72 -72
- package/server/services/rembg-service.py +334 -334
- package/server/shared/README.md +25 -25
- package/server/shared/deck.js +54 -54
- package/server/shared/time.js +49 -49
- package/server/style-pipeline/README.md +53 -53
- package/server/ws/broker.js +39 -36
- package/server/ws/browse-channel.js +177 -177
- package/server/ws/index.js +386 -386
- package/web/README.md +89 -89
- package/web/dist/assets/BrowserWindow-P_vIdh-G.js +11 -0
- package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-BpLv5082.js} +3 -3
- package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-DSAU1-tD.js} +4 -4
- package/web/dist/assets/MermaidBlock-DR1GikCv.js +313 -0
- package/web/dist/assets/SiteWindow-92uBuRRR.js +18 -0
- package/web/dist/assets/abnfDiagram-VCTEODGH-uA8Esix4.js +1 -0
- package/web/dist/assets/arc-D_khH_xN.js +1 -0
- package/web/dist/assets/architectureDiagram-5GKGNRK7-DydJHqOt.js +36 -0
- package/web/dist/assets/blockDiagram-NRAW4CY4-B7wBbKiW.js +129 -0
- package/web/dist/assets/c4Diagram-UCG6FXSJ-6mrH0x7Y.js +38 -0
- package/web/dist/assets/channel-DGkccMO1.js +1 -0
- package/web/dist/assets/chunk-2Q5K7J3B-Cuikho5Q.js +1 -0
- package/web/dist/assets/chunk-5VM5RSS4-3-KZ5AUz.js +15 -0
- package/web/dist/assets/chunk-F27PBJKO-Bkwq2gGU.js +1 -0
- package/web/dist/assets/chunk-G27WJ6UU-BLyQzdGE.js +231 -0
- package/web/dist/assets/chunk-JWPE2WC7-B7HxxQds.js +1 -0
- package/web/dist/assets/chunk-LCL6LL3I-CtXrnUwF.js +206 -0
- package/web/dist/assets/chunk-POPQ4Y6H-dxddZlXt.js +1 -0
- package/web/dist/assets/chunk-SVP7TREG-DpkmrgHn.js +88 -0
- package/web/dist/assets/chunk-XXDRQBXY-BwZ7RbpQ.js +1 -0
- package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +1 -0
- package/web/dist/assets/cose-bilkent-JH36ORCC-DGFOsMUt.js +1 -0
- package/web/dist/assets/cynefin-OW5HDTMX-DUwn3ehS.js +178 -0
- package/web/dist/assets/cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js +62 -0
- package/web/dist/assets/cytoscape.esm-BjAbmLdb.js +331 -0
- package/web/dist/assets/dagre-3AP2YEHR-CyourLfz.js +4 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/diagram-S7CK7UJ4-BTMbx2np.js +30 -0
- package/web/dist/assets/diagram-UQ7AKVKN-UK94_1-P.js +41 -0
- package/web/dist/assets/diagram-VSXAHHWV-C7zS-v9v.js +3 -0
- package/web/dist/assets/diagram-VX7I27RA-DKJRa6CK.js +24 -0
- package/web/dist/assets/diagram-Z3DM3KII-kUbAfGAp.js +24 -0
- package/web/dist/assets/ebnfDiagram-PWID7BFC-CVW16_vb.js +1 -0
- package/web/dist/assets/erDiagram-SSCWMZ5O-BRQMFQ5y.js +99 -0
- package/web/dist/assets/flowDiagram-A5DVABFB-S34LpLtK.js +168 -0
- package/web/dist/assets/ganttDiagram-EL5Y4UJY-BFiqk1-G.js +292 -0
- package/web/dist/assets/gitGraphDiagram-WWUBYQGX-LZsrdyyv.js +106 -0
- package/web/dist/assets/index-GAamh1p4.js +2152 -0
- package/web/dist/assets/infoDiagram-RXCK75RN-0DvN6KIp.js +2 -0
- package/web/dist/assets/init-Gi6I4Gst.js +1 -0
- package/web/dist/assets/ishikawaDiagram-5VMMS53U-CaY3A-Bx.js +70 -0
- package/web/dist/assets/journeyDiagram-EYS64GPL-BBeiaCrj.js +139 -0
- package/web/dist/assets/kanban-definition-3QL26DDD-ux42DRXQ.js +89 -0
- package/web/dist/assets/layout-CnXiF_NB.js +1 -0
- package/web/dist/assets/linear-BMW0u6G6.js +1 -0
- package/web/dist/assets/mindmap-definition-FBJOCRG2-hUxSpdKn.js +96 -0
- package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/web/dist/assets/pegDiagram-XKGWAZYB-aCaQPH0w.js +1 -0
- package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-AcdOQ9Ij.js} +6 -6
- package/web/dist/assets/pieDiagram-E7YTZNPT-CmOEZ8zg.js +39 -0
- package/web/dist/assets/quadrantDiagram-AXDQQJYC-VW6fAhsV.js +7 -0
- package/web/dist/assets/railroadDiagram-O6MQD6OU-CKJ7Ba5K.js +1 -0
- package/web/dist/assets/requirementDiagram-EFPCY7ZU-pNGkq9Vw.js +84 -0
- package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-KAfChbGV.js} +1 -1
- package/web/dist/assets/sankeyDiagram-P5KCCOFB-BIMmDTfZ.js +40 -0
- package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BBW7wzKY.js +162 -0
- package/web/dist/assets/sizeCapture-X5ZJPWSS-DdkXmLYR.js +1 -0
- package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-CDUlhUP7.js} +1 -1
- package/web/dist/assets/stateDiagram-HBIQ2CUA-W313Uz16.js +1 -0
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +1 -0
- package/web/dist/assets/swimlanes-XN3QIQJK-C29TL26K.js +1 -0
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +8 -0
- package/web/dist/assets/timeline-definition-24CTP7MA-CF0JMObX.js +120 -0
- package/web/dist/assets/vennDiagram-4TSXK5OY-DrDFsaH3.js +34 -0
- package/web/dist/assets/wardleyDiagram-VM6X3IG4-CVrnRIS_.js +78 -0
- package/web/dist/assets/xychartDiagram-S5SC5T6Z-BvkN1zsT.js +7 -0
- package/web/dist/index.html +17 -17
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
- package/web/dist/assets/SiteWindow-D6eS-2gM.js +0 -18
- package/web/dist/assets/index-Cbl3ATvp.js +0 -2125
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* server/engine/agent/init-contract.js — 开局契约自检(2026-08-20 从 session-loop.js
|
|
3
|
-
* 拆出,行数棘轮;逻辑原样)。session-loop 在每条 system:init 到达时调一次。
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { recordIssue, signatureOf } from '../../lib/issues-store.js';
|
|
7
|
-
|
|
8
|
-
// ── 开局契约自检(2026-08-14 空壳钩子灭门案第 3 层,真正治本)──
|
|
9
|
-
//
|
|
10
|
-
// 背景:一个 `{ matcher: 'Bash' }` 空壳钩子条目让 SDK initialize 的大 try 吞掉
|
|
11
|
-
// TypeError → 全部程序化钩子 + 全部 in-process MCP server 无声蒸发,mcp_servers
|
|
12
|
-
// 里连 failed 都不留,会话照常跑 —— 潜伏六天。能静默这么久的结构性原因是这里
|
|
13
|
-
// 从来不消费 system:init:SDK 开局就把「会话里实际有哪些 server / 工具」告诉了
|
|
14
|
-
// 我们,但没人看。
|
|
15
|
-
//
|
|
16
|
-
// 现在对账:nodesign 必须 connected,且 server 实例声明的每个工具(探针实测
|
|
17
|
-
// deferred 工具也在 init.tools 里,27/27)都必须出现。不满足 → recordIssue 进
|
|
18
|
-
// 自动层 + throw 杀会话(外层 catch 走真错路径:markRunFailed + run.error 前端
|
|
19
|
-
// 显式可见)。已知代价:SDK 改 init 形状会误杀会话 —— 但误杀 5 分钟定位,
|
|
20
|
-
// 静默降级是 6 天暗账;工具残废的会话产出是负价值还烧钱,杀掉比放行仁慈。
|
|
21
|
-
export function assertInitContract(init, { sessionId, projectId, isResume, initialPermissionMode, platform, sdkModel, nodesignServer }) {
|
|
22
|
-
const problems = [];
|
|
23
|
-
const nd = (init.mcp_servers || []).find((s) => s.name === 'nodesign');
|
|
24
|
-
if (!nd) {
|
|
25
|
-
problems.push('mcp_servers 里没有 nodesign(in-process MCP server 蒸发,连 failed 状态都不留)');
|
|
26
|
-
} else if (nd.status !== 'connected') {
|
|
27
|
-
problems.push(`nodesign server status=${nd.status}(预期 connected)`);
|
|
28
|
-
}
|
|
29
|
-
const registered = new Set(init.tools || []);
|
|
30
|
-
const expected = (nodesignServer.toolNames || []).map((n) => `mcp__nodesign__${n}`);
|
|
31
|
-
const missing = expected.filter((n) => !registered.has(n));
|
|
32
|
-
if (missing.length) {
|
|
33
|
-
problems.push(`nodesign 工具缺 ${missing.length}/${expected.length}:${missing.join(', ')}`);
|
|
34
|
-
}
|
|
35
|
-
// 权限模式对账(2026-08-15):**要的和拿到的可能不一样,而且是静默的**。
|
|
36
|
-
// 实测:会话模型是 haiku 时 `permissionMode:'auto'` 会被无声降级成 'default',
|
|
37
|
-
// init 里照报 default,没有任何报错 —— 分类器一次都不跑,我们却以为它在把关。
|
|
38
|
-
// 只警告不杀:降级后的会话还能干活,杀掉代价大于收益;但必须在日志里喊出来。
|
|
39
|
-
const wantMode = isResume ? null : (initialPermissionMode === 'plan' ? 'plan' : platform.permissionModeDefault);
|
|
40
|
-
if (wantMode && init.permissionMode && init.permissionMode !== wantMode) {
|
|
41
|
-
console.warn(
|
|
42
|
-
`[session-loop] ⚠️ sid=${sessionId.slice(0, 8)} 权限模式被降级:要 ${wantMode},`
|
|
43
|
-
+ `实际 ${init.permissionMode}(模型 ${sdkModel} 可能不支持该模式)`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
if (!problems.length) {
|
|
47
|
-
console.info(
|
|
48
|
-
`[session-loop] sid=${sessionId.slice(0, 8)} init 契约自检 ✓ `
|
|
49
|
-
+ `(nodesign connected, ${expected.length}/${expected.length} tools, `
|
|
50
|
-
+ `mode=${init.permissionMode ?? '未报'})`,
|
|
51
|
-
);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const detail = problems.join(';');
|
|
55
|
-
// 自动层留案底(fail-soft:记录本身不能变成新故障源)。signature 只含缺失
|
|
56
|
-
// 集合不含 sessionId —— 同一种蒸发聚成一行计数。
|
|
57
|
-
try {
|
|
58
|
-
recordIssue({
|
|
59
|
-
source: 'auto',
|
|
60
|
-
toolName: 'session_init_contract',
|
|
61
|
-
summary: `开局契约自检失败:${detail.slice(0, 120)}`,
|
|
62
|
-
detail,
|
|
63
|
-
projectId,
|
|
64
|
-
sessionId,
|
|
65
|
-
signature: signatureOf(`session_init_contract|${missing.join(',')}|${nd ? nd.status : 'absent'}`),
|
|
66
|
-
});
|
|
67
|
-
} catch { /* ignore */ }
|
|
68
|
-
throw new Error(`开局契约自检失败(杀会话):${detail}`);
|
|
69
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* server/engine/agent/init-contract.js — 开局契约自检(2026-08-20 从 session-loop.js
|
|
3
|
+
* 拆出,行数棘轮;逻辑原样)。session-loop 在每条 system:init 到达时调一次。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { recordIssue, signatureOf } from '../../lib/issues-store.js';
|
|
7
|
+
|
|
8
|
+
// ── 开局契约自检(2026-08-14 空壳钩子灭门案第 3 层,真正治本)──
|
|
9
|
+
//
|
|
10
|
+
// 背景:一个 `{ matcher: 'Bash' }` 空壳钩子条目让 SDK initialize 的大 try 吞掉
|
|
11
|
+
// TypeError → 全部程序化钩子 + 全部 in-process MCP server 无声蒸发,mcp_servers
|
|
12
|
+
// 里连 failed 都不留,会话照常跑 —— 潜伏六天。能静默这么久的结构性原因是这里
|
|
13
|
+
// 从来不消费 system:init:SDK 开局就把「会话里实际有哪些 server / 工具」告诉了
|
|
14
|
+
// 我们,但没人看。
|
|
15
|
+
//
|
|
16
|
+
// 现在对账:nodesign 必须 connected,且 server 实例声明的每个工具(探针实测
|
|
17
|
+
// deferred 工具也在 init.tools 里,27/27)都必须出现。不满足 → recordIssue 进
|
|
18
|
+
// 自动层 + throw 杀会话(外层 catch 走真错路径:markRunFailed + run.error 前端
|
|
19
|
+
// 显式可见)。已知代价:SDK 改 init 形状会误杀会话 —— 但误杀 5 分钟定位,
|
|
20
|
+
// 静默降级是 6 天暗账;工具残废的会话产出是负价值还烧钱,杀掉比放行仁慈。
|
|
21
|
+
export function assertInitContract(init, { sessionId, projectId, isResume, initialPermissionMode, platform, sdkModel, nodesignServer }) {
|
|
22
|
+
const problems = [];
|
|
23
|
+
const nd = (init.mcp_servers || []).find((s) => s.name === 'nodesign');
|
|
24
|
+
if (!nd) {
|
|
25
|
+
problems.push('mcp_servers 里没有 nodesign(in-process MCP server 蒸发,连 failed 状态都不留)');
|
|
26
|
+
} else if (nd.status !== 'connected') {
|
|
27
|
+
problems.push(`nodesign server status=${nd.status}(预期 connected)`);
|
|
28
|
+
}
|
|
29
|
+
const registered = new Set(init.tools || []);
|
|
30
|
+
const expected = (nodesignServer.toolNames || []).map((n) => `mcp__nodesign__${n}`);
|
|
31
|
+
const missing = expected.filter((n) => !registered.has(n));
|
|
32
|
+
if (missing.length) {
|
|
33
|
+
problems.push(`nodesign 工具缺 ${missing.length}/${expected.length}:${missing.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
// 权限模式对账(2026-08-15):**要的和拿到的可能不一样,而且是静默的**。
|
|
36
|
+
// 实测:会话模型是 haiku 时 `permissionMode:'auto'` 会被无声降级成 'default',
|
|
37
|
+
// init 里照报 default,没有任何报错 —— 分类器一次都不跑,我们却以为它在把关。
|
|
38
|
+
// 只警告不杀:降级后的会话还能干活,杀掉代价大于收益;但必须在日志里喊出来。
|
|
39
|
+
const wantMode = isResume ? null : (initialPermissionMode === 'plan' ? 'plan' : platform.permissionModeDefault);
|
|
40
|
+
if (wantMode && init.permissionMode && init.permissionMode !== wantMode) {
|
|
41
|
+
console.warn(
|
|
42
|
+
`[session-loop] ⚠️ sid=${sessionId.slice(0, 8)} 权限模式被降级:要 ${wantMode},`
|
|
43
|
+
+ `实际 ${init.permissionMode}(模型 ${sdkModel} 可能不支持该模式)`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (!problems.length) {
|
|
47
|
+
console.info(
|
|
48
|
+
`[session-loop] sid=${sessionId.slice(0, 8)} init 契约自检 ✓ `
|
|
49
|
+
+ `(nodesign connected, ${expected.length}/${expected.length} tools, `
|
|
50
|
+
+ `mode=${init.permissionMode ?? '未报'})`,
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const detail = problems.join(';');
|
|
55
|
+
// 自动层留案底(fail-soft:记录本身不能变成新故障源)。signature 只含缺失
|
|
56
|
+
// 集合不含 sessionId —— 同一种蒸发聚成一行计数。
|
|
57
|
+
try {
|
|
58
|
+
recordIssue({
|
|
59
|
+
source: 'auto',
|
|
60
|
+
toolName: 'session_init_contract',
|
|
61
|
+
summary: `开局契约自检失败:${detail.slice(0, 120)}`,
|
|
62
|
+
detail,
|
|
63
|
+
projectId,
|
|
64
|
+
sessionId,
|
|
65
|
+
signature: signatureOf(`session_init_contract|${missing.join(',')}|${nd ? nd.status : 'absent'}`),
|
|
66
|
+
});
|
|
67
|
+
} catch { /* ignore */ }
|
|
68
|
+
throw new Error(`开局契约自检失败(杀会话):${detail}`);
|
|
69
|
+
}
|
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agent/isolation.js —— 会话隔离配置的单一来源(2026-08-15)
|
|
3
|
-
*
|
|
4
|
-
* 一件事被拆成两道闸,因为 SDK 的工具分两拨跑:
|
|
5
|
-
*
|
|
6
|
-
* ① sandbox → **只管 Bash**。bwrap 起独立 mount/net namespace:写只落工作区、
|
|
7
|
-
* 凭据读不到、env 里的 key 被 unset、AF_UNIX 与回环被切断、
|
|
8
|
-
* 外网走代理。开关在 runtime/platform.js(NODESIGN_SANDBOX=on)。
|
|
9
|
-
* ② settings.permissions.deny → 管 **Read / Grep / Glob / Write / Edit**。
|
|
10
|
-
* 这些是 SDK 进程内工具,根本不进 bwrap,沙盒对它们零作用。
|
|
11
|
-
*
|
|
12
|
-
* 缺一半等于没关。第三块(跨项目边界)在 hooks/pre-workspace-scope-guard.js ——
|
|
13
|
-
* deny 规则写不出"除了自己这个项目"(deny 压过 allow,项目又是动态新建的)。
|
|
14
|
-
*
|
|
15
|
-
* 08-15 开这套时真跑出来的四件事,改之前先读:
|
|
16
|
-
* 1. `dangerouslyDisableSandbox` 默认允许,agent 撞到偶发失败会自己拿它关沙盒
|
|
17
|
-
* —— 必须 allowUnsandboxedCommands:false 焊死。
|
|
18
|
-
* 2. deny 规则的路径必须是**双斜杠**绝对形式,单斜杠静默失效。
|
|
19
|
-
* 3. npm 默认缓存在 ~/.npm,沙盒里 HOME 不可写 → `npm i` EROFS,构建道整条断。
|
|
20
|
-
* 4. 目录级 denyRead 拦得住读文件,拦不住 `ls` 看文件名(接受:名字不是秘密)。
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import path from 'node:path';
|
|
24
|
-
import os from 'node:os';
|
|
25
|
-
import fs from 'node:fs/promises';
|
|
26
|
-
import { platform } from '../../runtime/platform.js';
|
|
27
|
-
import { autoModeSettings } from './auto-mode-rules.js';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 会话要用的缓存/临时目录一次备齐(2026-08-19 从 session-loop 下沉)。
|
|
31
|
-
*
|
|
32
|
-
* - npmCacheDir:共用 npm 缓存(数据根下;内容寻址 + 完整性校验,跨会话共用
|
|
33
|
-
* 不构成投毒面)。沙盒里 HOME 不可写,不给它 `npm i` 直接 EROFS。
|
|
34
|
-
* - agentTmpDir:沙盒里 Bash 的 $TMPDIR。SDK 按 CLAUDE_CODE_TMPDIR 派生沙盒
|
|
35
|
-
* tmp(没设就 os.tmpdir()),而派生出来的 /tmp/claude 在 bwrap 里只读 ——
|
|
36
|
-
* agent 装个 py7zr 连撞四堵墙、venv 静默失败(iss_msz25m5p_v5so)。
|
|
37
|
-
* ⚠️ 必须短:SDK 二进制里有条 warn —— 这个路径超 ~30 字节时 AF_UNIX socket
|
|
38
|
-
* 放不下,子进程 $TMPDIR **静默回退**默认值(= 白配)。所以挂 /tmp/nd 下、
|
|
39
|
-
* 按项目分目录;沙盒里遮兄弟目录见 buildIsolationOptions。
|
|
40
|
-
* - pip 缓存指进 agentTmpDir/pip,不然每条 pip 命令先吐一段
|
|
41
|
-
* ~/.cache/pip 不可写的 WARNING 噪音。
|
|
42
|
-
*
|
|
43
|
-
* 目录必须先存在 —— bwrap 绑一个不存在的路径会起不来;建不出来也不拦会话。
|
|
44
|
-
* envPatch 直接摊进 sdkEnv。
|
|
45
|
-
*/
|
|
46
|
-
export async function prepareAgentDirs({ dataRoot, projectId, sessionId }) {
|
|
47
|
-
const npmCacheDir = path.join(dataRoot, '.npm-cache');
|
|
48
|
-
const agentTmpRoot = path.join(os.tmpdir(), 'nd');
|
|
49
|
-
const agentTmpDir = path.join(agentTmpRoot, projectId || String(sessionId || 'anon').slice(0, 12));
|
|
50
|
-
for (const d of [npmCacheDir, path.join(agentTmpDir, 'pip')]) {
|
|
51
|
-
try { await fs.mkdir(d, { recursive: true }); } catch { /* 起不来也不该拦会话 */ }
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
npmCacheDir,
|
|
55
|
-
agentTmpRoot,
|
|
56
|
-
agentTmpDir,
|
|
57
|
-
envPatch: {
|
|
58
|
-
npm_config_cache: npmCacheDir,
|
|
59
|
-
CLAUDE_CODE_TMPDIR: agentTmpDir,
|
|
60
|
-
PIP_CACHE_DIR: path.join(agentTmpDir, 'pip'),
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* bwrap 垫片的 env(PATH 前插一个目录,里面的 `bwrap` 是我们的包装脚本)
|
|
67
|
-
*
|
|
68
|
-
* 治的是 `apply-seccomp: unshare(CLONE_NEWUSER): Invalid argument` 这个偶发
|
|
69
|
-
* —— 内核级竞态,机器越闲越容易撞,实测每十几次 Bash 调用炸一次,而且会把
|
|
70
|
-
* agent 带进错误推断(有一次它干脆自己拿 `dangerouslyDisableSandbox` 绕过去)。
|
|
71
|
-
* 原理与安全不变量写在 server/ops/sandbox-shim/bwrap 里。
|
|
72
|
-
*
|
|
73
|
-
* 📮 上游 issue:https://github.com/anthropics/claude-code/issues/86928
|
|
74
|
-
* ⏳ **临时设施**:垫片认的是 SDK 内部的命令前缀,**SDK 一升级前缀变了就静默
|
|
75
|
-
* 失效**(原样透传 = 悄悄退回那个偶发,不报错)。升级后查
|
|
76
|
-
* `<数据根>/.sandbox-shim.log` 里 `rewrote=1` 的计数还在不在涨;
|
|
77
|
-
* 上游修好之后连这个函数带 ops/sandbox-shim/ 一起删掉。
|
|
78
|
-
* 关掉:`NODESIGN_SANDBOX_SHIM=off`。
|
|
79
|
-
*/
|
|
80
|
-
export function sandboxShimEnv({ baseEnv = process.env, dataRoot } = {}) {
|
|
81
|
-
if (!platform.sandboxEnabled) return {};
|
|
82
|
-
if (process.env.NODESIGN_SANDBOX_SHIM === 'off') return {};
|
|
83
|
-
const dir = path.join(platform.repoRoot, 'server', 'ops', 'sandbox-shim');
|
|
84
|
-
return {
|
|
85
|
-
PATH: `${dir}${path.delimiter}${baseEnv.PATH || ''}`,
|
|
86
|
-
// 改写次数记一笔:SDK 升级后 pattern 变了会静默退回现状,靠这个计数发现
|
|
87
|
-
...(dataRoot ? { NODESIGN_SHIM_LOG: path.join(dataRoot, '.sandbox-shim.log') } : {}),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @param {object} o
|
|
93
|
-
* @param {string} o.cwdRoot 会话工作区(= sharedRoot,扁平化之后同一个目录)
|
|
94
|
-
* @param {string} o.sharedRoot 项目共享根
|
|
95
|
-
* @param {string} o.npmCacheDir 共用 npm 缓存(在数据根下,必须可写可读)
|
|
96
|
-
* @param {string} [o.agentTmpRoot] 所有项目沙盒 tmp 的根(/tmp/nd)—— 整体遮读
|
|
97
|
-
* @param {string} [o.agentTmpDir] 本项目的沙盒 tmp(CLAUDE_CODE_TMPDIR 指它)——
|
|
98
|
-
* 开可写可读天窗。两个一起传或都不传。
|
|
99
|
-
* @param {string} o.dataRoot PROJECTS_DATA_ROOT
|
|
100
|
-
* @param {object} o.env 传给 SDK 的 env(凭据抹除按它的键名算)
|
|
101
|
-
* @returns {{ sandbox: object, settings: object }} 直接摊进 query options
|
|
102
|
-
*/
|
|
103
|
-
export function buildIsolationOptions({ cwdRoot, sharedRoot, npmCacheDir, agentTmpRoot, agentTmpDir, dataRoot, env }) {
|
|
104
|
-
return {
|
|
105
|
-
sandbox: {
|
|
106
|
-
enabled: platform.sandboxEnabled,
|
|
107
|
-
failIfUnavailable: false,
|
|
108
|
-
// ⭐ 逃生门焊死:SDK 给 Bash 留了 `dangerouslyDisableSandbox` 参数,**默认允许**。
|
|
109
|
-
// 08-15 开沙盒当天就被 agent 自发用上了 —— 它撞到 apply-seccomp 偶发失败,
|
|
110
|
-
// 第三次自己带上这个参数关掉沙盒,然后读到了隔壁项目的文件(它老实汇报了,
|
|
111
|
-
// 所以我们看见了)。一个能被工具参数关掉的沙盒等于没有沙盒。
|
|
112
|
-
allowUnsandboxedCommands: false,
|
|
113
|
-
network: {
|
|
114
|
-
allowLocalBinding: false,
|
|
115
|
-
// 全域允许:这层留着不是为了管出口,是为了 Linux 上顺带切断 unix socket
|
|
116
|
-
// —— pm2 的 rpc.sock 就在家目录里,通了的话 `pm2 start` 能起一个沙盒外的
|
|
117
|
-
// 进程(完整逃逸)。只读挂载拦不住 socket 连接,实测过。
|
|
118
|
-
allowedDomains: ['*'],
|
|
119
|
-
},
|
|
120
|
-
filesystem: {
|
|
121
|
-
allowWrite: [
|
|
122
|
-
cwdRoot,
|
|
123
|
-
...(sharedRoot ? [
|
|
124
|
-
path.join(sharedRoot, '.claude', 'agent-memory'),
|
|
125
|
-
path.join(sharedRoot, 'assets'),
|
|
126
|
-
] : []),
|
|
127
|
-
npmCacheDir,
|
|
128
|
-
// 本项目的沙盒 tmp(CLAUDE_CODE_TMPDIR / $TMPDIR / pip 缓存都指这里)。
|
|
129
|
-
// 没有它 Bash 的 tmp 只读:venv 静默失败、pip --user EROFS、npm 装不上
|
|
130
|
-
...(agentTmpDir ? [agentTmpDir] : []),
|
|
131
|
-
],
|
|
132
|
-
denyWrite: ['/etc', '/usr', '/bin', '/sbin', '/private/etc'],
|
|
133
|
-
// 数据根整个盖住、再用 allowRead 给自己的工作区开天窗。
|
|
134
|
-
// ⚠️ 口径要准(08-18 上生产时实测纠正):`ls 数据根` **看得见别的项目的
|
|
135
|
-
// 目录名**(沙盒里实测列出了另一个 pid),拦住的是**进去读**——
|
|
136
|
-
// `cat <别人的>/board.json` 与 `ls <别人的>/` 都空。
|
|
137
|
-
// 原注释写的"连 ls 数据根都只看得见自己那一个条目"过强,是错的;
|
|
138
|
-
// platform.js 那边的说法才对:「目录级 denyRead 拦得住 cat,拦不住 ls
|
|
139
|
-
// 看文件名 —— 文件名不是秘密,接受」。
|
|
140
|
-
// agentTmpRoot(/tmp/nd)同 dataRoot 一个待遇:根整个遮住、自己的子目录
|
|
141
|
-
// 开天窗 —— 不遮的话 /tmp/nd/<别的项目>/ 就是跨项目读通道
|
|
142
|
-
denyRead: [...platform.credentialBlacklist(), dataRoot, ...(agentTmpRoot ? [agentTmpRoot] : [])],
|
|
143
|
-
allowRead: [cwdRoot, npmCacheDir, ...(agentTmpDir ? [agentTmpDir] : [])],
|
|
144
|
-
},
|
|
145
|
-
credentials: {
|
|
146
|
-
// filesystem 那层拦得住 `cat .env`,拦不住 `env` —— 服务端 process.env
|
|
147
|
-
// 原样继承给了 Bash 子进程,key 就明晃晃躺着。按名字在沙盒内 unset。
|
|
148
|
-
envVars: platform.secretEnvVarNames(env).map(name => ({ name, mode: 'deny' })),
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
settings: {
|
|
152
|
-
permissions: { deny: platform.protectedPathRules({ dataRoot }) },
|
|
153
|
-
// auto 模式的分类器规则(只在开了 auto 时注入,省得白占 settings)。
|
|
154
|
-
// ⚠️ 按节替换不是追加 —— 为什么只覆盖 environment / hard_deny 两节,
|
|
155
|
-
// 见 auto-mode-rules.js 文件头。
|
|
156
|
-
...(platform.autoModeEnabled ? { autoMode: autoModeSettings() } : {}),
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* agent/isolation.js —— 会话隔离配置的单一来源(2026-08-15)
|
|
3
|
+
*
|
|
4
|
+
* 一件事被拆成两道闸,因为 SDK 的工具分两拨跑:
|
|
5
|
+
*
|
|
6
|
+
* ① sandbox → **只管 Bash**。bwrap 起独立 mount/net namespace:写只落工作区、
|
|
7
|
+
* 凭据读不到、env 里的 key 被 unset、AF_UNIX 与回环被切断、
|
|
8
|
+
* 外网走代理。开关在 runtime/platform.js(NODESIGN_SANDBOX=on)。
|
|
9
|
+
* ② settings.permissions.deny → 管 **Read / Grep / Glob / Write / Edit**。
|
|
10
|
+
* 这些是 SDK 进程内工具,根本不进 bwrap,沙盒对它们零作用。
|
|
11
|
+
*
|
|
12
|
+
* 缺一半等于没关。第三块(跨项目边界)在 hooks/pre-workspace-scope-guard.js ——
|
|
13
|
+
* deny 规则写不出"除了自己这个项目"(deny 压过 allow,项目又是动态新建的)。
|
|
14
|
+
*
|
|
15
|
+
* 08-15 开这套时真跑出来的四件事,改之前先读:
|
|
16
|
+
* 1. `dangerouslyDisableSandbox` 默认允许,agent 撞到偶发失败会自己拿它关沙盒
|
|
17
|
+
* —— 必须 allowUnsandboxedCommands:false 焊死。
|
|
18
|
+
* 2. deny 规则的路径必须是**双斜杠**绝对形式,单斜杠静默失效。
|
|
19
|
+
* 3. npm 默认缓存在 ~/.npm,沙盒里 HOME 不可写 → `npm i` EROFS,构建道整条断。
|
|
20
|
+
* 4. 目录级 denyRead 拦得住读文件,拦不住 `ls` 看文件名(接受:名字不是秘密)。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import os from 'node:os';
|
|
25
|
+
import fs from 'node:fs/promises';
|
|
26
|
+
import { platform } from '../../runtime/platform.js';
|
|
27
|
+
import { autoModeSettings } from './auto-mode-rules.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 会话要用的缓存/临时目录一次备齐(2026-08-19 从 session-loop 下沉)。
|
|
31
|
+
*
|
|
32
|
+
* - npmCacheDir:共用 npm 缓存(数据根下;内容寻址 + 完整性校验,跨会话共用
|
|
33
|
+
* 不构成投毒面)。沙盒里 HOME 不可写,不给它 `npm i` 直接 EROFS。
|
|
34
|
+
* - agentTmpDir:沙盒里 Bash 的 $TMPDIR。SDK 按 CLAUDE_CODE_TMPDIR 派生沙盒
|
|
35
|
+
* tmp(没设就 os.tmpdir()),而派生出来的 /tmp/claude 在 bwrap 里只读 ——
|
|
36
|
+
* agent 装个 py7zr 连撞四堵墙、venv 静默失败(iss_msz25m5p_v5so)。
|
|
37
|
+
* ⚠️ 必须短:SDK 二进制里有条 warn —— 这个路径超 ~30 字节时 AF_UNIX socket
|
|
38
|
+
* 放不下,子进程 $TMPDIR **静默回退**默认值(= 白配)。所以挂 /tmp/nd 下、
|
|
39
|
+
* 按项目分目录;沙盒里遮兄弟目录见 buildIsolationOptions。
|
|
40
|
+
* - pip 缓存指进 agentTmpDir/pip,不然每条 pip 命令先吐一段
|
|
41
|
+
* ~/.cache/pip 不可写的 WARNING 噪音。
|
|
42
|
+
*
|
|
43
|
+
* 目录必须先存在 —— bwrap 绑一个不存在的路径会起不来;建不出来也不拦会话。
|
|
44
|
+
* envPatch 直接摊进 sdkEnv。
|
|
45
|
+
*/
|
|
46
|
+
export async function prepareAgentDirs({ dataRoot, projectId, sessionId }) {
|
|
47
|
+
const npmCacheDir = path.join(dataRoot, '.npm-cache');
|
|
48
|
+
const agentTmpRoot = path.join(os.tmpdir(), 'nd');
|
|
49
|
+
const agentTmpDir = path.join(agentTmpRoot, projectId || String(sessionId || 'anon').slice(0, 12));
|
|
50
|
+
for (const d of [npmCacheDir, path.join(agentTmpDir, 'pip')]) {
|
|
51
|
+
try { await fs.mkdir(d, { recursive: true }); } catch { /* 起不来也不该拦会话 */ }
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
npmCacheDir,
|
|
55
|
+
agentTmpRoot,
|
|
56
|
+
agentTmpDir,
|
|
57
|
+
envPatch: {
|
|
58
|
+
npm_config_cache: npmCacheDir,
|
|
59
|
+
CLAUDE_CODE_TMPDIR: agentTmpDir,
|
|
60
|
+
PIP_CACHE_DIR: path.join(agentTmpDir, 'pip'),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* bwrap 垫片的 env(PATH 前插一个目录,里面的 `bwrap` 是我们的包装脚本)
|
|
67
|
+
*
|
|
68
|
+
* 治的是 `apply-seccomp: unshare(CLONE_NEWUSER): Invalid argument` 这个偶发
|
|
69
|
+
* —— 内核级竞态,机器越闲越容易撞,实测每十几次 Bash 调用炸一次,而且会把
|
|
70
|
+
* agent 带进错误推断(有一次它干脆自己拿 `dangerouslyDisableSandbox` 绕过去)。
|
|
71
|
+
* 原理与安全不变量写在 server/ops/sandbox-shim/bwrap 里。
|
|
72
|
+
*
|
|
73
|
+
* 📮 上游 issue:https://github.com/anthropics/claude-code/issues/86928
|
|
74
|
+
* ⏳ **临时设施**:垫片认的是 SDK 内部的命令前缀,**SDK 一升级前缀变了就静默
|
|
75
|
+
* 失效**(原样透传 = 悄悄退回那个偶发,不报错)。升级后查
|
|
76
|
+
* `<数据根>/.sandbox-shim.log` 里 `rewrote=1` 的计数还在不在涨;
|
|
77
|
+
* 上游修好之后连这个函数带 ops/sandbox-shim/ 一起删掉。
|
|
78
|
+
* 关掉:`NODESIGN_SANDBOX_SHIM=off`。
|
|
79
|
+
*/
|
|
80
|
+
export function sandboxShimEnv({ baseEnv = process.env, dataRoot } = {}) {
|
|
81
|
+
if (!platform.sandboxEnabled) return {};
|
|
82
|
+
if (process.env.NODESIGN_SANDBOX_SHIM === 'off') return {};
|
|
83
|
+
const dir = path.join(platform.repoRoot, 'server', 'ops', 'sandbox-shim');
|
|
84
|
+
return {
|
|
85
|
+
PATH: `${dir}${path.delimiter}${baseEnv.PATH || ''}`,
|
|
86
|
+
// 改写次数记一笔:SDK 升级后 pattern 变了会静默退回现状,靠这个计数发现
|
|
87
|
+
...(dataRoot ? { NODESIGN_SHIM_LOG: path.join(dataRoot, '.sandbox-shim.log') } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {object} o
|
|
93
|
+
* @param {string} o.cwdRoot 会话工作区(= sharedRoot,扁平化之后同一个目录)
|
|
94
|
+
* @param {string} o.sharedRoot 项目共享根
|
|
95
|
+
* @param {string} o.npmCacheDir 共用 npm 缓存(在数据根下,必须可写可读)
|
|
96
|
+
* @param {string} [o.agentTmpRoot] 所有项目沙盒 tmp 的根(/tmp/nd)—— 整体遮读
|
|
97
|
+
* @param {string} [o.agentTmpDir] 本项目的沙盒 tmp(CLAUDE_CODE_TMPDIR 指它)——
|
|
98
|
+
* 开可写可读天窗。两个一起传或都不传。
|
|
99
|
+
* @param {string} o.dataRoot PROJECTS_DATA_ROOT
|
|
100
|
+
* @param {object} o.env 传给 SDK 的 env(凭据抹除按它的键名算)
|
|
101
|
+
* @returns {{ sandbox: object, settings: object }} 直接摊进 query options
|
|
102
|
+
*/
|
|
103
|
+
export function buildIsolationOptions({ cwdRoot, sharedRoot, npmCacheDir, agentTmpRoot, agentTmpDir, dataRoot, env }) {
|
|
104
|
+
return {
|
|
105
|
+
sandbox: {
|
|
106
|
+
enabled: platform.sandboxEnabled,
|
|
107
|
+
failIfUnavailable: false,
|
|
108
|
+
// ⭐ 逃生门焊死:SDK 给 Bash 留了 `dangerouslyDisableSandbox` 参数,**默认允许**。
|
|
109
|
+
// 08-15 开沙盒当天就被 agent 自发用上了 —— 它撞到 apply-seccomp 偶发失败,
|
|
110
|
+
// 第三次自己带上这个参数关掉沙盒,然后读到了隔壁项目的文件(它老实汇报了,
|
|
111
|
+
// 所以我们看见了)。一个能被工具参数关掉的沙盒等于没有沙盒。
|
|
112
|
+
allowUnsandboxedCommands: false,
|
|
113
|
+
network: {
|
|
114
|
+
allowLocalBinding: false,
|
|
115
|
+
// 全域允许:这层留着不是为了管出口,是为了 Linux 上顺带切断 unix socket
|
|
116
|
+
// —— pm2 的 rpc.sock 就在家目录里,通了的话 `pm2 start` 能起一个沙盒外的
|
|
117
|
+
// 进程(完整逃逸)。只读挂载拦不住 socket 连接,实测过。
|
|
118
|
+
allowedDomains: ['*'],
|
|
119
|
+
},
|
|
120
|
+
filesystem: {
|
|
121
|
+
allowWrite: [
|
|
122
|
+
cwdRoot,
|
|
123
|
+
...(sharedRoot ? [
|
|
124
|
+
path.join(sharedRoot, '.claude', 'agent-memory'),
|
|
125
|
+
path.join(sharedRoot, 'assets'),
|
|
126
|
+
] : []),
|
|
127
|
+
npmCacheDir,
|
|
128
|
+
// 本项目的沙盒 tmp(CLAUDE_CODE_TMPDIR / $TMPDIR / pip 缓存都指这里)。
|
|
129
|
+
// 没有它 Bash 的 tmp 只读:venv 静默失败、pip --user EROFS、npm 装不上
|
|
130
|
+
...(agentTmpDir ? [agentTmpDir] : []),
|
|
131
|
+
],
|
|
132
|
+
denyWrite: ['/etc', '/usr', '/bin', '/sbin', '/private/etc'],
|
|
133
|
+
// 数据根整个盖住、再用 allowRead 给自己的工作区开天窗。
|
|
134
|
+
// ⚠️ 口径要准(08-18 上生产时实测纠正):`ls 数据根` **看得见别的项目的
|
|
135
|
+
// 目录名**(沙盒里实测列出了另一个 pid),拦住的是**进去读**——
|
|
136
|
+
// `cat <别人的>/board.json` 与 `ls <别人的>/` 都空。
|
|
137
|
+
// 原注释写的"连 ls 数据根都只看得见自己那一个条目"过强,是错的;
|
|
138
|
+
// platform.js 那边的说法才对:「目录级 denyRead 拦得住 cat,拦不住 ls
|
|
139
|
+
// 看文件名 —— 文件名不是秘密,接受」。
|
|
140
|
+
// agentTmpRoot(/tmp/nd)同 dataRoot 一个待遇:根整个遮住、自己的子目录
|
|
141
|
+
// 开天窗 —— 不遮的话 /tmp/nd/<别的项目>/ 就是跨项目读通道
|
|
142
|
+
denyRead: [...platform.credentialBlacklist(), dataRoot, ...(agentTmpRoot ? [agentTmpRoot] : [])],
|
|
143
|
+
allowRead: [cwdRoot, npmCacheDir, ...(agentTmpDir ? [agentTmpDir] : [])],
|
|
144
|
+
},
|
|
145
|
+
credentials: {
|
|
146
|
+
// filesystem 那层拦得住 `cat .env`,拦不住 `env` —— 服务端 process.env
|
|
147
|
+
// 原样继承给了 Bash 子进程,key 就明晃晃躺着。按名字在沙盒内 unset。
|
|
148
|
+
envVars: platform.secretEnvVarNames(env).map(name => ({ name, mode: 'deny' })),
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
settings: {
|
|
152
|
+
permissions: { deny: platform.protectedPathRules({ dataRoot }) },
|
|
153
|
+
// auto 模式的分类器规则(只在开了 auto 时注入,省得白占 settings)。
|
|
154
|
+
// ⚠️ 按节替换不是追加 —— 为什么只覆盖 environment / hard_deny 两节,
|
|
155
|
+
// 见 auto-mode-rules.js 文件头。
|
|
156
|
+
...(platform.autoModeEnabled ? { autoMode: autoModeSettings() } : {}),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|