mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge branch 'master' into worktree/schedule-conversational-after
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
import { Service } from '@deepseek-ai/cordis'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type {
|
||||
ConnectionHandle, IApiClient, SettingsNamespaceView,
|
||||
ConnectionHandle, IApiClient, SettingsNamespaceView, SettingsPathOpView,
|
||||
} from '@deepseek-ai/dsh-client-connection/client'
|
||||
import { rehydrateSchema, validateDraft } from '@deepseek-ai/dsh-client-schema-form'
|
||||
import {
|
||||
@@ -59,6 +59,8 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
|
||||
this.store = createSnapshotStore<SettingsScopeSnapshot<T>>({
|
||||
status: persistence === 'host' ? 'loading' : 'unavailable',
|
||||
value: undefined,
|
||||
base: undefined,
|
||||
user: undefined,
|
||||
revision: undefined,
|
||||
writable: false,
|
||||
mode: persistence,
|
||||
@@ -96,6 +98,20 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
|
||||
* @returns settlement after the write and any latest-write recovery read.
|
||||
*/
|
||||
set(field: string, value: unknown): Promise<void> {
|
||||
return this.write({ op: 'set', path: [field], value })
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue one field clear; see {@link SettingsScope.unset} for the ordering,
|
||||
* revision, and recovery contract.
|
||||
* @param field - scalar field inside the namespace section.
|
||||
* @returns settlement after the clear and any latest-write recovery read.
|
||||
*/
|
||||
unset(field: string): Promise<void> {
|
||||
return this.write({ op: 'unset', path: [field] })
|
||||
}
|
||||
|
||||
private write(op: SettingsPathOpView): Promise<void> {
|
||||
this.readGeneration += 1
|
||||
const generation = ++this.writeGeneration
|
||||
return this.enqueue(async () => {
|
||||
@@ -104,7 +120,7 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
|
||||
try {
|
||||
response = await this.api.settings.mutate({
|
||||
ns: this.spec.namespace,
|
||||
ops: [{ op: 'set', path: [field], value }],
|
||||
ops: [op],
|
||||
...(revision === undefined ? {} : { expectedRevision: revision }),
|
||||
})
|
||||
} catch (_settingsWriteFailure) {
|
||||
@@ -169,6 +185,8 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
|
||||
const decoded = publish ? this.decode(view) : undefined
|
||||
this.store.update((draft) => {
|
||||
draft.revision = view.revision
|
||||
draft.base = view.base
|
||||
draft.user = view.user
|
||||
if (writable !== undefined) draft.writable = writable
|
||||
if (decoded === undefined) return
|
||||
draft.status = 'ready'
|
||||
|
||||
@@ -293,6 +293,77 @@ describe('SettingsScopeController', () => {
|
||||
expect(describeCall).not.toHaveBeenCalled()
|
||||
expect(mutate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('carries the composition base and the user layer into the snapshot', async () => {
|
||||
const layered: SettingsNamespaceView = {
|
||||
...view({ preference: 'dark' }, 3),
|
||||
base: { preference: 'system' },
|
||||
user: { preference: 'dark' },
|
||||
}
|
||||
const describeCall = vi.fn()
|
||||
.mockResolvedValueOnce(ok({ writable: true, hasDocument: true, namespaces: [layered] }))
|
||||
const scope = new SettingsScopeController<UiTestSettings>(
|
||||
{ settings: { describe: describeCall } } as never,
|
||||
{ namespace: 'ui-test' },
|
||||
)
|
||||
|
||||
await scope.load()
|
||||
|
||||
expect(scope.getSnapshot()).toMatchObject({
|
||||
value: { preference: 'dark' },
|
||||
base: { preference: 'system' },
|
||||
user: { preference: 'dark' },
|
||||
})
|
||||
})
|
||||
|
||||
it('reports an inherited field as absent from the user layer', async () => {
|
||||
const inherited: SettingsNamespaceView = { ...view({ preference: 'system' }, 1), base: { preference: 'system' } }
|
||||
const describeCall = vi.fn()
|
||||
.mockResolvedValueOnce(ok({ writable: true, hasDocument: true, namespaces: [inherited] }))
|
||||
const scope = new SettingsScopeController<UiTestSettings>(
|
||||
{ settings: { describe: describeCall } } as never,
|
||||
{ namespace: 'ui-test' },
|
||||
)
|
||||
|
||||
await scope.load()
|
||||
|
||||
expect(scope.getSnapshot().user).toBeUndefined()
|
||||
})
|
||||
|
||||
it('clears one field through an unset op fenced by the held revision', async () => {
|
||||
const mutate = vi.fn().mockResolvedValueOnce(ok(view({ preference: 'system' }, 4)))
|
||||
const describeCall = vi.fn().mockResolvedValueOnce(described({ preference: 'dark' }, 3))
|
||||
const scope = new SettingsScopeController<UiTestSettings>(
|
||||
{ settings: { describe: describeCall, mutate } } as never,
|
||||
{ namespace: 'ui-test' },
|
||||
)
|
||||
await scope.load()
|
||||
|
||||
await scope.unset('preference')
|
||||
|
||||
expect(mutate).toHaveBeenCalledWith({
|
||||
ns: 'ui-test',
|
||||
ops: [{ op: 'unset', path: ['preference'] }],
|
||||
expectedRevision: 3,
|
||||
})
|
||||
expect(scope.getSnapshot()).toMatchObject({ value: { preference: 'system' }, revision: 4 })
|
||||
})
|
||||
|
||||
it('recovers the Host state when the latest clear is refused', async () => {
|
||||
const mutate = vi.fn().mockResolvedValueOnce(rejected())
|
||||
const describeCall = vi.fn()
|
||||
.mockResolvedValueOnce(described({ preference: 'dark' }, 3))
|
||||
.mockResolvedValueOnce(described({ preference: 'light' }, 5))
|
||||
const scope = new SettingsScopeController<UiTestSettings>(
|
||||
{ settings: { describe: describeCall, mutate } } as never,
|
||||
{ namespace: 'ui-test' },
|
||||
)
|
||||
await scope.load()
|
||||
|
||||
await scope.unset('preference')
|
||||
|
||||
expect(scope.getSnapshot()).toMatchObject({ value: { preference: 'light' }, revision: 5 })
|
||||
})
|
||||
})
|
||||
describe('SettingsScopeService.bind', () => {
|
||||
it('subscribes before the initial read and converges to the latest queued invalidation', async () => {
|
||||
|
||||
Reference in New Issue
Block a user