From 3b911359232d784ca336c07d54b1bb7c2e893d66 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 26 Jul 2026 07:25:47 +0800 Subject: [PATCH] fix(tasks): fail loud when the abstract seam is mounted directly Review finding (Codex round 1): abstract erases at runtime and @deepseek-ai/dsh-tasks used to be the mountable registry, so a stale composition row would register a ctx.tasks with no method implementations and fail far from the misconfiguration. The seam constructor now rejects direct mounts with a load-time pointer at dsh-tasks-local; the seam suite pins the fence, the Agent Note cost paragraph records the actual behavior, and the stale tool-pty README requirement line names the implementation package. --- .../architecture/2026-07-26-task-registry-seam.i18n.yaml | 4 ++-- .../architecture/2026-07-26-task-registry-seam.md | 2 +- .../architecture/2026-07-26-task-registry-seam.zh.md | 2 +- packages/pty/tool-pty/README.md | 2 +- packages/tasks/tasks/src/index.ts | 7 +++++++ packages/tasks/tasks/tests/service.spec.ts | 6 ++++++ 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml index 409bc30c12..530e12edae 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-26-task-registry-seam.md: b785eb75a632503def10fad583f6a68477cffef6 -2026-07-26-task-registry-seam.zh.md: bfb733a5e1060c9bfe2acc6c4769aa47443d0c9e +2026-07-26-task-registry-seam.md: d550b5b081a7980cceddd3c1eb65c3a9a175906f +2026-07-26-task-registry-seam.zh.md: 1088465b908fd905900aa11479a48632fff3fe6f diff --git a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md index b785eb75a6..d550b5b081 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md +++ b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md @@ -32,4 +32,4 @@ The seam keeps the in-process contract semantics unchanged: `TaskStart.run()` st Bought: the task registry now matches the repository-wide seam shape; a durable, remote, or instrumented registry is a sibling package implementing eight abstract methods, and no producer, control surface, or `TaskKindMap` extender changes when one lands. The seam README states the contract; the implementation README owns the lifecycle bookkeeping facts. The registry behavior suite (owner cleanup, settlement, waits, teardown) lives with `dsh-tasks-local`; the seam keeps a stub-subclass test pinning registration under `ctx.tasks` and single-service duplication behavior, plus the probe-based invariant suite. -Cost: one more package (manifest, tsconfig, README, invariant companion), and compositions must name the implementation package — a boot that loads only `@deepseek-ai/dsh-tasks` gets a pending `ctx.tasks` and producers fail with the standard missing-service behavior rather than a bespoke message. The misconfiguration diagnostics naming `dsh-tasks-local` accept staleness if a different backend becomes the recommended default. +Cost: one more package (manifest, tsconfig, README, invariant companion), and compositions must name the implementation package. `abstract` erases at runtime and this package name used to be the mountable registry, so the seam constructor fails loudly when mounted directly — a stale composition row gets "load an implementation such as @deepseek-ai/dsh-tasks-local" at load time instead of a half-registered `ctx.tasks` failing far from the misconfiguration. The misconfiguration diagnostics naming `dsh-tasks-local` accept staleness if a different backend becomes the recommended default. diff --git a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md index bfb733a5e1..1088465b90 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md @@ -32,4 +32,4 @@ Status: implemented 换来的是:任务注册表如今与全仓库通行的 seam 形态一致;持久化、远程或带插桩的注册表将是一个实现八个抽象方法的兄弟包,这样的注册表落地时,任何生产方、控制接口或 `TaskKindMap` 扩展方都无需改动。seam 包的 README 陈述契约;生命周期簿记方面的事实归实现包的 README 所有。注册表行为测试套件(所有者清理、结算、等待、拆除)随 `dsh-tasks-local` 存放;seam 包保留一个桩子类(stub subclass)测试,固定 `ctx.tasks` 下的注册行为与单一服务的重复注册行为,外加基于探针的不变式测试套件。 -代价是:多出一个包,即多一份 manifest(元数据清单)、tsconfig、README 与不变式配套插件;同时各组合必须点名实现包。若某次启动只加载 `@deepseek-ai/dsh-tasks`,`ctx.tasks` 将保持挂起,生产方会按标准的服务缺失行为失败,而不会收到一条专门定制的消息。若日后另一个后端成为推荐的默认选择,点名 `dsh-tasks-local` 的配置错误诊断信息将随之陈旧;这一点已被接受。 +代价是:多出一个包,即多一份 manifest(元数据清单)、tsconfig、README 与不变式配套插件;同时各组合必须点名实现包。`abstract` 在运行时会被擦除,而这个包名过去正是可挂载的具体注册表,因此 seam 的构造函数在被直接挂载时会响亮失败——一条过期的组合配置行会在加载时得到「load an implementation such as @deepseek-ai/dsh-tasks-local」,而不是一个方法残缺的 `ctx.tasks` 在远离错误配置处才失败。若日后另一个后端成为推荐的默认选择,点名 `dsh-tasks-local` 的配置错误诊断信息将随之陈旧;这一点已被接受。 diff --git a/packages/pty/tool-pty/README.md b/packages/pty/tool-pty/README.md index f4f1e7af7e..b16cb271f1 100644 --- a/packages/pty/tool-pty/README.md +++ b/packages/pty/tool-pty/README.md @@ -66,4 +66,4 @@ Append-only; new results follow the reusable request prefix. ## Known Limitations and Deferred Work - No named key sequence, TUI, BEL, resize, auto-start, or cross-agent sharing schema is exposed. -- Background mode requires both `@deepseek-ai/dsh-tasks` and its model-facing control surface. +- Background mode requires both `@deepseek-ai/dsh-tasks-local` and the model-facing control surface from `@deepseek-ai/dsh-tool-tasks`. diff --git a/packages/tasks/tasks/src/index.ts b/packages/tasks/tasks/src/index.ts index 17e617e8a7..e237aa0681 100644 --- a/packages/tasks/tasks/src/index.ts +++ b/packages/tasks/tasks/src/index.ts @@ -49,6 +49,13 @@ declare module 'cordis' { */ export abstract class TaskService extends Service { constructor(ctx: Context) { + // `abstract` erases at runtime, and this package name used to be the + // mountable concrete registry — a stale composition row would otherwise + // register a ctx.tasks with no method implementations and fail far from + // the misconfiguration. Fail loud at load instead. + if (new.target === TaskService) { + throw new Error('@deepseek-ai/dsh-tasks is the abstract task registry seam; load an implementation such as @deepseek-ai/dsh-tasks-local instead') + } super(ctx, 'tasks') } diff --git a/packages/tasks/tasks/tests/service.spec.ts b/packages/tasks/tasks/tests/service.spec.ts index d8d582e410..82fc415f51 100644 --- a/packages/tasks/tasks/tests/service.spec.ts +++ b/packages/tasks/tasks/tests/service.spec.ts @@ -79,4 +79,10 @@ describe('TaskService seam', () => { class SecondTaskService extends StubTaskService {} await expect(ctx.plugin(SecondTaskService)).rejects.toThrow(/service "tasks" has been registered/) }) + + it('mounting the abstract seam directly fails loudly at load (stale-composition fence)', async () => { + const ctx = new Context() + await expect(ctx.plugin(TaskService as unknown as typeof StubTaskService)) + .rejects.toThrow(/abstract task registry seam; load an implementation such as @deepseek-ai\/dsh-tasks-local/) + }) })