Files
deepseek-harness/packages/AGENTS.md
T
Tianyi Cui 7c168fca34 fix(acp): address Codex review — strict ctx.get, correct fiber-ownership doc + test
- AgentLoop.resume uses `this.ctx.get('sessionPersistence')` (strict) instead
  of the `, false` overload: still topology-independent, but an inactive/
  absent backend reads as undefined (rejected by the existing guard) rather
  than being handed back mid-teardown.
- Correct the bridge teardown comment: an ACP-created agent's registry entry
  binds to the BRIDGE fiber (the factory is reached through the bridge's
  traceable proxy, so AgentLoop.start's `this.ctx.effect` registration uses the
  caller context), not the AgentLoop fiber — so an ACP-only HMR dispose
  reclaims it. Add a regression test pinning that ownership.
- Sync the ctx.get guidance in the post-mortem, packages/AGENTS.md, and the
  dsh-code-review skill to the strict form.
2026-06-18 03:48:33 +08:00

3.9 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 and verifies the event-taxonomy table — but it does NOT cover this file or 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.