Files
deepseek-harness/packages/AGENTS.md
Tianyi Cui 7fa113be0e Merge remote-tracking branch 'origin/master' into codex/pr48-repo-hardening-rfcs
# Conflicts:
#	docs/adr/README.md
#	docs/rfc/009-session-persistence-and-resumability.md
#	docs/rfc/README.md
#	docs/rfc/implemented/2026-06-11-doc-sync-enforcement.md
#	docs/rfc/proposed/2026-06-14-acp-agent-client-protocol.md
#	examples/acp-agent/tests/acp.e2e.ts
#	packages/acp/README.md
#	packages/acp/src/index.ts
#	packages/acp/tests/stream-update.spec.ts
#	packages/agent-loop/src/loop.ts
#	packages/tools/src/index.ts
2026-06-18 23:41:14 +08:00

4.0 KiB

AGENTS.md — Harness Packages

This directory contains all @deepseek-ai/dsh-* harness packages. When editing code here, follow these conventions:

  • Effect-based registrations: every contribution (tool, section, adapter, agent, event listener) goes through ctx.effect() / ctx.on(), and register() methods return disposers. Never use bare arrays or manual cleanup.
  • Declaration merging: services declare their ctx key in declare module 'cordis' { interface Context { } } and their events in interface Events. Merge-extensible maps (ContentBlockMap, MessageSourceMap, FinishReasonMap, TurnTriggerMap, TurnEndReasonMap, SessionEventMap) are how plugins add new variants.
  • Waterfall semantics: ctx.waterfall listeners receive (...args, next); call next() to delegate, or return without it to short-circuit (veto). Never call next() after returning.
  • Plugin export shape — namespace OR default, never both. A service package exports the service class as export default (the Loader instantiates it). A function/namespace plugin exports name / inject / Config / apply as separate named exports and must NOT add export default — the cordis Loader's unwrapExports does exports.default ?? exports, so a stray default export collapses the module to the bare apply function and silently discards the inject/name/Config namespace, leaving the plugin with no injected services (it then throws cannot get property … without inject at load). See docs/postmortem/0001.
  • Read an optional (non-injected) service via ctx.get(name), not ctx.<name>. For a service a plugin reads opportunistically but deliberately leaves out of static inject (e.g. AgentLoop reading sessionPersistence), the ctx.<name> property proxy resolves by an ancestor-only fiber walk that throws when the call arrives through a foreign traceable shadow (the service lives on a sibling fiber). ctx.get(name) is the topology-independent global-store lookup, strict by default (an inactive/absent backend reads as undefined — prefer it over the ctx.get(name, false) overload, which also skips the active-state check). Services that ARE in static inject resolve fine via ctx.<name>. See docs/postmortem/0001.
  • Tests: vitest in packages/<name>/tests/*.spec.ts. Every registry needs an HMR-safety test (register a plugin, dispose its fiber, assert cleanup). Err on the side of more tests — edge cases, error paths, event ordering, races. A plugin shipped via cordis.yml also needs at least one test that drives it through the REAL Loader/export path (hand-built ctx.plugin({...}) mounts bypass unwrapExports and cannot catch a broken export shape) — see AGENTS.md § Defensive patterns "Line coverage is not behavior coverage". Real-API (with-key) e2e tests are cheap here (we are DeepSeek) and welcome — write many, especially smoke tests; see AGENTS.md § Secrets / .env.

Naming notes:

  • A service src/index.ts exports the service class as export default + all public types; a function/namespace plugin src/index.ts exports name/inject/Config/apply as named exports and NO default (see the plugin-export-shape rule above)
  • src/types.ts contain only types — no runtime code
  • Tests live at package level under tests/, not src/__tests__/
  • A package's README and module/JSDoc comments are part of the change: when you alter behavior (config keys, defaults, error codes, wire fields), update them in the same commit. CI runs pnpm run doc-sync, which typechecks fenced ts blocks in packages/*/README.md, verifies the event-taxonomy table, and checks markdown wrapping across this file too — but it does NOT catch prose drift (config keys, defaults, error codes), so those stay on the author.

Read the per-package README.md for package-specific details: service API, events, extension points, TODOs.