This directory contains all @deepseek-ai/dsh-* harness packages. When editing code here, follow these conventions:
ctx.effect() / ctx.on(), and register() methods return disposers. Never use bare arrays or manual cleanup.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.ctx.waterfall listeners receive (...args, next); call next() to delegate, or return without it to short-circuit (veto). Never call next() after returning.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.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.packages/<group>/<pkg>/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:
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 codetests/, not src/__tests__/pnpm run doc-sync, which typechecks fenced ts blocks in packages/*/*.md and packages/*/*/*.md, regenerates the cordis events/services catalog from the interface Events / interface Context declarations (failing if the committed copy is stale), 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. A new event needs an @mode tag on its JSDoc (the catalog generator hard-errors without it — see the root AGENTS.md).Read the per-package README.md for package-specific details: service API, events, extension points, TODOs.