@xiaobuyu/nodesign 0.0.1
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/LICENSE +661 -0
- package/README.md +140 -0
- package/bin/nodesign.js +176 -0
- package/package.json +104 -0
- package/server/api/README.md +45 -0
- package/server/api/_guard.js +75 -0
- package/server/api/admin.js +187 -0
- package/server/api/assets/docx-page.js +103 -0
- package/server/api/assets/helpers.js +22 -0
- package/server/api/assets/notes.js +144 -0
- package/server/api/assets.js +896 -0
- package/server/api/board.js +57 -0
- package/server/api/browse.js +138 -0
- package/server/api/canvas.js +444 -0
- package/server/api/chatai.js +324 -0
- package/server/api/exports/build-standalone.js +980 -0
- package/server/api/exports/cards.js +118 -0
- package/server/api/exports/docx-pdf.js +46 -0
- package/server/api/exports/export-page.js +142 -0
- package/server/api/exports/handoff.js +147 -0
- package/server/api/exports.js +690 -0
- package/server/api/instruction.js +75 -0
- package/server/api/local.js +115 -0
- package/server/api/me.js +116 -0
- package/server/api/memory.js +185 -0
- package/server/api/pending-changes.js +366 -0
- package/server/api/plugins.js +164 -0
- package/server/api/projects.js +220 -0
- package/server/api/publish.js +64 -0
- package/server/api/recent.js +73 -0
- package/server/api/sessions.js +589 -0
- package/server/api/skills.js +77 -0
- package/server/api/standalone-fit.js +168 -0
- package/server/api/turn-compose.js +180 -0
- package/server/api/turn-inflight.js +44 -0
- package/server/api/turn.js +669 -0
- package/server/auth/README.md +17 -0
- package/server/auth/middleware.js +138 -0
- package/server/auth/origin-guard.js +71 -0
- package/server/auth/session.js +108 -0
- package/server/auth/tier.js +113 -0
- package/server/auth/users-store.js +348 -0
- package/server/edit/README.md +29 -0
- package/server/engine/README.md +113 -0
- package/server/engine/agent/agent-shared.js +550 -0
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -0
- package/server/engine/agent/auto-mode-rules.js +78 -0
- package/server/engine/agent/context.js +418 -0
- package/server/engine/agent/events.js +464 -0
- package/server/engine/agent/hooks/canvas-validate.js +199 -0
- package/server/engine/agent/hooks/failure.js +120 -0
- package/server/engine/agent/hooks/file-events.js +75 -0
- package/server/engine/agent/hooks/lifecycle.js +233 -0
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -0
- package/server/engine/agent/hooks/post-guidance.js +206 -0
- package/server/engine/agent/hooks/post-subagent-report.js +104 -0
- package/server/engine/agent/hooks/post-trim.js +58 -0
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -0
- package/server/engine/agent/hooks/pre-defaults.js +84 -0
- package/server/engine/agent/hooks/pre-injectors.js +277 -0
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -0
- package/server/engine/agent/hooks/pre-starter-files.js +61 -0
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -0
- package/server/engine/agent/hooks/site-validate.js +94 -0
- package/server/engine/agent/hooks/tool-prompts.js +31 -0
- package/server/engine/agent/hooks/turn-state-memory.js +56 -0
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -0
- package/server/engine/agent/hooks.js +354 -0
- package/server/engine/agent/init-contract.js +69 -0
- package/server/engine/agent/isolation.js +159 -0
- package/server/engine/agent/model-context.js +359 -0
- package/server/engine/agent/model-table.js +292 -0
- package/server/engine/agent/plugin-loader.js +266 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -0
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -0
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -0
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -0
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -0
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -0
- package/server/engine/agent/prompts/tools/site-reference.md +106 -0
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -0
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -0
- package/server/engine/agent/session-loop.js +1024 -0
- package/server/engine/agent/session-model.js +164 -0
- package/server/engine/agent/skill.js +204 -0
- package/server/engine/agent/system-prompts.js +86 -0
- package/server/engine/agent/task-events.js +78 -0
- package/server/engine/agents/ds-extractor.md +132 -0
- package/server/engine/agents/explorer.md +190 -0
- package/server/engine/agents/index.js +230 -0
- package/server/engine/agents/schemas/design-system.json +156 -0
- package/server/engine/agents/schemas/tweak-schema.json +113 -0
- package/server/engine/agents/tweak-proposer.md +127 -0
- package/server/engine/agents/vision-checker.md +254 -0
- package/server/engine/browse/capture.js +422 -0
- package/server/engine/browse/card.js +137 -0
- package/server/engine/browse/handover.js +59 -0
- package/server/engine/browse/page-digest.js +119 -0
- package/server/engine/browse/refs.js +196 -0
- package/server/engine/browse/registry.js +307 -0
- package/server/engine/browse/screencast.js +170 -0
- package/server/engine/browse/state.js +121 -0
- package/server/engine/chatai/chat-log.js +79 -0
- package/server/engine/chatai/index.js +193 -0
- package/server/engine/chatai/openai-compat.js +152 -0
- package/server/engine/chatai/orchestrate.js +309 -0
- package/server/engine/chatai/perform.js +40 -0
- package/server/engine/chatai/summarize.js +79 -0
- package/server/engine/mcp/capability-gate.js +52 -0
- package/server/engine/mcp/index.js +339 -0
- package/server/engine/mcp/param-sanitizer.js +142 -0
- package/server/engine/mcp/tools/arrange-on-board.js +124 -0
- package/server/engine/mcp/tools/artifact-session.js +278 -0
- package/server/engine/mcp/tools/browse-computer.js +421 -0
- package/server/engine/mcp/tools/browse-find-batch.js +183 -0
- package/server/engine/mcp/tools/browse-screenshot.js +157 -0
- package/server/engine/mcp/tools/browse.js +480 -0
- package/server/engine/mcp/tools/build-docx.js +101 -0
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -0
- package/server/engine/mcp/tools/create-on-board.js +155 -0
- package/server/engine/mcp/tools/crystallize-skill.js +168 -0
- package/server/engine/mcp/tools/deliver-files.js +0 -0
- package/server/engine/mcp/tools/explain-style.js +237 -0
- package/server/engine/mcp/tools/export-handoff.js +133 -0
- package/server/engine/mcp/tools/expose-tweaks.js +149 -0
- package/server/engine/mcp/tools/generate-image.js +842 -0
- package/server/engine/mcp/tools/get-computed-styles.js +164 -0
- package/server/engine/mcp/tools/get-pending-changes.js +205 -0
- package/server/engine/mcp/tools/h3box-ssh.js +84 -0
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -0
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -0
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -0
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -0
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -0
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -0
- package/server/engine/mcp/tools/helpers/rembg.js +273 -0
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -0
- package/server/engine/mcp/tools/highlight.js +68 -0
- package/server/engine/mcp/tools/list-pages.js +222 -0
- package/server/engine/mcp/tools/lookup-tags.js +59 -0
- package/server/engine/mcp/tools/navigate-to-page.js +60 -0
- package/server/engine/mcp/tools/organize-board.js +64 -0
- package/server/engine/mcp/tools/paint-still.js +434 -0
- package/server/engine/mcp/tools/pin-to-board.js +130 -0
- package/server/engine/mcp/tools/preview-deck.js +130 -0
- package/server/engine/mcp/tools/profile-scroll.js +305 -0
- package/server/engine/mcp/tools/publish-site.js +102 -0
- package/server/engine/mcp/tools/query-elements.js +195 -0
- package/server/engine/mcp/tools/read-board.js +103 -0
- package/server/engine/mcp/tools/read-document.js +93 -0
- package/server/engine/mcp/tools/read-page.js +264 -0
- package/server/engine/mcp/tools/read-tavern-json.js +143 -0
- package/server/engine/mcp/tools/record-decision.js +140 -0
- package/server/engine/mcp/tools/relate-on-board.js +110 -0
- package/server/engine/mcp/tools/remove-background.js +391 -0
- package/server/engine/mcp/tools/report-issue.js +115 -0
- package/server/engine/mcp/tools/roll-film.js +266 -0
- package/server/engine/mcp/tools/screenshot-docx.js +138 -0
- package/server/engine/mcp/tools/screenshot-url.js +195 -0
- package/server/engine/mcp/tools/screenshot.js +583 -0
- package/server/engine/mcp/tools/tier-gate.js +96 -0
- package/server/engine/mcp/tools/trace-motion.js +215 -0
- package/server/engine/mcp/tools/web-search.js +548 -0
- package/server/engine/motion/inventory.js +349 -0
- package/server/engine/perception/session.js +235 -0
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -0
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -0
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -0
- 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 -0
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -0
- package/server/engine/runs/active-runs.js +698 -0
- package/server/engine/runs/live-turn.js +252 -0
- package/server/engine/runs/store.js +364 -0
- package/server/engine/runs/turn-relay.js +217 -0
- package/server/engine/runtime/workspace.js +164 -0
- package/server/index.js +235 -0
- package/server/lib/artifact-file-path.js +80 -0
- package/server/lib/artifact-target.js +284 -0
- package/server/lib/asset-refs.js +239 -0
- package/server/lib/async-queue.js +104 -0
- package/server/lib/auto-relations.js +130 -0
- package/server/lib/binding-types.js +60 -0
- package/server/lib/board-kind-sizes.js +62 -0
- package/server/lib/board-relations.js +133 -0
- package/server/lib/browse-proxy.js +180 -0
- package/server/lib/canvas-id.js +36 -0
- package/server/lib/cover.js +227 -0
- package/server/lib/danbooru-tags.js +291 -0
- package/server/lib/doc-extract.js +156 -0
- package/server/lib/docx/build-from-source.js +260 -0
- package/server/lib/docx/build.js +490 -0
- package/server/lib/docx/dump-styles.js +287 -0
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -0
- package/server/lib/docx/merge-runs.js +129 -0
- package/server/lib/docx/numbering.js +218 -0
- package/server/lib/docx/order.js +152 -0
- package/server/lib/docx/rawzip.js +172 -0
- package/server/lib/docx/render.js +92 -0
- package/server/lib/docx/text-lint.js +225 -0
- package/server/lib/docx/tokens.js +335 -0
- package/server/lib/docx/units.js +29 -0
- package/server/lib/docx/xml.js +291 -0
- package/server/lib/docx-pages.js +187 -0
- package/server/lib/export-collect.js +289 -0
- package/server/lib/export-package.js +210 -0
- package/server/lib/html-srcset.js +145 -0
- package/server/lib/image-variant.js +460 -0
- package/server/lib/ingress/forward-openai-chat.js +305 -0
- package/server/lib/ingress/openai-chat.js +491 -0
- package/server/lib/ingress/session-notice.js +66 -0
- package/server/lib/ingress/session-routes.js +68 -0
- package/server/lib/ingress/slot-probe.js +130 -0
- package/server/lib/ingress/upstream-billing.js +52 -0
- package/server/lib/ingress/upstream-fail-streak.js +72 -0
- package/server/lib/ingress/upstream-truncation.js +48 -0
- package/server/lib/issues-store.js +182 -0
- package/server/lib/kinds/deck.js +72 -0
- package/server/lib/kinds/docx.js +283 -0
- package/server/lib/kinds/file-kinds.js +113 -0
- package/server/lib/kinds/index.js +202 -0
- package/server/lib/kinds/site.js +161 -0
- package/server/lib/model-ingress.js +543 -0
- package/server/lib/moderation.js +255 -0
- package/server/lib/notice-store.js +103 -0
- package/server/lib/plugin-install.js +104 -0
- package/server/lib/plugin-validator.js +721 -0
- package/server/lib/publish-store.js +115 -0
- package/server/lib/quick-summary.js +52 -0
- package/server/lib/quota.js +270 -0
- package/server/lib/rate-window.js +29 -0
- package/server/lib/reference-assets.js +92 -0
- package/server/lib/region-shot.js +135 -0
- package/server/lib/safe-path.js +73 -0
- package/server/lib/sdk-session.js +33 -0
- package/server/lib/showcase-store.js +92 -0
- package/server/lib/site-publish.js +465 -0
- package/server/lib/ssrf-guard.js +432 -0
- package/server/lib/task-scan.js +158 -0
- package/server/lib/tavern-json.js +154 -0
- package/server/lib/video-variant.js +237 -0
- package/server/lib/workspace-path.js +33 -0
- package/server/ops/fix-sdk-musl.mjs +40 -0
- package/server/ops/install-macos-fonts.sh +114 -0
- package/server/ops/macos-fonts.conf +336 -0
- package/server/ops/sandbox-shim/bwrap +57 -0
- package/server/projects/assets-summary.js +119 -0
- package/server/projects/auto-name.js +53 -0
- package/server/projects/board-store.js +640 -0
- package/server/projects/move-entry.js +103 -0
- package/server/projects/store.js +270 -0
- package/server/projects/ui-config.js +54 -0
- package/server/projects/workspace-templates.js +53 -0
- package/server/projects/workspace.js +1025 -0
- package/server/runtime/capabilities.js +153 -0
- package/server/runtime/local-config.js +177 -0
- package/server/runtime/local-env.js +88 -0
- package/server/runtime/platform.js +342 -0
- package/server/runtime/profile.js +72 -0
- package/server/services/rembg-launcher.js +313 -0
- package/server/services/rembg-service.py +334 -0
- package/server/shared/README.md +25 -0
- package/server/shared/deck.js +54 -0
- package/server/shared/time.js +49 -0
- package/server/style-pipeline/README.md +53 -0
- package/server/ws/broker.js +36 -0
- package/server/ws/browse-channel.js +177 -0
- package/server/ws/index.js +386 -0
- package/web/README.md +89 -0
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +11 -0
- package/web/dist/assets/DeckWindow-4G0xX7P3.js +16 -0
- package/web/dist/assets/DocxWindow-BXQsoPkh.js +16 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/SiteWindow-D6eS-2gM.js +18 -0
- package/web/dist/assets/again-D8tfgKKO.webp +0 -0
- package/web/dist/assets/caveat-nd-CP6HlsNg.woff2 +0 -0
- package/web/dist/assets/clock-xJhvfYpS.webp +0 -0
- package/web/dist/assets/film-cut-BBKMwH7q.webp +0 -0
- package/web/dist/assets/film-still-BZ_DL9Tt.webp +0 -0
- package/web/dist/assets/index-C0KUf6Wd.css +1 -0
- package/web/dist/assets/index-Cbl3ATvp.js +2125 -0
- package/web/dist/assets/ink-desk-BRbxeDsR.webp +0 -0
- package/web/dist/assets/ink-night-Bz4BT3XH.webp +0 -0
- package/web/dist/assets/ink-portrait-C0DM4V8Q.webp +0 -0
- package/web/dist/assets/longcang-regular-C0uvTuKa.woff2 +0 -0
- 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-DTjo2geW.js +31 -0
- package/web/dist/assets/question-8H4HSqxh.webp +0 -0
- package/web/dist/assets/reject-BaPJqElj.webp +0 -0
- package/web/dist/assets/rotate-cw-LMT3x1Zj.js +11 -0
- package/web/dist/assets/rp-portrait-CbrGpew2.webp +0 -0
- package/web/dist/assets/rp-street-C4jVKjxx.webp +0 -0
- package/web/dist/assets/square-dashed-mouse-pointer-CBoAkfhi.js +11 -0
- package/web/dist/assets/tangle-DIxHVNuT.webp +0 -0
- package/web/dist/assets/thisone-BylZKglV.webp +0 -0
- package/web/dist/assets/thumb-DO1wfK1I.webp +0 -0
- package/web/dist/index.html +18 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/remove-background.js — remove_background MCP tool
|
|
3
|
+
*
|
|
4
|
+
* 调用名:
|
|
5
|
+
* mcp__nodesign__remove_background
|
|
6
|
+
*
|
|
7
|
+
* Schema:
|
|
8
|
+
* inputPath: string workspace 相对路径(assets/photo.jpg 等)
|
|
9
|
+
* outputName?: string 不带后缀;default `<inputBaseName>-nobg`
|
|
10
|
+
* overwrite?: boolean 同名 outputName 已存在时是否覆盖(default false → 加 -<ts> 后缀)
|
|
11
|
+
*
|
|
12
|
+
* Returns CallToolResult:
|
|
13
|
+
* { type: 'text', text: '...' }, // caption + path
|
|
14
|
+
* { type: 'image', source: { type:'base64', media_type, data } } // 抠完的预览
|
|
15
|
+
*
|
|
16
|
+
* 工作流:
|
|
17
|
+
* - 解析路径(防 traversal,候选 workspaceRoot / sharedRoot)→ 拿绝对路径
|
|
18
|
+
* - Read 源图字节
|
|
19
|
+
* - helpers/rembg.js spawn .venv-rembg python → rembg U²-Net 抠图
|
|
20
|
+
* - 落 assets/generated/<name>.png(RGBA 必须 PNG,强制 .png 扩展)
|
|
21
|
+
* - 返 caption + image content block 让 agent 立刻 vision 看到效果
|
|
22
|
+
*
|
|
23
|
+
* 跟 generate_image 的关系(2026-05-11 拆分):
|
|
24
|
+
* 原 generate_image 上的 removeBackground:true flag 已删,独立成本工具。
|
|
25
|
+
* 理由:抠图作为 generate_image 的 flag 只能在生图当下用,但实际场景更广
|
|
26
|
+
* ——用户上传的产品照、之前生过的图、截图、任何 workspace 里的图都该能抠。
|
|
27
|
+
* 独立工具复用 helpers/rembg.js 0 重复代码,agent 按需调。
|
|
28
|
+
*
|
|
29
|
+
* Fail-soft:rembg 不可用 / subprocess 失败时返 isError=true 让 agent 看到原因
|
|
30
|
+
* (依赖缺 / 超时 / 进程崩),决定 fallback(用原图 / 改 prompt 重生 / 先解决环境)。
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
import crypto from 'node:crypto';
|
|
35
|
+
import { patchBoard } from '../../../projects/board-store.js';
|
|
36
|
+
import fs from 'node:fs/promises';
|
|
37
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
38
|
+
import { z } from 'zod';
|
|
39
|
+
import { removeBackground as rembgRemove, isAvailable as rembgIsAvailable, REMBG_SETUP_HINT } from './helpers/rembg.js';
|
|
40
|
+
|
|
41
|
+
const SUPPORTED_INPUT_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.tiff']);
|
|
42
|
+
|
|
43
|
+
// quality enum → 内部 (model, alphaMatting) 映射
|
|
44
|
+
//
|
|
45
|
+
// 2026-07-31 重排。起因是 balanced/best 长期被 env 顶到 fast,等于只有一档能用。
|
|
46
|
+
// service 侧给 alpha matting 加了分辨率上限(见 rembg-service.py 的
|
|
47
|
+
// _remove_with_am_cap)之后,"AM 太慢所以 fast 不能开 AM" 这条前提不成立了:
|
|
48
|
+
// AM 在 ≤1024 长边上解,代价从分钟级掉到 1-2 秒。
|
|
49
|
+
//
|
|
50
|
+
// 本机实测(1023×1537 = 1.57MP,warm session,单核 Neoverse-V2):
|
|
51
|
+
// isnet-general-use 无 AM 6.6s 峰值 892MB ✓
|
|
52
|
+
// isnet-general-use + AM 8.4s 峰值 923MB ✓ ← balanced 换成它
|
|
53
|
+
// birefnet-lite 无 AM 18-21s 峰值 2435MB ✗ OOM killed
|
|
54
|
+
// birefnet 那档撑爆内存的是模型推理本身,不是 AM(关掉 AM 照样死),ort 的
|
|
55
|
+
// arena 调优也没救回来。所以在加 swap 之前它就是不可用,best 仍会被 env 顶掉。
|
|
56
|
+
//
|
|
57
|
+
// 边缘质量差异(同一张图,alpha 通道统计):
|
|
58
|
+
// 无 AM 半透明杂散像素 34.4%(halo 就是这些)
|
|
59
|
+
// + AM 半透明杂散像素 9.9%,真过渡像素反而略增
|
|
60
|
+
// 观感上 AM 那档发丝更细更自然,但整体略"雾"(alpha 从 1024 插值回原尺寸)。
|
|
61
|
+
// 两种取向各有适用,所以是两档而不是一档取代另一档。
|
|
62
|
+
//
|
|
63
|
+
// best 从 birefnet-general(880MB) 降到 lite:880MB 那个从来没在这台机器上跑起来
|
|
64
|
+
// 过,连模型都没下载。换到更大的机器时把它加回来即可。
|
|
65
|
+
const QUALITY_MAP = {
|
|
66
|
+
fast: { model: 'isnet-general-use', alphaMatting: false },
|
|
67
|
+
balanced: { model: 'isnet-general-use', alphaMatting: true },
|
|
68
|
+
best: { model: 'birefnet-general-lite', alphaMatting: true },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// style 轴(2026-08-02):与 quality 正交。二次元/插画/生成立绘 → isnet-anime
|
|
72
|
+
// (动漫线稿专训,与 isnet-general-use 同量级 ~900MB 峰值,这台机器承载得起)。
|
|
73
|
+
// 只换 fast/balanced 的底模;best 是通用分割且被 QUALITY_CAP 禁着,不参与。
|
|
74
|
+
const ANIME_MODEL = 'isnet-anime';
|
|
75
|
+
function resolveModelCfg(quality, style) {
|
|
76
|
+
const base = QUALITY_MAP[quality];
|
|
77
|
+
if (style === 'anime' && base.model === 'isnet-general-use') {
|
|
78
|
+
return { ...base, model: ANIME_MODEL };
|
|
79
|
+
}
|
|
80
|
+
return base;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 质量上限(env NODESIGN_REMBG_QUALITY_CAP)。birefnet 系模型一次推理峰值 2.4GB+,
|
|
84
|
+
// 在小内存机器上会被 OOM killer 直接杀掉 —— 而且杀的是常驻 service 进程,连带
|
|
85
|
+
// fast 档一起死。
|
|
86
|
+
// 超上限**显式拒绝并说明原因**(agent 看得见、能改档重试),不做静默降档:
|
|
87
|
+
// 静默降档意味着 agent 以为拿到了 birefnet 的分割质量,实际是 isnet 的。
|
|
88
|
+
const QUALITY_ORDER = ['fast', 'balanced', 'best'];
|
|
89
|
+
function qualityCapError(quality) {
|
|
90
|
+
const cap = process.env.NODESIGN_REMBG_QUALITY_CAP;
|
|
91
|
+
if (!cap || !QUALITY_ORDER.includes(cap)) return null;
|
|
92
|
+
if (QUALITY_ORDER.indexOf(quality) <= QUALITY_ORDER.indexOf(cap)) return null;
|
|
93
|
+
return `quality "${quality}" 在这台机器上被禁用(上限 "${cap}"):它用的 birefnet `
|
|
94
|
+
+ '模型一次推理峰值内存 2.4GB+,会触发 OOM kill 并连带杀掉常驻抠图服务。'
|
|
95
|
+
+ `请改用 quality: "${cap}" 重试 —— 边缘质量问题(halo / 毛边 / 发丝)"balanced" `
|
|
96
|
+
+ '已经能解决,"best" 只在主体形状本身被分割错时才有意义。'
|
|
97
|
+
+ '确实需要更强分割时告知用户:这台机器要先加 swap 或换更大内存的机器。';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 解析 workspace 相对路径到绝对路径。防 traversal。
|
|
102
|
+
* @returns {Promise<{ abs: string, baseName: string, ext: string }>}
|
|
103
|
+
*/
|
|
104
|
+
async function resolveInputPath(relPath, workspaceRoot, sharedRoot) {
|
|
105
|
+
if (path.isAbsolute(relPath)) {
|
|
106
|
+
throw new Error(`inputPath must be a workspace-relative path; got absolute: ${relPath}`);
|
|
107
|
+
}
|
|
108
|
+
const candidates = [workspaceRoot];
|
|
109
|
+
if (sharedRoot) candidates.push(sharedRoot);
|
|
110
|
+
|
|
111
|
+
let abs = null;
|
|
112
|
+
for (const base of candidates) {
|
|
113
|
+
const candidate = path.resolve(base, relPath);
|
|
114
|
+
if (candidate === base || candidate.startsWith(base + path.sep)) {
|
|
115
|
+
try {
|
|
116
|
+
await fs.access(candidate);
|
|
117
|
+
abs = candidate;
|
|
118
|
+
break;
|
|
119
|
+
} catch {
|
|
120
|
+
// 文件不在这个 root 下,试下一个
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!abs) {
|
|
125
|
+
throw new Error(`inputPath not found in workspace or shared: ${relPath}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const ext = path.extname(abs).toLowerCase();
|
|
129
|
+
if (!SUPPORTED_INPUT_EXT.has(ext)) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`inputPath must be one of ${[...SUPPORTED_INPUT_EXT].join('/')}; got: ${ext}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const baseName = path.basename(abs, ext);
|
|
135
|
+
return { abs, baseName, ext };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function safeBaseName(s) {
|
|
139
|
+
return String(s || '')
|
|
140
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
141
|
+
.replace(/^-+|-+$/g, '')
|
|
142
|
+
.slice(0, 64);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {object} deps
|
|
147
|
+
* @param {string} deps.workspaceRoot
|
|
148
|
+
* @param {string|null} [deps.sharedRoot]
|
|
149
|
+
* @param {object} [deps.ctx]
|
|
150
|
+
*/
|
|
151
|
+
export function makeRemoveBackgroundTool({ workspaceRoot, sharedRoot = null, projectId = null, ctx } = {}) {
|
|
152
|
+
void ctx; // currently no event emit; sharing pattern w/ other tools
|
|
153
|
+
|
|
154
|
+
return tool(
|
|
155
|
+
'remove_background',
|
|
156
|
+
`Remove the background from any image in the workspace, output a transparent RGBA PNG.
|
|
157
|
+
|
|
158
|
+
⚠️ FIRST DECISION IS "style", NOT "quality". Anime / illustration / character
|
|
159
|
+
art → pass style:"anime". Photos, product shots, screenshots → leave the default.
|
|
160
|
+
Getting this wrong does not soften an edge, it loses a whole limb: a pale-clothed
|
|
161
|
+
anime figure on a light background came back with both legs cut off under the
|
|
162
|
+
default model, and came back clean under style:"anime". The quality knob below is
|
|
163
|
+
a much smaller effect than this one — read this line first, then that section.
|
|
164
|
+
|
|
165
|
+
Use this when:
|
|
166
|
+
- A user-uploaded reference photo has a background that clashes with your canvas
|
|
167
|
+
- A previously-generated image (you didn't pass removeBackground at gen time, or it was generated by a different tool) needs to become an overlay
|
|
168
|
+
- A screenshot or any other workspace image needs the subject isolated
|
|
169
|
+
|
|
170
|
+
Implementation: long-running Python service (rembg / onnxruntime) accessed via
|
|
171
|
+
Unix socket; cold fallback per-call spawn when service down. isnet ML
|
|
172
|
+
segmentation + trimap alpha matting post-process (pymatting) for clean edges.
|
|
173
|
+
|
|
174
|
+
QUALITY OPTIONS (tradeoff: speed vs edge character):
|
|
175
|
+
- balanced: isnet-general-use + alpha matting (~8s warm). DEFAULT — cuts
|
|
176
|
+
semi-transparent stray pixels (halo) from ~34% to ~10% and keeps
|
|
177
|
+
finer wisps, at the cost of a slightly softer overall edge.
|
|
178
|
+
Alpha matting runs capped at 1024px long edge then the alpha is
|
|
179
|
+
scaled back, so cost does NOT grow with input megapixels.
|
|
180
|
+
- fast: isnet-general-use, no alpha matting (~7s warm). Step DOWN to this
|
|
181
|
+
for hard-edged subjects where you want a crisp decisive cut and
|
|
182
|
+
softness would read as blur: product shots, icons, logos, UI
|
|
183
|
+
screenshots, flat-color graphics. Also for large batches where
|
|
184
|
+
the 1s/image difference adds up.
|
|
185
|
+
- best: birefnet-general-lite + alpha matting. Strongest segmentation, but
|
|
186
|
+
needs ~2.5GB RAM for inference — may be disabled by the machine cap
|
|
187
|
+
(the tool tells you explicitly if so; it never silently downgrades).
|
|
188
|
+
|
|
189
|
+
Which to pick: stay on the default. Step down to fast for hard-edged / graphic
|
|
190
|
+
subjects. Only reach for best when balanced mis-segments the subject SHAPE
|
|
191
|
+
itself (a model problem, not an edge problem) — balanced already fixes edges.
|
|
192
|
+
|
|
193
|
+
LIMITATIONS:
|
|
194
|
+
- Heuristic ML segmentation — clean cuts when subject has clear visual boundaries
|
|
195
|
+
- **Subject and background close in lightness (pale subject on a white ground)
|
|
196
|
+
can lose whole regions**, not just edge quality. The caption reports how much
|
|
197
|
+
of the frame survived as foreground — a number far below what you expect means
|
|
198
|
+
it cut away part of the subject; switch style / quality and run it again
|
|
199
|
+
- Subjects with thin transparent elements (glass / smoke / wispy hair / fine
|
|
200
|
+
fabric / fur) may have soft or inaccurate edges even on "best" quality
|
|
201
|
+
- For pure-shape icons / logos, prefer SVG (lucide-react import) — true vector
|
|
202
|
+
transparency, no ML guesswork
|
|
203
|
+
|
|
204
|
+
WHEN NOT TO USE:
|
|
205
|
+
- Decorative backgrounds / patterns / textures (these ARE backgrounds, don't remove)
|
|
206
|
+
- Full-page covers / hero images (the bg IS the design)
|
|
207
|
+
- Simple line icons (use lucide-react SVG instead)
|
|
208
|
+
|
|
209
|
+
Returns: text caption with output path + image content block (preview the result).`,
|
|
210
|
+
{
|
|
211
|
+
inputPath: z
|
|
212
|
+
.string()
|
|
213
|
+
.min(1)
|
|
214
|
+
.describe('Workspace-relative path to the source image. Must be inside the workspace or shared assets dir (e.g., "assets/user-photo.jpg", "assets/generated/coffee.png"). Supported formats: png/jpg/jpeg/webp/gif/bmp/tiff.'),
|
|
215
|
+
outputName: z
|
|
216
|
+
.string()
|
|
217
|
+
.max(64)
|
|
218
|
+
.optional()
|
|
219
|
+
.describe('Output filename without extension. Default: "<inputBaseName>-nobg". Always written as .png (RGBA).'),
|
|
220
|
+
overwrite: z
|
|
221
|
+
.boolean()
|
|
222
|
+
.optional()
|
|
223
|
+
.describe('If output file already exists: false (default) → append "-<timestamp>" to avoid overwrite; true → replace existing.'),
|
|
224
|
+
quality: z
|
|
225
|
+
.enum(['fast', 'balanced', 'best'])
|
|
226
|
+
.optional()
|
|
227
|
+
.describe('Speed vs edge character. Default "balanced" (isnet + alpha matting, ~8s warm): cuts stray semi-transparent pixels (halo) ~34%→~10% and keeps finer wisps, slightly softer overall edge; alpha matting is capped at 1024px long edge so cost does NOT scale with input megapixels — never pre-resize for speed. Step DOWN to "fast" (isnet, no alpha matting, ~7s warm) for hard-edged subjects where softness reads as blur: product shots, icons, logos, UI screenshots, flat-color graphics — and for large batches. "best" (birefnet-general-lite + alpha matting): strongest segmentation but needs ~2.5GB RAM; may be disabled by the machine cap, in which case the tool says so explicitly rather than silently downgrading. Only reach for best when balanced mis-segments the subject SHAPE — balanced already fixes edges.'),
|
|
228
|
+
style: z
|
|
229
|
+
.enum(['general', 'anime'])
|
|
230
|
+
.optional()
|
|
231
|
+
.describe('Model family, orthogonal to quality. "anime" switches fast/balanced to isnet-anime — trained on anime/illustration linework. Use it for generated character art (立绘), stickers, and any 2D illustration; flat-color fills and clean line edges segment noticeably better than the photo-trained default. Keep "general" (default) for photos, product shots, screenshots. Same speed/memory class; "best" ignores style.'),
|
|
232
|
+
},
|
|
233
|
+
// 默认 balanced(2026-07-31):AM 加了分辨率上限之后只比 fast 慢 1.7s、多占
|
|
234
|
+
// 31MB,而 halo 从 34% 降到 10%。默认该给更好的那个,crisp 需求让 agent 显式
|
|
235
|
+
// 降到 fast —— 反过来(默认 fast,需要好边缘时升档)依赖 agent 先看出有 halo,
|
|
236
|
+
// 而它多数时候不会回头看抠完的图。
|
|
237
|
+
async ({ inputPath, outputName, overwrite = false, quality = 'balanced', style = 'general' }) => {
|
|
238
|
+
// 0a. 质量上限(小内存机器禁 birefnet,显式拒绝不静默降档)
|
|
239
|
+
const capErr = qualityCapError(quality);
|
|
240
|
+
if (capErr) {
|
|
241
|
+
return { content: [{ type: 'text', text: `remove_background: ${capErr}` }], isError: true };
|
|
242
|
+
}
|
|
243
|
+
// 0. 检查 rembg 可用
|
|
244
|
+
const avail = await rembgIsAvailable();
|
|
245
|
+
if (!avail.available) {
|
|
246
|
+
return {
|
|
247
|
+
content: [{
|
|
248
|
+
type: 'text',
|
|
249
|
+
text: `remove_background failed: rembg unavailable (${avail.reason}). Setup once: ${REMBG_SETUP_HINT}`,
|
|
250
|
+
}],
|
|
251
|
+
isError: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 1. 解析 + 读源图
|
|
256
|
+
let resolved;
|
|
257
|
+
try {
|
|
258
|
+
resolved = await resolveInputPath(inputPath, workspaceRoot, sharedRoot);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: 'text', text: `remove_background failed: ${err.message}` }],
|
|
262
|
+
isError: true,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const inputBuf = await fs.readFile(resolved.abs);
|
|
266
|
+
|
|
267
|
+
// 2. 决定输出名 + 文件名
|
|
268
|
+
const baseName = outputName
|
|
269
|
+
? (safeBaseName(outputName) || `${resolved.baseName}-nobg`)
|
|
270
|
+
: `${safeBaseName(resolved.baseName)}-nobg`;
|
|
271
|
+
|
|
272
|
+
// 3. 落档目录(跟 generate_image 同:sharedRoot 优先,跨 session 共享)
|
|
273
|
+
const useShared = !!sharedRoot;
|
|
274
|
+
const outDir = path.join(
|
|
275
|
+
useShared ? sharedRoot : workspaceRoot,
|
|
276
|
+
'assets',
|
|
277
|
+
'generated',
|
|
278
|
+
);
|
|
279
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
280
|
+
|
|
281
|
+
// 4. 处理 overwrite + 防同名覆盖
|
|
282
|
+
let fileName = `${baseName}.png`;
|
|
283
|
+
let absOut = path.join(outDir, fileName);
|
|
284
|
+
if (!overwrite) {
|
|
285
|
+
try {
|
|
286
|
+
await fs.access(absOut);
|
|
287
|
+
// 已存在 → 加 timestamp 后缀
|
|
288
|
+
const ts = Date.now();
|
|
289
|
+
fileName = `${baseName}-${ts}.png`;
|
|
290
|
+
absOut = path.join(outDir, fileName);
|
|
291
|
+
} catch {
|
|
292
|
+
// 不存在,原名可用
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 5. 抠图——按 quality 选 model + alphaMatting
|
|
297
|
+
// service warm 走 Unix socket(本机实测 fast 6.6s / balanced 8.4s);
|
|
298
|
+
// fallback spawn cold 走 .venv-rembg per-call,每次多付 20-40s 模型 load。
|
|
299
|
+
//
|
|
300
|
+
// timeout 留的余量比实测大一个数量级,因为这台机器只有 1 核:同时有生图预热
|
|
301
|
+
// 或导出截图在跑的时候,同一个调用能慢好几倍。宁可等也别误杀一次成功的抠图。
|
|
302
|
+
// AM 加了 1024 分辨率上限之后不再随输入 MP 增长,所以 balanced 不用再留
|
|
303
|
+
// 原来那种"1-2MP 容易 100s+"的巨量余量。
|
|
304
|
+
const qualityCfg = resolveModelCfg(quality, style);
|
|
305
|
+
const timeoutByQuality = {
|
|
306
|
+
fast: 60_000, // 1min(无 AM,cold load 也够)
|
|
307
|
+
balanced: 120_000, // 2min(warm 8.4s,留 14 倍余量给 CPU 争抢)
|
|
308
|
+
best: 300_000, // 5min(birefnet 推理本身就重,且可能触发模型下载)
|
|
309
|
+
};
|
|
310
|
+
const t0 = Date.now();
|
|
311
|
+
const rgba = await rembgRemove(inputBuf, {
|
|
312
|
+
model: qualityCfg.model,
|
|
313
|
+
alphaMatting: qualityCfg.alphaMatting,
|
|
314
|
+
timeoutMs: timeoutByQuality[quality],
|
|
315
|
+
});
|
|
316
|
+
const elapsed = Date.now() - t0;
|
|
317
|
+
if (!rgba) {
|
|
318
|
+
return {
|
|
319
|
+
content: [{
|
|
320
|
+
type: 'text',
|
|
321
|
+
text: `remove_background failed: rembg subprocess returned null (timeout / spawn error / model load failure). Quality=${quality}, model=${qualityCfg.model}. See server logs.`,
|
|
322
|
+
}],
|
|
323
|
+
isError: true,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 6. 写盘
|
|
328
|
+
await fs.writeFile(absOut, rgba);
|
|
329
|
+
console.log(
|
|
330
|
+
`[remove-background] ${inputPath} → ${fileName} (${rgba.length}B) in ${elapsed}ms `
|
|
331
|
+
+ `[quality=${quality} model=${qualityCfg.model} alphaMatting=${qualityCfg.alphaMatting}]`,
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// 7. agent 看到的相对路径(相对 cwd = sessions/<sid>/)
|
|
335
|
+
// 跟 generate_image 一致:sessions/<sid>/assets 是 softlink → shared/assets
|
|
336
|
+
const agentRelPath = path.posix.join('assets', 'generated', fileName);
|
|
337
|
+
|
|
338
|
+
// 7.5 自动谱系(2026-08-14 北极星路线4尾巴):抠图产物机器可证地
|
|
339
|
+
// 「改自」原图 —— 事件驱动一次性落线(用户删了不会再长出来,跟 ref
|
|
340
|
+
// 对账层的治理规则刻意不同)。fail-soft:关系落不上不挡抠图。
|
|
341
|
+
if (projectId) {
|
|
342
|
+
try {
|
|
343
|
+
const toRel = String(inputPath)
|
|
344
|
+
.replace(/^(\.\/)+/, '')
|
|
345
|
+
.replace(/^(\.\.\/)+shared\//, '');
|
|
346
|
+
const bid = `b:auto:df:${crypto.createHash('sha1').update(`${agentRelPath}|${toRel}`).digest('hex').slice(0, 12)}`;
|
|
347
|
+
await patchBoard(projectId, {
|
|
348
|
+
bindings: { [bid]: { type: 'derives-from', from: agentRelPath, to: toRel, by: 'auto' } },
|
|
349
|
+
});
|
|
350
|
+
} catch { /* 谱系是锦上添花 */ }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 前景占比(2026-08-18)。以前返回值里没有任何东西能让 agent 在不看图的
|
|
354
|
+
// 情况下判断这次抠废了 —— 一个 agent 连着十九张图全靠肉眼才发现整条腿被
|
|
355
|
+
// 当成背景切掉了,而它两次报障的共同核心诉求就是这一个数字:
|
|
356
|
+
// 「如果 caption 里有『前景占 18%』,我第一时间就知道腿丢了」。
|
|
357
|
+
let fgNote = '';
|
|
358
|
+
try {
|
|
359
|
+
const { default: sharp } = await import('sharp');
|
|
360
|
+
const { data, info } = await sharp(rgba).ensureAlpha()
|
|
361
|
+
.raw().toBuffer({ resolveWithObject: true });
|
|
362
|
+
let solid = 0;
|
|
363
|
+
const total = info.width * info.height;
|
|
364
|
+
for (let i = 3; i < data.length; i += info.channels) if (data[i] > 128) solid++;
|
|
365
|
+
fgNote = ` foreground=${((solid / total) * 100).toFixed(0)}% of frame`;
|
|
366
|
+
} catch { /* 量不出来就不报,别因为一个诊断数字挡住抠图 */ }
|
|
367
|
+
|
|
368
|
+
// 8. 返 caption + image content block 让 agent 直接 vision 看
|
|
369
|
+
const caption = [
|
|
370
|
+
`Removed background from ${inputPath}`,
|
|
371
|
+
`→ ${agentRelPath}`,
|
|
372
|
+
`(RGBA PNG, ${(rgba.length / 1024).toFixed(1)} KB, ${elapsed}ms,`
|
|
373
|
+
+ ` style=${style || 'general'} quality=${quality} model=${qualityCfg.model}${fgNote})`,
|
|
374
|
+
].join(' ');
|
|
375
|
+
|
|
376
|
+
// MCP image content block 格式:顶层 data + mimeType(不是 Anthropic API 的
|
|
377
|
+
// { source: { type:'base64', media_type, data } } 形式)。SDK validator 用
|
|
378
|
+
// MCP schema 校验,错误格式会让 tool call result 整个被拒。
|
|
379
|
+
return {
|
|
380
|
+
content: [
|
|
381
|
+
{ type: 'text', text: caption },
|
|
382
|
+
{
|
|
383
|
+
type: 'image',
|
|
384
|
+
data: rgba.toString('base64'),
|
|
385
|
+
mimeType: 'image/png',
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/report-issue.js — report_issue MCP tool
|
|
3
|
+
* (2026-08-02 由 report_friction 扩容改名:加 kind 轴,bug / friction / idea)
|
|
4
|
+
*
|
|
5
|
+
* agent 主动给维护者写信:harness 的故障(bug)、能用但绕路的摩擦(friction)、
|
|
6
|
+
* 干活时冒出来的改进想法(idea)。三类走同一张 issues 表同一套指纹聚合。
|
|
7
|
+
*
|
|
8
|
+
* 跟自动层(PostToolUseFailure → issues 表 source='auto')的分工:
|
|
9
|
+
* 自动层记"发生了什么"——某工具失败 N 次,不依赖 agent 自觉,但指不出修法。
|
|
10
|
+
* 这一层补"为什么难受、期望怎样"——一条 "screenshot 超时 12 次" 没有信息量,
|
|
11
|
+
* 一条 "截长站点页时我只想要首屏,但只能 fullPage 然后自己裁" 才指向修法。
|
|
12
|
+
*
|
|
13
|
+
* 措辞戒律(继承自 report_friction,扩 idea 后同样适用):
|
|
14
|
+
* - 压住"遇到困难就上报"的倾向:上报**不是**绕路的替代品,照常把活儿干完。
|
|
15
|
+
* - idea 通道要具体到可实施——"工具链很棒" 这种话没有收件人,不收。
|
|
16
|
+
* - 同类按指纹聚合累加,不是刷一万条重复。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { recordIssue, signatureOf } from '../../../lib/issues-store.js';
|
|
22
|
+
import { getProject } from '../../../projects/store.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {object} deps
|
|
26
|
+
* @param {string} [deps.projectId]
|
|
27
|
+
* @param {string} [deps.sessionId]
|
|
28
|
+
* @param {import('../../agent/context.js').AgentContext} [deps.ctx]
|
|
29
|
+
*/
|
|
30
|
+
export function makeReportIssueTool({ projectId, sessionId, ctx }) {
|
|
31
|
+
return tool(
|
|
32
|
+
'report_issue',
|
|
33
|
+
`Write to the maintainer of this HARNESS — the tools and environment you work
|
|
34
|
+
in. This goes to their issue list, not to the user. Three kinds:
|
|
35
|
+
|
|
36
|
+
- "bug": something in the harness behaved WRONGLY — a tool crashed on valid
|
|
37
|
+
input, returned corrupt output, or contradicted its own description.
|
|
38
|
+
- "friction": it worked, but the path was painful — a missing parameter, a
|
|
39
|
+
return shape you had to fight, a workaround you have now done more than once
|
|
40
|
+
(the workaround IS the report).
|
|
41
|
+
- "idea": nothing is broken, but while working you saw something the harness
|
|
42
|
+
could do better or do at all — a tool that should exist, a flag that would
|
|
43
|
+
collapse three steps into one, a default that fights real usage. The
|
|
44
|
+
maintainer reads every one of these and good ones get built — if a concrete
|
|
45
|
+
improvement occurred to you, report it rather than let it evaporate.
|
|
46
|
+
|
|
47
|
+
This is NOT a substitute for doing the work. Finish the task the way you would
|
|
48
|
+
have anyway — route around the problem, deliver the result — and log this on
|
|
49
|
+
the side. Never report and then hand back less than you could have.
|
|
50
|
+
|
|
51
|
+
Do NOT report:
|
|
52
|
+
- Your own mistakes (wrong path, malformed edit) — not harness issues
|
|
53
|
+
- One-off flakes that a retry fixed
|
|
54
|
+
- Anything about the design work itself (that belongs in the conversation)
|
|
55
|
+
- Praise or vague sentiment ("the tools are great", "X could be better
|
|
56
|
+
somehow") — a report with no actionable content is noise
|
|
57
|
+
|
|
58
|
+
Be concrete. "screenshot is slow" is useless. "screenshot_canvas on a 6000px
|
|
59
|
+
site page takes ~14s and returns an image too large to read, so I crop by hand
|
|
60
|
+
every time; a viewportOnly flag would remove the whole detour" is actionable.`,
|
|
61
|
+
{
|
|
62
|
+
kind: z.enum(['bug', 'friction', 'idea'])
|
|
63
|
+
.describe('bug = behaved wrongly · friction = worked but painful · idea = improvement worth building'),
|
|
64
|
+
summary: z.string().min(8).max(200)
|
|
65
|
+
.describe('One line. bug/friction: what is wrong, concrete. idea: the improvement itself, not "improve X".'),
|
|
66
|
+
detail: z.string().min(20).max(3000)
|
|
67
|
+
.describe('What you were doing and what happened (bug/friction: include the workaround you used; idea: the real situation that made you want this).'),
|
|
68
|
+
// 选填(2026-08-18):以前是必填,agent 不传就被 zod 打回 —— **信箱把上报挡在
|
|
69
|
+
// 门外**,库里有 4 次这样的失败。能提当然好,提不出来也别丢掉这条上报。
|
|
70
|
+
expectation: z.string().min(10).max(1500).optional()
|
|
71
|
+
.describe('If you can name it: bug → the correct behaviour; friction → what would remove the detour; idea → what it looks like once built and what it unlocks. Leave it out rather than padding it.'),
|
|
72
|
+
toolName: z.string().max(80).optional()
|
|
73
|
+
.describe('The tool involved, if it is about one (e.g. "mcp__nodesign__screenshot_canvas").'),
|
|
74
|
+
},
|
|
75
|
+
async ({ kind, summary, detail, expectation, toolName }) => {
|
|
76
|
+
try {
|
|
77
|
+
const rec = recordIssue({
|
|
78
|
+
source: 'agent',
|
|
79
|
+
kind,
|
|
80
|
+
toolName: toolName || null,
|
|
81
|
+
summary,
|
|
82
|
+
detail,
|
|
83
|
+
expectation,
|
|
84
|
+
projectId,
|
|
85
|
+
sessionId,
|
|
86
|
+
userId: projectId ? getProject(projectId)?.ownerId : null,
|
|
87
|
+
// 按"摘要 + 工具"归一化:同一个抱怨反复出现是累加计数,不是刷屏
|
|
88
|
+
signature: signatureOf(`${toolName || ''}|${summary}`),
|
|
89
|
+
});
|
|
90
|
+
if (!rec) {
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: 'text', text: 'Could not write the report (logged server-side). Carry on with the task.' }],
|
|
93
|
+
isError: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
ctx?.emit?.({ type: 'run.friction_reported', kind, summary, toolName: toolName || null, count: rec.count });
|
|
98
|
+
} catch { /* emit fail-safe */ }
|
|
99
|
+
return {
|
|
100
|
+
content: [{
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: rec.count > 1
|
|
103
|
+
? `Logged (this is the ${rec.count}th time this one has come up). Now carry on and finish the task.`
|
|
104
|
+
: 'Logged. Now carry on and finish the task.',
|
|
105
|
+
}],
|
|
106
|
+
};
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: 'text', text: `report_issue failed: ${err?.message || String(err)}` }],
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
}
|