dsh-plugin-guide 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE.md +26 -0
- package/README.es.md +139 -0
- package/README.hi.md +138 -0
- package/README.md +165 -0
- package/README.pt.md +139 -0
- package/README.zh-CN.md +164 -0
- package/SKILL.md +69 -0
- package/cordis.patch.yml +4 -0
- package/guide/links.md +92 -0
- package/guide/plugin-dev-guide.md +429 -0
- package/guide/quick-reference.es.md +176 -0
- package/guide/quick-reference.hi.md +176 -0
- package/guide/quick-reference.md +176 -0
- package/guide/quick-reference.pt.md +176 -0
- package/guide/quick-reference.zh-CN.md +175 -0
- package/index.js +58 -0
- package/package.json +83 -0
- package/references/community-ecosystem.md +201 -0
- package/references/community-repo-deep-dive.md +695 -0
- package/references/cordis-paper-and-community.md +326 -0
- package/references/harness-repo.md +654 -0
- package/references/official-docs/AGENTS.md +149 -0
- package/references/official-docs/BENCHMARK.md +3 -0
- package/references/official-docs/CLAUDE.md +1 -0
- package/references/official-docs/CONTRIBUTING.i18n.yaml +6 -0
- package/references/official-docs/CONTRIBUTING.md +23 -0
- package/references/official-docs/CONTRIBUTING.zh.md +23 -0
- package/references/official-docs/LICENSE +21 -0
- package/references/official-docs/README.i18n.yaml +6 -0
- package/references/official-docs/README.md +35 -0
- package/references/official-docs/README.zh.md +74 -0
- package/references/official-docs/SNAPSHOT.md +16 -0
- package/references/official-docs/THIRD_PARTY_NOTICES.md +192 -0
- package/references/official-docs/docs/AGENTS.md +75 -0
- package/references/official-docs/docs/agent-lifecycle.i18n.yaml +6 -0
- package/references/official-docs/docs/agent-lifecycle.md +82 -0
- package/references/official-docs/docs/agent-lifecycle.zh.md +84 -0
- package/references/official-docs/docs/api-gateway.i18n.yaml +6 -0
- package/references/official-docs/docs/api-gateway.md +164 -0
- package/references/official-docs/docs/api-gateway.zh.md +164 -0
- package/references/official-docs/docs/architecture.i18n.yaml +6 -0
- package/references/official-docs/docs/architecture.md +129 -0
- package/references/official-docs/docs/architecture.zh.md +133 -0
- package/references/official-docs/docs/capability-seams.i18n.yaml +6 -0
- package/references/official-docs/docs/capability-seams.md +471 -0
- package/references/official-docs/docs/capability-seams.zh.md +473 -0
- package/references/official-docs/docs/config-catalog.i18n.yaml +6 -0
- package/references/official-docs/docs/config-catalog.md +3151 -0
- package/references/official-docs/docs/config-catalog.zh.md +3152 -0
- package/references/official-docs/docs/cookbook/adding-a-conversation-node.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/adding-a-conversation-node.md +233 -0
- package/references/official-docs/docs/cookbook/adding-a-conversation-node.zh.md +233 -0
- package/references/official-docs/docs/cookbook/adding-a-package.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/adding-a-package.md +118 -0
- package/references/official-docs/docs/cookbook/adding-a-package.zh.md +118 -0
- package/references/official-docs/docs/cookbook/adding-a-tool.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/adding-a-tool.md +94 -0
- package/references/official-docs/docs/cookbook/adding-a-tool.zh.md +96 -0
- package/references/official-docs/docs/cookbook/adding-a-vendored-package.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/adding-a-vendored-package.md +59 -0
- package/references/official-docs/docs/cookbook/adding-a-vendored-package.zh.md +59 -0
- package/references/official-docs/docs/cookbook/adding-an-llm-adapter.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/adding-an-llm-adapter.md +43 -0
- package/references/official-docs/docs/cookbook/adding-an-llm-adapter.zh.md +43 -0
- package/references/official-docs/docs/cookbook/extension-cookbook.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/extension-cookbook.md +129 -0
- package/references/official-docs/docs/cookbook/extension-cookbook.zh.md +131 -0
- package/references/official-docs/docs/cookbook/maintaining-dsh-code-review.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/maintaining-dsh-code-review.md +64 -0
- package/references/official-docs/docs/cookbook/maintaining-dsh-code-review.zh.md +64 -0
- package/references/official-docs/docs/cookbook/responding-to-pr-review-on-a-stack.i18n.yaml +6 -0
- package/references/official-docs/docs/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
- package/references/official-docs/docs/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
- package/references/official-docs/docs/cordis-api/context.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-api/context.md +364 -0
- package/references/official-docs/docs/cordis-api/context.zh.md +366 -0
- package/references/official-docs/docs/cordis-api/events.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-api/events.md +207 -0
- package/references/official-docs/docs/cordis-api/events.zh.md +209 -0
- package/references/official-docs/docs/cordis-api/fiber.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-api/fiber.md +375 -0
- package/references/official-docs/docs/cordis-api/fiber.zh.md +377 -0
- package/references/official-docs/docs/cordis-api/inherited.md +39 -0
- package/references/official-docs/docs/cordis-api/registry.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-api/registry.md +152 -0
- package/references/official-docs/docs/cordis-api/registry.zh.md +154 -0
- package/references/official-docs/docs/cordis-api/service.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-api/service.md +102 -0
- package/references/official-docs/docs/cordis-api/service.zh.md +104 -0
- package/references/official-docs/docs/cordis-primer.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-primer.md +44 -0
- package/references/official-docs/docs/cordis-primer.zh.md +50 -0
- package/references/official-docs/docs/cordis-tutorial/01-first-plugin.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/01-first-plugin.md +95 -0
- package/references/official-docs/docs/cordis-tutorial/01-first-plugin.zh.md +95 -0
- package/references/official-docs/docs/cordis-tutorial/02-lifecycle-and-effects.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/references/official-docs/docs/cordis-tutorial/02-lifecycle-and-effects.zh.md +98 -0
- package/references/official-docs/docs/cordis-tutorial/03-services.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/03-services.md +98 -0
- package/references/official-docs/docs/cordis-tutorial/03-services.zh.md +98 -0
- package/references/official-docs/docs/cordis-tutorial/04-events.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/04-events.md +144 -0
- package/references/official-docs/docs/cordis-tutorial/04-events.zh.md +144 -0
- package/references/official-docs/docs/cordis-tutorial/05-config.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/05-config.md +84 -0
- package/references/official-docs/docs/cordis-tutorial/05-config.zh.md +84 -0
- package/references/official-docs/docs/cordis-tutorial/06-composition-and-hmr.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/references/official-docs/docs/cordis-tutorial/06-composition-and-hmr.zh.md +113 -0
- package/references/official-docs/docs/cordis-tutorial/07-into-the-harness.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/07-into-the-harness.md +107 -0
- package/references/official-docs/docs/cordis-tutorial/07-into-the-harness.zh.md +107 -0
- package/references/official-docs/docs/cordis-tutorial/index.i18n.yaml +6 -0
- package/references/official-docs/docs/cordis-tutorial/index.md +60 -0
- package/references/official-docs/docs/cordis-tutorial/index.zh.md +62 -0
- package/references/official-docs/docs/defensive-patterns.i18n.yaml +6 -0
- package/references/official-docs/docs/defensive-patterns.md +33 -0
- package/references/official-docs/docs/defensive-patterns.zh.md +33 -0
- package/references/official-docs/docs/development.i18n.yaml +6 -0
- package/references/official-docs/docs/development.md +171 -0
- package/references/official-docs/docs/development.zh.md +171 -0
- package/references/official-docs/docs/event-producer-consumer.i18n.yaml +6 -0
- package/references/official-docs/docs/event-producer-consumer.md +76 -0
- package/references/official-docs/docs/event-producer-consumer.zh.md +78 -0
- package/references/official-docs/docs/glossary.i18n.yaml +6 -0
- package/references/official-docs/docs/glossary.md +45 -0
- package/references/official-docs/docs/glossary.zh.md +45 -0
- package/references/official-docs/docs/graph-atlas.i18n.yaml +6 -0
- package/references/official-docs/docs/graph-atlas.md +24 -0
- package/references/official-docs/docs/graph-atlas.zh.md +26 -0
- package/references/official-docs/docs/i18n/README.i18n.yaml +6 -0
- package/references/official-docs/docs/i18n/README.md +60 -0
- package/references/official-docs/docs/i18n/README.zh.md +60 -0
- package/references/official-docs/docs/i18n/style-samples.md +87 -0
- package/references/official-docs/docs/i18n/terminology.md +214 -0
- package/references/official-docs/docs/i18n/translation-prompt.md +263 -0
- package/references/official-docs/docs/i18n/translation-rules.i18n.yaml +6 -0
- package/references/official-docs/docs/i18n/translation-rules.md +69 -0
- package/references/official-docs/docs/i18n/translation-rules.zh.md +69 -0
- package/references/official-docs/docs/module-graph.i18n.yaml +6 -0
- package/references/official-docs/docs/module-graph.md +1638 -0
- package/references/official-docs/docs/module-graph.zh.md +1640 -0
- package/references/official-docs/docs/persistence-catalog.i18n.yaml +6 -0
- package/references/official-docs/docs/persistence-catalog.md +944 -0
- package/references/official-docs/docs/persistence-catalog.zh.md +946 -0
- package/references/official-docs/docs/postmortem/0001-acp-default-export-drops-inject.i18n.yaml +6 -0
- package/references/official-docs/docs/postmortem/0001-acp-default-export-drops-inject.md +113 -0
- package/references/official-docs/docs/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
- package/references/official-docs/docs/postmortem/0002-js-expression-disabled-filesystem-tools.i18n.yaml +6 -0
- package/references/official-docs/docs/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
- package/references/official-docs/docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
- package/references/official-docs/docs/postmortem/0003-web-agent-gui-feedback-loop.i18n.yaml +6 -0
- package/references/official-docs/docs/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
- package/references/official-docs/docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
- package/references/official-docs/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.i18n.yaml +6 -0
- package/references/official-docs/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
- package/references/official-docs/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
- package/references/official-docs/docs/postmortem/README.i18n.yaml +6 -0
- package/references/official-docs/docs/postmortem/README.md +18 -0
- package/references/official-docs/docs/postmortem/README.zh.md +18 -0
- package/references/official-docs/docs/rescope.i18n.yaml +6 -0
- package/references/official-docs/docs/rescope.md +53 -0
- package/references/official-docs/docs/rescope.zh.md +53 -0
- package/references/official-docs/docs/subsystems/README.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/README.md +55 -0
- package/references/official-docs/docs/subsystems/README.zh.md +55 -0
- package/references/official-docs/docs/subsystems/approval.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/approval.md +170 -0
- package/references/official-docs/docs/subsystems/approval.zh.md +170 -0
- package/references/official-docs/docs/subsystems/attachment.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/attachment.md +115 -0
- package/references/official-docs/docs/subsystems/attachment.zh.md +115 -0
- package/references/official-docs/docs/subsystems/client-modules.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/client-modules.md +118 -0
- package/references/official-docs/docs/subsystems/client-modules.zh.md +118 -0
- package/references/official-docs/docs/subsystems/code-runtime.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/code-runtime.md +191 -0
- package/references/official-docs/docs/subsystems/code-runtime.zh.md +191 -0
- package/references/official-docs/docs/subsystems/commands.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/commands.md +187 -0
- package/references/official-docs/docs/subsystems/commands.zh.md +187 -0
- package/references/official-docs/docs/subsystems/compaction.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/compaction.md +238 -0
- package/references/official-docs/docs/subsystems/compaction.zh.md +238 -0
- package/references/official-docs/docs/subsystems/core.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/core.md +1070 -0
- package/references/official-docs/docs/subsystems/core.zh.md +1078 -0
- package/references/official-docs/docs/subsystems/credentials.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/credentials.md +133 -0
- package/references/official-docs/docs/subsystems/credentials.zh.md +133 -0
- package/references/official-docs/docs/subsystems/extensions.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/extensions.md +364 -0
- package/references/official-docs/docs/subsystems/extensions.zh.md +364 -0
- package/references/official-docs/docs/subsystems/feedback.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/feedback.md +266 -0
- package/references/official-docs/docs/subsystems/feedback.zh.md +266 -0
- package/references/official-docs/docs/subsystems/filesystem.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/filesystem.md +495 -0
- package/references/official-docs/docs/subsystems/filesystem.zh.md +495 -0
- package/references/official-docs/docs/subsystems/goal.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/goal.md +277 -0
- package/references/official-docs/docs/subsystems/goal.zh.md +277 -0
- package/references/official-docs/docs/subsystems/invariants.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/invariants.md +88 -0
- package/references/official-docs/docs/subsystems/invariants.zh.md +88 -0
- package/references/official-docs/docs/subsystems/jobs.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/jobs.md +290 -0
- package/references/official-docs/docs/subsystems/jobs.zh.md +290 -0
- package/references/official-docs/docs/subsystems/llm-streaming.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/llm-streaming.md +888 -0
- package/references/official-docs/docs/subsystems/llm-streaming.zh.md +894 -0
- package/references/official-docs/docs/subsystems/lsp.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/lsp.md +202 -0
- package/references/official-docs/docs/subsystems/lsp.zh.md +202 -0
- package/references/official-docs/docs/subsystems/permission-presets.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/permission-presets.md +131 -0
- package/references/official-docs/docs/subsystems/permission-presets.zh.md +131 -0
- package/references/official-docs/docs/subsystems/persistence.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/persistence.md +385 -0
- package/references/official-docs/docs/subsystems/persistence.zh.md +385 -0
- package/references/official-docs/docs/subsystems/plan.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/plan.md +87 -0
- package/references/official-docs/docs/subsystems/plan.zh.md +87 -0
- package/references/official-docs/docs/subsystems/sandbox.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/sandbox.md +218 -0
- package/references/official-docs/docs/subsystems/sandbox.zh.md +218 -0
- package/references/official-docs/docs/subsystems/schedule.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/schedule.md +186 -0
- package/references/official-docs/docs/subsystems/schedule.zh.md +186 -0
- package/references/official-docs/docs/subsystems/scope.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/scope.md +59 -0
- package/references/official-docs/docs/subsystems/scope.zh.md +59 -0
- package/references/official-docs/docs/subsystems/session-projection.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/session-projection.md +262 -0
- package/references/official-docs/docs/subsystems/session-projection.zh.md +262 -0
- package/references/official-docs/docs/subsystems/session-query.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/session-query.md +495 -0
- package/references/official-docs/docs/subsystems/session-query.zh.md +495 -0
- package/references/official-docs/docs/subsystems/session-reference.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/session-reference.md +108 -0
- package/references/official-docs/docs/subsystems/session-reference.zh.md +108 -0
- package/references/official-docs/docs/subsystems/session-telemetry.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/session-telemetry.md +194 -0
- package/references/official-docs/docs/subsystems/session-telemetry.zh.md +194 -0
- package/references/official-docs/docs/subsystems/session-title.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/session-title.md +204 -0
- package/references/official-docs/docs/subsystems/session-title.zh.md +204 -0
- package/references/official-docs/docs/subsystems/session.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/session.md +849 -0
- package/references/official-docs/docs/subsystems/session.zh.md +851 -0
- package/references/official-docs/docs/subsystems/settings.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/settings.md +310 -0
- package/references/official-docs/docs/subsystems/settings.zh.md +310 -0
- package/references/official-docs/docs/subsystems/shell.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/shell.md +303 -0
- package/references/official-docs/docs/subsystems/shell.zh.md +303 -0
- package/references/official-docs/docs/subsystems/skills.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/skills.md +331 -0
- package/references/official-docs/docs/subsystems/skills.zh.md +331 -0
- package/references/official-docs/docs/subsystems/spill.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/spill.md +117 -0
- package/references/official-docs/docs/subsystems/spill.zh.md +117 -0
- package/references/official-docs/docs/subsystems/storage.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/storage.md +229 -0
- package/references/official-docs/docs/subsystems/storage.zh.md +229 -0
- package/references/official-docs/docs/subsystems/subagent.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/subagent.md +734 -0
- package/references/official-docs/docs/subsystems/subagent.zh.md +736 -0
- package/references/official-docs/docs/subsystems/subprocess.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/subprocess.md +324 -0
- package/references/official-docs/docs/subsystems/subprocess.zh.md +324 -0
- package/references/official-docs/docs/subsystems/system-prompt.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/system-prompt.md +207 -0
- package/references/official-docs/docs/subsystems/system-prompt.zh.md +207 -0
- package/references/official-docs/docs/subsystems/terminal.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/terminal.md +184 -0
- package/references/official-docs/docs/subsystems/terminal.zh.md +184 -0
- package/references/official-docs/docs/subsystems/token-meter.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/token-meter.md +90 -0
- package/references/official-docs/docs/subsystems/token-meter.zh.md +90 -0
- package/references/official-docs/docs/subsystems/tools.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/tools.md +720 -0
- package/references/official-docs/docs/subsystems/tools.zh.md +720 -0
- package/references/official-docs/docs/subsystems/typert.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/typert.md +336 -0
- package/references/official-docs/docs/subsystems/typert.zh.md +336 -0
- package/references/official-docs/docs/subsystems/user-questions.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/user-questions.md +178 -0
- package/references/official-docs/docs/subsystems/user-questions.zh.md +178 -0
- package/references/official-docs/docs/subsystems/web-server.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/web-server.md +108 -0
- package/references/official-docs/docs/subsystems/web-server.zh.md +108 -0
- package/references/official-docs/docs/subsystems/web.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/web.md +199 -0
- package/references/official-docs/docs/subsystems/web.zh.md +199 -0
- package/references/official-docs/docs/subsystems/workflow.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/workflow.md +278 -0
- package/references/official-docs/docs/subsystems/workflow.zh.md +278 -0
- package/references/official-docs/docs/subsystems/workspace.i18n.yaml +6 -0
- package/references/official-docs/docs/subsystems/workspace.md +228 -0
- package/references/official-docs/docs/subsystems/workspace.zh.md +228 -0
- package/references/official-docs/docs/testing.i18n.yaml +6 -0
- package/references/official-docs/docs/testing.md +49 -0
- package/references/official-docs/docs/testing.zh.md +49 -0
- package/references/official-docs/docs/tool-catalog.i18n.yaml +6 -0
- package/references/official-docs/docs/tool-catalog.md +1873 -0
- package/references/official-docs/docs/tool-catalog.zh.md +1878 -0
- package/references/official-docs/docs/tool-execution-pipeline.i18n.yaml +6 -0
- package/references/official-docs/docs/tool-execution-pipeline.md +62 -0
- package/references/official-docs/docs/tool-execution-pipeline.zh.md +64 -0
- package/references/official-docs/docs/user/develop/basic/config.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/basic/config.md +106 -0
- package/references/official-docs/docs/user/develop/basic/config.zh.md +106 -0
- package/references/official-docs/docs/user/develop/basic/index.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/basic/index.md +144 -0
- package/references/official-docs/docs/user/develop/basic/index.zh.md +144 -0
- package/references/official-docs/docs/user/develop/basic/publish.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/basic/publish.md +183 -0
- package/references/official-docs/docs/user/develop/basic/publish.zh.md +183 -0
- package/references/official-docs/docs/user/develop/basic/tool.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/basic/tool.md +52 -0
- package/references/official-docs/docs/user/develop/basic/tool.zh.md +52 -0
- package/references/official-docs/docs/user/develop/framework/events.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/framework/events.md +143 -0
- package/references/official-docs/docs/user/develop/framework/events.zh.md +143 -0
- package/references/official-docs/docs/user/develop/framework/index.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/framework/index.md +137 -0
- package/references/official-docs/docs/user/develop/framework/index.zh.md +137 -0
- package/references/official-docs/docs/user/develop/framework/service.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/framework/service.md +148 -0
- package/references/official-docs/docs/user/develop/framework/service.zh.md +150 -0
- package/references/official-docs/docs/user/develop/practice/index.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/practice/index.md +155 -0
- package/references/official-docs/docs/user/develop/practice/index.zh.md +155 -0
- package/references/official-docs/docs/user/develop/practice/llm-adapter.i18n.yaml +6 -0
- package/references/official-docs/docs/user/develop/practice/llm-adapter.md +188 -0
- package/references/official-docs/docs/user/develop/practice/llm-adapter.zh.md +188 -0
- package/references/official-docs/docs/user/guide/index.i18n.yaml +6 -0
- package/references/official-docs/docs/user/guide/index.md +30 -0
- package/references/official-docs/docs/user/guide/index.zh.md +30 -0
- package/references/official-docs/docs/user/guide/providers-custom-form.png +0 -0
- package/references/official-docs/docs/user/guide/providers-custom-form.zh.png +0 -0
- package/references/official-docs/docs/user/guide/providers-models-page.png +0 -0
- package/references/official-docs/docs/user/guide/providers-models-page.zh.png +0 -0
- package/references/official-docs/docs/user/guide/providers.i18n.yaml +6 -0
- package/references/official-docs/docs/user/guide/providers.md +98 -0
- package/references/official-docs/docs/user/guide/providers.zh.md +98 -0
- package/references/official-docs/docs/user/guide/python-sdk.i18n.yaml +6 -0
- package/references/official-docs/docs/user/guide/python-sdk.md +104 -0
- package/references/official-docs/docs/user/guide/python-sdk.zh.md +104 -0
- package/references/official-docs/docs/user/index.i18n.yaml +6 -0
- package/references/official-docs/docs/user/index.md +11 -0
- package/references/official-docs/docs/user/index.zh.md +11 -0
- package/references/official-docs/docs/web-styling.i18n.yaml +6 -0
- package/references/official-docs/docs/web-styling.md +25 -0
- package/references/official-docs/docs/web-styling.zh.md +25 -0
- package/references/official-docs/examples/AGENTS.md +20 -0
- package/references/official-docs/packages/AGENTS.md +27 -0
- package/references/official-docs/packages/README.md +69 -0
- package/references/official-docs/vendor/README.md +60 -0
- package/references/official-docs/website-docs.ts +524 -0
- package/references/sources.md +137 -0
- package/references/upstream-cordis.md +594 -0
- package/references/website-pages.md +668 -0
- package/scripts/archive-discussions.ps1 +154 -0
- package/scripts/download-community-articles.ps1 +188 -0
- package/scripts/download-community-repos.ps1 +232 -0
- package/scripts/download-sources.ps1 +195 -0
- package/scripts/gen-topic-snapshot.ps1 +91 -0
- package/scripts/install-skill.ps1 +63 -0
- package/scripts/sync-official-docs.ps1 +130 -0
- package/scripts/verify-kit.ps1 +191 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Workspaces
|
|
2
|
+
|
|
3
|
+
English | [中文](workspace.zh.md)
|
|
4
|
+
|
|
5
|
+
A workspace is the persistent record of a directory the user works in: a stable id over a canonical path, a display title, and the ordered account of sessions that belong to it. The subsystem is one package ([dsh-workspace](../../packages/workspace/workspace), `ctx.workspaceRegistry`) — an optional host-side capability, not part of the agent-loop spine, and invisible to models (no tools, no prompt text, no session events). It stores its records through the [storage domain form](storage.md) and validates session membership against [`SessionHeader.cwd`](persistence.md#sessionheader--metadata-beside-the-log), so `storageDomain` and `sessionPersistence` are mandatory startup dependencies: an unavailable persistence peer leaves the plugin pending rather than being mistaken for an empty history. Design record: [domain KV storage Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md); bootstrap and GUI ordering: [Workspace UI product-flow Agent Note](../../.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md).
|
|
6
|
+
|
|
7
|
+
Source: [`packages/workspace/workspace/src/types.ts`](../../packages/workspace/workspace/src/types.ts)
|
|
8
|
+
|
|
9
|
+
## Identity
|
|
10
|
+
|
|
11
|
+
```ts type-equiv
|
|
12
|
+
/**
|
|
13
|
+
* Identifies one workspace record. A generated uuid, never the path: path
|
|
14
|
+
* normalization rewrites paths, and a reference anchor must stay stable.
|
|
15
|
+
*/
|
|
16
|
+
type WorkspaceId = Branded<'WorkspaceId'>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`WorkspaceId` is a [branded id](core.md#branded-ids). Path identity is separate: `realpathNormalize` (`fs.realpath`; trailing slashes, `..`, and symlinks resolved) is the one uniqueness canon — workspace paths are stored canonicalized, uniqueness is string equality of canonical paths (a symlink to an owned directory collides), and attach-time session cwd checks go through the same canon.
|
|
20
|
+
|
|
21
|
+
## The workspace entity
|
|
22
|
+
|
|
23
|
+
Consumers see only the `Workspace` interface; the implementation stays package-private.
|
|
24
|
+
|
|
25
|
+
```ts type-equiv
|
|
26
|
+
/**
|
|
27
|
+
* One workspace: a stable id over an existing directory, a display title, and
|
|
28
|
+
* an ordered candidate account of sessions. Membership requires both an id in
|
|
29
|
+
* that account and a session header whose canonical cwd equals the workspace
|
|
30
|
+
* path. Consumers only see this interface; the implementation stays private.
|
|
31
|
+
*/
|
|
32
|
+
interface Workspace {
|
|
33
|
+
/** Stable record id (generated uuid). */
|
|
34
|
+
readonly id: WorkspaceId
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Canonical directory path: the `fs.realpath` of the path given at create
|
|
38
|
+
* time (trailing slashes, `..`, and symlinks all resolved). Never rewritten
|
|
39
|
+
* afterwards, even when the directory disappears (see {@link status}).
|
|
40
|
+
*/
|
|
41
|
+
readonly path: string
|
|
42
|
+
|
|
43
|
+
/** Display title. Defaults to `basename(path)` at create; duplicates are allowed. */
|
|
44
|
+
readonly title: string
|
|
45
|
+
|
|
46
|
+
/** ISO-8601 creation instant, stamped at create and never rewritten. */
|
|
47
|
+
readonly createdAt: string
|
|
48
|
+
|
|
49
|
+
/** ISO-8601 instant of the last durable mutation (create counts as one). */
|
|
50
|
+
readonly updatedAt: string
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Header-validated sessions in manually owned order: a new session is
|
|
54
|
+
* prepended at attach, explicit reordering goes through
|
|
55
|
+
* `insertSessionBefore`, and activity never reorders. The durable candidate
|
|
56
|
+
* account is filtered synchronously: missing headers, invalid cwd values,
|
|
57
|
+
* and canonical cwd mismatches are never returned. A subsequent workspace
|
|
58
|
+
* mutation prunes those filtered candidates durably.
|
|
59
|
+
*/
|
|
60
|
+
readonly sessionIds: readonly SessionId[]
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Replace the display title durably.
|
|
64
|
+
* @param title - New title; any string, duplicates across workspaces allowed.
|
|
65
|
+
* @returns resolution after durability.
|
|
66
|
+
*/
|
|
67
|
+
setTitle(title: string): Promise<void>
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prepend a session to this workspace's candidate account. An already
|
|
71
|
+
* accounted id resolves without writing, aside from the durable
|
|
72
|
+
* filtered-candidate prune every accepted mutation performs. A new id's
|
|
73
|
+
* live or persisted
|
|
74
|
+
* header cwd must resolve to an existing directory equal to {@link path};
|
|
75
|
+
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
|
76
|
+
* writing.
|
|
77
|
+
* @param sessionId - The session to record.
|
|
78
|
+
* @returns resolution after durability.
|
|
79
|
+
*/
|
|
80
|
+
attachSession(sessionId: SessionId): Promise<void>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Move an accounted session within the manual order, DOM-insertBefore-like:
|
|
84
|
+
* with an anchor the session lands before it, without one it appends to the
|
|
85
|
+
* end. Only the moved id changes position. A session or anchor absent from
|
|
86
|
+
* the account rejects without writing; a move to the current position
|
|
87
|
+
* resolves without writing, aside from the durable filtered-candidate
|
|
88
|
+
* prune every accepted mutation performs; decided on the domain write
|
|
89
|
+
* chain.
|
|
90
|
+
* @param sessionId - The accounted session to move.
|
|
91
|
+
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
|
92
|
+
* @returns resolution after durability.
|
|
93
|
+
*/
|
|
94
|
+
insertSessionBefore(sessionId: SessionId, beforeSessionId?: SessionId): Promise<void>
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Remove a session from this workspace's account. Idempotent: an id not on
|
|
98
|
+
* the account resolves without writing, aside from the durable
|
|
99
|
+
* filtered-candidate prune every accepted mutation performs; decided on
|
|
100
|
+
* the domain write chain like attach. Never touches the session's own stored log.
|
|
101
|
+
* @param sessionId - The session to remove.
|
|
102
|
+
* @returns resolution after durability.
|
|
103
|
+
*/
|
|
104
|
+
detachSession(sessionId: SessionId): Promise<void>
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Live directory check, uncached: whether {@link path} currently exists and
|
|
108
|
+
* is a directory. A missing directory never mutates the record — the
|
|
109
|
+
* directory may only be temporarily moved.
|
|
110
|
+
* @returns `'ok'` when the directory exists, `'missing-dir'` otherwise.
|
|
111
|
+
*/
|
|
112
|
+
status(): Promise<'ok' | 'missing-dir'>
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Ownership truth is the record's ordered `sessionIds`, never derived from session cwd — but membership requires both: an id on the account and a header whose canonical cwd equals the workspace path, so one session structurally belongs to at most one workspace. Failed writes reject (`insertSessionBefore` account errors as `WorkspaceMoveInvalidError`, storage failures as plain errors); every accepted mutation stamps `updatedAt` and durably prunes candidates that no longer pass the membership check.
|
|
117
|
+
|
|
118
|
+
## The registry: `ctx.workspaceRegistry`
|
|
119
|
+
|
|
120
|
+
`WorkspaceRegistry` ([signatures](#ctxworkspaceregistry--workspaceregistry)) owns registration and resolution. `create(path, title?)` canonicalizes the path, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? basename(path)` prepended to the durable registry order — a new record cannot duplicate an existing display title (`WorkspaceNameConflictError`). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup resolves exactly the marked mutation — by deleting the marked table row, which completes an interrupted delete and rolls back an interrupted create (the registration is re-creatable, so rollback is the safe direction) — and an unmarked order/table mismatch fails loud as corruption.
|
|
121
|
+
|
|
122
|
+
Sessions get their cwd at create time from whoever creates them, not from this registry — the API gateway resolves a new session's cwd from the chosen workspace's `path` (falling back to an explicit or default cwd), creates the session so the cwd lands in its immutable [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log), then calls `attachSession`, which re-validates that stored header cwd against the workspace path. On the first successful start, the registry bootstraps history from persisted headers alone (`id`, `cwd`, `createdAt` — never event bodies), grouping sessions with a valid canonical cwd into per-directory workspaces, newest first; the initialized marker is written last so an interrupted bootstrap resumes safely. The bootstrap is one-time: cwd-less legacy sessions stay Ungrouped, and sessions created afterwards join a workspace only through `attachSession`.
|
|
123
|
+
|
|
124
|
+
## Consumers
|
|
125
|
+
|
|
126
|
+
[dsh-host-apiproxy](../../packages/host/apiproxy) is the product consumer: it serves workspace CRUD to GUI clients over `ctx.workspaceRegistry` and performs the create-session-then-attach flow above. [dsh-agent-instructions](../../packages/context/agent-instructions) is **not** a consumer despite the name: it discovers AGENTS.md-style instruction files under an agent's own cwd and never touches `ctx.workspaceRegistry` — the shared word refers to the user's working directory, not to this registry's entities.
|
|
127
|
+
|
|
128
|
+
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
|
129
|
+
|
|
130
|
+
<a id="cordis-surface"></a>
|
|
131
|
+
|
|
132
|
+
## Cordis API
|
|
133
|
+
|
|
134
|
+
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
|
|
135
|
+
|
|
136
|
+
<a id="ctxdirectorypicker--directorypicker-abstract-seam"></a>
|
|
137
|
+
|
|
138
|
+
### `ctx.directoryPicker` — `DirectoryPicker` (abstract seam)
|
|
139
|
+
|
|
140
|
+
Abstract directory-picking service. Subclass, implement `capability()`, and load the subclass as a plugin — it registers as `ctx.directoryPicker` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). The capability object must be stable for the service lifetime: consumers may capture it across calls.
|
|
141
|
+
|
|
142
|
+
```ts cordis-catalog
|
|
143
|
+
/**
|
|
144
|
+
* The backend's interaction capability.
|
|
145
|
+
* @returns the discriminated capability consumers switch on.
|
|
146
|
+
*/
|
|
147
|
+
abstract capability(): DirectoryPickerCapability
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Source: [`packages/host/directory-picker/src/index.ts:131`](../../packages/host/directory-picker/src/index.ts)
|
|
151
|
+
|
|
152
|
+
<a id="ctxworkspaceregistry--workspaceregistry"></a>
|
|
153
|
+
|
|
154
|
+
### `ctx.workspaceRegistry` — `WorkspaceRegistry`
|
|
155
|
+
|
|
156
|
+
Durable workspace registry. Startup waits for `sessionPersistence`, builds one canonical-cwd header index, and completes the one-time history bootstrap before the service becomes active. The persistence dependency is mandatory so an unavailable peer can never be mistaken for an empty history and commit the initialized marker.
|
|
157
|
+
|
|
158
|
+
```ts cordis-catalog
|
|
159
|
+
/**
|
|
160
|
+
* Create or reuse a workspace for an existing directory. The path is
|
|
161
|
+
* canonicalized through `fs.realpath`; a nonexistent path rejects with the
|
|
162
|
+
* original error and a non-directory rejects. Repeated calls for the same
|
|
163
|
+
* canonical path return the existing entity without changing its title.
|
|
164
|
+
* A newly created workspace is prepended to the durable registry order.
|
|
165
|
+
* Different canonical paths may share a display title.
|
|
166
|
+
* @param path - Existing directory to own, in any path spelling.
|
|
167
|
+
* @param title - Display title used only when a new record is created.
|
|
168
|
+
* @returns the existing or newly durable workspace.
|
|
169
|
+
*/
|
|
170
|
+
async create(path: string, title?: string): Promise<Workspace>
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Look up a workspace by id.
|
|
174
|
+
* @param id - Workspace id.
|
|
175
|
+
* @returns the workspace, or `undefined` when unknown.
|
|
176
|
+
*/
|
|
177
|
+
get(id: WorkspaceId): Workspace | undefined
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Synchronous workspace projection in durable registry order. Every
|
|
181
|
+
* entity's `sessionIds` getter is already filtered by the startup/live
|
|
182
|
+
* canonical-cwd header index; this method performs no persistence reads.
|
|
183
|
+
* @returns a fresh ordered array of workspace entities.
|
|
184
|
+
*/
|
|
185
|
+
list(): Workspace[]
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Delete one workspace registration while retaining its directory and every
|
|
189
|
+
* session log. The durable order is updated before the table deletion; a
|
|
190
|
+
* failed table write restores the prior order and keeps the entity
|
|
191
|
+
* published. Unknown ids are an idempotent no-op for domain callers.
|
|
192
|
+
* @param id - Workspace registration to remove.
|
|
193
|
+
* @returns `true` when a record was deleted, `false` when it was unknown.
|
|
194
|
+
*/
|
|
195
|
+
delete(id: WorkspaceId): Promise<boolean>
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Move one workspace within the durable display order, DOM-insertBefore-like.
|
|
199
|
+
* With an anchor it lands before that workspace; without one it appends.
|
|
200
|
+
* @param id - Workspace to move.
|
|
201
|
+
* @param beforeId - Workspace anchor; omitted appends.
|
|
202
|
+
* @returns the complete committed workspace order.
|
|
203
|
+
*/
|
|
204
|
+
insertBefore(id: WorkspaceId, beforeId?: WorkspaceId): Promise<readonly WorkspaceId[]>
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Archive one session durably. The session must exist (live or in session
|
|
208
|
+
* persistence); its workspace accounting — or lack of one — is irrelevant.
|
|
209
|
+
* An already archived id resolves without writing.
|
|
210
|
+
* @param sessionId - The session to archive.
|
|
211
|
+
* @returns resolution after durability.
|
|
212
|
+
*/
|
|
213
|
+
archiveSession(sessionId: SessionId): Promise<void>
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve by canonical directory path without creating or mutating a
|
|
217
|
+
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
218
|
+
* directory returns `undefined`.
|
|
219
|
+
* @param path - Existing directory path in any spelling.
|
|
220
|
+
* @returns the workspace owning the canonical path, when one exists.
|
|
221
|
+
*/
|
|
222
|
+
async resolveByPath(path: string): Promise<Workspace | undefined>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Types: [SessionId](core.md)
|
|
226
|
+
|
|
227
|
+
Source: [`packages/workspace/workspace/src/index.ts:92`](../../packages/workspace/workspace/src/index.ts)
|
|
228
|
+
<!-- END GENERATED cordis-surface -->
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# 工作区
|
|
2
|
+
|
|
3
|
+
[English](workspace.md) | 中文
|
|
4
|
+
|
|
5
|
+
工作区(workspace)是用户工作目录的持久记录:一个建立在规范路径之上的稳定 id、一个显示标题,以及归属于它的会话的有序账本。该子系统是单个包(package)([dsh-workspace](../../packages/workspace/workspace),`ctx.workspaceRegistry`)——一项宿主侧可选能力,不属于 agent loop(智能体循环)主干,并且对模型不可见(没有工具、没有提示词文本、没有会话事件)。它通过[存储领域数据形式](storage.md)存储自己的记录,并对照 [`SessionHeader.cwd`](persistence.md#sessionheader--metadata-beside-the-log) 校验会话成员资格,因此 `storageDomain` 与 `sessionPersistence` 是必需的启动依赖:持久化这一依赖不可用时,插件保持 pending,而不是把这种不可用误当作空历史。设计记录:[领域 KV 存储 Agent Note(agent 决策记录)](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md);引导与 GUI 顺序:[Workspace UI 产品流程 Agent Note](../../.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md)。
|
|
6
|
+
|
|
7
|
+
源码:[`packages/workspace/workspace/src/types.ts`](../../packages/workspace/workspace/src/types.ts)
|
|
8
|
+
|
|
9
|
+
## 标识
|
|
10
|
+
|
|
11
|
+
```ts type-equiv
|
|
12
|
+
/**
|
|
13
|
+
* Identifies one workspace record. A generated uuid, never the path: path
|
|
14
|
+
* normalization rewrites paths, and a reference anchor must stay stable.
|
|
15
|
+
*/
|
|
16
|
+
type WorkspaceId = Branded<'WorkspaceId'>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`WorkspaceId` 是[品牌化 id](core.md#branded-ids)。路径标识与之分离:`realpathNormalize`(`fs.realpath`;尾部斜杠、`..` 与符号链接全部解析)是唯一的一套唯一性规范——工作区路径以规范化形式存储,唯一性即规范路径的字符串相等(指向已被拥有目录的符号链接会与之冲突),attach 时的会话 cwd 检查也走同一套规范。
|
|
20
|
+
|
|
21
|
+
## 工作区实体
|
|
22
|
+
|
|
23
|
+
消费方只看到 `Workspace` 接口;实现保持包内私有。
|
|
24
|
+
|
|
25
|
+
```ts type-equiv
|
|
26
|
+
/**
|
|
27
|
+
* One workspace: a stable id over an existing directory, a display title, and
|
|
28
|
+
* an ordered candidate account of sessions. Membership requires both an id in
|
|
29
|
+
* that account and a session header whose canonical cwd equals the workspace
|
|
30
|
+
* path. Consumers only see this interface; the implementation stays private.
|
|
31
|
+
*/
|
|
32
|
+
interface Workspace {
|
|
33
|
+
/** Stable record id (generated uuid). */
|
|
34
|
+
readonly id: WorkspaceId
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Canonical directory path: the `fs.realpath` of the path given at create
|
|
38
|
+
* time (trailing slashes, `..`, and symlinks all resolved). Never rewritten
|
|
39
|
+
* afterwards, even when the directory disappears (see {@link status}).
|
|
40
|
+
*/
|
|
41
|
+
readonly path: string
|
|
42
|
+
|
|
43
|
+
/** Display title. Defaults to `basename(path)` at create; duplicates are allowed. */
|
|
44
|
+
readonly title: string
|
|
45
|
+
|
|
46
|
+
/** ISO-8601 creation instant, stamped at create and never rewritten. */
|
|
47
|
+
readonly createdAt: string
|
|
48
|
+
|
|
49
|
+
/** ISO-8601 instant of the last durable mutation (create counts as one). */
|
|
50
|
+
readonly updatedAt: string
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Header-validated sessions in manually owned order: a new session is
|
|
54
|
+
* prepended at attach, explicit reordering goes through
|
|
55
|
+
* `insertSessionBefore`, and activity never reorders. The durable candidate
|
|
56
|
+
* account is filtered synchronously: missing headers, invalid cwd values,
|
|
57
|
+
* and canonical cwd mismatches are never returned. A subsequent workspace
|
|
58
|
+
* mutation prunes those filtered candidates durably.
|
|
59
|
+
*/
|
|
60
|
+
readonly sessionIds: readonly SessionId[]
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Replace the display title durably.
|
|
64
|
+
* @param title - New title; any string, duplicates across workspaces allowed.
|
|
65
|
+
* @returns resolution after durability.
|
|
66
|
+
*/
|
|
67
|
+
setTitle(title: string): Promise<void>
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prepend a session to this workspace's candidate account. An already
|
|
71
|
+
* accounted id resolves without writing, aside from the durable
|
|
72
|
+
* filtered-candidate prune every accepted mutation performs. A new id's
|
|
73
|
+
* live or persisted
|
|
74
|
+
* header cwd must resolve to an existing directory equal to {@link path};
|
|
75
|
+
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
|
76
|
+
* writing.
|
|
77
|
+
* @param sessionId - The session to record.
|
|
78
|
+
* @returns resolution after durability.
|
|
79
|
+
*/
|
|
80
|
+
attachSession(sessionId: SessionId): Promise<void>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Move an accounted session within the manual order, DOM-insertBefore-like:
|
|
84
|
+
* with an anchor the session lands before it, without one it appends to the
|
|
85
|
+
* end. Only the moved id changes position. A session or anchor absent from
|
|
86
|
+
* the account rejects without writing; a move to the current position
|
|
87
|
+
* resolves without writing, aside from the durable filtered-candidate
|
|
88
|
+
* prune every accepted mutation performs; decided on the domain write
|
|
89
|
+
* chain.
|
|
90
|
+
* @param sessionId - The accounted session to move.
|
|
91
|
+
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
|
92
|
+
* @returns resolution after durability.
|
|
93
|
+
*/
|
|
94
|
+
insertSessionBefore(sessionId: SessionId, beforeSessionId?: SessionId): Promise<void>
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Remove a session from this workspace's account. Idempotent: an id not on
|
|
98
|
+
* the account resolves without writing, aside from the durable
|
|
99
|
+
* filtered-candidate prune every accepted mutation performs; decided on
|
|
100
|
+
* the domain write chain like attach. Never touches the session's own stored log.
|
|
101
|
+
* @param sessionId - The session to remove.
|
|
102
|
+
* @returns resolution after durability.
|
|
103
|
+
*/
|
|
104
|
+
detachSession(sessionId: SessionId): Promise<void>
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Live directory check, uncached: whether {@link path} currently exists and
|
|
108
|
+
* is a directory. A missing directory never mutates the record — the
|
|
109
|
+
* directory may only be temporarily moved.
|
|
110
|
+
* @returns `'ok'` when the directory exists, `'missing-dir'` otherwise.
|
|
111
|
+
*/
|
|
112
|
+
status(): Promise<'ok' | 'missing-dir'>
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
所有权的真源是记录中有序的 `sessionIds`,绝不从会话 cwd 派生——但成员资格要求两者同时成立:账本上有其 id,且 header 的规范 cwd 等于工作区路径,因此一个会话在结构上至多属于一个工作区。失败的写入会拒绝(`insertSessionBefore` 的账本错误以 `WorkspaceMoveInvalidError` 拒绝,存储失败以普通错误拒绝);每次被接受的变更都盖上 `updatedAt` 时间戳,并持久修剪不再通过成员资格检查的候选项。
|
|
117
|
+
|
|
118
|
+
## 注册表:`ctx.workspaceRegistry`
|
|
119
|
+
|
|
120
|
+
`WorkspaceRegistry`([签名](#ctxworkspaceregistry--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好解决被标记的那次变更——通过删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create(注册可以重建,因此回滚是安全方向)——而没有标记的顺序/表不一致则作为损坏大声失败。
|
|
121
|
+
|
|
122
|
+
会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次成功启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑。引导只发生这一次:没有 cwd 的历史遗留会话保持 Ungrouped,此后创建的会话只能通过 `attachSession` 加入工作区。
|
|
123
|
+
|
|
124
|
+
## 消费方
|
|
125
|
+
|
|
126
|
+
[dsh-host-apiproxy](../../packages/host/apiproxy) 是产品消费方:它经 `ctx.workspaceRegistry` 向 GUI 客户端提供工作区的 CRUD,并执行上文「先建会话再 attach」的流程。[dsh-agent-instructions](../../packages/context/agent-instructions) 尽管名字如此,却**不是**消费方:它在 agent 自己的 cwd 下发现 AGENTS.md 风格的指令文件,从不触碰 `ctx.workspaceRegistry`——两者共用的这个词指的是用户的工作目录,而非本注册表的实体。
|
|
127
|
+
|
|
128
|
+
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
|
129
|
+
|
|
130
|
+
<a id="cordis-surface"></a>
|
|
131
|
+
|
|
132
|
+
## Cordis API
|
|
133
|
+
|
|
134
|
+
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
|
|
135
|
+
|
|
136
|
+
<a id="ctxdirectorypicker--directorypicker-abstract-seam"></a>
|
|
137
|
+
|
|
138
|
+
### `ctx.directoryPicker` — `DirectoryPicker` (abstract seam)
|
|
139
|
+
|
|
140
|
+
Abstract directory-picking service. Subclass, implement `capability()`, and load the subclass as a plugin — it registers as `ctx.directoryPicker` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). The capability object must be stable for the service lifetime: consumers may capture it across calls.
|
|
141
|
+
|
|
142
|
+
```ts cordis-catalog
|
|
143
|
+
/**
|
|
144
|
+
* The backend's interaction capability.
|
|
145
|
+
* @returns the discriminated capability consumers switch on.
|
|
146
|
+
*/
|
|
147
|
+
abstract capability(): DirectoryPickerCapability
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Source: [`packages/host/directory-picker/src/index.ts:131`](../../packages/host/directory-picker/src/index.ts)
|
|
151
|
+
|
|
152
|
+
<a id="ctxworkspaceregistry--workspaceregistry"></a>
|
|
153
|
+
|
|
154
|
+
### `ctx.workspaceRegistry` — `WorkspaceRegistry`
|
|
155
|
+
|
|
156
|
+
Durable workspace registry. Startup waits for `sessionPersistence`, builds one canonical-cwd header index, and completes the one-time history bootstrap before the service becomes active. The persistence dependency is mandatory so an unavailable peer can never be mistaken for an empty history and commit the initialized marker.
|
|
157
|
+
|
|
158
|
+
```ts cordis-catalog
|
|
159
|
+
/**
|
|
160
|
+
* Create or reuse a workspace for an existing directory. The path is
|
|
161
|
+
* canonicalized through `fs.realpath`; a nonexistent path rejects with the
|
|
162
|
+
* original error and a non-directory rejects. Repeated calls for the same
|
|
163
|
+
* canonical path return the existing entity without changing its title.
|
|
164
|
+
* A newly created workspace is prepended to the durable registry order.
|
|
165
|
+
* Different canonical paths may share a display title.
|
|
166
|
+
* @param path - Existing directory to own, in any path spelling.
|
|
167
|
+
* @param title - Display title used only when a new record is created.
|
|
168
|
+
* @returns the existing or newly durable workspace.
|
|
169
|
+
*/
|
|
170
|
+
async create(path: string, title?: string): Promise<Workspace>
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Look up a workspace by id.
|
|
174
|
+
* @param id - Workspace id.
|
|
175
|
+
* @returns the workspace, or `undefined` when unknown.
|
|
176
|
+
*/
|
|
177
|
+
get(id: WorkspaceId): Workspace | undefined
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Synchronous workspace projection in durable registry order. Every
|
|
181
|
+
* entity's `sessionIds` getter is already filtered by the startup/live
|
|
182
|
+
* canonical-cwd header index; this method performs no persistence reads.
|
|
183
|
+
* @returns a fresh ordered array of workspace entities.
|
|
184
|
+
*/
|
|
185
|
+
list(): Workspace[]
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Delete one workspace registration while retaining its directory and every
|
|
189
|
+
* session log. The durable order is updated before the table deletion; a
|
|
190
|
+
* failed table write restores the prior order and keeps the entity
|
|
191
|
+
* published. Unknown ids are an idempotent no-op for domain callers.
|
|
192
|
+
* @param id - Workspace registration to remove.
|
|
193
|
+
* @returns `true` when a record was deleted, `false` when it was unknown.
|
|
194
|
+
*/
|
|
195
|
+
delete(id: WorkspaceId): Promise<boolean>
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Move one workspace within the durable display order, DOM-insertBefore-like.
|
|
199
|
+
* With an anchor it lands before that workspace; without one it appends.
|
|
200
|
+
* @param id - Workspace to move.
|
|
201
|
+
* @param beforeId - Workspace anchor; omitted appends.
|
|
202
|
+
* @returns the complete committed workspace order.
|
|
203
|
+
*/
|
|
204
|
+
insertBefore(id: WorkspaceId, beforeId?: WorkspaceId): Promise<readonly WorkspaceId[]>
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Archive one session durably. The session must exist (live or in session
|
|
208
|
+
* persistence); its workspace accounting — or lack of one — is irrelevant.
|
|
209
|
+
* An already archived id resolves without writing.
|
|
210
|
+
* @param sessionId - The session to archive.
|
|
211
|
+
* @returns resolution after durability.
|
|
212
|
+
*/
|
|
213
|
+
archiveSession(sessionId: SessionId): Promise<void>
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve by canonical directory path without creating or mutating a
|
|
217
|
+
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
218
|
+
* directory returns `undefined`.
|
|
219
|
+
* @param path - Existing directory path in any spelling.
|
|
220
|
+
* @returns the workspace owning the canonical path, when one exists.
|
|
221
|
+
*/
|
|
222
|
+
async resolveByPath(path: string): Promise<Workspace | undefined>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Types: [SessionId](core.md)
|
|
226
|
+
|
|
227
|
+
Source: [`packages/workspace/workspace/src/index.ts:92`](../../packages/workspace/workspace/src/index.ts)
|
|
228
|
+
<!-- END GENERATED cordis-surface -->
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write docs/testing.md
|
|
5
|
+
testing.md: 8ed81412a954bcd1d4b5d84eb4133ed081092764
|
|
6
|
+
testing.zh.md: 5662db51847ba2f4e297d0bd3d4af0d024fa29fb
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Testing policy
|
|
2
|
+
|
|
3
|
+
English | [中文](testing.zh.md)
|
|
4
|
+
|
|
5
|
+
How this repo tests, tier by tier, and the rules that keep a green suite meaningful. Commands live in root [AGENTS.md](../AGENTS.md); linked Agent Notes carry the rationale.
|
|
6
|
+
|
|
7
|
+
## Tiers
|
|
8
|
+
|
|
9
|
+
- **Unit** (`pnpm run test`): vitest over package and example specs under their `tests/**` directories plus repository script specs under `scripts/**/*.spec.ts`; tests stay with the code area they exercise. Every registry gets an HMR-safety test (dispose the contributing fiber, assert cleanup). Prefer edge cases, error paths, event ordering, concurrency races, and permanent tests for contract regressions (see `packages/core/agent-loop/tests/contract-regressions.spec.ts`).
|
|
10
|
+
- **Coverage gate** (`pnpm run test:coverage`): the gating run, per-file 100% on `packages/*/*/src`. An uncovered line is often dead code the gate is correctly flagging for deletion, not a missing test to bolt on. Line coverage is necessary, never sufficient — it proves lines ran, not that the feature works as shipped. Per-file 100% on `packages/shell/pwsh-local/src` needs a real `pwsh`: without one its executor suites self-skip and `vitest.config.ts` exempts the file so pwsh-less hosts stay green, while CI runners ship pwsh and enforce the full bar.
|
|
11
|
+
- **Real-API e2e** (`pnpm run test:e2e`): with-key tests against live provider APIs — the DeepSeek model plus provider-specific smokes that gate on their own keys (`EXA_API_KEY`, `PERPLEXITY_API_KEY`, …); each suite self-skips without its key so keyless CI stays green ([real-API e2e Agent Note](../.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md)).
|
|
12
|
+
- **Snapshot** (`pnpm run test:snapshot`): keyless expected outputs cover external behavior — transport contracts and presentation, while persisted logs pin assembled backend behavior. ACP boots the real automation-server example, replays a recorded session, and diffs normalized JSON-RPC plus the re-persisted log ([ACP snapshot Agent Note](../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md)); headless backend scenarios boot their explicit example composition through an unexported JSONL test driver, while `apps/cli` separately owns product `dsh --profile headless` acceptance. Use `pnpm run test:snapshot:record` when a model transcript changes and `pnpm run test:snapshot:refresh` when replay input remains valid; review every JSONL and expected-output diff. One ACP scenario (`text-turn`) pins full system-prompt/tool-schema content; other fixtures tokenize it so an edit churns one line ([pinned-header Agent Note](../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)).
|
|
13
|
+
- **Web browser snapshot** (`pnpm run test:web`; required Linux PR gate): Chromium compares replayed browser output with `apps/web/tests/snapshots/`. CI forces read-only `DSH_SNAPSHOT=replay`, never writing expected outputs; record/refresh stay local and every diff is reviewed ([web e2e lane](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md), [CI gate decision](../.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md)). `test:web` [builds first](../.agents/notes/implemented/bug-fix/2026-07-28-themed-scrollbars-and-reserved-gutter.md) for plugin CSS.
|
|
14
|
+
|
|
15
|
+
Committed session-format JSONL uses the canonical packed-row layout, and the keyless snapshot gate discovers every such fixture by its `session` header; the [temporary migrator](../scripts/migrate-packed-session-fixtures.ts) rewrites older fixture layouts.
|
|
16
|
+
|
|
17
|
+
## The with-key policy: inference is cheap here
|
|
18
|
+
|
|
19
|
+
We are DeepSeek — do not ration real-API tests. A no-key test proves plumbing; only a with-key run proves the agent works against a real model. Cover file-writing prompts, multi-turn conversations, tool use, and mid-stream cancellation. Highest-value are **smoke tests** that boot the real example, send one prompt, and check the world — they catch the "green unit tests, broken product" class that mocks cannot ([postmortem 0001](postmortem/0001-acp-default-export-drops-inject.md)). Self-skip keeps secretless CI and keyless contributors unblocked; it is not a cost signal. Every example ships keyless and with-key smokes ([examples/AGENTS.md](../examples/AGENTS.md)).
|
|
20
|
+
|
|
21
|
+
## Prefer the real implementation over a mock
|
|
22
|
+
|
|
23
|
+
Mock only the expensive or non-deterministic boundary (LLM adapter, network, clock); keep everything downstream real. A hand-rolled stand-in proves the bridge moves bytes, not that the shipping tool behaves as asserted. Bridge tool-call tests use the scripted mock model with the real tool and executor: `makeBridgeHarness({ withBash: true })` plugs in `dsh-bash-local` and `dsh-tool-bash`, then runs `echo`.
|
|
24
|
+
|
|
25
|
+
Recovery tests separate pre/post-chunk failures by step and prove failed chunks derive no message or tool side effect. Cover exhaustion, cancellation, policy composition, persistence, status, wire counts, transport-closing idle timeouts, and shipping Loader composition.
|
|
26
|
+
|
|
27
|
+
## Verify the world, not the self-report
|
|
28
|
+
|
|
29
|
+
An e2e assertion re-runs the command or re-reads the file externally; a keyword probe on the agent's own output lets a cheating agent pass. Assert untouched files are byte-identical. e2e tests own their resources: create the harness in the test, dispose in `afterEach` (even on failure/retry/timeout); shared fixtures live in a plain `tests/harness.ts`, never another `*.e2e.ts` (importing a spec re-registers its `describe` and duplicates real API calls).
|
|
30
|
+
|
|
31
|
+
## Test the real entry path
|
|
32
|
+
|
|
33
|
+
- Product-visible plugins require a non-unit REAL-composition test. Hand-built `ctx.plugin(...)` suites are insufficient: boot test-only `cordis.yml` through Loader and app/process, mock only external services or nondeterministic inputs, and assert model-visible request/log, durable state, or user-visible output. Keep opt-ins out of shipped defaults.
|
|
34
|
+
- A guard only guards if the regression actually fails it. For a plugin without `inject` (bundle/composition plugins), a Loader smoke stays green when a default export replaces the required named exports — add an explicit `expect('default' in mod).toBe(false)` plus an `unwrapExports` round-trip assertion, and prove it: introduce the regression, watch red, revert.
|
|
35
|
+
- "Real entry path" means the published artifact: a package `bin` runs built `lib/bin.js` under plain `node`, exposing failures tsx masks (settle races, module resolution, swallowed load failures). The same applies to non-index runtime entries (the worker-thread sibling `lib/worker.cjs`) and singleton modules shared across bundles (`packages/sdk/server/tests/built-scope-carrier.e2e.ts`). Keep the built-artifact smokes green (`packages/examples/*/tests/built-bin.e2e.ts`, `packages/code-runtime/code-runtime-worker-thread/tests/built-lib.e2e.ts`), and assert a genuinely-missing config exits non-zero.
|
|
36
|
+
|
|
37
|
+
## Test resolution: source plane only
|
|
38
|
+
|
|
39
|
+
- Every vitest config points vite-tsconfig-paths at `tsconfig.base.json`; bare workspace imports resolve to `src` ([layout](development.md#typescript-project-layout)), never through package `exports` to built `lib/` — stale artifacts there load a second copy of module singletons. Built artifacts are consumed only explicitly: `lib`-mode subprocesses and the built smokes below.
|
|
40
|
+
|
|
41
|
+
## Test subprocess launch modes
|
|
42
|
+
|
|
43
|
+
- CI and build-having test lanes run every example or Cordis-config subprocess from built `lib/` through the shared dual-mode launcher. Do not hand-write `--import tsx` for these subprocesses.
|
|
44
|
+
- Protocol and operating-system fixtures that do not load Cordis run erasable `.ts` directly with Node, without tsx or the root paths map.
|
|
45
|
+
- Only a test whose subject is source-path resolution may select `src`; state that contract in the test.
|
|
46
|
+
|
|
47
|
+
## When a snapshot test is required
|
|
48
|
+
|
|
49
|
+
Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless scenario in the same PR through a runnable example's owning snapshot suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP automation scenarios use `examples/<name>/tests/snapshots/`, a scenario table over the [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) suite factory (`examples/acp-agent` is primary); `examples/headless-agent` owns the internal canonical-event JSONL snapshots and replay fixtures. The `pwsh-tool-turn` ACP scenario boots real `pwsh` and skips where it is absent. Completed interactive-terminal journeys use JSONL-driven scenarios under `apps/cli/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. Browser-rendered web GUI journeys use `apps/web/tests/snapshots/`. New capability seams, lifecycle variants, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 测试策略
|
|
2
|
+
|
|
3
|
+
[English](testing.md) | 中文
|
|
4
|
+
|
|
5
|
+
本文说明本仓库的分层测试方式,以及保持绿色测试套件有意义的规则。命令见根目录 [AGENTS.md](../AGENTS.md);相关 Agent Note 承载设计动机。
|
|
6
|
+
|
|
7
|
+
## 层级
|
|
8
|
+
|
|
9
|
+
- **单元测试**(`pnpm run test`):vitest 运行包和示例各自的 `tests/**` 目录下的测试,以及匹配 `scripts/**/*.spec.ts` 的仓库脚本测试;测试文件与其所覆盖的代码区域放在一起。每个注册表都有一个 HMR(热模块替换)安全测试(对向该注册表贡献内容的 fiber 执行 dispose(资源释放),并断言清理完成)。优先覆盖边界情况、错误路径、事件顺序、并发竞态,以及针对约定回归的永久测试(见 `packages/core/agent-loop/tests/contract-regressions.spec.ts`)。
|
|
10
|
+
- **覆盖率门禁**(`pnpm run test:coverage`):门禁级运行,对 `packages/*/*/src` 按文件 100% 覆盖。未覆盖的行往往是门禁正确标记出的死代码(应删除),而非需要补写的测试。行覆盖率是必要条件,但永远不是充分条件:它证明行被执行过,不证明功能按交付预期工作。`packages/shell/pwsh-local/src` 的按文件 100% 覆盖需要真实的 `pwsh`:缺少它时其执行器套件会自动跳过,`vitest.config.ts` 会豁免该文件以使无 pwsh 的主机保持绿色,而 CI runner 自带 pwsh,仍按完整标准执行门禁。
|
|
11
|
+
- **真实 API e2e**(`pnpm run test:e2e`):带密钥测试调用真实提供方 API,包括 DeepSeek 模型以及各提供方特有的冒烟测试;这些测试各自由自己的密钥控制(`EXA_API_KEY`、`PERPLEXITY_API_KEY` 等),缺少密钥时套件会自动跳过,使 keyless CI 保持绿色([真实 API e2e Agent Note](../.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md))。
|
|
12
|
+
- **快照**(`pnpm run test:snapshot`):无密钥预期输出覆盖对外行为(传输约定与呈现),持久化日志则固定组装后的后端行为。ACP 启动真实的自动化服务器示例、回放录制会话,并对归一化 JSON-RPC 与重新持久化的日志执行 diff([ACP 快照 Agent Note](../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md));headless 后端场景通过未导出的 JSONL 测试 driver 启动各自显式的示例组装,而 `apps/cli` 则单独负责产品 CLI(命令行界面)`dsh --profile headless` 的验收。当模型 transcript(文本记录)发生变化时使用 `pnpm run test:snapshot:record`,回放输入仍然有效时使用 `pnpm run test:snapshot:refresh`;请审查每一处 JSONL 与预期输出差异。一个 ACP 场景(`text-turn`)固定完整的系统提示词与工具 schema 内容;其他 fixture(测试前置数据)将其 token 化,因此修改只会扰动一行([pinned-header Agent Note](../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md))。
|
|
13
|
+
- **Web 浏览器快照**(`pnpm run test:web`;必需的 Linux PR(Pull Request)门禁):Chromium 将回放后的浏览器输出与 `apps/web/tests/snapshots/` 比较。CI 强制只读的 `DSH_SNAPSHOT=replay`,绝不写入预期输出;record/refresh 留在本地,每处 diff 都须评审([web e2e 车道](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md)、[CI 门禁决策](../.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md))。`test:web` 会[先构建](../.agents/notes/implemented/bug-fix/2026-07-28-themed-scrollbars-and-reserved-gutter.md)以交付插件 CSS。
|
|
14
|
+
|
|
15
|
+
签入仓库的会话格式 JSONL 使用规范打包行布局,无密钥快照门禁会通过 `session` header 发现每一份此类 fixture;[临时迁移器](../scripts/migrate-packed-session-fixtures.ts)会改写旧版 fixture 布局。
|
|
16
|
+
|
|
17
|
+
## 带密钥策略:推理(inference)在这里很便宜
|
|
18
|
+
|
|
19
|
+
我们是 DeepSeek,不要吝惜真实 API 测试。无密钥测试只能证明底层通路;只有带密钥运行才能证明 agent(智能体)能对接真实模型正常工作。覆盖文件写入提示词、包含多个轮次的对话、工具使用和流中取消。价值最高的是**冒烟测试**:启动真实示例、发送一条提示词,并检查外部世界;它们能捕获「单元测试全绿、产品却坏了」这一类 mock 无法发现的问题([事故复盘 0001](postmortem/0001-acp-default-export-drops-inject.md))。自动跳过让无密钥 CI 和无密钥贡献者不受阻塞;它不是成本信号。每个示例都提供无密钥和带密钥冒烟测试([examples/AGENTS.md](../examples/AGENTS.md))。
|
|
20
|
+
|
|
21
|
+
## 优先使用真实实现而非 mock
|
|
22
|
+
|
|
23
|
+
只 mock 开销高或不确定的边界(LLM(大语言模型)适配器、网络、时钟);下游一切保持真实。手写替身只能证明桥接层在搬运字节,不能证明交付的工具行为符合断言。桥接工具调用测试将脚本化 mock 模型与真实工具和执行器配合使用:`makeBridgeHarness({ withBash: true })` 接入 `dsh-bash-local` 与 `dsh-tool-bash`,然后运行 `echo`。
|
|
24
|
+
|
|
25
|
+
恢复测试按步骤区分分片前与分片后的失败,并证明失败分片不会派生出消息或工具副作用。覆盖耗尽、取消、策略组合、持久化、状态、协议计数、会关闭传输的空闲超时,以及交付的 Loader 组合。
|
|
26
|
+
|
|
27
|
+
## 验证外部世界,而非自我报告
|
|
28
|
+
|
|
29
|
+
e2e 断言应重新运行命令或从外部重新读取文件;对 agent 自身输出做关键词探测会让作弊的 agent 通过。断言未修改的文件逐字节一致。e2e 测试自行管理资源:在测试中创建 harness,在 `afterEach` 中 dispose(即使失败/重试/超时也要释放);共享 fixture 放在普通的 `tests/harness.ts` 中,绝不放在另一个 `*.e2e.ts` 中(导入一个 spec 会重新注册其 `describe`,导致真实 API 调用重复执行)。
|
|
30
|
+
|
|
31
|
+
## 测试真实入口路径
|
|
32
|
+
|
|
33
|
+
- 产品可见的插件必须有一个非单元的真实组合测试。手动构建的 `ctx.plugin(...)` 套件不够:通过 Loader 和 app/process 启动仅用于测试的 `cordis.yml`,只 mock 外部服务或非确定性输入,断言模型可见的请求/日志、持久状态或用户可见输出。不要把 opt-in 选项混入交付默认值。
|
|
34
|
+
- 一个守卫只有在回归真的能让它失败时才有效。对于没有 `inject` 的插件(bundle/组合插件),Loader 冒烟测试在默认导出替换必需的具名导出时仍然绿着——需要添加显式的 `expect('default' in mod).toBe(false)` 加 `unwrapExports` 往返断言,并证明它有效:引入回归、观察变红、回退。
|
|
35
|
+
- 「真实入口路径」指已发布的产物:包的 `bin` 所运行的是构建后的 `lib/bin.js`,并由普通 `node` 执行,从而暴露 tsx 会掩盖的失败(结算竞态、模块解析、被吞掉的加载失败)。同样的规则适用于非 index 运行时入口(worker-thread 的同级文件 `lib/worker.cjs`),也适用于多个 bundle 共享的单例模块(`packages/sdk/server/tests/built-scope-carrier.e2e.ts`)。保持构建产物冒烟测试绿色(`packages/examples/*/tests/built-bin.e2e.ts`、`packages/code-runtime/code-runtime-worker-thread/tests/built-lib.e2e.ts`),并断言真正缺失的配置以非零状态退出。
|
|
36
|
+
|
|
37
|
+
## 测试解析:仅限源码
|
|
38
|
+
|
|
39
|
+
- 每个 vitest 配置都将 vite-tsconfig-paths 指向 `tsconfig.base.json`;工作区包的裸导入解析到 `src`([布局](development.md#typescript-project-layout)),绝不会经由包的 `exports` 解析到构建后的 `lib/`,因为其中的陈旧产物会加载第二份模块单例。构建产物只在显式指定时使用:以 `lib` 模式运行的子进程,以及下文的构建产物冒烟测试。
|
|
40
|
+
|
|
41
|
+
## 测试子进程启动模式
|
|
42
|
+
|
|
43
|
+
- CI 与已有构建产物的测试通道通过共享双模式启动器,从构建后的 `lib/` 运行每个示例或 Cordis 配置子进程。不要为这些子进程手写 `--import tsx`。
|
|
44
|
+
- 不加载 Cordis 的协议与操作系统 fixture 直接通过 Node 运行使用可擦除语法的 `.ts` 文件,不经过 tsx 或根路径映射。
|
|
45
|
+
- 只有测试对象本身是源码路径解析时,才可以选择 `src`;在测试中写明这一约定。
|
|
46
|
+
|
|
47
|
+
## 何时需要快照测试
|
|
48
|
+
|
|
49
|
+
每项非平凡的模型可见、协议可见或人类可见变更,都必须在同一 PR 中,通过可运行示例所属的快照套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 自动化场景使用 `examples/<name>/tests/snapshots/`,即基于 [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) 套件工厂的场景表(`examples/acp-agent` 为主套件);`examples/headless-agent` 拥有内部规范事件 JSONL 快照与回放 fixture。`pwsh-tool-turn` ACP 场景启动真实 `pwsh`,在无 `pwsh` 的主机上跳过。已完成的交互式终端旅程使用 `apps/cli/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。浏览器渲染的 Web GUI 旅程使用上述 Web 应用快照套件。新的能力 seam、生命周期变体或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write docs/tool-catalog.md
|
|
5
|
+
tool-catalog.md: 50563c97c6cd5496871ea7fa52c4823a56b088fd
|
|
6
|
+
tool-catalog.zh.md: ed0c7e3f70cffbecd3d20a1556bcb0cd4204df00
|