Files
deepseek-harness/packages/agent/src/index.ts
T
Tianyi Cui cb6bee3d03 Add ESLint: typescript-eslint strict-type-checked + stylistic formatting
Flat config with two layers. Correctness (type-checked): the headline
rules for this codebase are no-floating-promises / no-misused-promises
(a lost promise in the agent loop is our primary bug class),
switch-exhaustiveness-check (we switch over merge-extensible unions
everywhere), no-unnecessary-condition, require-await, and
no-explicit-any. Style (@stylistic): 2-space, no semicolons, single
quotes, trailing commas, max-len 140 — the existing house style, now
enforced instead of drifting between agents. vendor/ is excluded
(vendored source keeps upstream style); tests relax the rules that
fight test ergonomics (non-null assertions after expects, async mock
signatures, non-Error throws).

Code adjusted to pass: registry disposers wrap ctx.effect's
promise-returning disposer behind a sync () => void (our public API),
BlockAssembler gains an invariant-checking mustGet instead of non-null
assertions, lastTurnNumber uses findLast, waterfall tails return
Promise.resolve instead of async-without-await arrows, and the two
deliberate suppressions (non-exhaustive derivation switch, unbound
execute pass-through) carry justification comments.

yarn lint / yarn lint:fix added.
2026-06-11 14:17:58 +08:00

64 lines
1.8 KiB
TypeScript

/**
* Agent registry service. Tracks live agents so plugins can find them without
* depending on the concrete loop package. Agent creation belongs to the loop.
*
* @module @deepseek-ai/dsh-agent
*/
import { Context, Service } from 'cordis'
import type { Agent } from './types.ts'
export * from './types.ts'
declare module 'cordis' {
interface Context {
agents: AgentRegistry
}
}
/**
* Agent registry (`ctx.agents`): tracks live agents so UI, hook, and
* orchestrator plugins can find them without depending on the concrete loop
* package. Agent *creation* belongs to whichever plugin implements the Agent
* interface (phase 1: `@deepseek-ai/dsh-agent-loop`).
*/
export class AgentRegistry extends Service {
private store = new Map<string, Agent>()
constructor(ctx: Context) {
super(ctx, 'agents')
}
/**
* Register a live agent. Throws if an agent with the same id is already
* registered. Emits `agent/created` on registration and `agent/disposed`
* when the calling fiber is disposed. Returns the disposer.
*/
register(agent: Agent): () => void {
const dispose = this.ctx.effect(() => {
if (this.store.has(agent.id)) {
throw new Error(`agent "${agent.id}" is already registered`)
}
this.store.set(agent.id, agent)
this.ctx.emit('agent/created', agent)
return () => {
this.store.delete(agent.id)
this.ctx.emit('agent/disposed', agent)
}
}, 'agents.register()')
// ctx.effect's disposer returns Promise<void>; our disposer API is
// synchronous fire-and-forget — discard the (always-resolved) promise.
return () => void dispose()
}
get(id: string): Agent | undefined {
return this.store.get(id)
}
list(): Agent[] {
return [...this.store.values()]
}
}
export default AgentRegistry