fix(settings): use the host's @deepseek-ai/schemastery so volatile prefs apply
Every side-card switch was dead: settings.update returned OK and cordis.patch.yml was written, yet the running plugin never saw the new value and nothing reported an error. Root cause: the plugin declared Config with the plain npm `schemastery` (3.18.0), which has no volatile support. The host resolves plugin config via `runtime.Config['~standard'].validate()` — i.e. the plugin's OWN schemastery — so `meta.volatile` nodes were never wrapped in cosmokit volatile references. `cordis-plugin-loader`'s `_commitVolatile()` bails out as soon as `volatileEntries(fiber.config)` is empty, so every volatile-only edit was silently dropped: no in-place update, no plugin restart. Fix: import z from '@deepseek-ai/schemastery' (peer provided by the host at mount; pinned 3.18.4 as a devDependency for the dev tree), matching the repo's existing pnpm-workspace convention for @deepseek-ai/* peers. Verified: probe reports tabsIsVolatile=true and revision climbing 0->6 across six writes, each read-back matching the written value (previously the value was frozen and revision stuck at 1). Also in this change: - apes tab ids lose their colon (apes:tasks -> apesTasks, ...): the host's settings projection dropped map keys containing a colon. - sidebar prefs ns follows the profile entry id (`better-sidebar`). - subagent rows degrade to summary rows when the root catalog never hydrates. - docs/feedback-0.2.0-rc2-contract-gaps.md: four host contract gaps, gap 4 documenting the silent volatile-write drop above. Gates: typecheck 0, lint 0, build 0, test 131 files / 1385 passed.
This commit is contained in:
+13
-13
@@ -2,7 +2,7 @@
|
||||
* apes:* tab registration tests (Phase 1/1b → C7: 5 tabs). The fork registers
|
||||
* its information-layer tabs (tasks / agents / apps / messages / profile)
|
||||
* through the same BetterSidebarService the upstream builtins use (dogfood).
|
||||
* `apes:tasks` is back (C7 reversal of C6): the native DSH session tree focuses
|
||||
* `apesTasks` is back (C7 reversal of C6): the native DSH session tree focuses
|
||||
* ONE lineage, while this panel is a flat, whole-workspace list with a running
|
||||
* badge and an idle filter — the overlap is complementary.
|
||||
*
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
tasksShowInactive,
|
||||
} from '../src/client/apes/tabs.tsx'
|
||||
|
||||
const APES_TAB_IDS = ['apes:agents', 'apes:apps', 'apes:messages', 'apes:profile', 'apes:tasks']
|
||||
const APES_TAB_IDS = ['apesAgents', 'apesApps', 'apesMessages', 'apesProfile', 'apesTasks']
|
||||
|
||||
function setup(): {
|
||||
service: ReturnType<typeof createBetterSidebarService>
|
||||
@@ -119,10 +119,10 @@ describe('apes:* tab registrations', () => {
|
||||
try {
|
||||
const orders = {
|
||||
tasks: service.getTab(TASKS_TAB_ID)?.order,
|
||||
agents: service.getTab('apes:agents')?.order,
|
||||
apps: service.getTab('apes:apps')?.order,
|
||||
messages: service.getTab('apes:messages')?.order,
|
||||
profile: service.getTab('apes:profile')?.order,
|
||||
agents: service.getTab('apesAgents')?.order,
|
||||
apps: service.getTab('apesApps')?.order,
|
||||
messages: service.getTab('apesMessages')?.order,
|
||||
profile: service.getTab('apesProfile')?.order,
|
||||
}
|
||||
// C8: order 40-50 places apes tabs immediately after the native builtins
|
||||
// (10-35), not in a far-away 900+ band — visual unity in the tab strip.
|
||||
@@ -149,7 +149,7 @@ describe('apes:* tab registrations', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('apes:tasks plugin toggles', () => {
|
||||
describe('apesTasks plugin toggles', () => {
|
||||
it('declares the showInactive switch under the tasks descriptor', () => {
|
||||
const descriptor = apesTabs.find(d => d.id === TASKS_TAB_ID)
|
||||
const toggles = descriptor?.settings?.pluginToggles ?? []
|
||||
@@ -166,7 +166,7 @@ describe('apes:tasks plugin toggles', () => {
|
||||
expect(tasksShowInactive(store)).toBe(true)
|
||||
})
|
||||
|
||||
it('reads the boolean from pluginSettings[apes:tasks]', () => {
|
||||
it('reads the boolean from pluginSettings[apesTasks]', () => {
|
||||
const store = {
|
||||
getPrefs: () => ({ pluginSettings: { [TASKS_TAB_ID]: { [TASKS_SHOW_INACTIVE_KEY]: false } } }),
|
||||
} as unknown as TabComponentProps['store']
|
||||
@@ -183,7 +183,7 @@ describe('apes:tasks plugin toggles', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('apes:tasks row selection (PURE)', () => {
|
||||
describe('apesTasks row selection (PURE)', () => {
|
||||
it('ranks current first, then running, then idle', () => {
|
||||
const list = snap('idle-b', [
|
||||
row('idle-a'),
|
||||
@@ -219,7 +219,7 @@ describe('apes:tasks row selection (PURE)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('apes:agents badge + topology count', () => {
|
||||
describe('apesAgents badge + topology count', () => {
|
||||
it('counts the subagents of the current session tree only', () => {
|
||||
const list = snap('main', [
|
||||
row('main'),
|
||||
@@ -251,12 +251,12 @@ describe('apes badge contract', () => {
|
||||
})
|
||||
|
||||
it('badges the agents tab with the subagent count, null when childless', () => {
|
||||
expect(badgeOf('apes:agents', snap('main', [row('main'), row('c1', { origin: 'subagent', parentId: 'main' })]), 'main')).toBe(1)
|
||||
expect(badgeOf('apes:agents', snap('main', [row('main')]), 'main')).toBeNull()
|
||||
expect(badgeOf('apesAgents', snap('main', [row('main'), row('c1', { origin: 'subagent', parentId: 'main' })]), 'main')).toBe(1)
|
||||
expect(badgeOf('apesAgents', snap('main', [row('main')]), 'main')).toBeNull()
|
||||
})
|
||||
|
||||
it('never badges the placeholder/static tabs', () => {
|
||||
for (const id of ['apes:apps', 'apes:messages', 'apes:profile']) {
|
||||
for (const id of ['apesApps', 'apesMessages', 'apesProfile']) {
|
||||
expect(apesTabs.find(d => d.id === id)?.badge, `${id} badge`).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('dsh-apes-sidebar plugin export shape', () => {
|
||||
|
||||
it('registers the side card preferences schema with the documented defaults', async () => {
|
||||
const { PrefsSchema, SIDEBAR_PREFS_NS, SIDEBAR_PREFS_DEFAULTS } = await import('../src/config.ts')
|
||||
expect(SIDEBAR_PREFS_NS).toBe('dsh-apes-sidebar')
|
||||
expect(SIDEBAR_PREFS_NS).toBe('better-sidebar')
|
||||
const resolved = (PrefsSchema as unknown as {
|
||||
(input: Record<string, unknown> | undefined): Record<string, unknown>
|
||||
})(undefined)
|
||||
|
||||
+12
-12
@@ -11,7 +11,7 @@ import { join, resolve as resolvePath } from 'node:path'
|
||||
import { SettingsConflictError, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
|
||||
import { apply, mediaTypeForPath, wsCloseReasonOf } from '../src/index.ts'
|
||||
import { SidebarError } from '../src/wire.ts'
|
||||
import { SIDEBAR_PREFS_DEFAULTS } from '../src/prefs-shared.ts'
|
||||
import { SIDEBAR_PREFS_DEFAULTS, SIDEBAR_PREFS_NS } from '../src/prefs-shared.ts'
|
||||
import { encodeHtmlUrl } from '../src/html-route.ts'
|
||||
import * as git from '../src/git.ts'
|
||||
import { listDirectory } from '../src/fs-tree.ts'
|
||||
@@ -839,11 +839,11 @@ describe('side card settings routes', () => {
|
||||
// 0.2.0-rc.2: the plugin's Config schema is auto-registered by the loader.
|
||||
// The mock pre-seeds the sidebar namespace so describe() returns it even
|
||||
// when the plugin no longer calls register() explicitly.
|
||||
if (!pre || !('dsh-apes-sidebar' in pre)) {
|
||||
if (!pre || !(SIDEBAR_PREFS_NS in pre)) {
|
||||
// Use SIDEBAR_PREFS_DEFAULTS as the resolved value for the sidebar ns:
|
||||
// the real SettingsForms service applies the Config schema defaults
|
||||
// when no stored value exists, which is exactly what this constant is.
|
||||
namespaces.set('dsh-apes-sidebar', { schema: (input: unknown) => input ?? SIDEBAR_PREFS_DEFAULTS, value: undefined, revision: 0 })
|
||||
namespaces.set(SIDEBAR_PREFS_NS, { schema: (input: unknown) => input ?? SIDEBAR_PREFS_DEFAULTS, value: undefined, revision: 0 })
|
||||
}
|
||||
for (const [ns, value] of Object.entries(pre ?? {})) {
|
||||
namespaces.set(ns, { schema: (input: unknown) => input ?? {}, value, revision: 0 })
|
||||
@@ -979,7 +979,7 @@ describe('side card settings routes', () => {
|
||||
|
||||
it('shell.get reflects the settings-page override with the quotes stripped', async () => {
|
||||
const route = mountWithSettings(createFakeSettings({
|
||||
'dsh-apes-sidebar': {
|
||||
[SIDEBAR_PREFS_NS]: {
|
||||
terminalShell: '"C:\\Program Files\\PowerShell\\7\\pwsh.exe"',
|
||||
terminalShellArgs: '-NoLogo',
|
||||
},
|
||||
@@ -1184,7 +1184,7 @@ describe('agent terminal tool gating', () => {
|
||||
const handlerRef: { current: ((ns: string) => void) | null } = { current: null }
|
||||
const settings = {
|
||||
describe: () => [{
|
||||
ns: 'dsh-apes-sidebar',
|
||||
ns: SIDEBAR_PREFS_NS,
|
||||
value: { ...SIDEBAR_PREFS_DEFAULTS, agentTerminalTools: enabled },
|
||||
applies: 'live' as const,
|
||||
revision: 0,
|
||||
@@ -1215,18 +1215,18 @@ describe('agent terminal tool gating', () => {
|
||||
expect(live()).toBe(0)
|
||||
// Flipping the setting on registers all eight tools.
|
||||
enabled = true
|
||||
handlerRef.current?.('dsh-apes-sidebar')
|
||||
handlerRef.current?.(SIDEBAR_PREFS_NS)
|
||||
expect(live()).toBe(8)
|
||||
expect(disposed).toBe(0)
|
||||
// Flipping it back off unregisters them (and releases any agent terminals).
|
||||
enabled = false
|
||||
handlerRef.current?.('dsh-apes-sidebar')
|
||||
handlerRef.current?.(SIDEBAR_PREFS_NS)
|
||||
expect(live()).toBe(0)
|
||||
expect(disposed).toBe(8)
|
||||
// And a redundant toggle registers them fresh (no double-registration per
|
||||
// flip: the guard only skips when the tools are already live).
|
||||
enabled = true
|
||||
handlerRef.current?.('dsh-apes-sidebar')
|
||||
handlerRef.current?.(SIDEBAR_PREFS_NS)
|
||||
expect(live()).toBe(8)
|
||||
expect(registered).toBe(16)
|
||||
})
|
||||
@@ -1241,7 +1241,7 @@ describe('agent sidebar-open tool gating', () => {
|
||||
const handlerRef: { current: ((ns: string) => void) | null } = { current: null }
|
||||
const settings = {
|
||||
describe: () => [{
|
||||
ns: 'dsh-apes-sidebar',
|
||||
ns: SIDEBAR_PREFS_NS,
|
||||
value: { ...SIDEBAR_PREFS_DEFAULTS, agentOpenTools: enabled },
|
||||
applies: 'live' as const,
|
||||
revision: 0,
|
||||
@@ -1272,17 +1272,17 @@ describe('agent sidebar-open tool gating', () => {
|
||||
expect(live()).toBe(0)
|
||||
// Flipping the setting on registers the single sidebar_open tool.
|
||||
enabled = true
|
||||
handlerRef.current?.('dsh-apes-sidebar')
|
||||
handlerRef.current?.(SIDEBAR_PREFS_NS)
|
||||
expect(live()).toBe(1)
|
||||
expect(disposed).toBe(0)
|
||||
// Flipping it back off unregisters it (and drains the undelivered queue).
|
||||
enabled = false
|
||||
handlerRef.current?.('dsh-apes-sidebar')
|
||||
handlerRef.current?.(SIDEBAR_PREFS_NS)
|
||||
expect(live()).toBe(0)
|
||||
expect(disposed).toBe(1)
|
||||
// And a redundant toggle registers it fresh (no double-registration).
|
||||
enabled = true
|
||||
handlerRef.current?.('dsh-apes-sidebar')
|
||||
handlerRef.current?.(SIDEBAR_PREFS_NS)
|
||||
expect(live()).toBe(1)
|
||||
expect(registered).toBe(2)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user