better-dsh 0.2.4 → 0.2.5-b
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/THIRD_PARTY_NOTICES.md +49 -0
- package/cordis.patch.yml +58 -16
- package/docs/10_plans/2026-09-23-remote-execution-framework.md +236 -0
- package/docs/10_plans/2026-09-23-remote-framework-impl.md +2083 -0
- package/docs/10_plans/2026-09-26-lsp-ast-reminder.md +64 -0
- package/docs/20_specs/agent/spec.md +54 -0
- package/docs/20_specs/ast/spec.md +34 -0
- package/docs/20_specs/compaction-recall/spec.md +46 -0
- package/docs/20_specs/ctx/spec.md +107 -0
- package/docs/20_specs/dsh/spec.md +47 -0
- package/docs/20_specs/dvc/spec.md +87 -0
- package/docs/20_specs/escalation-guidance/spec.md +44 -0
- package/docs/20_specs/fs-scheme-resolution/spec.md +37 -0
- package/docs/20_specs/hash-edit/spec.md +41 -0
- package/docs/20_specs/http-read/spec.md +73 -0
- package/docs/20_specs/kernel-provisioning/spec.md +53 -0
- package/docs/20_specs/lsp/spec.md +121 -0
- package/docs/20_specs/mobile-layout/spec.md +108 -0
- package/docs/20_specs/model-failover/spec.md +20 -0
- package/docs/20_specs/plugins-page-components/spec.md +79 -0
- package/docs/20_specs/preact-ui-shell/spec.md +22 -0
- package/docs/20_specs/repl-dispatch-resilience/spec.md +21 -0
- package/docs/20_specs/skill/spec.md +58 -0
- package/docs/20_specs/tool-surface/spec.md +222 -0
- package/docs/20_specs/url-schema/spec.md +148 -0
- package/docs/20_specs/web-trust-fence/spec.md +43 -0
- package/docs/50_test-reports/2026-09-17-plugins/351/241/265/345/205/255/347/273/204/344/273/266/346/213/206/345/210/206/345/256/236/346/265/213/346/212/245/345/221/212.md +76 -0
- package/docs/50_test-reports/2026-09-22-4988/345/217/214/345/271/263/351/235/242/345/220/257/345/212/250/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +76 -0
- package/docs/50_test-reports/2026-09-22-better-dsh/347/273/204/344/273/266/347/203/255/346/217/222/346/213/224/344/270/216/344/276/235/350/265/226/347/272/247/350/201/224/345/256/236/346/265/213/346/212/245/345/221/212.md +134 -0
- package/docs/50_test-reports/2026-09-23-remote-framework/345/256/236/346/265/213/346/212/245/345/221/212.md +92 -0
- package/docs/50_test-reports/2026-09-23-remote-framework/347/254/254/344/270/200/344/272/272/347/247/260/345/244/215/346/265/213/346/212/245/345/221/212.md +125 -0
- package/docs/50_test-reports/2026-09-23-ssh/345/267/245/345/205/267assisted-pty/344/274/232/350/257/235framing/345/256/236/346/265/213/346/212/245/345/221/212.md +207 -0
- package/docs/50_test-reports/2026-09-24-v0.1.7-rc.1/345/257/271/351/275/220/350/275/256-compaction/344/270/216failover/350/256/276/347/275/256/351/235/242/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +65 -0
- package/docs/50_test-reports/2026-09-27-lsp-ast-reminder-4999/345/256/236/346/265/213/346/212/245/345/221/212.md +53 -0
- package/docs/50_test-reports/2026-09-28-ctx-scheme/351/225/277/344/274/232/350/257/235utility/345/256/236/346/265/213/346/212/245/345/221/212.md +155 -0
- package/docs/50_test-reports/2026-10-01-v0.2.4d-web-password-gate/345/256/236/346/265/213/346/212/245/345/221/212.md +105 -0
- package/docs/50_test-reports/2026-10-02-better-dsh-schemastery/344/276/235/350/265/226/345/243/260/346/230/216/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +62 -0
- package/docs/50_test-reports/2026-10-02-ctx-navigation/345/256/236/346/265/213/346/212/245/345/221/212.md +137 -0
- package/docs/50_test-reports/2026-10-02-v0.2.0-rc.2/345/257/271/351/275/220/350/275/256/345/256/236/346/265/213/346/212/245/345/221/212.md +38 -0
- package/docs/50_test-reports/2026-10-03-better-dsh-tool-surface-repl-dvc-browser-ast-lsp-4999/345/256/236/346/265/213/346/212/245/345/221/212.md +185 -0
- package/docs/50_test-reports/2026-10-03-physical-import/344/270/216llm-completion-ast/347/233/270/345/257/271/350/267/257/345/276/204/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +101 -0
- package/docs/50_test-reports/2026-10-03-schemastery-cosmokit/346/272/220/347/240/201/345/206/205/350/201/224/345/256/236/346/265/213/346/212/245/345/221/212.md +63 -0
- package/docs/50_test-reports/2026-10-03-zero-npm-runtime-deps/345/256/236/346/265/213/346/212/245/345/221/212.md +82 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/MdxPage-CTY7atqe.js +11 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/extract_omp.mjs +42 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/fetch_pi.py +52 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/jsx-runtime-BHwPObl3.js +13 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/omp_extracted.json +1 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/omp_pages.txt +35 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/package.json +1 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/pi_convert.py +49 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/pi_pages.txt +30 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/.tools/serialize_omp.py +250 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/README.md +80 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/Overview.md +88 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/acp.md +151 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/advisor.md +185 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/agents-and-roles.md +168 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/approvals.md +200 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/cli.md +381 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/code-intelligence.md +225 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/collab.md +207 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/commit.md +169 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/compaction.md +195 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/computer.md +224 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/context-files.md +184 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/custom-models.md +284 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/custom-tools.md +248 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/debugging.md +254 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/editing.md +157 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/env.md +552 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/extension-authoring.md +378 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/files.md +144 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/github.md +175 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/goal.md +156 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/handoff.md +139 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/hooks.md +257 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/keybindings.md +204 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/magic-keywords.md +135 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/marketplace.md +269 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/mcp-authoring.md +243 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/mcp.md +260 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/memory.md +306 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/modes.md +214 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/pages.txt +56 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/plan.md +126 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/plugins.md +287 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/prewalk.md +122 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/prompt-templates.md +149 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/providers.md +229 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/quickstart.md +117 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/review.md +248 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/roles.md +247 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/rpc.md +523 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/sdk.md +260 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/secrets.md +199 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/security.md +260 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/session-format.md +254 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/session-tree.md +135 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/sessions.md +223 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/settings.md +290 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/skills.md +187 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/slash.md +328 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/subagent-authoring.md +165 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/subagents.md +149 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/themes.md +230 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/tools.md +120 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/ttsr.md +242 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/using.md +214 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/vibe.md +187 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/omp/web.md +233 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/compaction.md +427 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/containerization.md +162 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/custom-provider.md +782 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/development.md +96 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/environment-variables.md +104 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/extensions.md +3058 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/index.md +89 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/json.md +105 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/keybindings.md +244 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/llama-cpp.md +106 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/models.md +587 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/packages.md +234 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/pages.txt +30 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/prompt-templates.md +101 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/providers.md +327 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/quickstart.md +172 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/rpc.md +1679 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/sdk.md +1239 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/security.md +64 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/session-format.md +454 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/sessions.md +150 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/settings.md +374 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/shell-aliases.md +18 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/skills.md +241 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/terminal-setup.md +199 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/termux.md +135 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/themes.md +328 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/tmux.md +68 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/tui.md +965 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/usage.md +315 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/pi-and-omp/pi/windows.md +44 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/SKILL.md +147 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch01-user-guide.md +123 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch02-plugin-basics.md +127 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch03-plugin-framework-practice.md +120 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch04-cordis-fundamentals.md +129 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch05-cordis-api.md +115 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch06-architecture.md +109 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch07-capability-seams.md +97 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch08-internals-pipelines.md +111 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch09-api-gateway.md +106 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch10-config-catalog.md +97 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch11-tool-catalog.md +121 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch12-persistence-catalog.md +109 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch13-subsystems-core-session.md +106 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch14-subsystems-agent-loop.md +111 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch15-subsystems-tool-execution.md +106 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch16-subsystems-session-data.md +132 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch17-subsystems-llm-streaming.md +128 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch18-subsystems-web-ui.md +142 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch19-cookbook.md +149 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch20-development-testing.md +87 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch21-postmortems.md +82 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch22-i18n.md +86 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/cheatsheet.md +67 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/glossary.md +57 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/patterns.md +88 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/AGENTS.md +75 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/agent-lifecycle.md +84 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/agent-lifecycle.zh.md +86 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/api-gateway.md +164 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/api-gateway.zh.md +164 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/architecture.md +150 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/architecture.zh.md +154 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/capability-seams.md +543 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/capability-seams.zh.md +545 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/config-catalog.md +3473 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/config-catalog.zh.md +3474 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-package.md +117 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-package.zh.md +119 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-remote-api.md +197 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-remote-api.zh.md +197 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-settings-card.md +102 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-settings-card.zh.md +102 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-tool.md +101 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-tool.zh.md +103 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-vendored-package.md +59 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-vendored-package.zh.md +59 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-an-llm-adapter.md +43 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-an-llm-adapter.zh.md +43 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/extension-cookbook.md +132 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/extension-cookbook.zh.md +136 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/maintaining-dsh-code-review.md +64 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/maintaining-dsh-code-review.zh.md +64 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/context.md +364 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/context.zh.md +366 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/events.md +207 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/events.zh.md +209 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/fiber.md +375 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/fiber.zh.md +377 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/inherited.md +39 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/registry.md +152 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/registry.zh.md +154 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/service.md +102 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/service.zh.md +104 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-primer.md +45 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-primer.zh.md +51 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/01-first-plugin.md +95 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/01-first-plugin.zh.md +95 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/02-lifecycle-and-effects.zh.md +98 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/03-services.md +98 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/03-services.zh.md +98 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/04-events.md +144 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/04-events.zh.md +144 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/05-config.md +84 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/05-config.zh.md +84 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/06-composition-and-hmr.zh.md +113 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/07-into-the-harness.md +108 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/07-into-the-harness.zh.md +108 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/index.md +60 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/index.zh.md +62 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/deepseek-llm-api-wire-extensions.md +163 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/deepseek-llm-api-wire-extensions.zh.md +163 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/defensive-patterns.md +33 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/defensive-patterns.zh.md +35 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/development.md +167 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/development.zh.md +173 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/event-producer-consumer.md +86 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/event-producer-consumer.zh.md +88 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/glossary.md +45 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/glossary.zh.md +45 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/graph-atlas.md +22 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/graph-atlas.zh.md +24 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/README.md +60 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/README.zh.md +62 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/style-samples.md +87 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/terminology.md +214 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/translation-prompt.md +263 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/translation-rules.md +69 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/translation-rules.zh.md +69 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/module-graph.md +1411 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/module-graph.zh.md +1413 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/persistence-catalog.md +1075 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/persistence-catalog.zh.md +1077 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0001-acp-default-export-drops-inject.md +113 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/README.md +18 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/README.zh.md +18 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/rescope.md +53 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/rescope.zh.md +53 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/README.md +61 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/README.zh.md +61 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/agent-team.md +207 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/agent-team.zh.md +207 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/approval.md +170 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/approval.zh.md +170 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/attachment.md +351 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/attachment.zh.md +351 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/client-modules.md +168 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/client-modules.zh.md +168 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/code-runtime.md +195 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/code-runtime.zh.md +195 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/commands.md +219 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/commands.zh.md +219 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/compaction.md +238 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/compaction.zh.md +238 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/conversation.md +258 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/conversation.zh.md +258 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/core.md +1209 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/core.zh.md +1219 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/credentials.md +329 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/credentials.zh.md +329 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/extensions.md +382 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/extensions.zh.md +382 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/feedback.md +266 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/feedback.zh.md +266 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/filesystem.md +505 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/filesystem.zh.md +505 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/goal.md +277 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/goal.zh.md +277 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/invariants.md +88 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/invariants.zh.md +88 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/jobs.md +290 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/jobs.zh.md +290 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/llm-streaming.md +1080 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/llm-streaming.zh.md +1086 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/lsp.md +202 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/lsp.zh.md +202 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/permission-presets.md +131 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/permission-presets.zh.md +131 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/persistence.md +395 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/persistence.zh.md +395 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/plan.md +87 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/plan.zh.md +87 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/sandbox.md +220 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/sandbox.zh.md +220 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/schedule.md +192 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/schedule.zh.md +192 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/scope.md +59 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/scope.zh.md +59 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-projection.md +354 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-projection.zh.md +354 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-query.md +509 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-query.zh.md +509 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-reference.md +219 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-reference.zh.md +219 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-telemetry.md +194 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-telemetry.zh.md +194 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-title.md +204 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-title.zh.md +204 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session.md +1155 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session.zh.md +1159 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/settings.md +405 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/settings.zh.md +405 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/shell.md +303 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/shell.zh.md +303 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/skills.md +354 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/skills.zh.md +354 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/slots.md +175 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/slots.zh.md +175 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/spill.md +117 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/spill.zh.md +117 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/storage.md +260 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/storage.zh.md +260 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subagent.md +766 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subagent.zh.md +770 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subprocess.md +324 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subprocess.zh.md +324 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/system-prompt.md +220 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/system-prompt.zh.md +220 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/terminal.md +184 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/terminal.zh.md +184 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/todo.md +32 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/todo.zh.md +32 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/token-meter.md +105 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/token-meter.zh.md +105 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/tools.md +720 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/tools.zh.md +720 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/typert.md +343 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/typert.zh.md +343 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/user-questions.md +178 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/user-questions.zh.md +178 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-client.md +95 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-client.zh.md +95 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-server.md +154 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-server.zh.md +154 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web.md +206 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web.zh.md +206 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/webhook.md +70 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/webhook.zh.md +70 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workflow.md +278 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workflow.zh.md +278 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workspace.md +321 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workspace.zh.md +321 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/testing.md +54 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/testing.zh.md +54 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-catalog.md +2225 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-catalog.zh.md +2233 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-execution-pipeline.md +62 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-execution-pipeline.zh.md +64 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/config.md +106 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/config.zh.md +106 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/index.md +144 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/index.zh.md +144 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/publish.md +183 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/publish.zh.md +183 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/tool.md +52 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/tool.zh.md +52 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/events.md +143 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/events.zh.md +143 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/index.md +137 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/index.zh.md +137 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/service.md +148 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/service.zh.md +150 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/dynamic-cordis.md +15 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/dynamic-cordis.zh.md +15 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/index.md +155 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/index.zh.md +155 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/llm-adapter.md +189 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/llm-adapter.zh.md +189 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/github-review.md +102 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/github-review.zh.md +102 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/index.md +30 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/index.zh.md +30 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/mcp-memory.md +101 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/mcp-memory.zh.md +101 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/network-proxy.md +85 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/network-proxy.zh.md +85 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/providers.md +190 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/providers.zh.md +190 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/python-sdk.md +150 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/python-sdk.zh.md +150 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/schedule.md +21 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/schedule.zh.md +21 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/index.md +11 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/index.zh.md +11 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/web-styling.md +29 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/web-styling.zh.md +29 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/site-pages-map.txt +234 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/site-pages.json +232 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src-README.md +63 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src-scripts/crawl.py +103 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src-scripts/docs-tree.json +82965 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src-scripts/download.py +56 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src-scripts/gen_mapping.py +125 -0
- package/docs/80_upstream-docs-and-skills/dsh-dev-skill/src-scripts/pages.json +232 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/api-plugins.json +1 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/api-skills.json +4 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/_category_.json +8 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/acp-internals.md +181 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/adding-platform-adapters.md +788 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/adding-providers.md +464 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/adding-tools.md +211 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/agent-loop.md +242 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/architecture.md +279 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/billing-lifecycle.md +184 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/browser-provider-plugin.md +153 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/browser-supervisor.md +198 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/chronos-managed-cron-contract.md +227 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/cli-internals.md +85 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/codebase-ownership.md +34 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/completion-backlog-delivery.md +52 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/context-compression-and-caching.md +642 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/context-engine-plugin.md +272 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/contributing.md +303 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/creating-skills.md +438 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/cron-internals.md +380 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/desktop-plugin-sdk.md +952 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/egress-internals.md +319 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/extending-the-cli.md +192 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/gateway-internals.md +297 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/gateway-monitoring.md +309 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/gateway-session-lifecycle.md +621 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/image-gen-provider-plugin.md +316 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/memory-provider-plugin.md +452 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/micro-compaction.md +406 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/middleware.md +266 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/model-provider-plugin.md +327 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/multiplexing-gateway.md +228 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/observer-hooks.md +331 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/plugin-llm-access.md +506 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/plugins/index.md +1775 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/programmatic-integration.md +212 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/prompt-assembly.md +328 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/provider-runtime.md +222 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/relay-connector-contract.md +790 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/relay-shared-metrics.md +487 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/secret-source-plugin.md +192 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/session-storage.md +523 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/state-db-recovery.md +107 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/streaming-tts.md +98 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/subagent-lifecycle-api.md +61 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/terminal-environment-plugin.md +140 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/tools-runtime.md +250 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/trajectory-format.md +232 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/video-gen-provider-plugin.md +231 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/web-search-provider-plugin.md +260 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/developer-guide/worktree-ui-dev.md +168 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/_category_.json +8 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/installation.md +200 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/learning-path.md +169 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/nix-setup.md +1234 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/platform-support.md +50 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/quickstart.md +399 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/termux.md +301 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/getting-started/updating.md +374 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/_category_.json +6 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/agent-email-address.md +93 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/automate-with-cron.md +292 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/automation-blueprints.md +595 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/aws-bedrock.md +196 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/azure-foundry.md +334 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/cron-script-only.md +247 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/cron-troubleshooting.md +235 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/daily-briefing-bot.md +272 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/delegation-patterns.md +242 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/desktop-native-signin.md +125 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/github-pr-review-agent.md +303 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/google-gemini.md +270 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/google-vertex.md +148 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/local-llm-on-mac.md +252 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/local-ollama-setup.md +338 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/manage-hermes-cloud-with-mcp.md +176 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/microsoft-graph-app-registration.md +180 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/migrate-from-openclaw.md +252 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/minimax-oauth.md +226 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/oauth-over-ssh.md +172 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/operate-teams-meeting-pipeline.md +295 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/pipe-script-output.md +249 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/python-library.md +333 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/run-hermes-with-nous-portal.md +276 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/run-nemotron-3-ultra-free.md +123 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/secure-hermes-on-a-work-machine.md +182 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/team-telegram-assistant.md +441 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/tips.md +238 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/troubleshooting-agent-quality.md +145 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/use-mcp-with-hermes.md +541 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/use-soul-with-hermes.md +264 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/use-voice-mode-with-hermes.md +473 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/webhook-github-pr-review.md +336 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/work-with-skills.md +290 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/guides/xai-grok-oauth.md +243 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/index.mdx +155 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/integrations/buzz.md +57 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/integrations/index.md +126 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/integrations/nous-portal.md +286 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/integrations/providers.md +1681 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/_category_.json +8 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/automation-blueprints-catalog.mdx +36 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/cli-commands.md +1900 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/cli-symbols.md +126 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/environment-variables.md +952 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/faq.md +874 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/mcp-config-reference.md +439 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/model-catalog.md +121 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/optional-skills-catalog.md +303 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/profile-commands.md +544 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/skills-catalog.md +132 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/slash-commands.md +336 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/tools-reference.md +399 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/reference/toolsets-reference.md +171 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/_category_.json +8 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/bot-mode.md +309 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/checkpoints-and-rollback.md +283 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/cli.md +591 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/configuration.md +2901 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/configuring-models.md +414 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/desktop.md +728 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/docker.md +896 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/egress/index.md +10 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/egress/iron-proxy.md +570 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/egress/network-isolation.md +201 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/_category_.json +8 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/acp.md +407 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/api-server.md +810 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/batch-processing.md +230 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/browser.md +901 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/built-in-plugins.md +366 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/code-execution.md +319 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/codex-app-server-runtime.md +460 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/computer-use.md +636 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/context-files.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/context-references.md +142 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/credential-pools.md +308 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/credential-vault.md +122 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/cron.md +1262 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/curator.md +409 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/delegation.md +664 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/deliverable-mode.md +134 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/document-extraction.md +58 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/extending-the-dashboard.md +927 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/fallback-providers.md +444 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/goals.md +293 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/heartbeat.md +68 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/honcho.md +276 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/hooks.md +2007 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/image-generation.md +347 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/kanban-multi-gateway.md +52 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/kanban-tutorial.md +310 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/kanban-worker-lanes.md +142 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/kanban.md +1327 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/loops.md +122 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/lsp.md +307 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/mcp.md +957 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/memory-providers.md +699 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/memory.md +466 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/mixture-of-agents.md +246 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/overview.md +57 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/personality.md +289 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/pets.md +236 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/plugin-catalog.md +212 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/plugins.md +847 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/provider-routing.md +229 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/skills.md +1112 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/skins.md +271 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/spotify.md +279 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/subscription-proxy.md +203 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/tool-gateway.md +223 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/tool-search.md +277 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/tools.md +261 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/tts.md +744 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/vision.md +214 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/voice-mode.md +608 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/wake-word.md +343 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/web-dashboard.md +1206 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/web-search.md +506 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/features/x-search.md +167 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/git-worktrees.md +196 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/import-from-other-agents.md +74 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/local-models.md +140 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/managed-scope.md +157 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/_category_.json +8 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/a2a.md +130 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/bluebubbles.md +171 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/buzz.md +162 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/dingtalk.md +283 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/discord.md +936 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/email.md +223 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/feishu.md +599 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/google_chat.md +430 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/homeassistant.md +269 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/index.md +859 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/irc.md +78 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/line.md +201 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/matrix.md +910 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/mattermost.md +340 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/msgraph-webhook.md +142 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/ntfy.md +157 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/open-webui.md +296 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/photon.md +255 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/qqbot.md +123 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/raft.md +70 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/relay.md +212 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/signal.md +262 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/simplex.md +151 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/slack.md +1093 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/sms.md +203 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/teams-meetings.md +242 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/teams.md +288 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/telegram.md +1373 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/webhooks.md +694 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/wecom-callback.md +179 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/wecom.md +306 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/weixin.md +333 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/whatsapp-cloud.md +417 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/whatsapp.md +283 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/messaging/yuanbao.md +342 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/multi-connection-desktop.md +361 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/multi-profile-gateways.md +1062 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/profile-distributions.md +757 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/profiles.md +418 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/secrets/bitwarden.md +155 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/secrets/command.md +50 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/secrets/index.md +50 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/secrets/onepassword.md +169 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/security.md +892 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/sessions.md +990 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/apple/apple-apple-notes.md +110 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/apple/apple-apple-reminders.md +146 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/apple/apple-findmy.md +149 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/apple/apple-imessage.md +118 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-claude-code.md +763 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-codex.md +169 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-computer-use.md +355 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md +230 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-merge-reconciler.md +176 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-opencode.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-architecture-diagram.md +165 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-ascii-art.md +338 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-ascii-video.md +265 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-baoyu-infographic.md +256 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-claude-design.md +668 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-comfyui.md +616 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-design-md.md +238 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-excalidraw.md +212 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-humanizer.md +663 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-manim-video.md +292 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-p5js.md +576 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-popular-web-designs.md +223 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-pretext.md +238 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-sketch.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/creative/creative-songwriting-and-ai-music.md +319 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/devops/devops-sdlc-review.md +195 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/email/email-email-inbox-triage.md +115 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/email/email-himalaya.md +319 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/github/github-codebase-inspection.md +132 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/github/github-github-auth.md +265 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/github/github-github-code-review.md +499 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/github/github-github-issue-to-pr.md +95 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/github/github-github-issues.md +388 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/github/github-github-pr-workflow.md +385 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/github/github-github-repo-management.md +534 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/media/media-gif-search.md +106 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/media/media-songsee.md +98 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/media/media-youtube-content.md +100 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/mlops/mlops-evaluation-evaluating-llms-harness.md +512 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/mlops/mlops-evaluation-weights-and-biases.md +613 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/mlops/mlops-huggingface-hub.md +100 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/mlops/mlops-inference-llama-cpp.md +267 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/mlops/mlops-inference-serving-llms-vllm.md +387 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/note-taking/note-taking-obsidian.md +85 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-airtable.md +243 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-box.md +132 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-document-to-action-items.md +99 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-docx.md +213 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-google-workspace.md +348 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-maps.md +210 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-meeting-action-items.md +105 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-nano-pdf.md +70 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-notion.md +463 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-ocr-and-documents.md +193 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-pdf.md +142 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-powerpoint.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-product-price-monitor.md +97 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-session-librarian.md +122 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-teams-meeting-pipeline.md +129 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-weekly-review-planning.md +99 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/productivity/productivity-xlsx.md +213 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/research/research-arxiv.md +300 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/research/research-blocked-page-recovery.md +155 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/research/research-blogwatcher.md +152 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/research/research-competitor-news-monitor.md +106 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/research/research-grounded-citations.md +274 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/research/research-llm-wiki.md +526 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/research/research-research-paper-writing.md +1645 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/smart-home/smart-home-openhue.md +127 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/social-media/social-media-xurl.md +450 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-codebase-inspection.md +132 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-dogfood.md +183 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-github.md +73 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-hermes-agent-skill-authoring.md +230 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-inspecting-hermes-desktop-dom.md +177 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-node-inspect-debugger.md +337 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-python-debugpy.md +391 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-requesting-code-review.md +298 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-simplify-code.md +288 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-spike.md +217 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-systematic-debugging.md +429 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/software-development/software-development-test-driven-development.md +380 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/bundled/web/web-blocked-page-recovery.md +155 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/google-workspace.md +191 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-antigravity-cli.md +259 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-blackbox.md +161 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-dynamic-workflow.md +198 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-grok.md +326 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-honcho.md +446 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-openhands.md +167 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/blockchain/blockchain-evm.md +227 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/blockchain/blockchain-hyperliquid.md +228 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/blockchain/blockchain-solana.md +225 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/communication/communication-one-three-one-rule.md +114 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-ai-presenter-video.md +211 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-archify.md +66 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-ascii-art.md +338 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-audiocraft-audio-generation.md +588 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-auteur.md +195 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-baoyu-article-illustrator.md +225 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-baoyu-comic.md +264 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-comfyui.md +616 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-concept-diagrams.md +379 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-creative-ideation.md +192 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-draw-your-font.md +169 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-dream-loop.md +297 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-excalidraw.md +212 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-heartmula.md +191 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-hyperframes.md +222 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-impeccable.md +56 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-ip-as-logo.md +171 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-kanban-video-orchestrator.md +220 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-meme-generation.md +147 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-mono-color.md +158 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-pixel-art.md +234 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-pretext.md +238 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-simple-english.md +362 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-sketch.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-social-media-content-calendar.md +88 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-system-atlas.md +105 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-tldraw-offline.md +294 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/creative/creative-unreal-mcp.md +269 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/data-science/data-science-jupyter-notebook.md +195 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/devops/devops-actual-setup.md +164 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/devops/devops-docker-management.md +298 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/devops/devops-hermes-s6-container-supervision.md +198 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/devops/devops-inference-sh-cli.md +173 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/devops/devops-pinggy-tunnel.md +326 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/devops/devops-setup-wizard-generator.md +133 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/devops/devops-watchers.md +126 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/dogfood/dogfood-adversarial-ux-test.md +209 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/email/email-agentmail.md +104 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-3-statement-model.md +451 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-comps-analysis.md +682 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-dcf-model.md +1288 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-excel-author.md +262 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-lbo-model.md +309 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-merger-model.md +162 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-polymarket.md +97 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-pptx-author.md +191 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/finance/finance-stocks.md +112 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/gaming/gaming-minecraft-modpack-server.md +209 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/gaming/gaming-pokemon-player.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/health/health-fitness-nutrition.md +259 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/health/health-neuroskill-bci.md +470 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mcp/mcp-fastmcp.md +315 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mcp/mcp-mcp-oauth-remote-gateway.md +389 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mcp/mcp-mcporter.md +138 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/migration/migration-openclaw-migration.md +316 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-accelerate.md +366 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-chroma.md +425 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-clip.md +272 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-evaluation-evaluating-llms-harness.md +512 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-evaluation-weights-and-biases.md +613 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-faiss.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-flash-attention.md +388 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-guidance.md +595 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-huggingface-tokenizers.md +535 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-inference-llama-cpp.md +267 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-inference-outlines.md +679 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-inference-serving-llms-vllm.md +387 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-instructor.md +759 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-lambda-labs.md +568 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-llava.md +323 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-modal.md +368 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-models-huggingface-hub.md +100 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-models-segment-anything-model.md +525 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-nemo-curator.md +415 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-obliteratus.md +360 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-peft.md +452 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-pinecone.md +396 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-pytorch-fsdp.md +97 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-pytorch-lightning.md +365 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-qdrant.md +522 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-research-dspy.md +609 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-saelens.md +439 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-simpo.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-slime.md +486 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-stable-diffusion.md +542 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-tensorrt-llm.md +208 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-torchtitan.md +404 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-training-axolotl.md +181 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-training-trl-fine-tuning.md +512 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-training-unsloth.md +98 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/mlops/mlops-whisper.md +336 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/payments/payments-mpp-agent.md +142 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/payments/payments-stripe-link-cli.md +202 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/payments/payments-stripe-projects.md +138 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-canvas.md +114 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-decision-questionnaire.md +133 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-here-now.md +231 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-live-dashboard.md +126 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-memento-flashcards.md +336 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-property-listings.md +121 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-shop.md +238 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-shopify.md +377 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-siyuan.md +305 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/productivity/productivity-telephony.md +435 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-bioinformatics.md +254 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-blogwatcher.md +162 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-darwinian-evolver.md +217 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-domain-intel.md +121 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-drug-discovery.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-duckduckgo-search.md +255 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-gitnexus-explorer.md +232 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-osint-investigation.md +295 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-parallel-cli.md +411 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-pinecone-research.md +125 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-qmd.md +459 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-research-paper-writing.md +1645 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-rss-feeds.md +114 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-scrapling.md +351 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/research/research-searxng-search.md +212 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/security/security-1password.md +173 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/security/security-godmode.md +421 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/security/security-oss-forensics.md +426 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/security/security-sherlock.md +208 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/security/security-unbroker.md +331 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/security/security-web-pentest.md +337 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/smart-home/smart-home-openhue.md +127 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/social-media/social-media-reddit-reading.md +146 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/software-development/software-development-ast-grep.md +307 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/software-development/software-development-code-wiki.md +463 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/software-development/software-development-grill-me.md +135 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/software-development/software-development-pr-lens.md +160 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/software-development/software-development-rest-graphql-debug.md +532 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/software-development/software-development-subagent-driven-development.md +370 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/web-development/web-development-cloudflare-temporary-deploy.md +144 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/web-development/web-development-har-derived-api-client.md +180 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/web-development/web-development-page-agent.md +207 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/web-development/web-development-publish-site.md +174 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/web-development/web-development-scrollcraft.md +257 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/skills/optional/yuanbao/yuanbao-yuanbao.md +127 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/tui.md +313 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/which-file-does-what.md +57 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/windows-native.md +326 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-guide/windows-wsl-quickstart.md +358 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docs/user-stories.mdx +10 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/docusaurus.config.ts +210 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/acp-internals.md +181 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/adding-platform-adapters.md +686 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/adding-providers.md +459 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/adding-tools.md +209 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/agent-loop.md +239 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/architecture.md +277 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/browser-supervisor.md +160 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/context-compression-and-caching.md +328 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/context-engine-plugin.md +193 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/contributing.md +253 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/creating-skills.md +375 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/cron-internals.md +236 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/extending-the-cli.md +192 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/gateway-internals.md +267 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/image-gen-provider-plugin.md +288 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/memory-provider-plugin.md +258 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/model-provider-plugin.md +267 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/plugin-llm-access.md +401 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/plugins/index.md +1156 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/programmatic-integration.md +126 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/prompt-assembly.md +273 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/provider-runtime.md +208 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/session-storage.md +420 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/tools-runtime.md +236 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/trajectory-format.md +222 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/video-gen-provider-plugin.md +231 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/developer-guide/web-search-provider-plugin.md +260 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/getting-started/installation.md +204 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/getting-started/learning-path.md +154 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/getting-started/nix-setup.md +975 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/getting-started/quickstart.md +352 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/getting-started/termux.md +236 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/getting-started/updating.md +231 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/automate-with-cron.md +266 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/automation-blueprints.md +595 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/aws-bedrock.md +170 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/azure-foundry.md +334 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/cron-script-only.md +247 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/cron-troubleshooting.md +227 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/daily-briefing-bot.md +268 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/delegation-patterns.md +239 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/github-pr-review-agent.md +303 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/google-gemini.md +254 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/local-llm-on-mac.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/local-ollama-setup.md +317 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/microsoft-graph-app-registration.md +180 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/migrate-from-openclaw.md +244 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/minimax-oauth.md +228 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/oauth-over-ssh.md +96 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/operate-teams-meeting-pipeline.md +288 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/pipe-script-output.md +217 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/python-library.md +333 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/run-hermes-with-nous-portal.md +273 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/team-telegram-assistant.md +441 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/tips.md +234 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/use-mcp-with-hermes.md +490 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/use-soul-with-hermes.md +264 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/use-voice-mode-with-hermes.md +469 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/webhook-github-pr-review.md +329 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/work-with-skills.md +290 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/guides/xai-grok-oauth.md +243 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/index.mdx +117 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/integrations/buzz.md +57 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/integrations/index.md +100 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/integrations/nous-portal.md +272 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/integrations/providers.md +1433 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/cli-commands.md +1259 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/environment-variables.md +678 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/faq.md +859 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/mcp-config-reference.md +250 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/model-catalog.md +103 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/optional-skills-catalog.md +220 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/profile-commands.md +467 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/skills-catalog.md +156 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/slash-commands.md +272 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/tools-reference.md +261 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/reference/toolsets-reference.md +162 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/checkpoints-and-rollback.md +251 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/cli.md +445 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/configuration.md +1746 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/configuring-models.md +239 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/docker.md +595 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/acp.md +341 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/api-server.md +456 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/batch-processing.md +229 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/browser.md +629 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/built-in-plugins.md +269 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/code-execution.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/codex-app-server-runtime.md +441 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/computer-use.md +144 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/context-files.md +218 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/context-references.md +142 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/credential-pools.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/cron.md +761 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/curator.md +248 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/delegation.md +355 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/deliverable-mode.md +91 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/extending-the-dashboard.md +907 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/fallback-providers.md +408 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/goals.md +180 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/honcho.md +233 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/hooks.md +1388 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/image-generation.md +150 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/kanban-tutorial.md +308 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/kanban-worker-lanes.md +117 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/kanban.md +863 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/lsp.md +217 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/mcp.md +591 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/memory-providers.md +570 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/memory.md +233 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/overview.md +52 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/personality.md +271 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/plugins.md +346 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/provider-routing.md +200 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/skills.md +760 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/skins.md +271 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/spotify.md +279 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/subscription-proxy.md +163 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/tool-gateway.md +187 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/tools.md +223 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/tts.md +456 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/vision.md +210 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/voice-mode.md +521 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/web-dashboard.md +351 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/web-search.md +422 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/features/x-search.md +147 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/git-worktrees.md +173 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/bluebubbles.md +143 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/dingtalk.md +283 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/discord.md +843 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/email.md +190 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/feishu.md +538 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/google_chat.md +283 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/homeassistant.md +252 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/index.md +569 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/line.md +199 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/matrix.md +677 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/mattermost.md +340 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/msgraph-webhook.md +137 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/ntfy.md +155 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/open-webui.md +296 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/qqbot.md +123 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/signal.md +257 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/simplex.md +98 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/slack.md +614 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/sms.md +203 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/teams-meetings.md +233 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/teams.md +255 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/telegram.md +1241 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/webhooks.md +601 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/wecom-callback.md +149 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/wecom.md +292 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/weixin.md +312 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/whatsapp.md +236 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/messaging/yuanbao.md +341 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/profile-distributions.md +573 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/profiles.md +272 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/secrets/bitwarden.md +143 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/secrets/index.md +9 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/security.md +664 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/sessions.md +609 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/apple/apple-apple-notes.md +106 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/apple/apple-apple-reminders.md +114 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/apple/apple-findmy.md +147 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/apple/apple-imessage.md +118 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/apple/apple-macos-computer-use.md +175 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-claude-code.md +763 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-codex.md +143 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md +947 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-opencode.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-architecture-diagram.md +165 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-ascii-art.md +338 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-ascii-video.md +261 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-baoyu-infographic.md +256 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-claude-design.md +609 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-comfyui.md +547 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-design-md.md +189 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-excalidraw.md +210 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-humanizer.md +594 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-manim-video.md +289 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-p5js.md +574 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-popular-web-designs.md +211 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-pretext.md +238 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-sketch.md +238 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/creative/creative-songwriting-and-ai-music.md +305 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/email/email-himalaya.md +305 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/github/github-codebase-inspection.md +132 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/github/github-github-auth.md +265 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/github/github-github-code-review.md +499 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/github/github-github-issues.md +388 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/github/github-github-pr-workflow.md +385 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/github/github-github-repo-management.md +534 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/media/media-gif-search.md +106 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/media/media-songsee.md +98 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/media/media-youtube-content.md +93 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/mlops/mlops-evaluation-evaluating-llms-harness.md +512 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/mlops/mlops-evaluation-weights-and-biases.md +609 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/mlops/mlops-huggingface-hub.md +100 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/mlops/mlops-inference-llama-cpp.md +267 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/mlops/mlops-inference-serving-llms-vllm.md +386 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/note-taking/note-taking-obsidian.md +81 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-airtable.md +243 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-google-workspace.md +325 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-maps.md +196 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-nano-pdf.md +69 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-notion.md +463 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-ocr-and-documents.md +190 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-powerpoint.md +257 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/productivity/productivity-teams-meeting-pipeline.md +127 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/research/research-arxiv.md +300 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/research/research-blogwatcher.md +152 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/research/research-llm-wiki.md +469 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/research/research-research-paper-writing.md +2395 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/smart-home/smart-home-openhue.md +124 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/social-media/social-media-xurl.md +428 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-dogfood.md +181 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-hermes-agent-skill-authoring.md +183 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-node-inspect-debugger.md +337 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-python-debugpy.md +391 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-requesting-code-review.md +287 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-spike.md +217 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-systematic-debugging.md +385 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/bundled/software-development/software-development-test-driven-development.md +361 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/google-workspace.md +191 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-blackbox.md +162 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/autonomous-ai-agents/autonomous-ai-agents-honcho.md +446 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/blockchain/blockchain-evm.md +227 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/blockchain/blockchain-hyperliquid.md +210 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/blockchain/blockchain-solana.md +206 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/communication/communication-one-three-one-rule.md +114 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/creative/creative-audiocraft-audio-generation.md +588 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/creative/creative-concept-diagrams.md +379 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/creative/creative-heartmula.md +189 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/creative/creative-hyperframes.md +205 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/creative/creative-kanban-video-orchestrator.md +173 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/creative/creative-meme-generation.md +147 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/data-science/data-science-jupyter-notebook.md +180 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/devops/devops-docker-management.md +297 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/devops/devops-inference-sh-cli.md +173 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/devops/devops-pinggy-tunnel.md +327 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/devops/devops-watchers.md +126 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/dogfood/dogfood-adversarial-ux-test.md +209 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/email/email-agentmail.md +143 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-3-statement-model.md +451 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-comps-analysis.md +682 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-dcf-model.md +1288 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-excel-author.md +262 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-lbo-model.md +309 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-merger-model.md +162 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-polymarket.md +94 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-pptx-author.md +191 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/finance/finance-stocks.md +108 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/health/health-fitness-nutrition.md +255 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/health/health-neuroskill-bci.md +431 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mcp/mcp-fastmcp.md +315 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mcp/mcp-mcporter.md +138 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/migration/migration-openclaw-migration.md +316 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-accelerate.md +350 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-chroma.md +425 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-clip.md +272 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-faiss.md +240 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-flash-attention.md +381 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-guidance.md +591 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-huggingface-tokenizers.md +535 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-inference-outlines.md +671 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-instructor.md +759 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-lambda-labs.md +568 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-llava.md +323 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-modal.md +362 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-models-segment-anything-model.md +525 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-nemo-curator.md +401 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-peft.md +452 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-pinecone.md +377 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-pytorch-fsdp.md +145 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-pytorch-lightning.md +365 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-qdrant.md +514 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-saelens.md +407 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-simpo.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-slime.md +486 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-stable-diffusion.md +542 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-tensorrt-llm.md +206 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-torchtitan.md +378 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-training-axolotl.md +181 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-training-trl-fine-tuning.md +477 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-training-unsloth.md +98 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/mlops/mlops-whisper.md +336 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/productivity/productivity-canvas.md +114 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/productivity/productivity-here-now.md +231 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/productivity/productivity-memento-flashcards.md +336 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/productivity/productivity-shop.md +354 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/productivity/productivity-shopify.md +377 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/productivity/productivity-siyuan.md +305 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/productivity/productivity-telephony.md +435 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-bioinformatics.md +252 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-darwinian-evolver.md +188 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-domain-intel.md +117 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-drug-discovery.md +237 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-duckduckgo-search.md +255 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-gitnexus-explorer.md +213 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-osint-investigation.md +243 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-parallel-cli.md +411 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-qmd.md +435 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-scrapling.md +351 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/research/research-searxng-search.md +229 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/security/security-1password.md +173 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/security/security-oss-forensics.md +421 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/security/security-sherlock.md +208 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/software-development/software-development-rest-graphql-debug.md +531 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/web-development/web-development-page-agent.md +207 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/skills/optional/yuanbao/yuanbao-yuanbao.md +125 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/tui.md +256 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/windows-native.md +325 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-guide/windows-wsl-quickstart.md +332 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/i18n/zh-Hans/user-stories.mdx +10 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/llms-full.txt +88833 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/llms.txt +267 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/site-pages-map.txt +928 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src/site-pages.json +4218 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src-README.md +101 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src-scripts/clone_repo.sh +17 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src-scripts/crawl.py +82 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src-scripts/gen_mapping.py +112 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src-scripts/retry-none.json +305 -0
- package/docs/80_upstream-docs-and-skills/hermes-dev-skill/src-scripts/verify_orphans.py +53 -0
- package/docs/specs/ctx/spec.md +78 -6
- package/docs/specs/model-picker-ios-blur/spec.md +168 -0
- package/docs/specs/plugins-page-components/spec.md +79 -0
- package/docs/specs/zero-npm-runtime-deps/spec.md +85 -0
- package/docs/superpowers/plans/2026-10-02-ctx-navigation.md +319 -0
- package/docs/superpowers/specs/2026-10-02-ctx-navigation-design.md +209 -0
- package/lib/BrowserWebSocketTransport-FbPg_VFT.js +39 -0
- package/lib/BrowserWebSocketTransport-Q5SUMk7_.js +4 -0
- package/lib/LaunchOptions-DlBKKKLC.js +4 -0
- package/lib/LaunchOptions-RNO1YIoJ.js +17 -0
- package/lib/NodeWebSocketTransport-BdrEPUFe.js +3629 -0
- package/lib/NodeWebSocketTransport-DyU2tm5L.js +4 -0
- package/lib/bidi-D0POLRy7.js +16106 -0
- package/lib/chunk-DQxbwPpZ.js +30 -0
- package/lib/client/index.js +20 -20
- package/lib/compaction/index.d.ts +57 -0
- package/lib/compaction/index.js +145 -0
- package/lib/failover/index.d.ts +59 -0
- package/lib/failover/index.js +147 -0
- package/lib/fs-aware/sandbox-plugin.js +22 -22
- package/lib/index-DZQNwO7f.d.ts +208 -0
- package/lib/index-browser-rqlXyoed.js +21061 -0
- package/lib/index.d.ts +5 -36
- package/lib/index.js +503 -8096
- package/lib/kernel-bridge.py +172 -0
- package/lib/kernel-env.js +1 -1
- package/lib/lib-C8_jXvwA.js +894 -0
- package/lib/{lsp-server-registry-DkaYmTwt.js → lsp-server-registry-C05QLdw0.js} +13 -13
- package/lib/{lsp-server-registry-_hk-Wcia.js → lsp-server-registry-LUjrrLxk.js} +1 -1
- package/lib/main-7c69ae95.js +1432 -0
- package/lib/main-Dlv0Jm8U.js +3 -0
- package/lib/mobile/plugin.d.ts +29 -0
- package/lib/mobile/plugin.js +54 -0
- package/lib/native-capture-BEByDXYM.js +68 -0
- package/lib/puppeteer-core-BIIJGWPE.js +1136 -0
- package/lib/py-sdk.js +1 -1
- package/lib/remote/plugin.d.ts +24 -0
- package/lib/remote/plugin.js +1095 -0
- package/lib/url-schemes/index.d.ts +88 -0
- package/lib/url-schemes/index.js +11427 -0
- package/lib/util-DgR7HQUl.js +2953 -0
- package/lib/web-password.d.ts +156 -0
- package/lib/web-password.js +562 -0
- package/lib/web-trust-B27wcZEw.js +361 -0
- package/lib/web-trust-PIXuF0bN.d.ts +106 -0
- package/lib/web-trust.d.ts +2 -0
- package/lib/web-trust.js +4 -0
- package/lib/{wrap-JFjcWwZf.js → wrap-0VA0uFx8.js} +156 -153
- package/package.json +109 -77
- /package/lib/{kernel-env-hxaihi9C.js → kernel-env-BLPZE-I3.js} +0 -0
- /package/lib/{py-sdk-Chvy92MB.js → py-sdk-D7klhpWb.js} +0 -0
|
@@ -0,0 +1,2083 @@
|
|
|
1
|
+
# Remote 执行框架(`remote` 工具)Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** 落地 2026-09-23 设计文档的全新阶段——一个 `remote` 工具(`dashr-remote` composition row),双轨执行(oneshot 默认 / pty opt-in)、CLI 子进程 transports(ssh / docker / incus)、BYO-PTY `spawn` 通道、随机 Nonce 收帧、10min 空闲 TTL、断联透明冷启——在 4999 系 dev 实例第一人称验收。
|
|
6
|
+
|
|
7
|
+
**Architecture:** 全新实现(不基于 RM0 分支——RM0 的 ssh2 库 + 连接池 + 准备层路线被本设计否决,分支存档不动)。新代码在 `better-dsh/src/remote/` 下:纯模块(framing / target 路由 / argv builders)先行 TDD, runners(oneshot 子进程、pty 会话池)用本地真 `bash` 与 `script(1)` 夹具测试,零网络依赖;工具面与插件行复用 RM0 已验证的注册形态(`defineTool` + `- id: dashr-remote` bundle patch 行)。零新增 npm 依赖(ssh2 移出依赖面;只加 `node:child_process` / `node:crypto` stdlib)。
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript(ESM,`.ts` import specifiers)、node:child_process、node:crypto、util-linux `script(1)`(宿主 PTY 托管)、schemastery row config、vitest(serial suite)。
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/10_plans/2026-09-23-remote-execution-framework.md`(本计划论证之源;设计文档为约束权威,本计划在其留白处以 Rulings 裁决)。
|
|
12
|
+
|
|
13
|
+
## Global Constraints(spec 逐条摘录,全部任务隐含遵守)
|
|
14
|
+
|
|
15
|
+
- **双轨收敛**:One-shot = 第 2 层(`bash -lc` 经 CLI 传输,进程退出码即收尾,**零字符串扫描**);Persistent = 第 4 层(PTY + Nonce 收帧)。每阵营严格各一,不做第 1/3 层。
|
|
16
|
+
- **One-shot argv 形态(§五.1 原文)**:SSH = `ssh -o BatchMode=yes <target> "bash -lc '<cmd>'"`(不分配 tty);Docker = `docker exec -i <target> bash -lc "<cmd>"`(不加 `-t`);Incus = `incus exec <target> -- bash -lc "<cmd>"`(不加 `-t`)。
|
|
17
|
+
- **Nonce 收帧(§五.2)**:每次下发前 Host 生成 8 字节随机串 `$NONCE`;命令末尾收帧指令 `printf "\033]133;D;%s;%s\007" "$NONCE" "$?"`;**只有匹配当前轮次 Nonce 才收帧**;远端偶然打印的静态 `\033]133;D;` 一律当普通输出,不得截断流或窜改退出码。
|
|
18
|
+
- **空闲 TTL = 10 分钟**(默认 600s,可配),到期自动关连接释放资源;下次调用透明冷启动。
|
|
19
|
+
- **断联自愈**:底层绝不重建 shell 内存状态;统一行为 = 重建干净初始会话并返回明确提示 `[remote: session reconnected to fresh shell; cwd reset to default]`(此字符串逐字使用)。
|
|
20
|
+
- **Strict Non-Goals(§六)**:① 后台进程输出不隔离、不清洗(使用者显式 `cmd > log 2>&1 &`);② 不快照/回放 shell 临时环境变量;③ 不暴露终端行列调整接口(内部固定 40×120)。
|
|
21
|
+
- **错误透明**:连接失败 / 找不到容器 / 权限拒绝,原生 stderr 原样返回,不过度包裹(Soft Fail-close)。
|
|
22
|
+
- **便利性而非保姆性(§三)**:参数面 = `{target?, spawn?, cmd, mode?, stdin?, timeout?}`;target 与 spawn 二选一;spawn 时 mode 锁定 `'pty'`;无静态注册要求。
|
|
23
|
+
- **Target 智能路由(§二.2)**:容器前缀 `docker:x` / `incus:x` → 容器传输;普通名称 / IP / 域名 → SSH;URL 形态(`ws://`)本期不做。
|
|
24
|
+
- **测试纪律**:vitest 全绿;新增规格先测后码(TDD);单测不得要求外网或真实远端主机。
|
|
25
|
+
- **交付形态**:`dashr-remote` 为 better-dsh 内 composition row(subpath export + tsdown entry + bundle patch insert),插件管理器可开关,不新建 npm 包。
|
|
26
|
+
- **发布红线**:npm publish 需 4999 第一人称实测 + 报告 + user 放行(Development Operation Contract);本计划只到 dev 实例验收。
|
|
27
|
+
|
|
28
|
+
## Rulings(本计划对 spec 留白的裁决)
|
|
29
|
+
|
|
30
|
+
1. **基座分支**:`remote-framework` 分支自 `main`(已核验 `main` 与 `dashr-bun-port-m1` 的 `better-dsh/` 零差异);worktree `.worktrees/remote-framework`。RM0 分支(`remote-tool-rm0` / `arch/…`)存档不动,其代码不合并、不 import——但 `src/remote/tool.ts` 的注册形态(defineTool + 审计事件)与 `plugin.ts` 的行形态被本计划照抄结构。
|
|
31
|
+
2. **传输 = 系统 CLI,宿主无 PTY 库**:ssh pty = `ssh -tt -o BatchMode=yes -- <host> bash` 走宿主管道(远端 pty 的行规程吃 `\x03` 成 SIGINT——与 ssh2 pty channel 同构);incus pty = `incus exec --force-interactive`(API 代理容器侧 pty,宿主管道);**docker pty 与一切 `spawn`(BYO-PTY)= util-linux `script -qfec '…' /dev/null` 托管宿主 PTY**(docker CLI 对 `-t` 要求 stdin 是 TTY;node-pty 是原生模块,违反零 lifecycle-script 包红线,否决)。script 命令串前置 `stty -echo 2>/dev/null ; exec ` 关掉本地 pty 回显。
|
|
32
|
+
3. **marker 为三字段超集**:`\x1b]133;D;<nonce>;<exit>;<cwd>\x07`(spec 两字段 + cwd 只读回传)。cwd 是状态**报告**不是状态重建(Non-Goal ② 不破)——`cd` 后 Agent 需要知道自己在哪。终止符收 BEL 与 ST(`\x1b\\`)两种。nonce = `crypto.randomBytes(8).toString('hex')`(16 hex 字符)。
|
|
33
|
+
4. **无 C 标记**:nonce 门控使 OSC 133 C(PS0)不必要——init 之后的流里,帧间内容只可能是本轮输出(PS1/PS2 已哑)。比 RM0 少一个解析维度。
|
|
34
|
+
5. **init 协议**:会话冷启后先发 `stty -echo 2>/dev/null ; stty rows 40 cols 120 2>/dev/null ; PS1='' ; PS2='' ; export TERM=dumb ; printf '\033]133;D;<INIT_NONCE>;0;%s\007' "$PWD"\n`,INIT_NONCE 帧到达 = ready;帧前字节(回显残响/ssh banner)全部丢弃。固定 40×120 在此落位(Non-Goal ③)。
|
|
35
|
+
6. **会话键与 Agent 隔离**:pty 会话键 = `<agentSessionKey>|t:<target>` 或 `|s:<spawn>`;agentSessionKey 默认 `exec.agent?.id`(无 agent 用常量 `no-session`)。同一 Agent 跨 turn 保持状态(spec 场景 2),并发 Agent 互不污染(RM0 Ruling 7 先例)。同键命令串行排队,不同键天然并发。
|
|
36
|
+
7. **意外死亡 vs TTL**:进程死亡(EOF/被杀)后会话对象**留在池内**(state=dead),下次 dispatch 同对象重启并置 `reconnected: true`(带提示行);TTL/row dispose 才出池(出池后重建是新对象 = 透明冷启,无提示)。
|
|
37
|
+
8. **超时语义**:默认 120s(row `execTimeoutSec`)。pty 超时 = 写 `\x03` + **紧随注入同 nonce 的 130-marker 行**(`printf '\033]133;D;<NONCE>;130;%s\007' "$PWD"`——交互 bash 被打断后放弃当前命令行剩余部分,尾部 marker 不再执行,注入行在 bash 回到读取态后补帧;若 bash 续行则原 marker 先帧、注入行帧被 parser 丢弃——两路皆 exit 130,会话存活)→ 3s 宽限无帧则 kill 进程组,`exit: null`。oneshot 超时 = SIGTERM → 2s → SIGKILL(**进程组杀**——后台子进程占管道时 close 永不触发,实测必挂),`timedOut: true`。超时秒数非正/非有限 → dispatch 前 `E_BAD_TIMEOUT` 拒绝。
|
|
38
|
+
9. **杀进程纪律**:全部子进程 `detached: true` 起进程组,杀 = `process.kill(-pid, SIGTERM)` → 2s → SIGKILL(script 会连带 docker CLI 一并收掉)。
|
|
39
|
+
10. **输出纪律**:回模型前机械剥残余 ANSI + CRLF→LF(RM0 连续性);超帽保留尾窗 + 前置 `[truncated: showing last N of M chars]`(row `maxOutputChars` 默认 30000)。oneshot stdout/stderr 分离保留;pty 合流是诚实代价(spec §4.2 精神)。
|
|
40
|
+
11. **stdin 语义**:oneshot = 管道直写后 close;pty = 紧随命令行裸写(REPL 式 best-effort——cmd 不读则残行被 bash 当下一条命令执行、输出计入本轮,Agent 看到后自纠)。工具层校验 `stdin` 依赖 `cmd` 在场。
|
|
41
|
+
12. **别名注册**:row config `containers: { 'ctr-1': 'incus:ctr-1' }` 形态(裸名 → 容器传输的"已知容器"判定);别名值必须是 `docker:`/`incus:` 前缀形态,否则 `E_TARGET_FORMAT`。裸名无别名命中 → ssh(本机 `~/.ssh/config` 里 ctr-1 同时是 ssh Host,显式前缀/别名优先于 ssh)。
|
|
42
|
+
13. **审计**:engine 回调 → 工具层 append session 事件 `dashr/remote-exec`(对齐 RM0 Ruling 10 / `tool/ptc-dispatch` 先例,成功与失败都记,best-effort 不破坏命令路径)。
|
|
43
|
+
14. **oneshot ssh 单层机械转义**:`bash -lc <shQuote(cmd)>` 作为 host 后的**单个 argv**(ssh 拼接 argv 后远端 sh 只做一次解析);shQuote = POSIX 单引号包裹。docker/incus 的 argv 直传无二次解析。
|
|
44
|
+
15. **不做的**:ControlMaster 复用(oneshot 天然并发,握手成本可接受,YAGNI);`ws://` 传输;forward/tmux/WebUI 终端(RM2 线);host key 管理(ssh CLI 的 known_hosts 即正道,RM0 的 TOFU pin 文件随 ssh2 路线一并退役);targets 列表面(发现是 agent 自己的活:`cat ~/.ssh/config`、`docker ps`、`incus list`——工具不做保姆注册表)。
|
|
45
|
+
16. **状态词汇表(user 2026-09-23 裁决,禁止歧义)**:本工具面向 LLM agent——它有语义理解与自愈能力,前提是不被误导。状态必须如实、无死灭暗示:**禁止 `offline`**(无人发起过连接当然是"未连接",该词零信息且诱导 agent 误判配置已死——实测 reasoning log 里 agent 确实困惑过)。词汇表:探测面 `reachable (in Nms)` / `unreachable — <原生 stderr 单行>` / 容器事实态(`running`/`stopped`/`exited`…来自 inspect/list,不臆造)/ `error — <原生错误>`;会话层 `session: idle Ns (connected)`(连接存在、当前无 activity)/ `session: busy (a command is running)` / `session: none — dials on first exec`(明说"未拨号"而非任何可达性断言)/ `session: died — next exec cold-starts a fresh shell`。
|
|
46
|
+
17. **status 探测时机 = on-demand**(user 两选项中选此):`remote({target})`(无 cmd)= 调用瞬间拨一次探测,**双重超时**:ssh `ConnectTimeout=5` + 外层 hard timeout 10s(防个别机器永不回复卡死 agent);探测结果即当时事实,永不启动期扫描、永不缓存(启动扫描会立即过期且拖慢 boot)。status 只报探测面 + 会话层两行,不含任何注册表断言。
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Task 0: worktree 与基座
|
|
51
|
+
|
|
52
|
+
**Files:**
|
|
53
|
+
- Create: `.worktrees/remote-framework/`(git worktree,branch `remote-framework` off `main`)
|
|
54
|
+
- Create(分支内): `docs/10_plans/2026-09-23-remote-execution-framework.md`(spec 副本)、`docs/10_plans/2026-09-23-remote-framework-impl.md`(本计划副本)
|
|
55
|
+
|
|
56
|
+
- [ ] **Step 1: 建 worktree + 分支**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cd /home/u1/workspaces/dashr
|
|
60
|
+
git worktree add .worktrees/remote-framework -b remote-framework main
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- [ ] **Step 2: spec + 本计划入库(分支自包含,executor 只读 worktree)**
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cp docs/10_plans/2026-09-23-remote-execution-framework.md docs/10_plans/2026-09-23-remote-framework-impl.md \
|
|
67
|
+
.worktrees/remote-framework/docs/10_plans/
|
|
68
|
+
cd .worktrees/remote-framework && git add docs/10_plans/ && git commit -m "docs(remote): framework spec + implementation plan (2026-09-23 stage)"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- [ ] **Step 3: node_modules 软链**(worktree 不带 untracked 文件;canonical 的 dev 依赖树直接复用,本计划零新增依赖故版本面一致;⚠ 勿与 canonical 同时跑 vitest——`.vite` 缓存会撞)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ln -s /home/u1/workspaces/dashr/better-dsh/node_modules /home/u1/workspaces/dashr/.worktrees/remote-framework/better-dsh/node_modules
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- [ ] **Step 4: 基线绿**
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cd /home/u1/workspaces/dashr/.worktrees/remote-framework/better-dsh && npm test
|
|
81
|
+
```
|
|
82
|
+
Expected: 全绿(main 基线)。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Task 1: Nonce 收帧纯模块 `nonce-framing.ts`
|
|
87
|
+
|
|
88
|
+
**Files:**
|
|
89
|
+
- Create: `better-dsh/src/remote/nonce-framing.ts`
|
|
90
|
+
- Test: `better-dsh/test/remote/nonce-framing.spec.ts`
|
|
91
|
+
|
|
92
|
+
**Interfaces(后续任务消费):**
|
|
93
|
+
```ts
|
|
94
|
+
export interface PtyFrame { exit: number; cwd: string | null }
|
|
95
|
+
export function genNonce(bytes?: number): string // 16 hex chars
|
|
96
|
+
export function wrapPtyCommand(cmd: string, nonce: string): string // 含尾 \n 的整行下发串
|
|
97
|
+
export function buildInitCommand(nonce: string): string // init 串含尾 \n
|
|
98
|
+
export function stripAnsi(s: string): string
|
|
99
|
+
export function normalizePtyText(s: string): string // stripAnsi + CRLF→LF + 孤 CR 删除
|
|
100
|
+
export function tailWindow(text: string, cap: number): { text: string; truncated?: number }
|
|
101
|
+
export interface FrameParserEvents { onOutput?(text: string): void; onFrame(frame: PtyFrame): void }
|
|
102
|
+
export interface NonceFrameParser { feed(chunk: string): void; flush(): void }
|
|
103
|
+
export function createNonceFrameParser(nonce: string, events: FrameParserEvents): NonceFrameParser
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- [ ] **Step 1: 写失败测试**
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { describe, expect, it } from 'vitest'
|
|
110
|
+
import {
|
|
111
|
+
buildInitCommand, createNonceFrameParser, genNonce, normalizePtyText, tailWindow, wrapPtyCommand,
|
|
112
|
+
} from '../../src/remote/nonce-framing.ts'
|
|
113
|
+
|
|
114
|
+
describe('command builders', () => {
|
|
115
|
+
it('wrapPtyCommand braces cmd and bakes nonce marker with $?/$PWD', () => {
|
|
116
|
+
const s = wrapPtyCommand('echo hi', 'abcd1234abcd1234')
|
|
117
|
+
expect(s).toContain('{\necho hi\n} ; printf')
|
|
118
|
+
expect(s).toContain(`'\\033]133;D;abcd1234abcd1234;%s;%s\\007' "$?" "$PWD"`)
|
|
119
|
+
expect(s.endsWith('\n')).toBe(true)
|
|
120
|
+
})
|
|
121
|
+
it('buildInitCommand silences the terminal and ends with the init marker', () => {
|
|
122
|
+
const s = buildInitCommand('ffffffffffffffff')
|
|
123
|
+
for (const frag of ["stty -echo 2>/dev/null", "stty rows 40 cols 120", "PS1=''", "PS2=''", 'export TERM=dumb'])
|
|
124
|
+
expect(s).toContain(frag)
|
|
125
|
+
expect(s).toContain(`'\\033]133;D;ffffffffffffffff;0;%s\\007' "$PWD"`)
|
|
126
|
+
})
|
|
127
|
+
it('genNonce is 16 hex chars and unique', () => {
|
|
128
|
+
expect(genNonce()).toMatch(/^[0-9a-f]{16}$/)
|
|
129
|
+
expect(new Set([genNonce(), genNonce(), genNonce(), genNonce()]).size).toBe(4)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('createNonceFrameParser', () => {
|
|
134
|
+
const MARK = (nonce: string, exit: number, cwd: string) =>
|
|
135
|
+
`\x1b]133;D;${nonce};${exit};${cwd}\x07`
|
|
136
|
+
|
|
137
|
+
it('emits pre-marker output normalized and parses exit + cwd', () => {
|
|
138
|
+
const out: string[] = []
|
|
139
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
140
|
+
const nonce = genNonce()
|
|
141
|
+
const p = createNonceFrameParser(nonce, { onOutput: (t) => out.push(t), onFrame: (f) => frames.push(f) })
|
|
142
|
+
p.feed(`hello\r\nworld\r\n${MARK(nonce, 3, '/data')}`)
|
|
143
|
+
expect(out.join('')).toBe('hello\nworld\n')
|
|
144
|
+
expect(frames).toEqual([{ exit: 3, cwd: '/data' }])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('survives the marker split at every chunk boundary', () => {
|
|
148
|
+
const nonce = genNonce()
|
|
149
|
+
const stream = `out${MARK(nonce, 0, '/tmp')}tail-is-dropped`
|
|
150
|
+
for (let split = 1; split < stream.length; split++) {
|
|
151
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
152
|
+
const p = createNonceFrameParser(nonce, { onFrame: (f) => frames.push(f) })
|
|
153
|
+
p.feed(stream.slice(0, split)); p.feed(stream.slice(split))
|
|
154
|
+
expect(frames, `split at ${split}`).toEqual([{ exit: 0, cwd: '/tmp' }])
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('a forged marker with a WRONG nonce is ordinary output, never frames', () => {
|
|
159
|
+
const nonce = genNonce()
|
|
160
|
+
const out: string[] = []
|
|
161
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
162
|
+
const p = createNonceFrameParser(nonce, { onOutput: (t) => out.push(t), onFrame: (f) => frames.push(f) })
|
|
163
|
+
p.feed(`ok\n${MARK('deadbeefdeadbeef', 99, '/pwn')}${MARK(nonce, 0, '/')}`)
|
|
164
|
+
expect(frames).toEqual([{ exit: 0, cwd: '/' }])
|
|
165
|
+
expect(out.join('')).toBe('ok\n') // forged OSC stripped as ANSI residue
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('accepts ST (ESC backslash) terminator and empty cwd -> null', () => {
|
|
169
|
+
const nonce = genNonce()
|
|
170
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
171
|
+
const p = createNonceFrameParser(nonce, { onFrame: (f) => frames.push(f) })
|
|
172
|
+
p.feed(`\x1b]133;D;${nonce};7;\x1b\\`)
|
|
173
|
+
expect(frames).toEqual([{ exit: 7, cwd: null }])
|
|
174
|
+
})
|
|
175
|
+
it('frames a cwd containing ; (terminal field — only BEL/ST end it)', () => {
|
|
176
|
+
const nonce = genNonce()
|
|
177
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
178
|
+
const p = createNonceFrameParser(nonce, { onFrame: (f) => frames.push(f) })
|
|
179
|
+
p.feed(`\x1b]133;D;${nonce};2;/pa;th\x07`)
|
|
180
|
+
expect(frames).toEqual([{ exit: 2, cwd: '/pa;th' }])
|
|
181
|
+
})
|
|
182
|
+
it('the spec\'s literal static 133-D marker (no nonce field) is ordinary output', () => {
|
|
183
|
+
const nonce = genNonce()
|
|
184
|
+
const out: string[] = []
|
|
185
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
186
|
+
const p = createNonceFrameParser(nonce, { onOutput: (t) => out.push(t), onFrame: (f) => frames.push(f) })
|
|
187
|
+
p.feed(`ok\n\x1b]133;D;0\x07text${MARK(nonce, 0, '/')}`)
|
|
188
|
+
expect(frames).toEqual([{ exit: 0, cwd: '/' }])
|
|
189
|
+
expect(out.join('')).toBe('ok\ntext') // 静态 marker 被 ANSI 剥洗当噪音清掉,正文保留
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('holds back a partial marker prefix at buffer end; flush releases it', () => {
|
|
193
|
+
const nonce = genNonce()
|
|
194
|
+
const out: string[] = []
|
|
195
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
196
|
+
const p = createNonceFrameParser(nonce, { onOutput: (t) => out.push(t), onFrame: (f) => frames.push(f) })
|
|
197
|
+
p.feed(`ok\n\x1b]133;D;${nonce.slice(0, 5)}`)
|
|
198
|
+
expect(out.join('')).toBe('ok\n')
|
|
199
|
+
p.flush()
|
|
200
|
+
expect(out.join('')).toContain(nonce.slice(0, 5))
|
|
201
|
+
expect(frames).toEqual([])
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('ignores everything after the first frame (post-frame bytes are next turn\'s business)', () => {
|
|
205
|
+
const nonce = genNonce()
|
|
206
|
+
const frames: Array<{ exit: number; cwd: string | null }> = []
|
|
207
|
+
const p = createNonceFrameParser(nonce, { onFrame: (f) => frames.push(f) })
|
|
208
|
+
p.feed(`${MARK(nonce, 1, '/a')}noise${MARK(nonce, 2, '/b')}`)
|
|
209
|
+
expect(frames).toEqual([{ exit: 1, cwd: '/a' }])
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe('text hygiene', () => {
|
|
214
|
+
it('normalizePtyText strips CSI/OSC and normalizes line endings', () => {
|
|
215
|
+
expect(normalizePtyText('a\r\nb\rc \x1b[31mred\x1b[0m\x1b]0;title\x07end')).toBe('a\nbc redend')
|
|
216
|
+
})
|
|
217
|
+
it('tailWindow caps from the tail with a disclosure header', () => {
|
|
218
|
+
const r = tailWindow('x'.repeat(100), 10)
|
|
219
|
+
expect(r.truncated).toBe(100)
|
|
220
|
+
expect(r.text).toBe('[truncated: showing last 10 of 100 chars]\n' + 'x'.repeat(10))
|
|
221
|
+
expect(tailWindow('short', 10)).toEqual({ text: 'short' })
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- [ ] **Step 2: 跑测试确认失败**
|
|
227
|
+
|
|
228
|
+
Run: `cd better-dsh && npm test -- test/remote/nonce-framing.spec.ts`
|
|
229
|
+
Expected: FAIL(模块不存在)。
|
|
230
|
+
|
|
231
|
+
- [ ] **Step 3: 最小实现**
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { randomBytes } from 'node:crypto'
|
|
235
|
+
|
|
236
|
+
/** 收帧帧体:退出码 + 只读回传的 cwd(Non-Goal ②:报告不重建)。 */
|
|
237
|
+
export interface PtyFrame { exit: number; cwd: string | null }
|
|
238
|
+
|
|
239
|
+
/** 8 字节随机串的 hex 形(spec §五.2:每次下发前生成)。 */
|
|
240
|
+
export function genNonce(bytes = 8): string {
|
|
241
|
+
return randomBytes(bytes).toString('hex')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** 命令整行:花括号复合体 + Nonce 收帧尾(spec §五.2 的 printf 逐字落地 + cwd 第三字段)。 */
|
|
245
|
+
export function wrapPtyCommand(cmd: string, nonce: string): string {
|
|
246
|
+
return `{\n${cmd}\n} ; printf '\\033]133;D;${nonce};%s;%s\\007' "$?" "$PWD"\n`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** init 整行:哑终端(echo/PS1/PS2/TERM)+ 固定 40×120(Non-Goal ③)+ INIT 帧。 */
|
|
250
|
+
export function buildInitCommand(nonce: string): string {
|
|
251
|
+
return `stty -echo 2>/dev/null ; stty rows 40 cols 120 2>/dev/null ; PS1='' ; PS2='' ; export TERM=dumb ; printf '\\033]133;D;${nonce};0;%s\\007' "$PWD"\n`
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const ANSI_RE = /\x1b(?:\][^\x07\x1b]*(?:\x07|\x1b\\)?|\[[0-9;?]*[ -/]*[@-~]|[@-Z\\-_])/g
|
|
255
|
+
|
|
256
|
+
export function stripAnsi(s: string): string {
|
|
257
|
+
return s.replace(ANSI_RE, '')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function normalizePtyText(s: string): string {
|
|
261
|
+
return stripAnsi(s).replace(/\r\n/g, '\n').replace(/\r/g, '')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function tailWindow(text: string, cap: number): { text: string; truncated?: number } {
|
|
265
|
+
if (text.length <= cap) return { text }
|
|
266
|
+
return { text: `[truncated: showing last ${cap} of ${text.length} chars]\n${text.slice(text.length - cap)}`, truncated: text.length }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface FrameParserEvents {
|
|
270
|
+
/** 输出文本:feed 路径经 normalize(剥 ANSI/规整换行);flush 路径为**裸放出**(残余字节原样,勿假设已剥洗)。 */
|
|
271
|
+
onOutput?(text: string): void
|
|
272
|
+
onFrame(frame: PtyFrame): void
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface NonceFrameParser { feed(chunk: string): void; flush(): void }
|
|
276
|
+
|
|
277
|
+
const FRAME_BODY_RE = /^(\d+);([^\x07\x1b]*)(?:\x07|\x1b\\)/ // cwd 是尾字段:合法路径可含 `;`,只有 BEL/ESC 终止它
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 单 nonce、单帧解析器:一轮 dispatch 一个实例。只认 `\x1b]133;D;<nonce>;`,
|
|
281
|
+
* 错误 nonce 的静态/伪造 marker 永不匹配,按普通输出流过(再经 ANSI 剥洗)。
|
|
282
|
+
* 缓冲尾若可能是 marker 前缀则扣留,flush() 时作为输出放出。
|
|
283
|
+
*/
|
|
284
|
+
export function createNonceFrameParser(nonce: string, events: FrameParserEvents): NonceFrameParser {
|
|
285
|
+
const prefix = `\x1b]133;D;${nonce};`
|
|
286
|
+
let buf = ''
|
|
287
|
+
let done = false
|
|
288
|
+
|
|
289
|
+
const emit = (raw: string): void => {
|
|
290
|
+
const text = normalizePtyText(raw)
|
|
291
|
+
if (text.length > 0) events.onOutput?.(text)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const holdback = (): number => {
|
|
295
|
+
const max = Math.min(buf.length, prefix.length - 1)
|
|
296
|
+
for (let k = max; k > 0; k--) if (buf.endsWith(prefix.slice(0, k))) return k
|
|
297
|
+
return 0
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const consume = (): void => {
|
|
301
|
+
if (done) return
|
|
302
|
+
const i = buf.indexOf(prefix)
|
|
303
|
+
if (i < 0) {
|
|
304
|
+
const keep = holdback()
|
|
305
|
+
emit(buf.slice(0, buf.length - keep))
|
|
306
|
+
buf = buf.slice(buf.length - keep)
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
emit(buf.slice(0, i))
|
|
310
|
+
const rest = buf.slice(i + prefix.length)
|
|
311
|
+
const m = FRAME_BODY_RE.exec(rest)
|
|
312
|
+
if (m === null) { buf = buf.slice(i); return } // 帧体未到齐,整段扣留
|
|
313
|
+
events.onFrame({ exit: Number(m[1]!), cwd: m[2]!.length > 0 ? m[2]! : null })
|
|
314
|
+
done = true // 帧后字节一律丢弃(下一轮是新解析器)
|
|
315
|
+
buf = ''
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
feed(chunk: string): void { buf += chunk; consume() },
|
|
320
|
+
// 流结束 = 裸放出扣留的残余(含未完成的 marker 前缀):不经过 ANSI 剥洗——
|
|
321
|
+
// stripAnsi 会把未闭合 OSC 整段吃掉,扣留字节就此蒸发,违背 flush 语义。
|
|
322
|
+
flush(): void { if (buf.length > 0) events.onOutput?.(buf); buf = '' },
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- [ ] **Step 4: 跑测试确认通过** — `npm test -- test/remote/nonce-framing.spec.ts` → PASS。
|
|
328
|
+
|
|
329
|
+
- [ ] **Step 5: Commit**
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
git add src/remote/nonce-framing.ts test/remote/nonce-framing.spec.ts
|
|
333
|
+
git commit -m "feat(remote): nonce-gated framing module — wrap/init/parse/strip/tail-window"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### Task 2: Target 智能路由 `target.ts`
|
|
339
|
+
|
|
340
|
+
**Files:**
|
|
341
|
+
- Create: `better-dsh/src/remote/target.ts`
|
|
342
|
+
- Test: `better-dsh/test/remote/target.spec.ts`
|
|
343
|
+
|
|
344
|
+
**Interfaces:**
|
|
345
|
+
```ts
|
|
346
|
+
export type TargetPlan = { kind: 'ssh'; host: string } | { kind: 'docker' | 'incus'; container: string }
|
|
347
|
+
export class TargetFormatError extends Error { readonly code = 'E_TARGET_FORMAT' }
|
|
348
|
+
export function resolveTarget(target: string, containerAliases?: Record<string, string>): TargetPlan
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
- [ ] **Step 1: 写失败测试**
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import { describe, expect, it } from 'vitest'
|
|
355
|
+
import { resolveTarget, TargetFormatError } from '../../src/remote/target.ts'
|
|
356
|
+
|
|
357
|
+
describe('resolveTarget', () => {
|
|
358
|
+
it('routes plain names / IPs / domains to ssh', () => {
|
|
359
|
+
expect(resolveTarget('dev4')).toEqual({ kind: 'ssh', host: 'dev4' })
|
|
360
|
+
expect(resolveTarget('137.131.54.174')).toEqual({ kind: 'ssh', host: '137.131.54.174' })
|
|
361
|
+
expect(resolveTarget('mac.example.com')).toEqual({ kind: 'ssh', host: 'mac.example.com' })
|
|
362
|
+
})
|
|
363
|
+
it('routes explicit container prefixes', () => {
|
|
364
|
+
expect(resolveTarget('docker:corti')).toEqual({ kind: 'docker', container: 'corti' })
|
|
365
|
+
expect(resolveTarget('incus:ctr-1')).toEqual({ kind: 'incus', container: 'ctr-1' })
|
|
366
|
+
})
|
|
367
|
+
it('registered bare container names beat ssh (ctr-1 is also an ssh Host)', () => {
|
|
368
|
+
expect(resolveTarget('ctr-1', { 'ctr-1': 'incus:ctr-1' })).toEqual({ kind: 'incus', container: 'ctr-1' })
|
|
369
|
+
})
|
|
370
|
+
it('ssh: explicit selector forces ssh even when an alias would match (Ruling P16)', () => {
|
|
371
|
+
expect(resolveTarget('ssh:ctr-1', { 'ctr-1': 'incus:ctr-1' })).toEqual({ kind: 'ssh', host: 'ctr-1' })
|
|
372
|
+
expect(resolveTarget('ssh:dev4')).toEqual({ kind: 'ssh', host: 'dev4' })
|
|
373
|
+
})
|
|
374
|
+
it('explicit prefix beats the alias map', () => {
|
|
375
|
+
expect(resolveTarget('incus:ctr-1', { 'ctr-1': 'docker:other' })).toEqual({ kind: 'incus', container: 'ctr-1' })
|
|
376
|
+
})
|
|
377
|
+
it('bad alias value and empty target fail loud', () => {
|
|
378
|
+
expect(() => resolveTarget('x', { x: 'dev4' })).toThrowError(TargetFormatError)
|
|
379
|
+
expect(() => resolveTarget(' ')).toThrowError(/E_TARGET_FORMAT/)
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
- [ ] **Step 2: 跑测试确认失败** — `npm test -- test/remote/target.spec.ts` → FAIL。
|
|
385
|
+
|
|
386
|
+
- [ ] **Step 3: 最小实现**
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
/** 解析后的传输计划:三种 CLI 传输之一。 */
|
|
390
|
+
export type TargetPlan =
|
|
391
|
+
| { kind: 'ssh'; host: string }
|
|
392
|
+
| { kind: 'docker'; container: string }
|
|
393
|
+
| { kind: 'incus'; container: string }
|
|
394
|
+
|
|
395
|
+
export class TargetFormatError extends Error {
|
|
396
|
+
readonly code = 'E_TARGET_FORMAT'
|
|
397
|
+
constructor(message: string) { super(message); this.name = 'TargetFormatError' }
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** 显式协议选择器(Ruling P16):可扩展新协议——加一个词即一条新传输腿。 */
|
|
401
|
+
const PREFIX_RE = /^(docker|incus|ssh):(.+)$/
|
|
402
|
+
/** 别名表值只接受容器形态(ssh 无别名意义——裸名即 ssh)。 */
|
|
403
|
+
const CONTAINER_PREFIX_RE = /^(docker|incus):(.+)$/
|
|
404
|
+
|
|
405
|
+
/** spec §二.2 智能路由(Ruling 12 + P16):显式选择器 > 别名表(人工 pin,撞名时赢)> ssh 缺省。 */
|
|
406
|
+
export function resolveTarget(target: string, containerAliases: Record<string, string> = {}): TargetPlan {
|
|
407
|
+
const raw = target.trim()
|
|
408
|
+
if (raw.length === 0) throw new TargetFormatError('[E_TARGET_FORMAT] remote: empty target')
|
|
409
|
+
const direct = PREFIX_RE.exec(raw)
|
|
410
|
+
if (direct !== null) {
|
|
411
|
+
if (direct[1] === 'ssh') return { kind: 'ssh', host: direct[2]! }
|
|
412
|
+
return { kind: direct[1] as 'docker' | 'incus', container: direct[2]! }
|
|
413
|
+
}
|
|
414
|
+
const aliased = containerAliases[raw]
|
|
415
|
+
if (aliased !== undefined) {
|
|
416
|
+
const m = CONTAINER_PREFIX_RE.exec(aliased)
|
|
417
|
+
if (m === null)
|
|
418
|
+
throw new TargetFormatError(
|
|
419
|
+
`[E_TARGET_FORMAT] remote: container alias '${raw}' must map to 'docker:<name>' or 'incus:<name>' (got ${JSON.stringify(aliased)})`)
|
|
420
|
+
return { kind: m[1] as 'docker' | 'incus', container: m[2]! }
|
|
421
|
+
}
|
|
422
|
+
return { kind: 'ssh', host: raw }
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
- [ ] **Step 4: 跑测试确认通过** → PASS。
|
|
427
|
+
|
|
428
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): smart target routing — prefix/alias/ssh"`(含 add)。
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### Task 3: 传输 argv builders `transports.ts`
|
|
433
|
+
|
|
434
|
+
**Files:**
|
|
435
|
+
- Create: `better-dsh/src/remote/transports.ts`
|
|
436
|
+
- Test: `better-dsh/test/remote/transports.spec.ts`
|
|
437
|
+
|
|
438
|
+
**Interfaces:**
|
|
439
|
+
```ts
|
|
440
|
+
export function shQuote(s: string): string
|
|
441
|
+
export function buildOneShotArgv(plan: TargetPlan, cmd: string): string[]
|
|
442
|
+
export function buildPtyArgv(plan: TargetPlan): string[]
|
|
443
|
+
export function buildSpawnArgv(spawnCommand: string): string[]
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
- [ ] **Step 1: 写失败测试**
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
import { describe, expect, it } from 'vitest'
|
|
450
|
+
import { buildOneShotArgv, buildPtyArgv, buildSpawnArgv, shQuote } from '../../src/remote/transports.ts'
|
|
451
|
+
|
|
452
|
+
describe('shQuote', () => {
|
|
453
|
+
it('wraps in single quotes and escapes embedded quotes POSIX-style', () => {
|
|
454
|
+
expect(shQuote('ls -la')).toBe(`'ls -la'`)
|
|
455
|
+
expect(shQuote("it's")).toBe(`'it'\\''s'`)
|
|
456
|
+
expect(shQuote('a\nb')).toBe(`'a\nb'`)
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
describe('buildOneShotArgv (spec §五.1 verbatim shapes)', () => {
|
|
461
|
+
it('ssh: BatchMode, no tty, one mechanically-quoted argv after the host', () => {
|
|
462
|
+
expect(buildOneShotArgv({ kind: 'ssh', host: 'dev4' }, "git status | head -1"))
|
|
463
|
+
.toEqual(['ssh', '-o', 'BatchMode=yes', '-T', '--', 'dev4', `bash -lc 'git status | head -1'`])
|
|
464
|
+
})
|
|
465
|
+
it('ssh survives a cmd containing single quotes (one mechanical layer)', () => {
|
|
466
|
+
const argv = buildOneShotArgv({ kind: 'ssh', host: 'dev4' }, "echo 'hi'")
|
|
467
|
+
expect(argv.at(-1)).toBe(`bash -lc 'echo '\\''hi'\\'''`)
|
|
468
|
+
})
|
|
469
|
+
it('docker: exec -i, argv-direct (no -t, no re-parse)', () => {
|
|
470
|
+
expect(buildOneShotArgv({ kind: 'docker', container: 'c1' }, 'echo x'))
|
|
471
|
+
.toEqual(['docker', 'exec', '-i', '--', 'c1', 'bash', '-lc', 'echo x'])
|
|
472
|
+
})
|
|
473
|
+
it('incus: exec without -t semantics (--force-noninteractive)', () => {
|
|
474
|
+
expect(buildOneShotArgv({ kind: 'incus', container: 'ctr-1' }, 'echo x'))
|
|
475
|
+
.toEqual(['incus', 'exec', '--force-noninteractive', 'ctr-1', '--', 'bash', '-lc', 'echo x'])
|
|
476
|
+
})
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
describe('buildPtyArgv', () => {
|
|
480
|
+
it('ssh: force-tty over host pipes, BatchMode', () => {
|
|
481
|
+
expect(buildPtyArgv({ kind: 'ssh', host: 'dev4' }))
|
|
482
|
+
.toEqual(['ssh', '-tt', '-o', 'BatchMode=yes', '--', 'dev4', 'bash'])
|
|
483
|
+
})
|
|
484
|
+
it('docker: script(1) hosts the PTY, local echo pre-silenced, exec replaces the shell', () => {
|
|
485
|
+
expect(buildPtyArgv({ kind: 'docker', container: 'my c' }))
|
|
486
|
+
.toEqual(['script', '-qfec', `stty -echo 2>/dev/null ; exec docker exec -it -- 'my c' bash`, '/dev/null'])
|
|
487
|
+
})
|
|
488
|
+
it('incus: force-interactive over host pipes', () => {
|
|
489
|
+
expect(buildPtyArgv({ kind: 'incus', container: 'ctr-1' }))
|
|
490
|
+
.toEqual(['incus', 'exec', '--force-interactive', 'ctr-1', '--', 'bash'])
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
describe('buildSpawnArgv (BYO-PTY)', () => {
|
|
495
|
+
it('hosts any spawn command under script with echo pre-silenced', () => {
|
|
496
|
+
expect(buildSpawnArgv('docker exec -it temp-worker bash'))
|
|
497
|
+
.toEqual(['script', '-qfec', 'stty -echo 2>/dev/null ; exec docker exec -it temp-worker bash', '/dev/null'])
|
|
498
|
+
})
|
|
499
|
+
})
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
- [ ] **Step 2: 跑测试确认失败** → FAIL。
|
|
503
|
+
|
|
504
|
+
- [ ] **Step 3: 最小实现**
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
import type { TargetPlan } from './target.ts'
|
|
508
|
+
|
|
509
|
+
/** POSIX 单引号机械转义(唯一一层转义,spec §三"一层机械转义")。 */
|
|
510
|
+
export function shQuote(s: string): string {
|
|
511
|
+
return `'${s.replaceAll("'", `'\\''`)}'`
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** One-shot(§五.1):不分配 tty,收尾 = 子进程退出码,零字符串扫描。 */
|
|
515
|
+
export function buildOneShotArgv(plan: TargetPlan, cmd: string): string[] {
|
|
516
|
+
switch (plan.kind) {
|
|
517
|
+
case 'ssh':
|
|
518
|
+
// ssh 把 host 后的 argv 用空格拼接交远端 shell 解析——预先整体 shQuote,
|
|
519
|
+
// 远端只做这一层解析,bash -lc 拿到原样 cmd。
|
|
520
|
+
return ['ssh', '-o', 'BatchMode=yes', '-T', '--', plan.host, `bash -lc ${shQuote(cmd)}`]
|
|
521
|
+
case 'docker':
|
|
522
|
+
return ['docker', 'exec', '-i', '--', plan.container, 'bash', '-lc', cmd]
|
|
523
|
+
case 'incus':
|
|
524
|
+
return ['incus', 'exec', '--force-noninteractive', plan.container, '--', 'bash', '-lc', cmd]
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Persistent pty(Ruling 2):ssh/incus 走宿主管道,docker 经 script(1) 托管宿主 PTY。 */
|
|
529
|
+
export function buildPtyArgv(plan: TargetPlan): string[] {
|
|
530
|
+
switch (plan.kind) {
|
|
531
|
+
case 'ssh':
|
|
532
|
+
return ['ssh', '-tt', '-o', 'BatchMode=yes', '--', plan.host, 'bash']
|
|
533
|
+
case 'docker':
|
|
534
|
+
return ['script', '-qfec', `stty -echo 2>/dev/null ; exec docker exec -it -- ${shQuote(plan.container)} bash`, '/dev/null']
|
|
535
|
+
case 'incus':
|
|
536
|
+
return ['incus', 'exec', '--force-interactive', plan.container, '--', 'bash']
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** BYO-PTY(§三 spawn):工具只提供 PTY 托管 + Nonce 注入 + 收帧。 */
|
|
541
|
+
export function buildSpawnArgv(spawnCommand: string): string[] {
|
|
542
|
+
return ['script', '-qfec', `stty -echo 2>/dev/null ; exec ${spawnCommand}`, '/dev/null']
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
- [ ] **Step 4: 跑测试确认通过** → PASS。
|
|
547
|
+
|
|
548
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): CLI transport argv builders (ssh/docker/incus + script-hosted BYO-PTY)"`。
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
### Task 4: One-shot runner `oneshot.ts`
|
|
553
|
+
|
|
554
|
+
**Files:**
|
|
555
|
+
- Create: `better-dsh/src/remote/oneshot.ts`
|
|
556
|
+
- Test: `better-dsh/test/remote/oneshot.spec.ts`
|
|
557
|
+
|
|
558
|
+
**Interfaces:**
|
|
559
|
+
```ts
|
|
560
|
+
export interface OneShotOptions { stdin?: string; timeoutSec?: number }
|
|
561
|
+
export interface OneShotResult { exit: number | null; timedOut: boolean; stdout: string; stderr: string; durationMs: number }
|
|
562
|
+
export async function runOneShot(argv: string[], opts?: OneShotOptions): Promise<OneShotResult>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
- [ ] **Step 1: 写失败测试**(本地真 `bash` 当 CLI 夹具,零网络)
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
import { describe, expect, it } from 'vitest'
|
|
569
|
+
import { runOneShot } from '../../src/remote/oneshot.ts'
|
|
570
|
+
|
|
571
|
+
const BASH = (cmd: string): string[] => ['bash', '-c', cmd]
|
|
572
|
+
|
|
573
|
+
describe('runOneShot', () => {
|
|
574
|
+
it('captures stdout, stderr and the binary exit code', async () => {
|
|
575
|
+
const r = await runOneShot(BASH('echo out; echo err >&2; exit 42'))
|
|
576
|
+
expect(r.exit).toBe(42)
|
|
577
|
+
expect(r.stdout).toBe('out\n')
|
|
578
|
+
expect(r.stderr).toBe('err\n')
|
|
579
|
+
expect(r.timedOut).toBe(false)
|
|
580
|
+
})
|
|
581
|
+
it('pipes stdin and closes it', async () => {
|
|
582
|
+
const r = await runOneShot(BASH('cat'), { stdin: 'line1\nline2\n' })
|
|
583
|
+
expect(r.exit).toBe(0)
|
|
584
|
+
expect(r.stdout).toBe('line1\nline2\n')
|
|
585
|
+
})
|
|
586
|
+
it('preserves utf8 across chunk boundaries', async () => {
|
|
587
|
+
const r = await runOneShot(BASH("printf '中%.0s' $(seq 1 20000); echo"))
|
|
588
|
+
expect(r.stdout).toBe('中'.repeat(20_000) + '\n')
|
|
589
|
+
expect(r.exit).toBe(0)
|
|
590
|
+
})
|
|
591
|
+
it('timeout: TERM ignored -> KILL escalation observable (exit null, >2s), flagged, bounded', async () => {
|
|
592
|
+
const r = await runOneShot(BASH('trap "" TERM; sleep 30'), { timeoutSec: 1 })
|
|
593
|
+
expect(r.timedOut).toBe(true)
|
|
594
|
+
expect(r.exit).toBe(null)
|
|
595
|
+
expect(r.durationMs).toBeGreaterThan(2_500)
|
|
596
|
+
expect(r.durationMs).toBeLessThan(8_000)
|
|
597
|
+
})
|
|
598
|
+
it('timeout bounds completion when a background descendant holds the pipes (process-group kill)', async () => {
|
|
599
|
+
const r = await runOneShot(BASH('sleep 8 & sleep 8'), { timeoutSec: 1 })
|
|
600
|
+
expect(r.timedOut).toBe(true)
|
|
601
|
+
expect(r.durationMs).toBeLessThan(5_000)
|
|
602
|
+
})
|
|
603
|
+
it('spawn failure surfaces the OS error verbatim (error transparency)', async () => {
|
|
604
|
+
const r = await runOneShot(['definitely-not-a-real-cli-xyz', '--version'])
|
|
605
|
+
expect(r.exit).toBe(null)
|
|
606
|
+
expect(r.stderr).toContain('spawn failed')
|
|
607
|
+
expect(r.stderr).toContain('ENOENT')
|
|
608
|
+
})
|
|
609
|
+
})
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
- [ ] **Step 2: 跑测试确认失败** → FAIL。
|
|
613
|
+
|
|
614
|
+
- [ ] **Step 3: 最小实现**
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
import { spawn } from 'node:child_process'
|
|
618
|
+
|
|
619
|
+
export interface OneShotOptions { stdin?: string; timeoutSec?: number }
|
|
620
|
+
export interface OneShotResult { exit: number | null; timedOut: boolean; stdout: string; stderr: string; durationMs: number }
|
|
621
|
+
|
|
622
|
+
/** 单流硬收集帽(4M UTF-16 code units,ASCII≈4MB):防失控输出吃内存;模型面截断由上层 tailWindow 负责。 */
|
|
623
|
+
const HARD_CAP = 4_000_000
|
|
624
|
+
|
|
625
|
+
/** One-shot:子进程退出码即收尾(spec §五.1),stdout/stderr 分离保留。 */
|
|
626
|
+
export async function runOneShot(argv: string[], opts: OneShotOptions = {}): Promise<OneShotResult> {
|
|
627
|
+
const started = Date.now()
|
|
628
|
+
// detached+进程组:超时杀整组——后台子进程若仍占着管道,只杀直接子进程会让 close
|
|
629
|
+
// 永不触发(实测 `sleep 30 &` 场景无限挂起);组杀与 Ruling 9 的 pty 杀法同构。
|
|
630
|
+
const child = spawn(argv[0]!, argv.slice(1), { stdio: ['pipe', 'pipe', 'pipe'], detached: true })
|
|
631
|
+
let stdout = ''
|
|
632
|
+
let stderr = ''
|
|
633
|
+
let timedOut = false
|
|
634
|
+
child.stdout.setEncoding('utf8').on('data', (d: string) => {
|
|
635
|
+
if (stdout.length < HARD_CAP) stdout = (stdout + d).slice(0, HARD_CAP)
|
|
636
|
+
})
|
|
637
|
+
child.stderr.setEncoding('utf8').on('data', (d: string) => {
|
|
638
|
+
if (stderr.length < HARD_CAP) stderr = (stderr + d).slice(0, HARD_CAP)
|
|
639
|
+
})
|
|
640
|
+
child.stdin.on('error', () => {}) // EPIPE on early exit is not ours to raise
|
|
641
|
+
if (opts.stdin !== undefined) child.stdin.write(opts.stdin)
|
|
642
|
+
child.stdin.end()
|
|
643
|
+
|
|
644
|
+
let termTimer: NodeJS.Timeout | undefined
|
|
645
|
+
let killTimer: NodeJS.Timeout | undefined
|
|
646
|
+
const clearTimers = (): void => {
|
|
647
|
+
if (termTimer !== undefined) clearTimeout(termTimer)
|
|
648
|
+
if (killTimer !== undefined) clearTimeout(killTimer)
|
|
649
|
+
}
|
|
650
|
+
if (opts.timeoutSec !== undefined) {
|
|
651
|
+
const killGroup = (sig: NodeJS.Signals): void => {
|
|
652
|
+
if (child.pid === undefined) { child.kill(sig); return }
|
|
653
|
+
try { process.kill(-child.pid, sig) } catch { /* group already gone */ }
|
|
654
|
+
}
|
|
655
|
+
termTimer = setTimeout(() => {
|
|
656
|
+
timedOut = true
|
|
657
|
+
killGroup('SIGTERM')
|
|
658
|
+
killTimer = setTimeout(() => killGroup('SIGKILL'), 2_000)
|
|
659
|
+
}, opts.timeoutSec * 1_000)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const exit = await new Promise<number | null>((resolve) => {
|
|
663
|
+
child.once('close', (code) => resolve(code))
|
|
664
|
+
child.once('error', (err) => {
|
|
665
|
+
stderr += `\n[remote: spawn failed: ${err.message}]`
|
|
666
|
+
resolve(null)
|
|
667
|
+
})
|
|
668
|
+
})
|
|
669
|
+
clearTimers()
|
|
670
|
+
return { exit, timedOut, stdout, stderr, durationMs: Date.now() - started }
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
- [ ] **Step 4: 跑测试确认通过** → PASS。
|
|
675
|
+
|
|
676
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): one-shot runner — exit code only, split streams, TERM/KILL timeout"`。
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
### Task 5: Persistent PTY 会话与池 `pty-session.ts`
|
|
681
|
+
|
|
682
|
+
**Files:**
|
|
683
|
+
- Create: `better-dsh/src/remote/pty-session.ts`
|
|
684
|
+
- Test: `better-dsh/test/remote/pty-session.spec.ts`
|
|
685
|
+
|
|
686
|
+
**Interfaces:**
|
|
687
|
+
```ts
|
|
688
|
+
export const RECONNECT_NOTICE = '[remote: session reconnected to fresh shell; cwd reset to default]'
|
|
689
|
+
export interface PtySessionOptions { key: string; argv: string[]; idleTtlSec: number; initTimeoutSec?: number; onDead?: () => void }
|
|
690
|
+
export interface PtyDispatchOptions { stdin?: string; timeoutSec?: number }
|
|
691
|
+
export interface PtyDispatchResult { output: string; exit: number | null; cwd: string | null; timedOut: boolean; reconnected: boolean; durationMs: number }
|
|
692
|
+
export type PtyState = 'cold' | 'starting' | 'ready' | 'dead' | 'disposed'
|
|
693
|
+
/** 会话运行时快照(status 面消费,Ruling 16 词汇源)。 */
|
|
694
|
+
export interface SessionSnapshot { state: PtyState; busy: boolean; idleMs: number | null; pid: number | null }
|
|
695
|
+
export class PtySession {
|
|
696
|
+
constructor(opts: PtySessionOptions)
|
|
697
|
+
get sessionState(): PtyState
|
|
698
|
+
get snapshot(): SessionSnapshot
|
|
699
|
+
dispatch(cmd: string, opts?: PtyDispatchOptions): Promise<PtyDispatchResult>
|
|
700
|
+
dispose(): Promise<void>
|
|
701
|
+
}
|
|
702
|
+
export interface PtyPoolDefaults { idleTtlSec: number; initTimeoutSec?: number }
|
|
703
|
+
export class PtyPool {
|
|
704
|
+
constructor(defaults: PtyPoolDefaults)
|
|
705
|
+
getOrCreate(key: string, argv: string[]): PtySession
|
|
706
|
+
get size(): number
|
|
707
|
+
inspect(key: string): SessionSnapshot | undefined
|
|
708
|
+
disposeAll(): Promise<void>
|
|
709
|
+
}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
- [ ] **Step 1: 写失败测试**(本地 `bash` 与 `script -qfec bash /dev/null` 当传输夹具——真协议、真 init、真帧,零网络)
|
|
713
|
+
|
|
714
|
+
```ts
|
|
715
|
+
import { describe, expect, it } from 'vitest'
|
|
716
|
+
import { PtyPool, PtySession } from '../../src/remote/pty-session.ts'
|
|
717
|
+
|
|
718
|
+
const OPTS = { idleTtlSec: 600 }
|
|
719
|
+
|
|
720
|
+
describe('PtySession over plain local bash', () => {
|
|
721
|
+
it('cold starts, frames output/exit/cwd', async () => {
|
|
722
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
723
|
+
const r = await s.dispatch('echo hello && cd /tmp && pwd')
|
|
724
|
+
expect(r.output.trim()).toBe('hello\n/tmp')
|
|
725
|
+
expect(r.exit).toBe(0)
|
|
726
|
+
expect(r.cwd).toBe('/tmp')
|
|
727
|
+
expect(r.reconnected).toBe(false)
|
|
728
|
+
await s.dispose()
|
|
729
|
+
})
|
|
730
|
+
it('multi-line cmd = one compound frame, exit is the last command\'s', async () => {
|
|
731
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
732
|
+
const r = await s.dispatch('echo a\necho b\nfalse')
|
|
733
|
+
expect(r.output).toBe('a\nb\n')
|
|
734
|
+
expect(r.exit).toBe(1)
|
|
735
|
+
await s.dispose()
|
|
736
|
+
})
|
|
737
|
+
it('state persists across dispatches (cwd/env)', async () => {
|
|
738
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
739
|
+
await s.dispatch('export FOO=bar && cd /tmp')
|
|
740
|
+
const r = await s.dispatch('echo "$FOO" && pwd')
|
|
741
|
+
expect(r.output).toBe('bar\n/tmp\n')
|
|
742
|
+
await s.dispose()
|
|
743
|
+
})
|
|
744
|
+
it('snapshot feeds the status face: ready+idle after dispatch, disposed after dispose', async () => {
|
|
745
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
746
|
+
await s.dispatch('echo hi')
|
|
747
|
+
expect(s.snapshot).toMatchObject({ state: 'ready', busy: false })
|
|
748
|
+
expect(s.snapshot.idleMs).toBeGreaterThanOrEqual(0)
|
|
749
|
+
const pool = new PtyPool({ idleTtlSec: 600 })
|
|
750
|
+
const s2 = pool.getOrCreate('t:x', ['bash'])
|
|
751
|
+
expect(pool.inspect('t:x')?.state).toBe('cold')
|
|
752
|
+
expect(pool.inspect('missing')).toBeUndefined()
|
|
753
|
+
await s2.dispatch('echo y')
|
|
754
|
+
expect(pool.inspect('t:x')).toMatchObject({ state: 'ready', busy: false })
|
|
755
|
+
await s.dispose(); await pool.disposeAll()
|
|
756
|
+
})
|
|
757
|
+
it('serializes concurrent dispatches on one session (clean A/B output = no stdin interleave)', async () => {
|
|
758
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
759
|
+
const [r1, r2] = await Promise.all([
|
|
760
|
+
s.dispatch('sleep 0.4 ; echo A'),
|
|
761
|
+
s.dispatch('echo B'),
|
|
762
|
+
])
|
|
763
|
+
expect(r1.output.trim()).toBe('A')
|
|
764
|
+
expect(r2.output.trim()).toBe('B')
|
|
765
|
+
expect(r1.durationMs).toBeGreaterThan(300)
|
|
766
|
+
await s.dispose()
|
|
767
|
+
})
|
|
768
|
+
it('unexpected shell death: partial + null exit; next dispatch reconnects with the notice', async () => {
|
|
769
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
770
|
+
const r1 = await s.dispatch('kill -9 $$')
|
|
771
|
+
expect(r1.exit).toBe(null)
|
|
772
|
+
const r2 = await s.dispatch('echo back')
|
|
773
|
+
expect(r2.reconnected).toBe(true)
|
|
774
|
+
expect(r2.output.trim()).toBe('back')
|
|
775
|
+
expect(r2.exit).toBe(0)
|
|
776
|
+
await s.dispose()
|
|
777
|
+
})
|
|
778
|
+
it('idle TTL disposes the session and drops it from the pool (transparent cold start, no notice)', async () => {
|
|
779
|
+
const pool = new PtyPool({ idleTtlSec: 1 })
|
|
780
|
+
const s = pool.getOrCreate('t:x', ['bash'])
|
|
781
|
+
await s.dispatch('echo hi')
|
|
782
|
+
await new Promise((r) => setTimeout(r, 1_800))
|
|
783
|
+
expect(s.sessionState).toBe('disposed')
|
|
784
|
+
expect(pool.size).toBe(0)
|
|
785
|
+
const s2 = pool.getOrCreate('t:x', ['bash'])
|
|
786
|
+
const r = await s2.dispatch('echo again')
|
|
787
|
+
expect(r.reconnected).toBe(false)
|
|
788
|
+
await pool.disposeAll()
|
|
789
|
+
})
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
describe('PtySession over script(1)-hosted PTY (BYO-PTY shape)', () => {
|
|
793
|
+
it('timeout sends ^C through the pty line discipline: framed exit 130, flagged interrupted', async () => {
|
|
794
|
+
const s = new PtySession({ key: 'k', argv: ['script', '-qfec', 'bash', '/dev/null'], ...OPTS })
|
|
795
|
+
const r = await s.dispatch('sleep 30', { timeoutSec: 1 })
|
|
796
|
+
expect(r.timedOut).toBe(true)
|
|
797
|
+
expect(r.durationMs).toBeLessThan(8_000)
|
|
798
|
+
expect(r.exit).toBe(130)
|
|
799
|
+
const r2 = await s.dispatch('echo alive') // session survived the interrupt
|
|
800
|
+
expect(r2.exit).toBe(0)
|
|
801
|
+
await s.dispose()
|
|
802
|
+
}, 20_000)
|
|
803
|
+
it('timeout kill fallback on a transport without interrupt (plain bash over pipes)', async () => {
|
|
804
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
805
|
+
const r = await s.dispatch('trap "" INT; sleep 30', { timeoutSec: 1 })
|
|
806
|
+
expect(r.timedOut).toBe(true)
|
|
807
|
+
expect(r.exit).toBe(null)
|
|
808
|
+
expect(r.durationMs).toBeLessThan(10_000)
|
|
809
|
+
await s.dispose()
|
|
810
|
+
}, 20_000)
|
|
811
|
+
it('forged static 133-D marker with wrong nonce never truncates the stream', async () => {
|
|
812
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
813
|
+
const r = await s.dispatch(`printf '\\x1b]133;D;deadbeefdeadbeef;99;/pwn\\x07' ; echo real`)
|
|
814
|
+
expect(r.exit).toBe(0)
|
|
815
|
+
expect(r.output.trim()).toBe('real')
|
|
816
|
+
await s.dispose()
|
|
817
|
+
})
|
|
818
|
+
it('caps dispatch output collection (tail ring) — runaway output cannot eat host memory', async () => {
|
|
819
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
820
|
+
const r = await s.dispatch("printf 'x%.0s' $(seq 1 5000000) ; echo ; echo TAILMARK")
|
|
821
|
+
expect(r.output.length).toBeLessThanOrEqual(4_000_001)
|
|
822
|
+
expect(r.output.trim().endsWith('TAILMARK')).toBe(true)
|
|
823
|
+
await s.dispose()
|
|
824
|
+
}, 30_000)
|
|
825
|
+
it('grace-expiry kill fallback reaps the process group (snapshot pid null, group gone)', async () => {
|
|
826
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
827
|
+
await s.dispatch('echo warm')
|
|
828
|
+
const pid = s.snapshot.pid
|
|
829
|
+
expect(pid).toBeTypeOf('number')
|
|
830
|
+
const r = await s.dispatch('trap "" INT; sleep 30', { timeoutSec: 1 })
|
|
831
|
+
expect(r.exit).toBe(null)
|
|
832
|
+
expect(r.timedOut).toBe(true)
|
|
833
|
+
expect(s.snapshot.pid).toBe(null)
|
|
834
|
+
await new Promise((r) => setTimeout(r, 1_200)) // 等组长异步收割(zombie 保留 PGID,同 tick 断言必假阴)
|
|
835
|
+
expect(() => process.kill(-pid!, 0)).toThrow()
|
|
836
|
+
await s.dispose()
|
|
837
|
+
}, 20_000)
|
|
838
|
+
it('dispose is terminal: stale-reference dispatch rejects E_SESSION_DISPOSED', async () => {
|
|
839
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
840
|
+
await s.dispatch('echo hi')
|
|
841
|
+
await s.dispose()
|
|
842
|
+
await expect(s.dispatch('echo late')).rejects.toThrow(/E_SESSION_DISPOSED/)
|
|
843
|
+
})
|
|
844
|
+
it('dispose mid-flight settles the in-flight dispatch promptly (exit null, not timedOut)', async () => {
|
|
845
|
+
const s = new PtySession({ key: 'k', argv: ['bash'], ...OPTS })
|
|
846
|
+
const p = s.dispatch('sleep 30', { timeoutSec: 30 })
|
|
847
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
848
|
+
await s.dispose()
|
|
849
|
+
const r = await p
|
|
850
|
+
expect(r.exit).toBe(null)
|
|
851
|
+
expect(r.timedOut).toBe(false)
|
|
852
|
+
expect(r.durationMs).toBeLessThan(5_000)
|
|
853
|
+
await new Promise((r2) => setTimeout(r2, 1_200)) // 等 dispose 的 killTree 兑现,防组泄漏到下一测试
|
|
854
|
+
}, 20_000)
|
|
855
|
+
})
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
- [ ] **Step 2: 跑测试确认失败** → FAIL。
|
|
859
|
+
|
|
860
|
+
- [ ] **Step 3: 最小实现**
|
|
861
|
+
|
|
862
|
+
```ts
|
|
863
|
+
import { spawn, type ChildProcess } from 'node:child_process'
|
|
864
|
+
import { buildInitCommand, createNonceFrameParser, genNonce, wrapPtyCommand } from './nonce-framing.ts'
|
|
865
|
+
|
|
866
|
+
/** spec §五.2 逐字。 */
|
|
867
|
+
export const RECONNECT_NOTICE = '[remote: session reconnected to fresh shell; cwd reset to default]'
|
|
868
|
+
|
|
869
|
+
export interface PtySessionOptions {
|
|
870
|
+
key: string
|
|
871
|
+
argv: string[]
|
|
872
|
+
idleTtlSec: number
|
|
873
|
+
initTimeoutSec?: number
|
|
874
|
+
/** 池回调:会话彻底出池(dispose)时触发;进程意外死亡不出池(Ruling 7)。 */
|
|
875
|
+
onDead?: () => void
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export interface PtyDispatchOptions { stdin?: string; timeoutSec?: number }
|
|
879
|
+
export interface PtyDispatchResult {
|
|
880
|
+
output: string
|
|
881
|
+
exit: number | null
|
|
882
|
+
cwd: string | null
|
|
883
|
+
timedOut: boolean
|
|
884
|
+
reconnected: boolean
|
|
885
|
+
durationMs: number
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
export type PtyState = 'cold' | 'starting' | 'ready' | 'dead' | 'disposed'
|
|
889
|
+
|
|
890
|
+
const DEFAULT_INIT_TIMEOUT_SEC = 30
|
|
891
|
+
const INTERRUPT_GRACE_MS = 3_000
|
|
892
|
+
/** pty 收集内存安全帽(Ruling P15,与 oneshot HARD_CAP 对齐):模型面只见尾窗,保留尾部。 */
|
|
893
|
+
const OUTPUT_HARD_CAP = 4_000_000
|
|
894
|
+
|
|
895
|
+
export class PtySession {
|
|
896
|
+
private proc: ChildProcess | null = null
|
|
897
|
+
private state: PtyState = 'cold'
|
|
898
|
+
private startPromise: Promise<void> | null = null
|
|
899
|
+
private queue: Promise<unknown> = Promise.resolve()
|
|
900
|
+
private idleTimer: NodeJS.Timeout | null = null
|
|
901
|
+
private onChunk: ((c: string) => void) | null = null
|
|
902
|
+
private deathWaiters: Array<() => void> = []
|
|
903
|
+
private diag = ''
|
|
904
|
+
private inFlight = 0
|
|
905
|
+
private lastSettleAt = 0
|
|
906
|
+
|
|
907
|
+
constructor(private readonly opts: PtySessionOptions) {}
|
|
908
|
+
|
|
909
|
+
get sessionState(): PtyState { return this.state }
|
|
910
|
+
|
|
911
|
+
/** status 面快照:state + busy(有命令在跑)+ idleMs(上次 settle 至今)+ pid(进程组头,kill 兜底可观测)。 */
|
|
912
|
+
get snapshot(): SessionSnapshot {
|
|
913
|
+
return { state: this.state, busy: this.inFlight > 0, idleMs: this.lastSettleAt > 0 ? Date.now() - this.lastSettleAt : null, pid: this.proc?.pid ?? null }
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** 同一会话命令严格串行(FIFO);死亡/冷态先重启。 */
|
|
917
|
+
dispatch(cmd: string, dOpts: PtyDispatchOptions = {}): Promise<PtyDispatchResult> {
|
|
918
|
+
const attempt = async (): Promise<PtyDispatchResult> => {
|
|
919
|
+
const started = Date.now()
|
|
920
|
+
const reconnected = this.state === 'dead'
|
|
921
|
+
if (this.state !== 'ready') await this.start()
|
|
922
|
+
this.inFlight++
|
|
923
|
+
return await new Promise<PtyDispatchResult>((resolve) => {
|
|
924
|
+
const nonce = genNonce()
|
|
925
|
+
let output = ''
|
|
926
|
+
let interrupted = false
|
|
927
|
+
let settled = false
|
|
928
|
+
let interruptTimer: NodeJS.Timeout | undefined
|
|
929
|
+
let killTimer: NodeJS.Timeout | undefined
|
|
930
|
+
const deathWaiter = (): void => finish({ output, exit: null, cwd: null, timedOut: interrupted })
|
|
931
|
+
const cleanup = (): void => {
|
|
932
|
+
this.onChunk = null
|
|
933
|
+
// I1(终审):已结算 dispatch 的死亡等待必须摘除——否则闭包钉住整段 output,
|
|
934
|
+
// 长命会话每调用累积一个 waiter(init 路径的 filter 同款形态)
|
|
935
|
+
this.deathWaiters = this.deathWaiters.filter((w) => w !== deathWaiter)
|
|
936
|
+
if (interruptTimer !== undefined) clearTimeout(interruptTimer)
|
|
937
|
+
if (killTimer !== undefined) clearTimeout(killTimer)
|
|
938
|
+
}
|
|
939
|
+
const finish = (r: Omit<PtyDispatchResult, 'durationMs' | 'reconnected'>): void => {
|
|
940
|
+
if (settled) return
|
|
941
|
+
settled = true
|
|
942
|
+
cleanup()
|
|
943
|
+
this.inFlight--
|
|
944
|
+
this.lastSettleAt = Date.now()
|
|
945
|
+
this.resetIdleTimer()
|
|
946
|
+
resolve({ ...r, reconnected, durationMs: Date.now() - started })
|
|
947
|
+
}
|
|
948
|
+
const parser = createNonceFrameParser(nonce, {
|
|
949
|
+
onOutput: (t) => {
|
|
950
|
+
output += t
|
|
951
|
+
// C1(终审):内存安全帽——无帽时一条 cat /dev/urandom 能在 120s 看门狗内
|
|
952
|
+
// 吃垮宿主 daemon。帽上切片避开代理对(首字符为低位代理则让出一字符)。
|
|
953
|
+
if (output.length > OUTPUT_HARD_CAP) {
|
|
954
|
+
output = output.slice(output.length - OUTPUT_HARD_CAP)
|
|
955
|
+
if (output.charCodeAt(0) >= 0xdc00 && output.charCodeAt(0) <= 0xdfff) output = output.slice(1)
|
|
956
|
+
}
|
|
957
|
+
},
|
|
958
|
+
onFrame: ({ exit, cwd }) => finish({ output, exit, cwd, timedOut: interrupted }),
|
|
959
|
+
})
|
|
960
|
+
this.onChunk = (c) => parser.feed(c)
|
|
961
|
+
this.write(wrapPtyCommand(cmd, nonce))
|
|
962
|
+
if (dOpts.stdin !== undefined) this.write(dOpts.stdin) // Ruling 11: REPL 式紧随
|
|
963
|
+
interruptTimer = setTimeout(() => {
|
|
964
|
+
interrupted = true
|
|
965
|
+
// P8 两段打断:\x03 中断前台作业后,交互 bash 会放弃当前命令行的剩余部分
|
|
966
|
+
// (尾部 marker 不再执行);紧随注入的同 nonce 130-marker 行在 bash 回到
|
|
967
|
+
// 读取态后被执行。无论 bash 弃行还是续行,帧都在宽限内到达且 exit=130,
|
|
968
|
+
// 会话存活;若命令 trap 掉 SIGINT,原 marker 先帧、注入行帧后字节被丢弃。
|
|
969
|
+
this.write('\x03')
|
|
970
|
+
this.write(`printf '\\033]133;D;${nonce};130;%s\\007' "$PWD"\n`)
|
|
971
|
+
killTimer = setTimeout(() => {
|
|
972
|
+
// Ruling 8 的 kill 兜底:宽限无帧 = 会话不可恢复,杀整组后收尸
|
|
973
|
+
// (否则 trap-INT 的卡死进程永生,重连后旧组再不可达)
|
|
974
|
+
this.killTree()
|
|
975
|
+
this.markDead()
|
|
976
|
+
finish({ output, exit: null, cwd: null, timedOut: true })
|
|
977
|
+
}, INTERRUPT_GRACE_MS)
|
|
978
|
+
}, dOpts.timeoutSec === undefined ? 120_000 : dOpts.timeoutSec * 1_000)
|
|
979
|
+
this.deathWaiters.push(deathWaiter)
|
|
980
|
+
})
|
|
981
|
+
}
|
|
982
|
+
const run = this.queue.then(attempt, attempt)
|
|
983
|
+
this.queue = run.then(() => undefined, () => undefined)
|
|
984
|
+
return run
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/** TTL / row teardown:尽力 exit,1s 后杀组,出池。 */
|
|
988
|
+
async dispose(): Promise<void> {
|
|
989
|
+
if (this.state === 'disposed') return
|
|
990
|
+
this.clearIdleTimer()
|
|
991
|
+
this.state = 'disposed'
|
|
992
|
+
this.startPromise = null
|
|
993
|
+
this.write('exit\n')
|
|
994
|
+
setTimeout(() => this.killTree(), 1_000).unref()
|
|
995
|
+
this.opts.onDead?.()
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
private write(s: string): void {
|
|
999
|
+
if (this.proc?.stdin?.writable !== true) return
|
|
1000
|
+
try { this.proc.stdin.write(s) } catch { /* dead: death waiter settles the dispatch */ }
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
private async start(): Promise<void> {
|
|
1004
|
+
if (this.state === 'disposed')
|
|
1005
|
+
throw new Error(`[E_SESSION_DISPOSED] remote pty session '${this.opts.key}' was disposed — the pool replaces disposed sessions; this reference is stale`)
|
|
1006
|
+
if (this.startPromise === null) {
|
|
1007
|
+
this.state = 'starting'
|
|
1008
|
+
this.startPromise = this.doStart().catch((err: unknown) => {
|
|
1009
|
+
this.startPromise = null
|
|
1010
|
+
this.markDead()
|
|
1011
|
+
throw err
|
|
1012
|
+
})
|
|
1013
|
+
}
|
|
1014
|
+
return this.startPromise
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
private async doStart(): Promise<void> {
|
|
1018
|
+
const proc = spawn(this.opts.argv[0]!, this.opts.argv.slice(1), {
|
|
1019
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1020
|
+
detached: true, // Ruling 9: 进程组杀法
|
|
1021
|
+
})
|
|
1022
|
+
this.proc = proc
|
|
1023
|
+
proc.stdout.setEncoding('utf8')
|
|
1024
|
+
proc.stdout.on('data', (d: string) => { this.onChunk?.(d) })
|
|
1025
|
+
proc.stderr.setEncoding('utf8').on('data', (d: string) => { this.diag = (this.diag + d).slice(-2_000) })
|
|
1026
|
+
proc.once('close', () => this.markDead())
|
|
1027
|
+
proc.once('error', (err) => { this.diag = (this.diag + `\n[remote: spawn failed: ${err.message}]`).slice(-2_000) })
|
|
1028
|
+
|
|
1029
|
+
const initTimeoutSec = this.opts.initTimeoutSec ?? DEFAULT_INIT_TIMEOUT_SEC
|
|
1030
|
+
const initNonce = genNonce()
|
|
1031
|
+
await new Promise<void>((resolve, reject) => {
|
|
1032
|
+
const timer = setTimeout(() => {
|
|
1033
|
+
this.killTree()
|
|
1034
|
+
reject(new Error(
|
|
1035
|
+
`[E_SESSION_START] remote pty session '${this.opts.key}' did not initialize in ${initTimeoutSec}s` +
|
|
1036
|
+
(this.diag.length > 0 ? `; transport stderr tail: ${this.diag}` : '')))
|
|
1037
|
+
}, initTimeoutSec * 1_000)
|
|
1038
|
+
const waiter = (): void => {
|
|
1039
|
+
clearTimeout(timer)
|
|
1040
|
+
reject(new Error(
|
|
1041
|
+
`[E_SESSION_DIED] remote pty session '${this.opts.key}' died before initializing` +
|
|
1042
|
+
(this.diag.length > 0 ? `; transport stderr tail: ${this.diag}` : '')))
|
|
1043
|
+
}
|
|
1044
|
+
this.deathWaiters.push(waiter)
|
|
1045
|
+
const parser = createNonceFrameParser(initNonce, {
|
|
1046
|
+
onFrame: () => {
|
|
1047
|
+
clearTimeout(timer)
|
|
1048
|
+
this.deathWaiters = this.deathWaiters.filter((w) => w !== waiter)
|
|
1049
|
+
if (this.state === 'disposed') {
|
|
1050
|
+
// dispose 竞态 init:不得复活已终态会话(否则重挂 idle timer + 幽灵 ready)
|
|
1051
|
+
reject(new Error(`[E_SESSION_DISPOSED] remote pty session '${this.opts.key}' disposed during init`))
|
|
1052
|
+
return
|
|
1053
|
+
}
|
|
1054
|
+
this.state = 'ready'
|
|
1055
|
+
this.resetIdleTimer()
|
|
1056
|
+
resolve()
|
|
1057
|
+
},
|
|
1058
|
+
})
|
|
1059
|
+
this.onChunk = (c) => parser.feed(c)
|
|
1060
|
+
this.write(buildInitCommand(initNonce))
|
|
1061
|
+
})
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private markDead(): void {
|
|
1065
|
+
if (this.state === 'disposed') {
|
|
1066
|
+
// dispose 是终态:不再翻转 state,但在飞 dispatch 的死亡等待仍需立即结算
|
|
1067
|
+
// (row teardown 不是超时——不得等 123s 看门狗,也不得错标 timedOut)
|
|
1068
|
+
const waiters = this.deathWaiters
|
|
1069
|
+
this.deathWaiters = []
|
|
1070
|
+
for (const w of waiters) w()
|
|
1071
|
+
return
|
|
1072
|
+
}
|
|
1073
|
+
this.state = 'dead'
|
|
1074
|
+
this.startPromise = null
|
|
1075
|
+
this.clearIdleTimer()
|
|
1076
|
+
const waiters = this.deathWaiters
|
|
1077
|
+
this.deathWaiters = []
|
|
1078
|
+
for (const w of waiters) w()
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
private resetIdleTimer(): void {
|
|
1082
|
+
this.clearIdleTimer()
|
|
1083
|
+
this.idleTimer = setTimeout(() => void this.dispose(), this.opts.idleTtlSec * 1_000)
|
|
1084
|
+
this.idleTimer.unref()
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private clearIdleTimer(): void {
|
|
1088
|
+
if (this.idleTimer !== null) { clearTimeout(this.idleTimer); this.idleTimer = null }
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
private killTree(): void {
|
|
1092
|
+
const pid = this.proc?.pid
|
|
1093
|
+
this.proc = null
|
|
1094
|
+
if (pid === undefined) return
|
|
1095
|
+
try { process.kill(-pid, 'SIGTERM') } catch { /* already gone */ }
|
|
1096
|
+
setTimeout(() => { try { process.kill(-pid, 'SIGKILL') } catch { /* already gone */ } }, 2_000).unref()
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
export interface PtyPoolDefaults { idleTtlSec: number; initTimeoutSec?: number }
|
|
1101
|
+
|
|
1102
|
+
/** 会话池:键 → 会话。意外死亡的会话留在池内(下次 dispatch 带 reconnect 提示)。 */
|
|
1103
|
+
export class PtyPool {
|
|
1104
|
+
private readonly sessions = new Map<string, PtySession>()
|
|
1105
|
+
|
|
1106
|
+
constructor(private readonly defaults: PtyPoolDefaults) {}
|
|
1107
|
+
|
|
1108
|
+
get size(): number { return this.sessions.size }
|
|
1109
|
+
|
|
1110
|
+
/** status 面:键上的会话快照(无会话 = undefined → "none — dials on first exec")。 */
|
|
1111
|
+
inspect(key: string): SessionSnapshot | undefined {
|
|
1112
|
+
return this.sessions.get(key)?.snapshot
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
getOrCreate(key: string, argv: string[]): PtySession {
|
|
1116
|
+
let s = this.sessions.get(key)
|
|
1117
|
+
if (s === undefined || s.sessionState === 'disposed') {
|
|
1118
|
+
s = new PtySession({ key, argv, ...this.defaults, onDead: () => this.sessions.delete(key) })
|
|
1119
|
+
this.sessions.set(key, s)
|
|
1120
|
+
}
|
|
1121
|
+
return s
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
async disposeAll(): Promise<void> {
|
|
1125
|
+
await Promise.all([...this.sessions.values()].map((s) => s.dispose()))
|
|
1126
|
+
this.sessions.clear()
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
- [ ] **Step 4: 跑测试确认通过** → PASS(130 断言由 Ruling P8 两段打断保证确定性:交互 bash 弃行→注入行补帧 130;续行→原 marker 报 130;trap INT 忽略→宽限后 kill 兜底 exit null,走另一测试)。
|
|
1132
|
+
|
|
1133
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): persistent pty session pool — init protocol, serial dispatch, ^C interrupt, TTL, reconnect notice"`。
|
|
1134
|
+
|
|
1135
|
+
---
|
|
1136
|
+
|
|
1137
|
+
### Task 6: `RemoteDriver` 调度器 `driver.ts`
|
|
1138
|
+
|
|
1139
|
+
**Files:**
|
|
1140
|
+
- Create: `better-dsh/src/remote/driver.ts`
|
|
1141
|
+
- Test: `better-dsh/test/remote/driver.spec.ts`
|
|
1142
|
+
|
|
1143
|
+
**Interfaces:**
|
|
1144
|
+
```ts
|
|
1145
|
+
export interface RemoteCallParams { target?: string; spawn?: string; cmd: string; mode?: 'oneshot' | 'pty'; stdin?: string; timeout?: number }
|
|
1146
|
+
export interface RemoteCallResult {
|
|
1147
|
+
mode: 'oneshot' | 'pty'; target: string; exit: number | null; cwd: string | null
|
|
1148
|
+
timedOut: boolean; reconnected: boolean; stdout: string; stderr?: string
|
|
1149
|
+
durationMs: number; truncated?: number
|
|
1150
|
+
}
|
|
1151
|
+
export interface RemoteAuditRecord { target: string; cmd: string; cwd?: string; exit?: number | null; durationMs?: number; error?: string }
|
|
1152
|
+
export interface RemoteDriverOptions {
|
|
1153
|
+
containers?: Record<string, string>
|
|
1154
|
+
execTimeoutSec?: number // default 120
|
|
1155
|
+
idleTtlSec?: number // default 600 (spec: 10min)
|
|
1156
|
+
maxOutputChars?: number // default 30000
|
|
1157
|
+
onAudit?: (r: RemoteAuditRecord) => void
|
|
1158
|
+
/** 测试缝:覆写 argv 构造(默认走 buildOneShotArgv / buildPtyArgv / buildSpawnArgv)。 */
|
|
1159
|
+
oneshotArgvFor?: (plan: TargetPlan, cmd: string) => string[]
|
|
1160
|
+
ptyArgvFor?: (plan: TargetPlan) => string[]
|
|
1161
|
+
spawnArgvFor?: (spawnCommand: string) => string[]
|
|
1162
|
+
}
|
|
1163
|
+
export class RemoteDriver {
|
|
1164
|
+
constructor(opts?: RemoteDriverOptions)
|
|
1165
|
+
call(params: RemoteCallParams, callCtx?: { sessionKey?: string }): Promise<RemoteCallResult>
|
|
1166
|
+
dispose(): Promise<void>
|
|
1167
|
+
}
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
- [ ] **Step 1: 写失败测试**(测试缝把 argv 指到本地 bash,其余全真)
|
|
1171
|
+
|
|
1172
|
+
```ts
|
|
1173
|
+
import { describe, expect, it } from 'vitest'
|
|
1174
|
+
import { RemoteDriver, type RemoteAuditRecord } from '../../src/remote/driver.ts'
|
|
1175
|
+
|
|
1176
|
+
const driverWith = (over: Partial<ConstructorParameters<typeof RemoteDriver>[0]> = {}): RemoteDriver =>
|
|
1177
|
+
new RemoteDriver({
|
|
1178
|
+
oneshotArgvFor: () => ['bash', '-lc', 'echo fixed'],
|
|
1179
|
+
ptyArgvFor: () => ['bash'],
|
|
1180
|
+
spawnArgvFor: () => ['bash'],
|
|
1181
|
+
idleTtlSec: 600,
|
|
1182
|
+
...over,
|
|
1183
|
+
})
|
|
1184
|
+
|
|
1185
|
+
describe('RemoteDriver validation', () => {
|
|
1186
|
+
it('rejects target+spawn and neither (E_PARAMS), and bad timeout (E_BAD_TIMEOUT)', async () => {
|
|
1187
|
+
const d = driverWith()
|
|
1188
|
+
await expect(d.call({ target: 'dev4', spawn: 'bash', cmd: 'x' })).rejects.toThrow(/E_PARAMS/)
|
|
1189
|
+
await expect(d.call({ cmd: 'x' })).rejects.toThrow(/E_PARAMS/)
|
|
1190
|
+
await expect(d.call({ target: 'dev4', cmd: 'x', timeout: 0 })).rejects.toThrow(/E_BAD_TIMEOUT/)
|
|
1191
|
+
await expect(d.call({ target: 'dev4', cmd: 'x', timeout: Number.POSITIVE_INFINITY })).rejects.toThrow(/E_BAD_TIMEOUT/)
|
|
1192
|
+
})
|
|
1193
|
+
it('spawn locks mode to pty even if oneshot was asked', async () => {
|
|
1194
|
+
const d = driverWith()
|
|
1195
|
+
const r = await d.call({ spawn: 'docker exec -it x bash', cmd: 'echo hi', mode: 'oneshot' })
|
|
1196
|
+
expect(r.mode).toBe('pty')
|
|
1197
|
+
await d.dispose()
|
|
1198
|
+
})
|
|
1199
|
+
})
|
|
1200
|
+
|
|
1201
|
+
describe('RemoteDriver routing', () => {
|
|
1202
|
+
it('oneshot default: runs the transport argv, returns binary exit + split stderr', async () => {
|
|
1203
|
+
const seen: string[][] = []
|
|
1204
|
+
const d = new RemoteDriver({
|
|
1205
|
+
oneshotArgvFor: (_plan, cmd) => { seen.push(['oneshot', cmd]); return ['bash', '-c', 'echo out; echo err >&2; exit 3'] },
|
|
1206
|
+
})
|
|
1207
|
+
const r = await d.call({ target: 'dev4', cmd: 'git status' })
|
|
1208
|
+
expect(r.mode).toBe('oneshot')
|
|
1209
|
+
expect(r.exit).toBe(3)
|
|
1210
|
+
expect(r.stdout).toBe('out\n')
|
|
1211
|
+
expect(r.stderr).toBe('err\n')
|
|
1212
|
+
expect(seen).toEqual([['oneshot', 'git status']])
|
|
1213
|
+
await d.dispose()
|
|
1214
|
+
})
|
|
1215
|
+
it('pty mode: session key isolates agents, state persists per key', async () => {
|
|
1216
|
+
const d = driverWith()
|
|
1217
|
+
await d.call({ target: 'dev4', mode: 'pty', cmd: 'cd /tmp' }, { sessionKey: 'a1' })
|
|
1218
|
+
const r = await d.call({ target: 'dev4', mode: 'pty', cmd: 'pwd' }, { sessionKey: 'a1' })
|
|
1219
|
+
expect(r.cwd).toBe('/tmp')
|
|
1220
|
+
const r2 = await d.call({ target: 'dev4', mode: 'pty', cmd: 'pwd' }, { sessionKey: 'a2' })
|
|
1221
|
+
expect(r2.cwd).not.toBe('/tmp') // different agent, fresh session
|
|
1222
|
+
await d.dispose()
|
|
1223
|
+
})
|
|
1224
|
+
it('truncates oversized output to the tail window with disclosure', async () => {
|
|
1225
|
+
const d = new RemoteDriver({
|
|
1226
|
+
maxOutputChars: 10,
|
|
1227
|
+
oneshotArgvFor: () => ['bash', '-c', 'printf "x%.0s" $(seq 1 100)'],
|
|
1228
|
+
})
|
|
1229
|
+
const r = await d.call({ target: 'dev4', cmd: 'x' })
|
|
1230
|
+
expect(r.truncated).toBe(100)
|
|
1231
|
+
expect(r.stdout).toContain('[truncated: showing last 10 of 100 chars]')
|
|
1232
|
+
await d.dispose()
|
|
1233
|
+
})
|
|
1234
|
+
it('emits one audit record per attempt (success, nonzero exit, and error paths)', async () => {
|
|
1235
|
+
const audit: RemoteAuditRecord[] = []
|
|
1236
|
+
const d = driverWith({ onAudit: (r) => audit.push(r) })
|
|
1237
|
+
await d.call({ target: 'dev4', cmd: 'echo ok' })
|
|
1238
|
+
const d255 = new RemoteDriver({
|
|
1239
|
+
onAudit: (r) => audit.push(r),
|
|
1240
|
+
oneshotArgvFor: () => ['bash', '-c', 'exit 255'],
|
|
1241
|
+
})
|
|
1242
|
+
await d255.call({ target: 'no-such-host-xyz', cmd: 'echo no' }).catch(() => {})
|
|
1243
|
+
await expect(d.call({ target: 'a', spawn: 'b', cmd: 'x' })).rejects.toThrow(/E_PARAMS/)
|
|
1244
|
+
expect(audit[0]).toMatchObject({ target: 'dev4', cmd: 'echo ok', exit: 0 })
|
|
1245
|
+
expect(audit[1]).toMatchObject({ target: 'no-such-host-xyz', exit: 255 })
|
|
1246
|
+
expect(audit[2]).toMatchObject({ target: 'a', cmd: 'x', error: expect.stringContaining('E_PARAMS') })
|
|
1247
|
+
await d.dispose(); await d255.dispose()
|
|
1248
|
+
})
|
|
1249
|
+
})
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
注:`no-such-host-xyz` 用例经 `exit 255` 缝走**非零退出的成功分支**审计(`runOneShot` 把 exit 收集为数据、不抛错);真正的 catch 审计路径用 `E_PARAMS` 拒绝断言(`target ?? spawn ?? '?'` 回退)。
|
|
1253
|
+
|
|
1254
|
+
- [ ] **Step 2: 跑测试确认失败** → FAIL。
|
|
1255
|
+
|
|
1256
|
+
- [ ] **Step 3: 最小实现**
|
|
1257
|
+
|
|
1258
|
+
```ts
|
|
1259
|
+
import { runOneShot } from './oneshot.ts'
|
|
1260
|
+
import { PtyPool } from './pty-session.ts'
|
|
1261
|
+
import { tailWindow } from './nonce-framing.ts'
|
|
1262
|
+
import { resolveTarget, type TargetPlan } from './target.ts'
|
|
1263
|
+
import { buildOneShotArgv, buildPtyArgv, buildSpawnArgv } from './transports.ts'
|
|
1264
|
+
|
|
1265
|
+
export interface RemoteCallParams {
|
|
1266
|
+
target?: string
|
|
1267
|
+
spawn?: string
|
|
1268
|
+
cmd: string
|
|
1269
|
+
mode?: 'oneshot' | 'pty'
|
|
1270
|
+
stdin?: string
|
|
1271
|
+
timeout?: number
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
export interface RemoteCallResult {
|
|
1275
|
+
mode: 'oneshot' | 'pty'
|
|
1276
|
+
target: string
|
|
1277
|
+
exit: number | null
|
|
1278
|
+
cwd: string | null
|
|
1279
|
+
timedOut: boolean
|
|
1280
|
+
reconnected: boolean
|
|
1281
|
+
stdout: string
|
|
1282
|
+
stderr?: string
|
|
1283
|
+
durationMs: number
|
|
1284
|
+
truncated?: number
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
export interface RemoteAuditRecord {
|
|
1288
|
+
target: string
|
|
1289
|
+
cmd: string
|
|
1290
|
+
cwd?: string
|
|
1291
|
+
exit?: number | null
|
|
1292
|
+
durationMs?: number
|
|
1293
|
+
error?: string
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
export interface RemoteDriverOptions {
|
|
1297
|
+
containers?: Record<string, string>
|
|
1298
|
+
execTimeoutSec?: number
|
|
1299
|
+
idleTtlSec?: number
|
|
1300
|
+
maxOutputChars?: number
|
|
1301
|
+
onAudit?: (r: RemoteAuditRecord) => void
|
|
1302
|
+
oneshotArgvFor?: (plan: TargetPlan, cmd: string) => string[]
|
|
1303
|
+
ptyArgvFor?: (plan: TargetPlan) => string[]
|
|
1304
|
+
spawnArgvFor?: (spawnCommand: string) => string[]
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/** 双轨调度器(spec §七 Phase 2):参数校验 → 路由 → oneshot 子进程 / pty 会话池。 */
|
|
1308
|
+
export class RemoteDriver {
|
|
1309
|
+
private readonly pool: PtyPool
|
|
1310
|
+
constructor(private readonly opts: RemoteDriverOptions = {}) {
|
|
1311
|
+
this.pool = new PtyPool({ idleTtlSec: opts.idleTtlSec ?? 600 })
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
async call(params: RemoteCallParams, callCtx: { sessionKey?: string } = {}): Promise<RemoteCallResult> {
|
|
1315
|
+
const started = Date.now()
|
|
1316
|
+
try {
|
|
1317
|
+
const routing = this.route(params)
|
|
1318
|
+
const result = await this.execute(params, routing, callCtx)
|
|
1319
|
+
this.audit({
|
|
1320
|
+
target: routing.display, cmd: params.cmd, cwd: result.cwd ?? undefined,
|
|
1321
|
+
exit: result.exit, durationMs: Date.now() - started,
|
|
1322
|
+
})
|
|
1323
|
+
return result
|
|
1324
|
+
} catch (error) {
|
|
1325
|
+
this.audit({
|
|
1326
|
+
target: params.target ?? params.spawn ?? '?', cmd: params.cmd,
|
|
1327
|
+
error: error instanceof Error ? error.message : String(error), durationMs: Date.now() - started,
|
|
1328
|
+
})
|
|
1329
|
+
throw error
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/** Ruling 13:审计 best-effort——钩子抛错不得影响命令路径(成功被毒化成失败+双重审计)。 */
|
|
1334
|
+
private audit(r: RemoteAuditRecord): void {
|
|
1335
|
+
try { this.opts.onAudit?.(r) } catch { /* audit hook failure is never the command's failure */ }
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
private route(params: RemoteCallParams): { mode: 'oneshot' | 'pty'; display: string; plan?: TargetPlan; spawn?: string } {
|
|
1339
|
+
const hasTarget = params.target !== undefined && params.target.length > 0
|
|
1340
|
+
const hasSpawn = params.spawn !== undefined && params.spawn.length > 0
|
|
1341
|
+
if (hasTarget && hasSpawn)
|
|
1342
|
+
throw new Error('[E_PARAMS] remote: pass exactly one of target or spawn, not both')
|
|
1343
|
+
if (!hasTarget && !hasSpawn)
|
|
1344
|
+
throw new Error('[E_PARAMS] remote: target or spawn is required (e.g. { target: "dev4", cmd: "git status" })')
|
|
1345
|
+
if (params.timeout !== undefined && (!Number.isFinite(params.timeout) || params.timeout <= 0))
|
|
1346
|
+
throw new Error(`[E_BAD_TIMEOUT] remote: timeout must be a positive number of seconds (got ${JSON.stringify(params.timeout)})`)
|
|
1347
|
+
if (params.stdin !== undefined && (params.cmd === undefined || params.cmd.length === 0))
|
|
1348
|
+
throw new Error('[E_STDIN_WITHOUT_CMD] remote: stdin requires cmd')
|
|
1349
|
+
const mode: 'oneshot' | 'pty' = hasSpawn ? 'pty' : params.mode ?? 'oneshot'
|
|
1350
|
+
if (hasSpawn) return { mode, display: params.spawn!, spawn: params.spawn }
|
|
1351
|
+
return { mode, display: params.target!, plan: resolveTarget(params.target!, this.opts.containers ?? {}) }
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
private async execute(params: RemoteCallParams, routing: { mode: 'oneshot' | 'pty'; display: string; plan?: TargetPlan; spawn?: string }, callCtx: { sessionKey?: string }): Promise<RemoteCallResult> {
|
|
1355
|
+
const timeoutSec = params.timeout ?? this.opts.execTimeoutSec ?? 120
|
|
1356
|
+
const cap = this.opts.maxOutputChars ?? 30_000
|
|
1357
|
+
if (routing.mode === 'oneshot') {
|
|
1358
|
+
const argv = (this.opts.oneshotArgvFor ?? buildOneShotArgv)(routing.plan!, params.cmd)
|
|
1359
|
+
const r = await runOneShot(argv, { stdin: params.stdin, timeoutSec })
|
|
1360
|
+
const out = tailWindow(r.stdout, cap)
|
|
1361
|
+
const err = r.stderr.length > 0 ? tailWindow(r.stderr, cap) : undefined
|
|
1362
|
+
return {
|
|
1363
|
+
mode: 'oneshot', target: routing.display, exit: r.exit, cwd: null,
|
|
1364
|
+
timedOut: r.timedOut, reconnected: false, stdout: out.text,
|
|
1365
|
+
...(err !== undefined ? { stderr: err.text } : {}), durationMs: r.durationMs,
|
|
1366
|
+
...(out.truncated !== undefined ? { truncated: out.truncated } : {}),
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const argv = routing.spawn !== undefined
|
|
1370
|
+
? (this.opts.spawnArgvFor ?? buildSpawnArgv)(routing.spawn)
|
|
1371
|
+
: (this.opts.ptyArgvFor ?? buildPtyArgv)(routing.plan!)
|
|
1372
|
+
const key = `${callCtx.sessionKey ?? 'no-session'}|${routing.spawn !== undefined ? `s:${routing.spawn}` : `t:${routing.display}`}`
|
|
1373
|
+
const session = this.pool.getOrCreate(key, argv)
|
|
1374
|
+
const r = await session.dispatch(params.cmd, { stdin: params.stdin, timeoutSec })
|
|
1375
|
+
const out = tailWindow(r.output, cap)
|
|
1376
|
+
return {
|
|
1377
|
+
mode: 'pty', target: routing.display, exit: r.exit, cwd: r.cwd,
|
|
1378
|
+
timedOut: r.timedOut, reconnected: r.reconnected, stdout: out.text,
|
|
1379
|
+
durationMs: r.durationMs, ...(out.truncated !== undefined ? { truncated: out.truncated } : {}),
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
async dispose(): Promise<void> { await this.pool.disposeAll() }
|
|
1384
|
+
}
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
- [ ] **Step 4: 跑测试确认通过** → PASS。
|
|
1388
|
+
|
|
1389
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): two-track driver — routing matrix, agent-scoped session keys, audit hook"`。
|
|
1390
|
+
|
|
1391
|
+
---
|
|
1392
|
+
|
|
1393
|
+
### Task 7: 状态面 `status.ts`(诚实词汇表 + on-demand 探测)
|
|
1394
|
+
|
|
1395
|
+
**Files:**
|
|
1396
|
+
- Create: `better-dsh/src/remote/status.ts`
|
|
1397
|
+
- Test: `better-dsh/test/remote/status.spec.ts`
|
|
1398
|
+
|
|
1399
|
+
**Interfaces:**
|
|
1400
|
+
```ts
|
|
1401
|
+
export type ProbeOutcome =
|
|
1402
|
+
| { state: 'reachable'; rttMs: number }
|
|
1403
|
+
| { state: 'unreachable'; detail: string } // 含原生 stderr 单行 / 超时标注
|
|
1404
|
+
| { state: 'container'; status: string } // running / stopped / exited …(事实态)
|
|
1405
|
+
| { state: 'error'; detail: string } // CLI 级错误(No such object 等,原样)
|
|
1406
|
+
export interface ProbeOptions {
|
|
1407
|
+
connectTimeoutSec?: number // default 5(ssh ConnectTimeout)
|
|
1408
|
+
hardTimeoutSec?: number // default 10(外层硬顶,Ruling 17 双重超时)
|
|
1409
|
+
runner?: typeof runOneShot // 测试缝
|
|
1410
|
+
}
|
|
1411
|
+
export async function probeTarget(plan: TargetPlan, opts?: ProbeOptions): Promise<ProbeOutcome>
|
|
1412
|
+
export function renderProbe(p: ProbeOutcome): string
|
|
1413
|
+
/** Ruling P12:renderSession 只消费 state/busy/idleMs 三字段(Pick 超集,测试字面量无需 pid)。 */
|
|
1414
|
+
export function renderSession(s: Pick<SessionSnapshot, 'state' | 'busy' | 'idleMs'> | undefined): string
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
- [ ] **Step 1: 写失败测试**
|
|
1418
|
+
|
|
1419
|
+
```ts
|
|
1420
|
+
import { describe, expect, it } from 'vitest'
|
|
1421
|
+
import { probeTarget, renderProbe, renderSession } from '../../src/remote/status.ts'
|
|
1422
|
+
import type { OneShotResult } from '../../src/remote/oneshot.ts'
|
|
1423
|
+
|
|
1424
|
+
const R = (over: Partial<OneShotResult>): OneShotResult =>
|
|
1425
|
+
({ exit: 0, timedOut: false, stdout: '', stderr: '', durationMs: 10, ...over })
|
|
1426
|
+
|
|
1427
|
+
describe('probeTarget vocabulary (Ruling 16 — offline is banned)', () => {
|
|
1428
|
+
it('ssh exit 0 -> reachable with rtt', async () => {
|
|
1429
|
+
const p = await probeTarget({ kind: 'ssh', host: 'dev4' }, { runner: async () => R({}) })
|
|
1430
|
+
expect(p).toMatchObject({ state: 'reachable' })
|
|
1431
|
+
expect(p.state === 'reachable' && p.rttMs).toBeGreaterThanOrEqual(0)
|
|
1432
|
+
expect(renderProbe(p)).toContain('reachable')
|
|
1433
|
+
})
|
|
1434
|
+
it('ssh failure -> unreachable + native stderr verbatim, never "offline"', async () => {
|
|
1435
|
+
const p = await probeTarget({ kind: 'ssh', host: 'x' }, {
|
|
1436
|
+
runner: async () => R({ exit: 255, stderr: 'ssh: connect to host x port 22: Connection timed out\r\n' }),
|
|
1437
|
+
})
|
|
1438
|
+
expect(p.state).toBe('unreachable')
|
|
1439
|
+
expect(p.state === 'unreachable' && p.detail).toContain('Connection timed out')
|
|
1440
|
+
const text = renderProbe(p)
|
|
1441
|
+
expect(text).toContain('unreachable')
|
|
1442
|
+
expect(text).not.toContain('offline')
|
|
1443
|
+
})
|
|
1444
|
+
it('ssh hang -> hard timeout labeled', async () => {
|
|
1445
|
+
const p = await probeTarget({ kind: 'ssh', host: 'x' }, {
|
|
1446
|
+
runner: async () => R({ exit: null, timedOut: true }),
|
|
1447
|
+
})
|
|
1448
|
+
expect(p).toMatchObject({ state: 'unreachable', detail: expect.stringContaining('probe timed out after 10s') })
|
|
1449
|
+
})
|
|
1450
|
+
it('docker inspect -> factual container state; native error passes through', async () => {
|
|
1451
|
+
const p1 = await probeTarget({ kind: 'docker', container: 'c1' }, { runner: async () => R({ stdout: 'running\n' }) })
|
|
1452
|
+
expect(p1).toEqual({ state: 'container', status: 'running' })
|
|
1453
|
+
const p2 = await probeTarget({ kind: 'docker', container: 'nope' }, {
|
|
1454
|
+
runner: async () => R({ exit: 1, stderr: 'Error: No such object: nope\n' }),
|
|
1455
|
+
})
|
|
1456
|
+
expect(p2).toMatchObject({ state: 'error', detail: expect.stringContaining('No such object') })
|
|
1457
|
+
})
|
|
1458
|
+
it('incus csv -> exact-name row status; missing name -> explicit error', async () => {
|
|
1459
|
+
const out = 'ctr-1,RUNNING\nctr-11,STOPPED\n'
|
|
1460
|
+
const p1 = await probeTarget({ kind: 'incus', container: 'ctr-1' }, { runner: async () => R({ stdout: out }) })
|
|
1461
|
+
expect(p1).toEqual({ state: 'container', status: 'RUNNING' })
|
|
1462
|
+
const p2 = await probeTarget({ kind: 'incus', container: 'zz' }, { runner: async () => R({ stdout: out }) })
|
|
1463
|
+
expect(p2).toMatchObject({ state: 'error', detail: expect.stringContaining("no container named 'zz'") })
|
|
1464
|
+
})
|
|
1465
|
+
})
|
|
1466
|
+
|
|
1467
|
+
describe('renderSession (session layer, Ruling 16)', () => {
|
|
1468
|
+
it('undefined -> none, says dial-on-first-exec, no reachability claim', () => {
|
|
1469
|
+
expect(renderSession(undefined)).toBe('session: none — dials on first exec')
|
|
1470
|
+
})
|
|
1471
|
+
it('ready+quiet -> idle with age; busy -> busy; dead -> died notice', () => {
|
|
1472
|
+
expect(renderSession({ state: 'ready', busy: false, idleMs: 45_000 })).toBe('session: idle 45s (connected)')
|
|
1473
|
+
expect(renderSession({ state: 'ready', busy: true, idleMs: 0 })).toBe('session: busy (a command is running)')
|
|
1474
|
+
expect(renderSession({ state: 'dead', busy: false, idleMs: null })).toBe('session: died — next exec cold-starts a fresh shell')
|
|
1475
|
+
})
|
|
1476
|
+
})
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
- [ ] **Step 2: 跑测试确认失败** — `npm test -- test/remote/status.spec.ts` → FAIL。
|
|
1480
|
+
|
|
1481
|
+
- [ ] **Step 3: 最小实现**
|
|
1482
|
+
|
|
1483
|
+
```ts
|
|
1484
|
+
import { runOneShot, type OneShotOptions, type OneShotResult } from './oneshot.ts'
|
|
1485
|
+
import type { SessionSnapshot } from './pty-session.ts'
|
|
1486
|
+
import type { TargetPlan } from './target.ts'
|
|
1487
|
+
|
|
1488
|
+
/** Ruling 16 词汇表的机器面。 */
|
|
1489
|
+
export type ProbeOutcome =
|
|
1490
|
+
| { state: 'reachable'; rttMs: number }
|
|
1491
|
+
| { state: 'unreachable'; detail: string }
|
|
1492
|
+
| { state: 'container'; status: string }
|
|
1493
|
+
| { state: 'error'; detail: string }
|
|
1494
|
+
|
|
1495
|
+
export interface ProbeOptions {
|
|
1496
|
+
connectTimeoutSec?: number
|
|
1497
|
+
hardTimeoutSec?: number
|
|
1498
|
+
runner?: (argv: string[], opts?: OneShotOptions) => Promise<OneShotResult>
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
function oneLine(s: string): string {
|
|
1502
|
+
return s.trim().split(/\r?\n/).filter(Boolean).at(-1) ?? ''
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* On-demand 探测(Ruling 17):调用瞬间拨一次,双重超时(ssh ConnectTimeout
|
|
1507
|
+
* 5s + 外层 hard 10s),结果即当时事实。绝不缓存、绝不在 boot 时扫。
|
|
1508
|
+
*/
|
|
1509
|
+
export async function probeTarget(plan: TargetPlan, opts: ProbeOptions = {}): Promise<ProbeOutcome> {
|
|
1510
|
+
const ct = opts.connectTimeoutSec ?? 5
|
|
1511
|
+
const hard = opts.hardTimeoutSec ?? 10
|
|
1512
|
+
const runner = opts.runner ?? runOneShot
|
|
1513
|
+
const started = Date.now()
|
|
1514
|
+
const argv =
|
|
1515
|
+
plan.kind === 'ssh'
|
|
1516
|
+
? ['ssh', '-o', 'BatchMode=yes', '-o', `ConnectTimeout=${ct}`, '-T', '--', plan.host, 'true']
|
|
1517
|
+
: plan.kind === 'docker'
|
|
1518
|
+
? ['docker', 'inspect', '--format', '{{.State.Status}}', plan.container]
|
|
1519
|
+
: ['incus', 'list', plan.container, '--format', 'csv', '--columns', 'ns']
|
|
1520
|
+
const r = await runner(argv, { timeoutSec: hard })
|
|
1521
|
+
if (plan.kind === 'ssh') {
|
|
1522
|
+
if (r.exit === 0 && !r.timedOut) return { state: 'reachable', rttMs: Date.now() - started }
|
|
1523
|
+
const detail = r.timedOut
|
|
1524
|
+
? `probe timed out after ${hard}s`
|
|
1525
|
+
: oneLine(r.stderr) || `ssh exited ${r.exit}`
|
|
1526
|
+
return { state: 'unreachable', detail }
|
|
1527
|
+
}
|
|
1528
|
+
if (plan.kind === 'docker') {
|
|
1529
|
+
if (r.exit === 0 && !r.timedOut) return { state: 'container', status: r.stdout.trim() }
|
|
1530
|
+
return { state: 'error', detail: oneLine(r.stderr) || `docker inspect exited ${r.exit}` }
|
|
1531
|
+
}
|
|
1532
|
+
const row = r.stdout.trim().split(/\r?\n/).find((l) => l.split(',')[0] === plan.container)
|
|
1533
|
+
if (r.exit === 0 && !r.timedOut && row !== undefined) return { state: 'container', status: row.split(',')[1]?.trim() ?? 'unknown' }
|
|
1534
|
+
if (r.exit === 0 && !r.timedOut) return { state: 'error', detail: `incus list has no container named '${plan.container}'` }
|
|
1535
|
+
return { state: 'error', detail: oneLine(r.stderr) || `incus exited ${r.exit}` }
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
export function renderProbe(p: ProbeOutcome): string {
|
|
1539
|
+
switch (p.state) {
|
|
1540
|
+
case 'reachable': return `probe: reachable in ${p.rttMs}ms (just now, on demand)`
|
|
1541
|
+
case 'unreachable': return `probe: unreachable — ${p.detail}`
|
|
1542
|
+
case 'error': return `probe: error — ${p.detail}`
|
|
1543
|
+
case 'container': return `probe: container ${p.status} (just now, on demand)`
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
export function renderSession(s: Pick<SessionSnapshot, 'state' | 'busy' | 'idleMs'> | undefined): string {
|
|
1548
|
+
if (s === undefined || s.state === 'cold' || s.state === 'starting' || s.state === 'disposed')
|
|
1549
|
+
return 'session: none — dials on first exec'
|
|
1550
|
+
if (s.state === 'dead') return 'session: died — next exec cold-starts a fresh shell'
|
|
1551
|
+
if (s.busy) return 'session: busy (a command is running)'
|
|
1552
|
+
return `session: idle ${Math.round((s.idleMs ?? 0) / 1000)}s (connected)`
|
|
1553
|
+
}
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+
- [ ] **Step 4: 跑测试确认通过** → PASS。
|
|
1557
|
+
|
|
1558
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): on-demand status probe — honest vocabulary (reachable/unreachable/container fact), session layer render"`。
|
|
1559
|
+
|
|
1560
|
+
---
|
|
1561
|
+
|
|
1562
|
+
### Task 8: driver 状态集成 + 模型面工具 `tool.ts`
|
|
1563
|
+
|
|
1564
|
+
**Files:**
|
|
1565
|
+
- Create: `better-dsh/src/remote/tool.ts`
|
|
1566
|
+
- Modify: `better-dsh/src/remote/driver.ts`(增 `status()` 方法 + `probeRunner` 测试缝)
|
|
1567
|
+
- Test: `better-dsh/test/remote/tool.spec.ts`
|
|
1568
|
+
|
|
1569
|
+
**Interfaces:**
|
|
1570
|
+
```ts
|
|
1571
|
+
// driver.ts 增量:
|
|
1572
|
+
export interface RemoteDriverOptions { /* …Task 6 全部字段… */ probeRunner?: ProbeOptions['runner'] }
|
|
1573
|
+
export class RemoteDriver { /* …Task 6 全部成员… */ status(target: string, callCtx?: { sessionKey?: string }): Promise<string> }
|
|
1574
|
+
// tool.ts:
|
|
1575
|
+
export function createRemoteTool(driver: RemoteDriver, opts?: { getSessionKey?: (exec: ToolRunContext) => string }): ToolDefinition
|
|
1576
|
+
```
|
|
1577
|
+
(`defineTool` / `ToolRunContext` 来自 `@deepseek-ai/dsh-tools`,`ContentBlock` 来自 `@deepseek-ai/dsh-llm`——照 RM0 `src/remote/tool.ts` 的 import 面;RM0 文件在 `.worktrees/remote-tool-rm0/better-dsh/src/remote/tool.ts`,结构样板。)
|
|
1578
|
+
|
|
1579
|
+
> **Ruling P13(2026-09-23,Task 8 实测裁决)**:本 Task 代码块以"tsc 零新增"门为准做 6 处**类型层** harmonization(RM0 形态):①value 信封 `cwd?: string` 且**非 null 才置键**(置 `cwd: null` 会越工具自身声明的 output schema——计划原稿 bug);②guard 后 `args.target as string` / `args.cmd as string`(带注释);③spec 的 execute 结果 cast(`Promise<unknown>` 面);④driver 增量 import 补 `type ProbeOptions`。运行时语义与断言逐字不变。
|
|
1580
|
+
|
|
1581
|
+
**driver.ts 增量代码(并入 Task 6 骨架):**
|
|
1582
|
+
|
|
1583
|
+
```ts
|
|
1584
|
+
import { probeTarget, renderProbe, renderSession } from './status.ts'
|
|
1585
|
+
// RemoteDriverOptions 增一个测试缝字段:
|
|
1586
|
+
// probeRunner?: ProbeOptions['runner']
|
|
1587
|
+
|
|
1588
|
+
/** Ruling 16/17:on-demand 探测 + 会话层,两行如实陈述。 */
|
|
1589
|
+
async status(target: string, callCtx: { sessionKey?: string } = {}): Promise<string> {
|
|
1590
|
+
const plan = resolveTarget(target, this.opts.containers ?? {})
|
|
1591
|
+
const head = `${target} — ${plan.kind === 'ssh' ? 'ssh host' : `${plan.kind} container`}`
|
|
1592
|
+
const probe = await probeTarget(plan, { runner: this.opts.probeRunner })
|
|
1593
|
+
const key = `${callCtx.sessionKey ?? 'no-session'}|t:${target}`
|
|
1594
|
+
return [head, renderProbe(probe), renderSession(this.pool.inspect(key))].join('\n')
|
|
1595
|
+
}
|
|
1596
|
+
```
|
|
1597
|
+
|
|
1598
|
+
- [ ] **Step 1: 写失败测试**
|
|
1599
|
+
|
|
1600
|
+
```ts
|
|
1601
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
1602
|
+
import { createRemoteTool } from '../../src/remote/tool.ts'
|
|
1603
|
+
import { RemoteDriver } from '../../src/remote/driver.ts'
|
|
1604
|
+
import type { ToolRunContext } from '@deepseek-ai/dsh-tools'
|
|
1605
|
+
|
|
1606
|
+
const fakeDriver = (): RemoteDriver =>
|
|
1607
|
+
new RemoteDriver({
|
|
1608
|
+
oneshotArgvFor: () => ['bash', '-c', 'echo out'],
|
|
1609
|
+
ptyArgvFor: () => ['bash'],
|
|
1610
|
+
spawnArgvFor: () => ['bash'],
|
|
1611
|
+
})
|
|
1612
|
+
|
|
1613
|
+
const fakeExec = (): { exec: ToolRunContext; appended: Array<[string, unknown]> } => {
|
|
1614
|
+
const appended: Array<[string, unknown]> = []
|
|
1615
|
+
const exec = {
|
|
1616
|
+
agent: { id: 'agent-1', session: { append: (k: string, v: unknown) => appended.push([k, v]) } },
|
|
1617
|
+
} as unknown as ToolRunContext
|
|
1618
|
+
return { exec, appended }
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
describe('createRemoteTool', () => {
|
|
1622
|
+
it('registers under the name "remote"', () => {
|
|
1623
|
+
expect(createRemoteTool(fakeDriver()).name).toBe('remote')
|
|
1624
|
+
})
|
|
1625
|
+
it('executes through the driver and returns the value envelope', async () => {
|
|
1626
|
+
const tool = createRemoteTool(fakeDriver())
|
|
1627
|
+
const { exec, appended } = fakeExec()
|
|
1628
|
+
const v = await tool.execute!({ target: 'dev4', cmd: 'echo out' } as never, exec)
|
|
1629
|
+
expect(v.text).toBe('out\n')
|
|
1630
|
+
expect(v.exit).toBe(0)
|
|
1631
|
+
expect(appended).toHaveLength(1)
|
|
1632
|
+
expect(appended[0]![0]).toBe('dashr/remote-exec')
|
|
1633
|
+
})
|
|
1634
|
+
it('renders output + footer (+cwd), stderr block, and the reconnect notice line', async () => {
|
|
1635
|
+
const tool = createRemoteTool(fakeDriver())
|
|
1636
|
+
const blocks = tool.output!.render!({} as never, {
|
|
1637
|
+
kind: 'exec', text: 'hello', exit: 0, durationMs: 1500, cwd: '/data', reconnected: true,
|
|
1638
|
+
} as never)
|
|
1639
|
+
const text = (blocks[0] as { type: string; text: string }).text
|
|
1640
|
+
expect(text).toContain('[remote: session reconnected to fresh shell; cwd reset to default]')
|
|
1641
|
+
expect(text).toContain('hello')
|
|
1642
|
+
expect(text).toContain('[exit 0 · 1.5s · cwd /data]')
|
|
1643
|
+
const blocks2 = tool.output!.render!({} as never, {
|
|
1644
|
+
kind: 'exec', text: 'x', exit: 1, durationMs: 100, stderr: 'boom', timedOut: true, truncated: 999,
|
|
1645
|
+
} as never)
|
|
1646
|
+
const text2 = (blocks2[0] as { type: string; text: string }).text
|
|
1647
|
+
expect(text2).toContain('interrupted')
|
|
1648
|
+
expect(text2).toContain('truncated, original 999 chars')
|
|
1649
|
+
expect(text2).toContain('[stderr]\nboom')
|
|
1650
|
+
})
|
|
1651
|
+
it('parameter violations fail before any execution', async () => {
|
|
1652
|
+
const tool = createRemoteTool(fakeDriver())
|
|
1653
|
+
const { exec } = fakeExec()
|
|
1654
|
+
await expect(tool.execute!({ target: 'a', spawn: 'b', cmd: 'x' } as never, exec)).rejects.toThrow(/E_PARAMS/)
|
|
1655
|
+
await expect(tool.execute!({ cmd: 'x' } as never, exec)).rejects.toThrow(/E_PARAMS/)
|
|
1656
|
+
await expect(tool.execute!({ spawn: 'docker exec -it x bash' } as never, exec)).rejects.toThrow(/E_PARAMS/)
|
|
1657
|
+
await expect(tool.execute!({} as never, exec)).rejects.toThrow(/discovery is yours/)
|
|
1658
|
+
})
|
|
1659
|
+
it('cmd-less target call = on-demand status, honest vocabulary, never "offline"', async () => {
|
|
1660
|
+
const d = new RemoteDriver({
|
|
1661
|
+
probeRunner: async () => ({ exit: 0, timedOut: false, stdout: '', stderr: '', durationMs: 240 }),
|
|
1662
|
+
})
|
|
1663
|
+
const tool = createRemoteTool(d)
|
|
1664
|
+
const { exec } = fakeExec()
|
|
1665
|
+
const v = await tool.execute!({ target: 'dev4' } as never, exec)
|
|
1666
|
+
expect(v.kind).toBe('status')
|
|
1667
|
+
expect(v.text).toContain('dev4 — ssh host')
|
|
1668
|
+
expect(v.text).toContain('reachable')
|
|
1669
|
+
expect(v.text).toContain('session: none — dials on first exec')
|
|
1670
|
+
expect(v.text).not.toContain('offline')
|
|
1671
|
+
})
|
|
1672
|
+
it('failed exec attempts append an error audit record before rethrowing', async () => {
|
|
1673
|
+
const tool = createRemoteTool(fakeDriver())
|
|
1674
|
+
const { exec, appended } = fakeExec()
|
|
1675
|
+
await expect(tool.execute!({ target: 'a', spawn: 'b', cmd: 'x' } as never, exec)).rejects.toThrow(/E_PARAMS/)
|
|
1676
|
+
expect(appended).toHaveLength(1)
|
|
1677
|
+
expect(appended[0]![1]).toMatchObject({ target: 'a', cmd: 'x', error: expect.stringContaining('E_PARAMS') })
|
|
1678
|
+
})
|
|
1679
|
+
it('garbage mode and stdin-without-cmd fail loud before any execution', async () => {
|
|
1680
|
+
const tool = createRemoteTool(fakeDriver())
|
|
1681
|
+
const { exec } = fakeExec()
|
|
1682
|
+
await expect(tool.execute!({ target: 'dev4', cmd: 'x', mode: 'pty ' } as never, exec)).rejects.toThrow(/E_BAD_MODE/)
|
|
1683
|
+
await expect(tool.execute!({ target: 'dev4', stdin: 's' } as never, exec)).rejects.toThrow(/E_STDIN_WITHOUT_CMD/)
|
|
1684
|
+
})
|
|
1685
|
+
})
|
|
1686
|
+
```
|
|
1687
|
+
|
|
1688
|
+
- [ ] **Step 2: 跑测试确认失败** → FAIL。
|
|
1689
|
+
|
|
1690
|
+
- [ ] **Step 3: 最小实现**(骨架照 RM0 tool.ts;description 按 spec §三/§四 范式写全)
|
|
1691
|
+
|
|
1692
|
+
```ts
|
|
1693
|
+
import { defineTool, type ToolDefinition, type ToolRunContext } from '@deepseek-ai/dsh-tools'
|
|
1694
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
1695
|
+
import type {} from '@deepseek-ai/dsh-session/types'
|
|
1696
|
+
import { RECONNECT_NOTICE } from './pty-session.ts'
|
|
1697
|
+
import type { RemoteCallResult, RemoteDriver } from './driver.ts'
|
|
1698
|
+
|
|
1699
|
+
/** 会话审计记录(RM0 Ruling 10 形态连续)。 */
|
|
1700
|
+
export interface RemoteExecAudit {
|
|
1701
|
+
target?: string
|
|
1702
|
+
cmd?: string
|
|
1703
|
+
cwd?: string
|
|
1704
|
+
exit?: number | null
|
|
1705
|
+
durationMs?: number
|
|
1706
|
+
error?: string
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
1710
|
+
interface SessionEventMap {
|
|
1711
|
+
'dashr/remote-exec': RemoteExecAudit
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
interface RemoteToolValue {
|
|
1716
|
+
kind: 'exec' | 'status'
|
|
1717
|
+
text: string
|
|
1718
|
+
exit: number | null
|
|
1719
|
+
durationMs: number
|
|
1720
|
+
/** P13:schema 推断为 string——非 null 才置键(`cwd: null` 会越 output schema)。 */
|
|
1721
|
+
cwd?: string
|
|
1722
|
+
stderr?: string
|
|
1723
|
+
timedOut?: boolean
|
|
1724
|
+
reconnected?: boolean
|
|
1725
|
+
truncated?: number
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const NO_SESSION_KEY = 'remote:no-session'
|
|
1729
|
+
|
|
1730
|
+
export function createRemoteTool(
|
|
1731
|
+
driver: RemoteDriver,
|
|
1732
|
+
opts: { getSessionKey?: (exec: ToolRunContext) => string } = {},
|
|
1733
|
+
): ToolDefinition {
|
|
1734
|
+
const getSessionKey = opts.getSessionKey ?? ((exec: ToolRunContext) => exec.agent?.id ?? NO_SESSION_KEY)
|
|
1735
|
+
return defineTool({
|
|
1736
|
+
name: 'remote',
|
|
1737
|
+
description:
|
|
1738
|
+
'Run a command on a remote host or container. Two tracks: oneshot (default) runs `bash -lc` statelessly — fast, ' +
|
|
1739
|
+
'parallel, zero residue, the exit code is the process\'s own; mode "pty" keeps one persistent terminal session ' +
|
|
1740
|
+
'per (agent, target): cwd/env survive across calls, Ctrl-C interrupts work, sudo password prompts are possible. ' +
|
|
1741
|
+
'`target` routes smartly: an ssh host name/IP/domain goes over ssh; "docker:<name>" / "incus:<name>" (or a ' +
|
|
1742
|
+
'server-registered alias) go to that container; an explicit "ssh:<name>" selector always forces ssh — use it ' +
|
|
1743
|
+
'when a bare name is ambiguous (e.g. registered as a container alias but also an ssh host). ' +
|
|
1744
|
+
'`spawn` is the BYO-PTY escape hatch: give the full command that ' +
|
|
1745
|
+
'starts an interactive shell (e.g. "docker exec -it img bash" or "ssh -t jump \'docker exec -it runner bash\'") ' +
|
|
1746
|
+
'and the tool hosts its PTY with nonce framing (mode locks to pty). Nested hops are dumb pipes — only the ' +
|
|
1747
|
+
'innermost bash frames. `stdin` feeds the command\'s input; `timeout` (seconds) interrupts then kills. Native ' +
|
|
1748
|
+
'transport errors pass through verbatim — self-correct from them. Background processes must redirect their own ' +
|
|
1749
|
+
'output (`cmd > log 2>&1 &`): the shared terminal is POSIX behavior, not a tool defect. ' +
|
|
1750
|
+
'Omit `cmd` (with `target`) for an on-demand status probe: the tool dials once right then, bounded (5s connect / ' +
|
|
1751
|
+
'10s hard), and reports honestly — "reachable"/"unreachable — <native error>" for ssh, the factual container ' +
|
|
1752
|
+
'state for docker/incus, plus the session layer ("idle Ns", "busy", or "none — dials on first exec"). It never ' +
|
|
1753
|
+
'reports "offline": a connection not existing says nothing about the target. Probes run at call time, never ' +
|
|
1754
|
+
'cached from boot. Discovery of valid names is yours: read ~/.ssh/config, `docker ps`, `incus list` with your ' +
|
|
1755
|
+
'local tools.',
|
|
1756
|
+
parameters: {
|
|
1757
|
+
target: { type: 'string', description: 'SSH host / IP / domain, or docker:<name> / incus:<name> container, or a registered alias. Omit when using spawn.' },
|
|
1758
|
+
spawn: { type: 'string', description: 'BYO-PTY: full command starting an interactive shell; the tool hosts the PTY + framing. Mode locks to pty.' },
|
|
1759
|
+
cmd: { type: 'string', description: 'One raw shell string parsed by the remote bash (multi-line = one compound; exit = last command\'s). Omit (with target) for an on-demand status probe.' },
|
|
1760
|
+
mode: { type: 'string', description: "'oneshot' (default) or 'pty' (persistent session)." },
|
|
1761
|
+
stdin: { type: 'string', description: 'Optional input fed to the command (oneshot pipe, or written right after it on the pty).' },
|
|
1762
|
+
timeout: { type: 'number', description: 'Per-command timeout in seconds (default 120). pty: Ctrl-C then kill.' },
|
|
1763
|
+
},
|
|
1764
|
+
output: {
|
|
1765
|
+
schema: {
|
|
1766
|
+
type: 'object',
|
|
1767
|
+
additionalProperties: false,
|
|
1768
|
+
properties: {
|
|
1769
|
+
kind: { type: 'string', required: true, enum: ['exec', 'status'] },
|
|
1770
|
+
text: { type: 'string', required: true },
|
|
1771
|
+
exit: { oneOf: [{ type: 'integer' }, { type: 'null' }] },
|
|
1772
|
+
durationMs: { type: 'integer' },
|
|
1773
|
+
cwd: { type: 'string' },
|
|
1774
|
+
stderr: { type: 'string' },
|
|
1775
|
+
timedOut: { type: 'boolean' },
|
|
1776
|
+
reconnected: { type: 'boolean' },
|
|
1777
|
+
truncated: { type: 'integer' },
|
|
1778
|
+
},
|
|
1779
|
+
},
|
|
1780
|
+
render: (_args, value): ContentBlock[] => [{ type: 'text', text: renderValue(value as RemoteToolValue) }],
|
|
1781
|
+
},
|
|
1782
|
+
async execute(args, exec): Promise<RemoteToolValue> {
|
|
1783
|
+
const hasCmd = typeof args.cmd === 'string' && args.cmd.length > 0
|
|
1784
|
+
const hasTarget = typeof args.target === 'string' && args.target.length > 0
|
|
1785
|
+
const hasSpawn = typeof args.spawn === 'string' && args.spawn.length > 0
|
|
1786
|
+
// fail-loud 三闸:垃圾 mode 不静默降级;status 面不吃 stdin。
|
|
1787
|
+
if (args.mode !== undefined && args.mode !== 'oneshot' && args.mode !== 'pty')
|
|
1788
|
+
throw new Error(`[E_BAD_MODE] remote: mode must be 'oneshot' or 'pty' (got ${JSON.stringify(args.mode)})`)
|
|
1789
|
+
// cmd 缺省 + target 在场 = on-demand status(Ruling 16/17);spawn 无可探测物。
|
|
1790
|
+
if (!hasCmd) {
|
|
1791
|
+
if (args.stdin !== undefined)
|
|
1792
|
+
throw new Error('[E_STDIN_WITHOUT_CMD] remote: stdin requires cmd — status probes take no input')
|
|
1793
|
+
if (hasSpawn)
|
|
1794
|
+
throw new Error('[E_PARAMS] remote: spawn is an exec channel — pass cmd (there is nothing to probe for BYO-PTY)')
|
|
1795
|
+
if (!hasTarget)
|
|
1796
|
+
throw new Error(
|
|
1797
|
+
'[E_PARAMS] remote: target or spawn is required; discovery is yours — read ~/.ssh/config, ' +
|
|
1798
|
+
'`docker ps`, `incus list` with your local tools')
|
|
1799
|
+
const text = await driver.status(args.target as string, { sessionKey: getSessionKey(exec) })
|
|
1800
|
+
return { kind: 'status', text, exit: null, durationMs: 0 }
|
|
1801
|
+
}
|
|
1802
|
+
try {
|
|
1803
|
+
const r: RemoteCallResult = await driver.call(
|
|
1804
|
+
{
|
|
1805
|
+
target: args.target, spawn: args.spawn, cmd: args.cmd as string,
|
|
1806
|
+
mode: args.mode === 'oneshot' || args.mode === 'pty' ? args.mode : undefined,
|
|
1807
|
+
stdin: args.stdin, timeout: args.timeout,
|
|
1808
|
+
},
|
|
1809
|
+
{ sessionKey: getSessionKey(exec) },
|
|
1810
|
+
)
|
|
1811
|
+
appendAudit(exec, { target: r.target, cmd: args.cmd, cwd: r.cwd ?? undefined, exit: r.exit, durationMs: r.durationMs })
|
|
1812
|
+
const v: RemoteToolValue = { kind: 'exec', text: r.stdout, exit: r.exit, durationMs: r.durationMs }
|
|
1813
|
+
if (r.cwd !== null) v.cwd = r.cwd
|
|
1814
|
+
if (r.stderr !== undefined) v.stderr = r.stderr
|
|
1815
|
+
if (r.timedOut) v.timedOut = true
|
|
1816
|
+
if (r.reconnected) v.reconnected = true
|
|
1817
|
+
if (r.truncated !== undefined) v.truncated = r.truncated
|
|
1818
|
+
return v
|
|
1819
|
+
} catch (error) {
|
|
1820
|
+
// Ruling 13:失败的 exec 尝试同样落审计(RM0 Ruling 10 形态),随后原样重抛
|
|
1821
|
+
appendAudit(exec, {
|
|
1822
|
+
target: args.target ?? args.spawn, cmd: args.cmd,
|
|
1823
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1824
|
+
})
|
|
1825
|
+
throw error
|
|
1826
|
+
}
|
|
1827
|
+
},
|
|
1828
|
+
})
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function renderValue(v: RemoteToolValue): string {
|
|
1832
|
+
if (v.kind === 'status') return v.text
|
|
1833
|
+
const parts: string[] = []
|
|
1834
|
+
if (v.reconnected === true) parts.push(RECONNECT_NOTICE)
|
|
1835
|
+
parts.push(v.text)
|
|
1836
|
+
const secs = ((v.durationMs ?? 0) / 1_000).toFixed(1)
|
|
1837
|
+
const markers: string[] = []
|
|
1838
|
+
if (v.timedOut === true) markers.push('interrupted')
|
|
1839
|
+
if (v.truncated !== undefined) markers.push(`truncated, original ${v.truncated} chars`)
|
|
1840
|
+
const suffix = markers.length > 0 ? ` · ${markers.join(' · ')}` : ''
|
|
1841
|
+
const cwdTag = typeof v.cwd === 'string' && v.cwd.length > 0 ? ` · cwd ${v.cwd}` : ''
|
|
1842
|
+
parts.push(`[exit ${v.exit} · ${secs}s${suffix}${cwdTag}]`)
|
|
1843
|
+
if (v.stderr !== undefined && v.stderr.length > 0) parts.push('[stderr]', v.stderr)
|
|
1844
|
+
return parts.join('\n')
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function appendAudit(exec: ToolRunContext, data: RemoteExecAudit): void {
|
|
1848
|
+
try { exec.agent?.session.append('dashr/remote-exec', data) } catch { /* best-effort by contract */ }
|
|
1849
|
+
}
|
|
1850
|
+
```
|
|
1851
|
+
|
|
1852
|
+
- [ ] **Step 4: 跑测试确认通过** → PASS。
|
|
1853
|
+
|
|
1854
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): model-facing remote tool — exec + on-demand status face, transparent errors, audit event"`。
|
|
1855
|
+
|
|
1856
|
+
---
|
|
1857
|
+
|
|
1858
|
+
### Task 9: `dashr-remote` 插件行接线 `plugin.ts` + 全量绿
|
|
1859
|
+
|
|
1860
|
+
**Files:**
|
|
1861
|
+
- Create: `better-dsh/src/remote/plugin.ts`
|
|
1862
|
+
- Modify: `better-dsh/package.json`(exports 增 `"./remote"`,紧随 `"./mobile"` 后)
|
|
1863
|
+
- Modify: `better-dsh/tsdown.config.ts`(entry 增 `'src/remote/plugin.ts'`)
|
|
1864
|
+
- Modify: `better-dsh/cordis.patch.yml`(bundles 段落内、`compaction-basic` 行之前插 `- id: dashr-remote` 行,注释风格对齐 RM0)
|
|
1865
|
+
- Test: `better-dsh/test/remote/plugin.spec.ts`
|
|
1866
|
+
|
|
1867
|
+
**Interfaces:**
|
|
1868
|
+
```ts
|
|
1869
|
+
export const name = 'dashr-remote'
|
|
1870
|
+
export const Config: z<RemoteRowConfig> // { containers: Record<string,string>; execTimeoutSec: number; idleTtlSec: number; maxOutputChars: number }
|
|
1871
|
+
export const apply = (ctx: Context, config: RemoteRowConfig): (() => void) | void
|
|
1872
|
+
export default { name, inject: [], Config, apply }
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
- [ ] **Step 1: 写失败测试**
|
|
1876
|
+
|
|
1877
|
+
```ts
|
|
1878
|
+
import { describe, expect, it } from 'vitest'
|
|
1879
|
+
import { apply, Config, name } from '../../src/remote/plugin.ts'
|
|
1880
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
1881
|
+
|
|
1882
|
+
describe('dashr-remote row', () => {
|
|
1883
|
+
it('Config defaults: no containers, 120s exec, 600s TTL, 30000 chars', () => {
|
|
1884
|
+
const c = Config(undefined) as { containers: unknown; execTimeoutSec: number; idleTtlSec: number; maxOutputChars: number }
|
|
1885
|
+
expect(c).toMatchObject({ containers: {}, execTimeoutSec: 120, idleTtlSec: 600, maxOutputChars: 30_000 })
|
|
1886
|
+
})
|
|
1887
|
+
it('mounts dormant without tools, registers the remote tool when tools compose, disposer is safe', () => {
|
|
1888
|
+
expect(name).toBe('dashr-remote')
|
|
1889
|
+
const registered: Array<{ name: string }> = []
|
|
1890
|
+
const fakeCtx = {
|
|
1891
|
+
inject: (_deps: string[], fn: (c: unknown) => void) => {
|
|
1892
|
+
fn({ tools: { register: (t: { name: string }) => registered.push(t) } })
|
|
1893
|
+
},
|
|
1894
|
+
} as unknown as Context
|
|
1895
|
+
const disposer = apply(fakeCtx, Config(undefined) as never) as () => void
|
|
1896
|
+
expect(registered).toHaveLength(1)
|
|
1897
|
+
expect(registered[0]!.name).toBe('remote')
|
|
1898
|
+
expect(() => disposer()).not.toThrow()
|
|
1899
|
+
})
|
|
1900
|
+
it('patch row and build wiring are in place', async () => {
|
|
1901
|
+
const patch = await import('node:fs').then((fs) => fs.readFileSync(new URL('../../cordis.patch.yml', import.meta.url), 'utf8'))
|
|
1902
|
+
expect(patch).toContain('- id: dashr-remote')
|
|
1903
|
+
expect(patch).toContain("name: 'better-dsh/remote'")
|
|
1904
|
+
const pkg = JSON.parse(await import('node:fs').then((fs) => fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')))
|
|
1905
|
+
expect(pkg.exports['./remote']).toEqual({ types: './lib/remote/plugin.d.ts', default: './lib/remote/plugin.js' })
|
|
1906
|
+
const tsd = await import('node:fs').then((fs) => fs.readFileSync(new URL('../../tsdown.config.ts', import.meta.url), 'utf8'))
|
|
1907
|
+
expect(tsd).toContain("'src/remote/plugin.ts'")
|
|
1908
|
+
})
|
|
1909
|
+
})
|
|
1910
|
+
```
|
|
1911
|
+
|
|
1912
|
+
- [ ] **Step 2: 跑测试确认失败** → FAIL。
|
|
1913
|
+
|
|
1914
|
+
- [ ] **Step 3: 最小实现**
|
|
1915
|
+
|
|
1916
|
+
```ts
|
|
1917
|
+
/**
|
|
1918
|
+
* `dashr-remote` — the remote-execution-framework row (plan
|
|
1919
|
+
* docs/10_plans/2026-09-23-remote-framework-impl.md Task 9; spec
|
|
1920
|
+
* docs/10_plans/2026-09-23-remote-execution-framework.md). Same row shape as
|
|
1921
|
+
* `src/mobile/plugin.ts` / the archived RM0 row: one bundle-patch row that,
|
|
1922
|
+
* when a `tools` service is composed, boots one RemoteDriver and registers
|
|
1923
|
+
* the model-facing `remote` tool. The driver (pty session pool + idle timers)
|
|
1924
|
+
* is disposed with the row's fiber. Zero static injects: loads dormant in
|
|
1925
|
+
* compositions without tools.
|
|
1926
|
+
*/
|
|
1927
|
+
import { Context } from '@deepseek-ai/cordis'
|
|
1928
|
+
import z from '@deepseek-ai/schemastery'
|
|
1929
|
+
import { RemoteDriver } from './driver.ts'
|
|
1930
|
+
import { createRemoteTool } from './tool.ts'
|
|
1931
|
+
|
|
1932
|
+
export const name = 'dashr-remote'
|
|
1933
|
+
export const inject: string[] = []
|
|
1934
|
+
|
|
1935
|
+
/** 已知容器裸名 → 'docker:<name>' / 'incus:<name>'(Ruling 12)。 */
|
|
1936
|
+
export interface RemoteRowConfig {
|
|
1937
|
+
containers: Record<string, string>
|
|
1938
|
+
execTimeoutSec: number
|
|
1939
|
+
idleTtlSec: number
|
|
1940
|
+
maxOutputChars: number
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
export const Config: z<RemoteRowConfig> = z
|
|
1944
|
+
.object({
|
|
1945
|
+
containers: z.dict(z.string()).default({}),
|
|
1946
|
+
execTimeoutSec: z.natural().min(1).default(120),
|
|
1947
|
+
idleTtlSec: z.natural().min(1).default(600),
|
|
1948
|
+
maxOutputChars: z.natural().min(1).default(30_000),
|
|
1949
|
+
})
|
|
1950
|
+
.default({ containers: {}, execTimeoutSec: 120, idleTtlSec: 600, maxOutputChars: 30_000 }) as unknown as z<RemoteRowConfig>
|
|
1951
|
+
|
|
1952
|
+
/** MUST stay an arrow(RM0 plugin.ts 同注:function 声明会被 cordis 当构造器 new 掉,返回的 disposer 丢失)。 */
|
|
1953
|
+
export const apply = (ctx: Context, config: RemoteRowConfig): (() => void) => {
|
|
1954
|
+
const driver = new RemoteDriver({
|
|
1955
|
+
containers: config.containers,
|
|
1956
|
+
execTimeoutSec: config.execTimeoutSec,
|
|
1957
|
+
idleTtlSec: config.idleTtlSec,
|
|
1958
|
+
maxOutputChars: config.maxOutputChars,
|
|
1959
|
+
})
|
|
1960
|
+
ctx.inject(['tools'], (toolsCtx: Context) => {
|
|
1961
|
+
toolsCtx.tools.register(
|
|
1962
|
+
createRemoteTool(driver, { getSessionKey: (exec) => exec.agent?.id ?? 'remote:no-session' }),
|
|
1963
|
+
)
|
|
1964
|
+
})
|
|
1965
|
+
return () => void driver.dispose()
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
export default { name, inject, Config, apply }
|
|
1969
|
+
```
|
|
1970
|
+
|
|
1971
|
+
package.json exports(`"./mobile"` 条目后插):
|
|
1972
|
+
```json
|
|
1973
|
+
"./remote": {
|
|
1974
|
+
"types": "./lib/remote/plugin.d.ts",
|
|
1975
|
+
"default": "./lib/remote/plugin.js"
|
|
1976
|
+
},
|
|
1977
|
+
```
|
|
1978
|
+
|
|
1979
|
+
tsdown.config.ts entry 数组 `'src/mobile/plugin.ts'` 后插 `'src/remote/plugin.ts'`。
|
|
1980
|
+
|
|
1981
|
+
cordis.patch.yml(RM0 同位、同注释风格):
|
|
1982
|
+
```yaml
|
|
1983
|
+
# Remote execution framework (plan 2026-09-23-remote-framework-impl.md):
|
|
1984
|
+
# `remote` tool face — two-track (oneshot default / pty opt-in), CLI
|
|
1985
|
+
# transports (ssh/docker/incus), BYO-PTY spawn, nonce framing, 10min TTL.
|
|
1986
|
+
# All config defaults, so the row carries no config block.
|
|
1987
|
+
- id: dashr-remote
|
|
1988
|
+
name: 'better-dsh/remote'
|
|
1989
|
+
```
|
|
1990
|
+
|
|
1991
|
+
- [ ] **Step 4: 全量绿 + 类型零**
|
|
1992
|
+
|
|
1993
|
+
```bash
|
|
1994
|
+
cd better-dsh
|
|
1995
|
+
npm test # 全量(含既有套件)
|
|
1996
|
+
npx tsc --noEmit # 0 errors
|
|
1997
|
+
```
|
|
1998
|
+
|
|
1999
|
+
- [ ] **Step 5: Commit** — `git commit -m "feat(remote): dashr-remote composition row — driver + tool wiring, exports/entry/patch"`。
|
|
2000
|
+
|
|
2001
|
+
---
|
|
2002
|
+
|
|
2003
|
+
### Task 10: Phase-4 实测回归(真 targets + 第一人称)与报告
|
|
2004
|
+
|
|
2005
|
+
**Files:**
|
|
2006
|
+
- Create: `docs/50_test-reports/2026-09-23-remote-framework实测报告.md`
|
|
2007
|
+
|
|
2008
|
+
**前置事实(已勘)**:`dev4` BatchMode SSH 可达(bash 5.2.21);本机 `incus` 有运行中 `ctr-1`;`docker` 有 `corti`/`jellyfin`(**勿动这两个**——临时容器自起自清);`/usr/bin/script` 在位;`~/.ssh/config` 的 `ctr-1` Host 与容器别名同名(别名优先路由是设计内行为)。
|
|
2009
|
+
|
|
2010
|
+
- [ ] **Step 1: rsync 进 monorepo + 双半构建**(AGENTS.md §二 日常回归循环,注意 lib/client 清洗陷阱)
|
|
2011
|
+
|
|
2012
|
+
```bash
|
|
2013
|
+
rsync -a --delete --exclude node_modules --exclude lib --exclude .venv-kernel --exclude .uv-cache --exclude docs \
|
|
2014
|
+
~/workspaces/dashr/.worktrees/remote-framework/better-dsh/ \
|
|
2015
|
+
~/workspaces/dashr/upstream/deepseek-harness/packages/better-dsh/better-dsh/
|
|
2016
|
+
cd ~/workspaces/dashr/upstream/deepseek-harness
|
|
2017
|
+
pnpm --filter better-dsh exec tsdown
|
|
2018
|
+
cd packages/better-dsh/better-dsh && ../../../node_modules/.bin/tsx scripts/build-client.ts && cd ../../..
|
|
2019
|
+
```
|
|
2020
|
+
|
|
2021
|
+
- [ ] **Step 2: 起 dev 实例**(`bash .tests/dsh-test1/start-4999.sh PORT=4986`;端口被占则换;boot 必须走构建产物 `apps/cli/lib/bin.js`——脚本已内置)
|
|
2022
|
+
|
|
2023
|
+
- [ ] **Step 3: 配置面冒烟**
|
|
2024
|
+
|
|
2025
|
+
```bash
|
|
2026
|
+
DSH_HOME=/home/u1/workspaces/dashr/.dsh-test node apps/cli/src/bin.ts web --dump-config | grep -B1 -A6 'dashr-remote'
|
|
2027
|
+
```
|
|
2028
|
+
Expected: `dashr-remote` 行在位(默认 config 生效)。鉴权拉 shell 页 → boot graph 含 `"id":"better-dsh"` 与 `/plugins/??better-dsh/remote/client 无关`(本 row 无 client 半——只验 lib 产物 `/lib/remote/plugin.js` 在 dist 内)。
|
|
2029
|
+
|
|
2030
|
+
- [ ] **Step 4: 第一人称实测矩阵**(真实 agent session 通过 web 界面调 `remote` 工具;每项记 exit/cwd/时长/原始输出):
|
|
2031
|
+
|
|
2032
|
+
| # | case | 调用 | 判据 |
|
|
2033
|
+
|---|---|---|---|
|
|
2034
|
+
| 1 | ssh oneshot | `{target:'dev4', cmd:'echo ok && git --version'}` | exit 0,输出含 ok 与 git 版本 |
|
|
2035
|
+
| 2 | ssh oneshot 退出码透传 | `{target:'dev4', cmd:'exit 42'}` | exit 42(无字符串解析) |
|
|
2036
|
+
| 3 | ssh oneshot 复合语法 | `{target:'dev4', cmd:'ls | head -1 && echo $HOME'}` | 管道+变量展开正常(单层转义证明) |
|
|
2037
|
+
| 4 | ssh pty 状态保持 | 两连击:`{target:'dev4',mode:'pty',cmd:'cd /tmp && export F=1'}` → `{target:'dev4',mode:'pty',cmd:'pwd && echo $F'}` | 第二击 `/tmp` + `1` |
|
|
2038
|
+
| 5 | ssh pty 多行单帧 | `{target:'dev4',mode:'pty',cmd:'echo a\necho b'}` | 一帧,a/b,exit 0 |
|
|
2039
|
+
| 6 | ssh pty 伪 marker 免疫 | `{target:'dev4',mode:'pty',cmd:"printf '\\x1b]133;D;deadbeefdeadbeef;99;/pwn\\x07'; echo real"}` | exit 0,输出 real,无 99 |
|
|
2040
|
+
| 7 | ssh pty 超时打断 | `{target:'dev4',mode:'pty',cmd:'sleep 300',timeout:2}` | interrupted,秒级返回,会话存活(后续 echo 正常) |
|
|
2041
|
+
| 8 | incus oneshot | `{target:'incus:ctr-1', cmd:'cat /etc/os-release | head -1'}` | exit 0 |
|
|
2042
|
+
| 9 | incus pty + 别名路由 | home 层 patch 加 `containers: {'ctr-1':'incus:ctr-1'}` 后 `{target:'ctr-1',mode:'pty',cmd:'cd / && pwd'}` | cwd `/` |
|
|
2043
|
+
| 10 | docker oneshot | 临时容器 `docker run -d --name dashr-remote-t1 ubuntu:24.04 sleep 600` → `{target:'docker:dashr-remote-t1', cmd:'echo in-docker'}` | exit 0 |
|
|
2044
|
+
| 11 | docker pty(script 托管) | `{target:'docker:dashr-remote-t1', mode:'pty', cmd:'cd /root && pwd'}` | cwd `/root` |
|
|
2045
|
+
| 12 | BYO-PTY spawn | `{spawn:'docker exec -it dashr-remote-t1 bash', cmd:'echo spawned'}` | exit 0,输出 spawned |
|
|
2046
|
+
| 13 | 错误透明 | `{target:'no-such-host-xyz', cmd:'echo x'}`(预期 ssh 原生报错)与 `{target:'docker:no-such-ctr', cmd:'x'}` | 原生 stderr 原样可见,agent 可自愈 |
|
|
2047
|
+
| 14 | 断联自愈 | pty 会话中 `{target:'dev4',mode:'pty',cmd:'kill -9 $$'}` → 再 `{target:'dev4',mode:'pty',cmd:'echo back'}` | 第二击含 `[remote: session reconnected to fresh shell; cwd reset to default]` 行 |
|
|
2048
|
+
| 15 | 后台输出 Non-Goal | `{target:'dev4',cmd:'nohup sleep 5 >/dev/null 2>&1 & echo bg-ok'}` | 输出干净 bg-ok(使用者重定向职责示范) |
|
|
2049
|
+
| 16 | status 可达探测 | `{target:'dev4'}`(无 cmd) | `probe: reachable in Nms`;**全文无 "offline" 字样** |
|
|
2050
|
+
| 17 | status 会话层 | case 4 之后 `{target:'dev4'}` | `session: idle Ns (connected)` |
|
|
2051
|
+
| 18 | status 容器事实/错误 | `{target:'docker:no-such-ctr'}` | `probe: error —` 行含 docker 原生 No such object |
|
|
2052
|
+
|
|
2053
|
+
结束清理:`docker rm -f dashr-remote-t1`;移除 home 层别名 patch(若加了)。
|
|
2054
|
+
|
|
2055
|
+
- [ ] **Step 5: 审计抽查** — 实测 session 的存储里 `dashr/remote-exec` 事件在案(target/cmd/exit/durationMs)。
|
|
2056
|
+
|
|
2057
|
+
- [ ] **Step 6: 写报告** — `docs/50_test-reports/2026-09-23-remote-framework实测报告.md`:环境(端口/sha/矩阵表)、18 case 结果、四层模型映射核对表、状态词汇表行为证据(矩阵 16-18:无 offline 歧义)、Non-Goal 三条的行为证据、遗留清单(如 130 断言环境差异、dev4 无 docker 的嵌套穿透未测等如实记录)。
|
|
2058
|
+
|
|
2059
|
+
- [ ] **Step 7: Commit** — `git add docs/50_test-reports/… && git commit -m "test(remote): phase-4 acceptance matrix report at dev instance (ssh/incus/docker/spawn)"`。
|
|
2060
|
+
|
|
2061
|
+
---
|
|
2062
|
+
|
|
2063
|
+
### Task 11: 收尾(finishing-a-development-branch)
|
|
2064
|
+
|
|
2065
|
+
- [ ] 全量 `npm test` + `npx tsc --noEmit` 最后一遍确认。
|
|
2066
|
+
- [ ] REQUIRED SUB-SKILL: `superpowers:finishing-a-development-branch` — 按 skill 走验证与选项呈现(merge / PR / 保留分支),**等 user 选择**;npm publish 不在本计划范围(Development Operation Contract:需 user 单次明确放行)。
|
|
2067
|
+
|
|
2068
|
+
---
|
|
2069
|
+
|
|
2070
|
+
## Self-Review 记录
|
|
2071
|
+
|
|
2072
|
+
- **Spec 覆盖**:§一 四层模型→双轨(Task 4/5);§二 命名与智能路由(Task 2);§三 API 契约 target/spawn/cmd/mode/stdin/timeout + 错误透明(Task 6/8);§四 场景 1-4(Task 10 矩阵 1-14);§五 oneshot 流水线 argv 逐字(Task 3)、nonce 收帧/TTL/断联自愈(Task 1/5 + 矩阵 6/7/14);§六 Non-Goals(矩阵 15 + Ruling 5 固定 40×120 + 无状态回放);§七 Phase 1-4(Task 1 / 4-5 / 2-3+6-9 / 10)。**user 2026-09-23 状态词汇裁决**(禁 offline;reachable/unreachable/容器事实态/idle/busy/none;on-demand 双重超时探测)→ Ruling 16/17 + Task 5 snapshot + Task 7 全任务 + Task 8 status 面 + 矩阵 16-18。无缺task。
|
|
2073
|
+
- **类型一致性**:`PtyFrame{exit,cwd}`(Task 1 定义,Task 5 消费);`TargetPlan`(Task 2 定义,Task 3/6/7 消费);`OneShotResult`(Task 4,Task 7 runner 缝消费);`PtyDispatchResult`(Task 5)在 Task 6 `execute` 合成 `RemoteCallResult`(Task 8 消费);`SessionSnapshot`(Task 5 定义,Task 7 `renderSession` / Task 8 `driver.status` 消费);`ProbeOutcome`(Task 7 定义,Task 8 消费);`RECONNECT_NOTICE`(Task 5 定义,Task 8 render 消费);`RemoteRowConfig`(Task 9)。已复核命名一致。
|
|
2074
|
+
- **占位符扫描**:各 Step 均含实码/实命令;Task 10 步骤 4 矩阵为实测脚本性步骤(验收性质,非代码占位)。
|
|
2075
|
+
- **已知测试环境风险**:130 断言确定性由 Ruling P8 两段打断(\x03 + 注入 130-marker 行)保证——两路(弃行/续行)皆 exit 130;trap INT 场景走 kill 兜底测试。若 CI 型环境无 /dev/ptmx,`script` 夹具会挂——本仓测试恒在真 Linux 跑,可接受。
|
|
2076
|
+
|
|
2077
|
+
## Post-acceptance 增量记录
|
|
2078
|
+
|
|
2079
|
+
- **Ruling P16**(user 2026-09-23 选 A):补 `ssh:` 显式协议选择器(与 docker:/incus: 组成可扩展选择器族,未来新传输加一词即成);撞名优先级维持"别名 override 赢"(人工显式意图),裸名缺省仍走 ssh;别名表值只接受容器形态(CONTAINER_PREFIX_RE 单独校验,ssh 无别名意义)。工具描述同步补 selector 消歧指引。若错:代价=极小(选择器仅显式路径)。
|
|
2080
|
+
- **Ruling P17**(user 实测发现,2026-09-23):session log 的自定义类型事件**不可持久化**——`KNOWN_SESSION_EVENT_TYPES` 为冻结原生集(catalog 刻意独立于插件),`Session.append()` 无 ignorable 通道,`tool/ptc-dispatch` 先例是原生类型而非插件自定义。审计迁出 session log → 插件自有存储 `$DSH_HOME/storages/better-dsh/remote-exec-audit.jsonl`(hashline/hostkeys 同款先例);tool 面改注入式 audit sink;被毒化会话以 `ignorable: true` 信封标记手术修复(loader 契约内合法,事件以无表面语义的 opaque 形态保留)。若错:代价=审计从 session 视图消失(改回即恢复),但 session-log 通道本就非法。
|
|
2081
|
+
- **Ruling P18**(user 2026-09-23,实测后裁决):**删除别名机制整条腿**。插件环境数据不得上 cordis 配置面(行重述热插是对组合平面的误用,env-var 级需求用了系统级 YAML);别名本身伪需求——显式选择器已全覆盖,发现走 on-demand probe(当下事实 > 任何别名表/定时扫盘;扫描器亦不建,运行态无人改配置)。`resolveTarget(target)` 单参化(裸名恒 ssh);行 config 只留运行旋钮(execTimeoutSec/idleTtlSec/maxOutputChars)。若错:代价=裸名容器便利消失,补插件私有 JSON 即可(P18 明确不走 cordis 面)。
|
|
2082
|
+
- **Ruling P19**(user 2026-09-23,attention 裁决):`remote({})` 空调用 = **roster 面(注意力第一现场)**。区分两个概念:roster = 把名字放进上下文(读 ~/.ssh/config hosts + docker ps -a + incus list + 池内活会话,纯本地廉价扫描,零可达性检查);probe = 单点验证(既有 on-demand status,第二现场)。"模型自己会发现"只在 attention 已存在后成立——工具 catalog 是 attention 的注入点,便利性(spec §三宗旨)要求入口自带清单。E_PARAMS 发现指引退役;描述文本改为 no-args 列清单。若错:代价=多一个只读面(几行渲染),可删。
|
|
2083
|
+
- **Ruling P20**(user 2026-09-24,name-first 策略):裸名**先按名匹配**——ssh config 字面 host → docker → incus(与 roster 同源的本地廉价扫描,撞名 ssh 优先,查无 → 默认 ssh 交原生错误);选择器(ssh:/docker:/incus:)降级为显式消歧备用,描述不再当主路径宣传。修正 P18 的过度矫正(裸名 ≠ 恒 ssh,容器名裸传即可达)。若错:代价=裸名多一次本地扫描(亚秒),回退为恒 ssh 一行。
|