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

@@ -1,8 +1,15 @@
{
"name": "@deepseek-ai/dsh-client-ui-subagent",
"description": "Subagent conversation catalog, continuation routing UI, and '@' reference source",
"version": "0.0.1",
"private": true,
"version": "0.0.1-rc.1",
"publishConfig": {
"access": "restricted"
},
"repository": {
"type": "git",
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
"directory": "packages/client/ui-subagent"
},
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
@@ -22,15 +29,17 @@
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"dshClient": {
"inject": [
"@deepseek-ai/dsh-client-locale",
"@deepseek-ai/dsh-client-runtime",
"@deepseek-ai/dsh-client-ui-conversation",
"@deepseek-ai/dsh-client-ui-primitives",
"@deepseek-ai/dsh-client-ui-slash"
],
"platform": "web"
"dsh": {
"client": {
"inject": [
"@deepseek-ai/dsh-client-locale",
"@deepseek-ai/dsh-client-runtime",
"@deepseek-ai/dsh-client-ui-conversation",
"@deepseek-ai/dsh-client-ui-primitives",
"@deepseek-ai/dsh-client-ui-slash"
],
"platform": "web"
}
},
"scripts": {
"bundle": "tsdown",
@@ -41,16 +50,16 @@
"react": "^18.2.0"
},
"peerDependencies": {
"@deepseek-ai/dsh-client-locale": "^0.0.1",
"@deepseek-ai/dsh-client-runtime": "^0.0.1",
"@deepseek-ai/dsh-client-ui-conversation": "^0.0.1",
"@deepseek-ai/dsh-client-ui-primitives": "^0.0.1",
"@deepseek-ai/dsh-client-ui-slash": "^0.0.1",
"@deepseek-ai/dsh-client-ui-slots": "^0.0.1",
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-subagent": "^0.0.1",
"@deepseek-ai/dsh-token-meter": "^0.0.1",
"cordis": "^4.0.0-rc.7"
"@deepseek-ai/dsh-client-locale": "workspace:^",
"@deepseek-ai/dsh-client-runtime": "workspace:^",
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
"@deepseek-ai/dsh-client-ui-slash": "workspace:^",
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-token-meter": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
},
"devDependencies": {
"@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -64,7 +73,7 @@
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-token-meter": "workspace:^",
"@types/react": "~18.3.1",
"cordis": "^4.0.0-rc.7"
"@deepseek-ai/cordis": "workspace:^"
},
"files": [
"lib/index.js",

View File

@@ -54,7 +54,6 @@
max-height: min(560px, calc(100vh - 140px));
padding: 4px;
overflow: auto;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 12px;
background: var(--dsw-specific-menu);
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);

View File

@@ -519,8 +519,14 @@ export function SubagentCatalogAction({
observedCatalogs.current.clear()
}, [])
// Visibility needs evidence of children (entries, summary-known descendants,
// or a failed load worth retrying). A bare loading catalog is not evidence:
// selecting any session schedules a refresh whose loading snapshot would
// otherwise flash the action in and out on childless sessions.
const visible = presentedCatalog !== undefined
&& (presentedCatalog.state !== 'ready' || presentedCatalog.entries.length > 0)
&& (presentedCatalog.state === 'error'
|| presentedCatalog.entries.length > 0
|| descendantCount > 0)
useEffect(() => {
if (visible || !open) return
setOpen(false)

View File

@@ -2,7 +2,7 @@
* Subagent reference plugin, node half. Pure UI plugin: the empty apply
* exists so the plugin appears in the host cordis.yml / Loader; the browser
* half ships via exports["./client"], discovered through the package.json
* dshClient declaration.
* dsh.client declaration.
*/
/** Host plugin body — no host-side behavior for this source plugin. */

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-ui-subagent'

View File

@@ -11,7 +11,7 @@
* projections. Direct driving is deliberate: this spec owns only the
* source's own contract.
*/
import { Context } from 'cordis'
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import {
SlotsService, type ConversationSnapshot, type SessionId, type SessionListState,
@@ -20,7 +20,7 @@ import {
import type { ComposerChainProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
import { SlashService } from '@deepseek-ai/dsh-client-ui-slash/client'
import type { ClientSessionContext, SlashSource } from '@deepseek-ai/dsh-client-ui-slash/client'
import { apply as applyLocale } from '@deepseek-ai/dsh-client-locale/client'
import { apply as applyLocale, inject as localeInject } from '@deepseek-ai/dsh-client-locale/client'
import {
SubagentCatalogAction, type SubagentCatalogInjected,
} from '../src/client/SubagentCatalogAction.tsx'
@@ -86,8 +86,9 @@ async function fullBench(sessions: SessionSummary[]) {
const face = sessionsWith(sessions)
ctx.provide('slash', { registerSource: (src: SlashSource) => { captured = src; return () => {} } })
ctx.provide('sessions', face)
ctx.provide('connection', { api: { settings: {} }, isLoopback: false } as never)
await provideSlotFaces(ctx)
await ctx.plugin({ inject: ['slots'], apply: applyLocale }).await()
await ctx.plugin({ inject: localeInject, apply: applyLocale }).await()
await ctx.plugin({ inject: [...inject], apply }).await()
return { source: captured!, face, ctx }
}
@@ -121,8 +122,9 @@ describe('apply', () => {
const ctx = new Context()
await ctx.plugin(SlashService).await()
ctx.provide('sessions', sessionsWith(FAMILY))
ctx.provide('connection', { api: { settings: {} }, isLoopback: false } as never)
await provideSlotFaces(ctx)
await ctx.plugin({ inject: ['slots'], apply: applyLocale }).await()
await ctx.plugin({ inject: localeInject, apply: applyLocale }).await()
const fiber = ctx.plugin({ inject: [...inject], apply })
await fiber.await()
const slash = ctx.get('slash') as SlashService

View File

@@ -523,22 +523,23 @@ describe('SubagentCatalogAction', () => {
expect(staleEmpty.openChild).not.toHaveBeenCalled()
})
it('renders empty loading and fallback error states without focusable rows', async () => {
it('hides a bare loading catalog and keeps the error fallback without focusable rows', async () => {
// Selecting any session schedules a catalog refresh; a loading snapshot
// with no other evidence of children must not flash the action in.
const loading = props(catalog({ entries: [], state: 'loading' }))
const view = render(<SubagentCatalogAction {...loading} />)
const trigger = screen.getByRole('button', { name: /0 个子代理/ })
fireEvent.click(trigger)
expect(screen.getByText('正在加载子代理…')).toBeTruthy()
fireEvent.keyDown(trigger, { key: 'ArrowDown' })
await Promise.resolve()
expect(screen.getByRole('tree')).toBeTruthy()
fireEvent.keyDown(screen.getByRole('tree'), { key: 'ArrowUp' })
expect(screen.queryByRole('button')).toBeNull()
view.unmount()
const failed = props(catalog({ entries: [], state: 'error', error: null }))
render(<SubagentCatalogAction {...failed} />)
fireEvent.click(screen.getByRole('button', { name: /0 个子代理/ }))
const trigger = screen.getByRole('button', { name: /0 个子代理/ })
fireEvent.click(trigger)
expect(screen.getByText('无法加载子代理')).toBeTruthy()
fireEvent.keyDown(trigger, { key: 'ArrowDown' })
await Promise.resolve()
expect(screen.getByRole('tree')).toBeTruthy()
fireEvent.keyDown(screen.getByRole('tree'), { key: 'ArrowUp' })
})
it('navigates from outside the tree and tolerates a deferred focus after unmount', async () => {