Merge remote-tracking branch 'origin/master' into worktree/web-background-tasks-display-258f7e

# Conflicts:
#	docs/subsystems/tasks.i18n.yaml
#	docs/subsystems/tasks.md
#	docs/subsystems/tasks.zh.md
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.md
#	packages/host/apiproxy/README.zh.md
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/tasks/tasks-local/src/index.ts
#	packages/tasks/tasks/README.i18n.yaml
#	packages/tasks/tasks/README.md
#	packages/tasks/tasks/README.zh.md
#	packages/tasks/tasks/src/index.ts
This commit is contained in:
Yichen Jiang
2026-08-11 11:57:33 +08:00
2697 changed files with 39978 additions and 18500 deletions

View File

@@ -2,7 +2,9 @@
import type {
ConversationSnapshot, ISession, SessionId, SessionSummary, WorkspaceListState,
} from '@deepseek-ai/dsh-client-runtime/client'
import { EMPTY_CHAT_SNAPSHOT } from '@deepseek-ai/dsh-client-runtime/client'
import {
EMPTY_CHAT_SNAPSHOT, EMPTY_CONVERSATION_VIEWS,
} from '@deepseek-ai/dsh-client-runtime/client'
/**
* Fixture overrides for the session behavior face: any subset of the
@@ -46,6 +48,7 @@ export interface SessionFixture {
export function conversationSnapshot(sessionId: SessionId): ConversationSnapshot {
return {
sessionId,
views: EMPTY_CONVERSATION_VIEWS,
chat: EMPTY_CHAT_SNAPSHOT,
nodes: [],
turnTimings: new Map(),

View File

@@ -5,7 +5,7 @@
* declaration, registration, scope, store, inject, rendering, updates, and
* disposal without hand-building the machinery per suite.
*
* Not part of the product plugin graph (no `dshClient`); feature packages
* Not part of the product plugin graph (no `dsh.client`); feature packages
* depend on it in devDependencies only. It copies no SlotCore/renderer/store
* machinery — everything mounts the production implementations.
* @module @deepseek-ai/dsh-client-test-runtime
@@ -14,8 +14,8 @@
* `keyof SlotMap & string` is the declare-merge key pattern (see ui-slots):
* this compilation unit sees only the runtime's 'root' row, but consumer
* programs merge their own keys in; the rule fires on the narrow-map view. */
import { Context, Inject } from 'cordis'
import type { Fiber, Plugin } from 'cordis'
import { Context, Inject } from '@deepseek-ai/cordis'
import type { Fiber, Plugin } from '@deepseek-ai/cordis'
import { createElement, Fragment, useSyncExternalStore } from 'react'
import type { ReactNode } from 'react'
import { act, render, within } from '@testing-library/react'
@@ -36,6 +36,8 @@ import type { Stabilizer } from './fixtures.ts'
export { domSnapshotSerializer, registerDomSnapshotSerializer } from './snapshot.ts'
export { FixtureSession, TestSessions } from './sessions.ts'
export { stubSettingsScope } from './settings-scope.ts'
export type { StubSettingsScope } from './settings-scope.ts'
export { TestWorkspaces } from './workspaces.ts'
export { conversationSnapshot, workspaceListState } from './fixtures.ts'
export type { SessionBehaviorOverrides, SessionFixture, Stabilizer } from './fixtures.ts'

View File

@@ -4,7 +4,7 @@
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { Context } from '@deepseek-ai/cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-client-test-runtime'

View File

@@ -1,5 +1,6 @@
/** Test-owned sessions face: the SlotsService host contract over declarative fixtures. */
import type { Context } from 'cordis'
import type { Context } from '@deepseek-ai/cordis'
import type { AttachmentIdType } from '@deepseek-ai/dsh-attachment'
import { createScope, scopeOf, SessionProvideChannel } from '@deepseek-ai/dsh-client-runtime/client'
import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import type {
@@ -88,6 +89,15 @@ export class FixtureSession implements SessionFace {
throw new Error(`test session "${this.sessionId}": prompt is not stubbed — supply it on the fixture's session face`)
}
/**
* Fail-loud stub; supply `readAttachment` on the fixture's session face to exercise it.
* @param _attachmentId - opaque durable attachment id.
* @returns never — always throws.
*/
readAttachment(_attachmentId: AttachmentIdType): never {
throw new Error(`test session "${this.sessionId}": readAttachment is not stubbed — supply it on the fixture's session face`)
}
/**
* Fail-loud stub; supply `updateQueue` on the fixture's session face to exercise it.
* @returns never — always throws.

View File

@@ -0,0 +1,48 @@
/** Test double for the client settings-scope seam. */
import { vi } from 'vitest'
import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/client'
/** Handle over one stubbed scope: the scope, its write spy, and publication controls. */
export interface StubSettingsScope<T> {
/** The scope face handed to the service under test. */
scope: SettingsScope<T>
/** Spy behind `scope.set`; resolves immediately. */
set: ReturnType<typeof vi.fn>
/** @returns how many listeners are currently subscribed (disposal assertions). */
listenerCount(): number
/**
* Replace part of the snapshot and notify subscribers, as a Host
* acceptance would.
* @param next - snapshot fields to replace.
*/
publish(next: Partial<SettingsScopeSnapshot<T>>): void
}
/**
* Build an in-memory settings scope for service specs: starts in the host
* loading state, records writes, and lets the test publish Host acceptances.
* @returns the stub handle.
*/
export function stubSettingsScope<T>(): StubSettingsScope<T> {
let snapshot: SettingsScopeSnapshot<T> = {
status: 'loading', value: undefined, revision: undefined, writable: false, mode: 'host',
}
const listeners = new Set<() => void>()
const set = vi.fn(() => Promise.resolve())
return {
scope: {
getSnapshot: () => snapshot,
subscribe: (listener) => {
listeners.add(listener)
return () => { listeners.delete(listener) }
},
set,
},
set,
listenerCount: () => listeners.size,
publish: (next) => {
snapshot = { ...snapshot, ...next }
for (const listener of [...listeners]) listener()
},
}
}

View File

@@ -73,18 +73,17 @@ export class TestWorkspaces implements IWorkspaces {
/**
* Create a Workspace (recorded). The default echoes a view derived from
* the input; stub for failure or list-coupled flows.
* @param input - exactly one Host create spelling.
* @param input - the Host create payload.
* @returns the created Workspace view.
*/
async create(input: { name: string } | { path: string }): Promise<WorkspaceView> {
async create(input: { path: string }): Promise<WorkspaceView> {
this.calls.push({ method: 'create', args: [input] })
const stub = this.stubs.get('create')
if (stub !== undefined) return await (stub(input) as Promise<WorkspaceView>)
const title = 'name' in input ? input.name : input.path
return {
workspaceId: `ws-${title}` as WorkspaceId,
title,
path: 'path' in input ? input.path : `/${input.name}`,
workspaceId: `ws-${input.path}` as WorkspaceId,
title: input.path,
path: input.path,
sessionIds: [],
} as unknown as WorkspaceView
}