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.
This commit is contained in:
Tianyi Cui
2026-07-26 07:25:47 +08:00
parent 71c564d801
commit 3b91135923
6 changed files with 18 additions and 5 deletions

View File

@@ -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')
}

View File

@@ -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/)
})
})