feat(apes-sidebar): Phase 0 工程骨架 — host/client 分裂 + i18n + 不变量面

从 0 重写的 v2.0.0 DSH 侧边栏插件第一阶段:
- 工程骨架:tsdown 双构建(host ESM + client CJS closure)、vitest、strict TS
- host 半:namespace 插件形状(name/inject/apply,无 default),minimal apply
- client 半:closure factory 注册 5 个 apes:* tab 占位 + zh/en i18n
- 不变量面:assert/semver/peer guard(dsh-better-sidebar >= 0.18.0)
- 依赖策略:dsh-better-sidebar=peerDependency(不打包),cordis=devDependency(仅类型)
- 门禁:pnpm typecheck + build + test 全绿(14 单测通过)
- AGENTS.md 工程约定 + .gitignore(排除 lib/node_modules/maxkb-integration)

模板:dsh-better-sidebar。作用域锁定信息层(tasks/agents/apps/messages/profile),
不含 file/terminal/browser(归 better-sidebar)、不含 team/room(归 background-agents)。
This commit is contained in:
figmar
2026-09-04 17:14:22 +08:00
parent c319fb106e
commit 0e91e99f49
19 changed files with 5838 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
/**
* Structural types for the cordis services apes-sidebar consumes, plus the
* Context face both halves share.
*
* The type base is the `@deepseek-ai/cordis` Context; the service members
* this plugin touches are restated below as structural mirrors and combined
* with the base by INTERSECTION. Intersection (not `declare module`
* augmentation) is deliberate: DSH's own packages already augment
* `@deepseek-ai/cordis` and the host and client packages declare *different*
* types for the same member — intersecting keeps every face available.
*
* This file must stay FREE of Node.js types: it is part of the
* CLIENT-reachable declaration graph (the `Context` in TabComponentProps and
* the `apesSidebar` augmentation), so a Node import here would leak into
* browser-only consumer builds.
*/
import type { Context as CordisContext } from '@deepseek-ai/cordis'
/** One session row the sidebar reads (client sessions list feed). */
export interface ApesSessionSummary {
id: string
cwd?: string
displayTitle: string
/** Coarse durable origin for navigation filtering (subagent children). */
origin?: 'subagent'
/** Durable direct parent session id (present on subagent children). */
parentId?: string
/** Whether the session's agent is currently running. */
running?: boolean
}
/** The client session list snapshot the sidebar subscribes to. */
export interface ApesSessionList {
current: string | undefined
byId: Record<string, ApesSessionSummary>
}
/** The client sessions service face (only the list feed + open are needed). */
export interface ApesSessionsService {
list: {
getSnapshot(): ApesSessionList
subscribe(fn: () => void): () => void
}
/** Select a listed session as current. */
open?(id: string): void
}
/** The client locale service face (mirror of dsh-client-locale's LocaleRuntime). */
export interface ApesLocaleService {
getSnapshot(): { active: string }
subscribe(fn: () => void): () => void
register(ns: string, locale: string, dict: Record<string, string>): () => void
}
/** The client slots service face (register returns the disposer). */
export interface ApesSlotsService {
register(options: Record<string, unknown>, component: unknown): () => void
inject(key: string, callback: () => () => void): () => void
}
/**
* The `ctx.apesSidebar` service face: a registry that lets apes-sidebar
* (and future sibling plugins) contribute sidebar tab descriptors to the
* better-sidebar host. For Phase 0 this is a minimal self-registry; in
* Phase 1 it is re-exported from the better-sidebar `BetterSidebarService`
* (type-only import through `dsh-better-sidebar/client/service`).
*
* NOTE: this is a PLACEHOLDER contract for the skeleton. Phase 1 replaces
* it with the real better-sidebar `BetterSidebarService` + `TabDescriptor`
* types (type-only imports, which are erased and never reach the bundle
* purity gate).
*/
export interface ApesSidebarTabDescriptor {
/** Unique id; also the `SidebarTab.type` value (`'apes:tasks'`, …). */
id: string
title: string | (() => string)
icon?: unknown
/** + menu sort order (ascending); default 100. */
order?: number
hidden?: boolean
/** Single-instance sugar. */
single?: boolean
/** Tab-strip badge: a number renders as a count, a string as-is. */
badge?: (ctx: ApesContext, state: unknown) => string | number | null | undefined
component: (props: Record<string, unknown>) => unknown
}
/** The `ctx.apesSidebar` service face (Phase 0 placeholder). */
export interface ApesSidebarService {
registerTab(descriptor: ApesSidebarTabDescriptor): () => void
getTabs(): readonly ApesSidebarTabDescriptor[]
subscribe(listener: () => void): () => void
}
/**
* The Context face this plugin's code resolves against. Intersecting the
* cordis base with the structural service mirrors keeps every face
* available without module-level conflicts.
*/
export type ApesContext = CordisContext & {
/** The client-side registry (client half only). */
apesSidebar?: ApesSidebarService
/** The client sessions feed (client half only). */
sessions?: ApesSessionsService
/** The client locale runtime (client half only). */
locale?: ApesLocaleService
/** The client slots service (client half only). */
slots?: ApesSlotsService
}