Files
dsh-context/tests/baselines.ts
T
bowenliang123 9873eb5836 fix(client): mount the client bundle once per page (issue #137)
The same package can mount twice into one page's slot ledger — a static
profile bundle plus a later activation of the same package. The
client-plugin runner force-assigns a fresh shadowing priority to every
registration, so the registry's same-id duplicate guard never fires across
the two mounts, and the conversation header — which renders the raw ledger
and matches the active view by entry id — paints both copies of every
list-slot seat.

apply() now reads the raw ledger first (SlotRegistry.entries) and mounts
nothing when this bundle's chat-to-Context jump entry is already seated.
The read fails open: an absent, shape-shifted, or hostile face reports
unseated and the mount proceeds, so an older line never blanks a working
mount. The entries seam is pinned per baseline.

The harness-side defects (the runner forcing a rank on additive list
slots, and the header rendering the raw ledger) are left to
deepseek-ai/deepseek-harness.
2026-10-12 08:39:17 +08:00

339 lines
16 KiB
TypeScript

/**
* The supported harness compatibility matrix — the ONE source of truth for
* which dsh baselines this plugin must work on, and how their seams differ.
*
* Consumed by:
* - tests/host/compat/ (always-on registry-contract drivers, vitest host lane)
* - tests/client/compat/ (always-on client-face matrix, vitest jsdom lane)
* - tests/compat/ (the `compat` vitest project: the real-code matrix boots
* the REAL dsh registry + settings + platform-table sources per baseline
* tag and runs the built plugin through them; plus the bundle smoke)
*
* Adding a future harness version = adding one entry here (its tag, its
* cordis, and a face for every seam that differs), then running the matrix.
* A failing probe names the seam — the connection point to re-fit or
* refactor — not just "it broke somewhere".
*/
/** The supported dsh tags, in lockstep with the BASELINES entries below. */
export type BaselineId = 'v0.1.7-rc.2' | 'v0.2.0-rc.2'
/** The harness web half's client faces, as far as the compat probes consume them. */
export interface ClientSeam {
/** The durable-image loader method the browser cards ride. */
imageFaceMethod: string
/** MarkdownText's chrome prop the plugin must hand every markdown render. */
markdownChrome: string
/** The platform module table the shell seeds (client-bundle requires must resolve). */
platformModules: readonly string[]
/** The faces the split timeline's on-demand detail channel rides
* (host/detail.ts + client/timelineSource.ts): the host's generic
* Connection RPC registry, the browser caller, and the projection
* registry's `stateOf` read. Each probe is a needle in the named source file of the baseline tag. */
detailChannel: {
/** `HostConnectionRpc` in the connection package's shared rpc source. */
hostRpcFile: string
hostRpcNeedle: string
/** The browser caller's `call(channel, endpoint, payload…)` in the connection client source. */
clientRpcFile: string
clientRpcNeedle: string
/** The registry's `stateOf` unit-state read (the detail endpoint's data source). */
registryFile: string
registryNeedle: string
}
/** The session-jump seam (the Context Dashboard's session cards and the
* Agent network card's nodes — issue #90). Every supported line selects a
* session through the view owner's `uiWorkspace.openSession` (the sidebar
* row click's own verb), re-spelled `openSession(target: SessionTarget)` by
* the 0.1.6 selection refactor, which also retired the sessions service's
* own `open(id)` spelling — absent on every supported line, probed so a
* resurrection names the seam instead of silently dead-ending the jump. */
sessionNav: {
/** The view-owner navigation verb's source, plus this line's signature spelling. */
workspaceFile: string
workspaceNeedle: string
/** The sessions contract source, plus whether this line still declares `open(id: SessionId)`. */
sessionsFile: string
sessionsOpen: boolean
}
/** The Context Insights page's first-level panel seam: the layout's keyed
* `main` slot (the page's seat — also the conversation panel's own) and
* the sidebar's `sidebar.panellist` list (the entry's seat), addressed by
* one shared id. Both spellings shipped with the sidebar/layout split and
* are present on every supported line; probed presence-as-declared so a
* rename names the seam instead of silently orphaning the page. */
insightPage: {
/** The panellist contract's declaring source, plus the slot's spelling in it. */
panellistFile: string
panellistNeedle: string
/** The layout's main-slot declaring source, plus the keyed declaration's spelling. */
mainFile: string
mainNeedle: string
}
/** The slot-ledger read the client's second-mount guard rides
* (`entrySeated` in src/client/services.ts, dsh-context#137): the
* `slots` service's raw `entries` view — the same view the session header
* renders — present on every supported line under the shared projection-
* registry contract; probed on the SERVICE that carries the call (the
* renderer's `SlotRegistry`, whose facade can drop it independently of the
* core's `SlotCore.entries`) so a rename names the seam instead of silently
* disabling the guard. */
ledger: {
/** The registry source declaring the entries view the plugin calls. */
file: string
/** The entries view's signature spelling in it. */
needle: string
}
/** The right Sidebar's tab seam — every supported generation ships it. The
* plugin's registration stays an OPTIONAL deferred inject: a below-baseline
* host (the gate's fallback composition) may lack the service entirely, and
* the client must simply never register the tab there instead of pending. */
sidebar: {
/** The tab-type registry service's providing source. */
serviceFile: string
serviceNeedle: string
/** The keyed body seat's declaring source. */
slotFile: string
slotNeedle: string
/** The keyed chip-title seat the plugin's tab type also registers into
* (the emblem-beside-label idiom the shipped files type uses). Present
* wherever the body seat is; probed separately so a rename names the seam. */
titleSlotNeedle: string
/** The navigation face the plugin's file-open affordance rides
* (`ctx.sidebarRight.openResource`): the controller's providing source and
* the needle in it. Same package as the registry, so present wherever the
* tab seam is; probed so a rename names the preview seam instead of silently degrading the affordance. */
nav: {
/** The controller source (the public navigation face's implementation). */
file: string
needle: string
}
/** The guide-entry contract the plugin's contribution must satisfy: one
* needle per field `SidebarRightGuideEntry` carries on this generation. */
guideEntry: {
/** The declaring source. */
file: string
fields: readonly string[]
}
}
}
export interface Baseline {
/** The dsh git tag in deepseek-ai/deepseek-harness (local checkout or CI fetch). */
id: BaselineId
tag: string
cordis: string
/** The @deepseek-ai/dsh-session release that line vendors. The staged
* session-projection sources import runtime values from it (SessionLogOffset /
* SessionSeq), so the compat driver must resolve the specifier to the tag's own generation. */
session: string
/** The durable-event families THIS line's log carries that the host fold
* switches on — the probe asserts every one exists in the tag's
* `KNOWN_SESSION_EVENT_TYPES`. Every supported line is the V4 generation,
* so the lists match; the matrix also reads the fold's own `case` labels out
* of `src/host/fold.ts` and asserts each is declared here, so a fold case
* added without a baseline list fails loudly instead of going unprobed. */
foldEventTypes: readonly string[]
client: ClientSeam
/** The step-boundary identity guard's host seam (src/host/stepIdentity.ts):
* the agent-loop file that dispatches the `agent/pre-step` waterfall and
* appends its decision's messages, plus a shipped context plugin proving
* the `prepend` listener option this guard rides on that line. */
stepGuard: {
loopFile: string
loopNeedles: readonly string[]
prependProofFile: string
}
/** The tag's settings-namespace surface. Every supported line is the V4+
* Config-form generation: configuration forms derive from each loader
* entry's own Config schema — the `settings.register` host face is gone
* (probed absent, so the plugin never calls it), and the browser half rides
* the `configForms` service and the Plugins page's keyed `plugins.bundle.config` seat. */
settings: {
serviceFile: string
register: boolean
/** The browser slot the preferences card registers on this generation. */
cardSlot: string
cardSlotFiles: readonly string[]
transport: 'configForms'
/** The transport's declaring source, plus whether it exists. */
transportFile: string
transportPresent: boolean
}
/** The balance capsule's host seam (src/host/balance.ts). The facts fold
* reads the DeepSeek API-key provider's settings row off the Config-form
* `describe()` projection — the provider row is the entry whose served
* value declares the top-level `apiKeyEnv` credential ref, preferred under
* the settings ids the generations have served it as. The retired
* registered-section `get(ns)` read is probed absent, so a resurrection
* names the seam instead of silently shadowing the projection. */
balance: {
settingsFile: string
/** The retired `get(ns)` section read — absent on every supported line. */
settingsGetNeedle: string
settingsGetPresent: boolean
/** The Config-form projection needle the fold reads. */
settingsDescribeNeedle: string | undefined
/** The provider sources carrying the plugin id and the section shape the fold matches. */
providerFiles: readonly { file: string; needles: readonly string[] }[]
}
/** The system-prompt injection seam (src/host/promptInjection.ts): the
* prompt-registry service must expose the ordered-section face and the
* centrally owned order anchors the plugin reads, and the loader must
* commit volatile-only config edits into the running references, so a
* preference edit applies from the next model step without remounting. */
prompt: {
serviceFile: string
sectionNeedle: string
orderAnchors: readonly string[]
loaderVolatileFile: string
loaderVolatileNeedle: string
}
}
/** One support-matrix entry. Every supported line is the V4 generation, so
* the seams are spelled once here; a future line that diverges on a needle outgrows the factory (inline its entry instead). */
function baseline(id: BaselineId, tag: string, session: string): Baseline {
return {
id,
tag,
cordis: '4.0.4',
session,
foldEventTypes: [
'request/header', 'request/context', 'step/start', 'step/end',
'user/message', 'developer/message', 'tool/call', 'tool/result', 'assistant/message', 'assistant/attempt',
'tool/ptc-dispatch',
'plan/mode', 'compaction/summary', 'compaction/prune', 'system/message',
'approval/asked', 'approval/decided', 'session/end-seed',
],
client: {
imageFaceMethod: 'imageUrl',
markdownChrome: 'labels',
platformModules: [
'react', 'react/jsx-runtime', 'react-dom', 'react-dom/client', '@deepseek-ai/cordis',
'@deepseek-ai/dsh-client-store',
'@deepseek-ai/dsh-client-ui-slots',
'@deepseek-ai/dsh-client-ui-primitives',
'@deepseek-ai/dsh-client-ui-dockkit',
],
detailChannel: {
hostRpcFile: 'packages/client/connection/src/rpc.ts',
hostRpcNeedle: 'HostConnectionRpc',
clientRpcFile: 'packages/client/connection/src/client/rpc.ts',
clientRpcNeedle: 'call(channel, endpoint, payload',
registryFile: 'packages/session/session-projection/src/index.ts',
registryNeedle: 'stateOf<',
},
sidebar: {
serviceFile: 'packages/client/ui-sidebar-right/src/client/index.ts',
serviceNeedle: 'sidebarRightTabs',
slotFile: 'packages/client/ui-sidebar-right/src/client/contract/slots.ts',
slotNeedle: 'sidebar.right.pane.tab',
titleSlotNeedle: 'sidebar.right.pane.tab.title',
nav: {
file: 'packages/client/ui-sidebar-right/src/client/service.ts',
needle: 'openResource(address',
},
guideEntry: {
file: 'packages/client/ui-sidebar-right/src/client/tab-registry.ts',
fields: [
'readonly order: number',
'readonly title: () => string',
'readonly description?: () => string',
'readonly icon?: ComponentType<IconProps>',
],
},
},
sessionNav: {
workspaceFile: 'packages/client/ui-workspace/src/client/navigation.ts',
workspaceNeedle: 'openSession(target: SessionTarget): void',
sessionsFile: 'packages/api/session-controller/src/client/contract/sessions.ts',
sessionsOpen: false,
},
insightPage: {
panellistFile: 'packages/client/ui-sidebar/src/client/contract/slots.ts',
panellistNeedle: "'sidebar.panellist'",
mainFile: 'packages/client/ui-layout/src/client/index.ts',
mainNeedle: "'main': { kind: 'keyed'",
},
ledger: {
file: 'packages/client/ui-renderer/src/client/registry.ts',
// Truncated before the parameter list: the probe greps the tag's
// sources as basic regexes, where `(` and `[` open groups/classes.
needle: 'entries(key: keyof SlotMap',
},
},
settings: {
serviceFile: 'packages/settings/settings/src/index.ts',
register: false,
cardSlot: 'plugins.bundle.config',
cardSlotFiles: ['packages/client/ui-plugin-manager/src/**'],
transport: 'configForms',
transportFile: 'packages/client/ui-settings/src/client/config-form.ts',
transportPresent: true,
},
balance: {
settingsFile: 'packages/settings/settings/src/index.ts',
settingsGetNeedle: 'get<const Namespace extends string>(ns:',
settingsGetPresent: false,
settingsDescribeNeedle: 'describe(options?: SettingsDescribeOptions)',
providerFiles: [
{
file: 'packages/llm/llm-deepseek-api-key/src/index.ts',
needles: ["export const name = 'llm-deepseek-api-key'"],
},
{
file: 'packages/llm/llm-deepseek-api-key/src/config.ts',
needles: ["apiKeyEnv: z.string().role('credential-ref').default('DEEPSEEK_API_KEY').volatile()"],
},
{
file: 'packages/llm/llm-deepseek/src/config.ts',
needles: ['baseURL: z.string().volatile()'],
},
],
},
stepGuard: {
loopFile: 'packages/core/agent-loop/src/agent.ts',
loopNeedles: ["'agent/pre-step'", "append('user/message'"],
prependProofFile: 'packages/context/time-context/src/index.ts',
},
prompt: {
serviceFile: 'packages/core/system-prompt/src/index.ts',
sectionNeedle: 'section(section: PromptSection)',
orderAnchors: ['HARNESS_IDENTITY', 'DEPLOYMENT_PERSONA_SUFFIX'],
loaderVolatileFile: 'vendor/loader/src/config/entry.ts',
loaderVolatileNeedle: 'updateVolatile(ref, source)',
},
}
}
export const BASELINES: readonly Baseline[] = [
// The oldest supported line — Session format V4: the fold's switched
// families run the current dialect (`system/message` surface nodes, the
// embedded assistant streams, `startSeq`/`endSeq` replacements,
// `tool/ptc-dispatch`), the tool result is a first-class role-`tool`
// message with lifted `toolCallId`/`isError`, and plugin configuration
// derives from the entry's own Config schema (the Plugins page's keyed
// `plugins.bundle.config` seat over the `configForms` transport). The
// baseline pins `0.1.7-rc.2`, the first release of the line with a
// complete npm dependency closure; rc.2 adds `startsSeries` to the first
// `request/header` of a resumed series and the `developer/message`
// tool-registry events. startsSeries is inert to the fold; registry messages contribute to injected context.
// 0.1.7 enforces plugin dsh-peer compatibility at startup and install
// (evaluatePluginCompatibility); this plugin's `>=0.1.7-rc.2` dsh peers
// satisfy every supported line under the gate's includePrerelease check.
baseline('v0.1.7-rc.2', 'dsh-v0.1.7-rc.2', '0.1.7-rc.2'),
// The 0.2.0 line continues the V4 Config-form generation: every seam the
// plugin rides is spelled exactly as at `0.1.7-rc.2` — the projection
// registry's register/snapshot/checkpoint/restore contract (drive,
// stateVersion, wire views), the platform module table, the right
// Sidebar's tab + chip-title seats and guide-entry contract, the
// session-jump verb, the detail channel's Connection RPC faces, the
// Plugins page's keyed `plugins.bundle.config` card slot over the
// `configForms` transport, the balance settings face, the step-boundary
// guard, and the durable event vocabulary (still the V4 generation — the
// session-format tree carries no v4-to-v5 migration). Cordis stays 4.0.4,
// and the line continues to enforce the plugin dsh-peer gate this plugin's `>=0.1.7-rc.2` peers satisfy.
baseline('v0.2.0-rc.2', 'dsh-v0.2.0-rc.2', '0.2.0-rc.2'),
]