From 75ea5899769e46daf0091aeb0220d8d13a5ed554 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:15:35 +0800 Subject: [PATCH] feat: single-source projection keys via domain ./client/types pure outlets --- .../session-title/session-title/package.json | 5 ++++ .../session-title/src/client/types.ts | 25 +++++++++++++++++++ .../session-title/session-title/src/index.ts | 17 +++++-------- packages/todo/tool-todo/package.json | 5 ++++ packages/todo/tool-todo/src/client/types.ts | 24 ++++++++++++++++++ packages/todo/tool-todo/src/index.ts | 17 +++++-------- tsconfig.base.json | 2 ++ 7 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 packages/session-title/session-title/src/client/types.ts create mode 100644 packages/todo/tool-todo/src/client/types.ts diff --git a/packages/session-title/session-title/package.json b/packages/session-title/session-title/package.json index 8ab2b3a880..7377c6b25a 100644 --- a/packages/session-title/session-title/package.json +++ b/packages/session-title/session-title/package.json @@ -15,12 +15,17 @@ "types": "./lib/types/invariant.d.ts", "default": "./lib/invariant.js" }, + "./client/types": { + "types": "./lib/types/client/types.d.ts", + "default": "./lib/types/client/types.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", "lib/invariant.js", + "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" diff --git a/packages/session-title/session-title/src/client/types.ts b/packages/session-title/session-title/src/client/types.ts new file mode 100644 index 0000000000..62ca756fff --- /dev/null +++ b/packages/session-title/session-title/src/client/types.ts @@ -0,0 +1,25 @@ +/** + * Pure-type client outlet of the title domain: the ONE home of the `title` + * projection-key declaration, importable from client aggregates without + * dragging this package's host-side value imports (cordis service, + * schemastery, the llm seam). The host entry (`index.ts`) imports this module + * type-only to reuse the same merge — one declaration serves both program + * sides. + * + * @module @deepseek-ai/dsh-session-title/client/types + */ + +// Marks this file a module so the declaration below AUGMENTS the projection +// table instead of declaring an ambient module. +export {} + +declare module '@deepseek-ai/dsh-session-projection/types' { + interface SessionProjectionMap { + /** + * The session's current normalized title — the latest `session/title` + * event's text (last-wins), or `null` before the first title lands. A + * plain string: the shape the client list rows consume. + */ + title: string | null + } +} diff --git a/packages/session-title/session-title/src/index.ts b/packages/session-title/session-title/src/index.ts index 51074749fb..ea5020b1a2 100644 --- a/packages/session-title/session-title/src/index.ts +++ b/packages/session-title/session-title/src/index.ts @@ -17,6 +17,12 @@ import type { } from '@deepseek-ai/dsh-session' // Type-only: resolves ctx.sessionProjections for the optional unit child. import type {} from '@deepseek-ai/dsh-session-projection' +// The `title` projection-key declaration lives in the client outlet (its one +// home). A PLAIN side-effect import, not `import type`: declaration emit +// elides type-only imports, and the aggregate programs resolve this package +// through its emitted declarations — the merge must survive in index.d.ts. +// The imported module is types-only, so the runtime edge is an empty module. +import './client/types.ts' import { fallbackSessionTitle, normalizeSessionTitle } from './normalize.ts' export { fallbackSessionTitle, normalizeSessionTitle, truncateTitleUtf8 } from './normalize.ts' @@ -103,17 +109,6 @@ declare module '@deepseek-ai/dsh-session' { } } -declare module '@deepseek-ai/dsh-session-projection/types' { - interface SessionProjectionMap { - /** - * The session's current normalized title — the latest `session/title` - * event's text (last-wins), or `null` before the first title lands. A - * plain string: the shape the client list rows consume. - */ - title: string | null - } -} - /** Per-session settlement tails for title-capability out-of-band writes. */ const SESSION_TITLE_WRITE_TAILS = new WeakMap>() diff --git a/packages/todo/tool-todo/package.json b/packages/todo/tool-todo/package.json index 66d3e66add..8c6d9b1a1b 100644 --- a/packages/todo/tool-todo/package.json +++ b/packages/todo/tool-todo/package.json @@ -15,12 +15,17 @@ "types": "./lib/types/invariant.d.ts", "default": "./lib/invariant.js" }, + "./client/types": { + "types": "./lib/types/client/types.d.ts", + "default": "./lib/types/client/types.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", "lib/invariant.js", + "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" diff --git a/packages/todo/tool-todo/src/client/types.ts b/packages/todo/tool-todo/src/client/types.ts new file mode 100644 index 0000000000..192f0a3adc --- /dev/null +++ b/packages/todo/tool-todo/src/client/types.ts @@ -0,0 +1,24 @@ +/** + * Pure-type client outlet of the todo domain: the ONE home of the `todos` + * projection-key declaration, importable from client aggregates without + * dragging this package's host-side value imports (dsh-tools, zod). The host + * entry (`index.ts`) imports this module type-only to reuse the same merge — + * one declaration serves both program sides. + * + * @module @deepseek-ai/dsh-tool-todo/client/types + */ + +import type { TodoItem } from '@deepseek-ai/dsh-session/types' + +export type { TodoItem } from '@deepseek-ai/dsh-session/types' + +declare module '@deepseek-ai/dsh-session-projection/types' { + interface SessionProjectionMap { + /** + * The agent's current whole todo list (the latest `todo/write` snapshot), + * or `null` before the first write. Whole-value rule: every `todo/write` + * carries the complete replacement list, so the fold is last-wins. + */ + todos: TodoItem[] | null + } +} diff --git a/packages/todo/tool-todo/src/index.ts b/packages/todo/tool-todo/src/index.ts index abdf006daf..68bab005c5 100644 --- a/packages/todo/tool-todo/src/index.ts +++ b/packages/todo/tool-todo/src/index.ts @@ -12,17 +12,12 @@ import { defineTool } from '@deepseek-ai/dsh-tools' import type { TodoItem } from '@deepseek-ai/dsh-session' // Type-only: resolves ctx.sessionProjections for the optional unit child. import type {} from '@deepseek-ai/dsh-session-projection' - -declare module '@deepseek-ai/dsh-session-projection/types' { - interface SessionProjectionMap { - /** - * The agent's current whole todo list (the latest `todo/write` snapshot), - * or `null` before the first write. Whole-value rule: every `todo/write` - * carries the complete replacement list, so the fold is last-wins. - */ - todos: TodoItem[] | null - } -} +// The `todos` projection-key declaration lives in the client outlet (its one +// home). A PLAIN side-effect import, not `import type`: declaration emit +// elides type-only imports, and the aggregate programs resolve this package +// through its emitted declarations — the merge must survive in index.d.ts. +// The imported module is types-only, so the runtime edge is an empty module. +import './client/types.ts' export const name = 'tool-todo' export const inject = ['tools'] diff --git a/tsconfig.base.json b/tsconfig.base.json index f2f42116be..9325c5af4e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -42,6 +42,8 @@ "@deepseek-ai/dsh-session/types": ["./packages/core/session/src/types.ts"], "@deepseek-ai/dsh-session/surface": ["./packages/core/session/src/surface.ts"], "@deepseek-ai/dsh-session-projection/types": ["./packages/session-projection/session-projection/src/types.ts"], + "@deepseek-ai/dsh-tool-todo/client/types": ["./packages/todo/tool-todo/src/client/types.ts"], + "@deepseek-ai/dsh-session-title/client/types": ["./packages/session-title/session-title/src/client/types.ts"], "@deepseek-ai/dsh-llm/types": ["./packages/llm/llm/src/types.ts"], "@deepseek-ai/dsh-llm/brand": ["./packages/llm/llm/src/brand.ts"], "@deepseek-ai/dsh-tools/presentation": ["./packages/core/tools/src/presentation.ts"],