Merge remote-tracking branch 'origin/master' into feat/web-session-log-export

# Conflicts:
#	docs/subsystems/persistence.i18n.yaml
#	docs/subsystems/persistence.md
#	docs/subsystems/persistence.zh.md
#	packages/client/ui-trajectory/README.i18n.yaml
#	packages/client/ui-trajectory/README.md
#	packages/client/ui-trajectory/README.zh.md
#	packages/client/ui-trajectory/package.json
#	packages/client/ui-trajectory/src/client/TrajectoryView.tsx
#	packages/client/ui-trajectory/src/client/index.ts
#	packages/client/ui-trajectory/tests/client-bundle.spec.ts
#	packages/client/ui-trajectory/tests/views.spec.tsx
#	packages/host/apiproxy/package.json
#	pnpm-lock.yaml
This commit is contained in:
_Kerman
2026-08-11 12:42:53 +08:00
2172 changed files with 30345 additions and 15794 deletions
@@ -8,9 +8,11 @@
*/
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { Context } from 'cordis'
import { Context } from '@deepseek-ai/cordis'
import { afterEach, describe, expect, it } from 'vitest'
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
import {
ConversationEventRegistry, ConversationViewRegistry, SlotsService,
} from '@deepseek-ai/dsh-client-runtime/client'
const PLUGIN_ID = '@deepseek-ai/dsh-client-ui-trajectory'
@@ -61,31 +63,41 @@ describe('tsdown client artifact', () => {
const { handoff, surface } = await loadArtifact()
expect(handoff.id).toBe(PLUGIN_ID)
expect(surface.apply).toBeTypeOf('function')
expect(surface.inject).toEqual(['slots', 'sessionHistory', 'locale'])
expect(surface.inject).toEqual([
'slots', 'conversationEvents', 'conversationViews', 'sessions', 'locale',
])
})
it.skipIf(code === undefined)('mounted as an object plugin, apply registers the view tab on the real ring', async () => {
const { surface } = await loadArtifact()
const ctx = new Context()
const slots = new SlotsService(ctx)
await ctx.plugin(ConversationEventRegistry).await()
await ctx.plugin(ConversationViewRegistry).await()
// The conversation entry's role: the ring must be declared before riders land.
slots.register({
name: 'root',
children: { 'conversation.view': { kind: 'list', scope: 'session' } },
}, (_p: { renderSlot?: unknown }) => null)
// The plugin reads sessionHistory for its per-session history source;
// slot availability is tracked by slots.inject, and the locale plugin
// backs the locale-aware view tab label (its settings scope needs a
// connection handle).
ctx.provide('sessionHistory', {})
// Paging is session-owned; this registration-only probe never renders the
// entry, so the binding stays deliberately empty. The locale plugin backs
// the locale-aware view tab label (its settings scope needs a connection
// handle).
ctx.provide('sessions', { binding: () => undefined })
ctx.provide('connection', { api: { settings: {} }, isLoopback: false } as never)
const locale = await import('@deepseek-ai/dsh-client-locale/client')
ctx.plugin({ inject: [...locale.inject], apply: locale.apply })
const fiber = ctx.plugin(surface as { apply: (ctx: Context) => void })
await fiber.await()
const events = ctx.get('conversationEvents') as ConversationEventRegistry
const views = ctx.get('conversationViews') as ConversationViewRegistry
expect(slots.entries('conversation.view').map(e => e.options.id)).toEqual(['trajectory'])
expect(events.entries().length).toBeGreaterThan(0)
expect(views.entries()).toHaveLength(1)
await fiber.dispose()
expect(slots.entries('conversation.view')).toHaveLength(0)
expect(events.entries()).toEqual([])
expect(views.entries()).toEqual([])
})
it.skipIf(code === undefined)('injects plugin-tagged module CSS during factory execution', async () => {