Merge remote-tracking branch 'origin/master' into feat/pwsh-tool

# Conflicts:
#	apps/cli/composition.md
#	docs/testing.i18n.yaml
#	docs/testing.md
#	docs/testing.zh.md
This commit is contained in:
Huanqi Cao
2026-08-04 17:39:13 +08:00
675 changed files with 9967 additions and 30309 deletions

View File

@@ -254,10 +254,6 @@ class SingleExeBuild {
'--config.node-linker=hoisted',
'--config.auto-install-peers=false',
'--config.link-workspace-packages=true',
// The production closure intentionally omits the patched dev-only
// @earendil-works/pi-tui package. The root frozen install still validates
// every patch; this exception is scoped only to the production deploy.
'--config.allow-unused-patches=true',
this.staging,
])
if (this.cli.dryRun) {

View File

@@ -97,7 +97,6 @@ function workspaceManifests(): WorkspaceManifest[] {
const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
'@deepseek-ai/dsh-helper': ['lib/assets'],
'@deepseek-ai/dsh-tui': ['lib/prompt.js'],
'@deepseek-ai/dsh-scripts': [
'lib/dev/tsdown-config.js',
'lib/local-plugin-loader-hooks.js',

View File

@@ -14,6 +14,10 @@ interface CssModulePlugin {
load?: (this: { addWatchFile: (id: string) => void }, id: string) => Promise<unknown>
}
function clientSourceMapPath(packagePath: string): string {
return fileURLToPath(new URL(`../packages/${packagePath}/lib/client.js.map`, import.meta.url))
}
function purityResolveId(): ResolveId {
// libEntry is spelled at every call site (no default) so the
// package-invariants text check can see the invariant entry per package.
@@ -74,6 +78,54 @@ describe('client bundle purity gate', () => {
})
})
describe('client bundle debug artifacts', () => {
it('emits source maps for plugin TS and TSX outside the Vite module graph', () => {
const configs = clientBundle('@deepseek-ai/dsh-client-test', ['lib/types/index.js', 'lib/types/invariant.js'])
expect(configs[1]?.sourcemap).toBe(true)
})
it('maps first-party sources to their repository package paths', () => {
const configs = clientBundle('@deepseek-ai/dsh-client-ui-goal', ['lib/types/index.js', 'lib/types/invariant.js'])
const outputOptions = configs[1]?.outputOptions
if (typeof outputOptions !== 'object' || outputOptions === null) throw new Error('client output options missing')
const transform = outputOptions.sourcemapPathTransform
if (transform === undefined) throw new Error('client sourcemap path transform missing')
const source = transform('../src/client/GoalBar.tsx', clientSourceMapPath('client/ui-goal'))
expect(source).toBe('../../../packages/client/ui-goal/src/client/GoalBar.tsx')
const resolved = new URL(source, 'https://dsh.test/plugins/@deepseek-ai/dsh-client-ui-goal/client.js.map')
expect(resolved.pathname).toBe('/packages/client/ui-goal/src/client/GoalBar.tsx')
})
it('maps dual-face host sources to the host package group', () => {
const configs = clientBundle('@deepseek-ai/dsh-host-directory-picker-native', ['lib/types/index.js'])
const outputOptions = configs[1]?.outputOptions
if (typeof outputOptions !== 'object' || outputOptions === null) throw new Error('client output options missing')
const transform = outputOptions.sourcemapPathTransform
if (transform === undefined) throw new Error('client sourcemap path transform missing')
const source = transform('../src/client/index.ts', clientSourceMapPath('host/directory-picker-native'))
expect(source).toBe('../../../packages/host/directory-picker-native/src/client/index.ts')
})
it('maps inlined workspace sources to packages and leaves dependencies outside it unchanged', () => {
const configs = clientBundle('@deepseek-ai/dsh-client-connection', ['lib/types/index.js'])
const outputOptions = configs[1]?.outputOptions
if (typeof outputOptions !== 'object' || outputOptions === null) throw new Error('client output options missing')
const transform = outputOptions.sourcemapPathTransform
if (transform === undefined) throw new Error('client sourcemap path transform missing')
const sourceMapPath = clientSourceMapPath('client/connection')
const workspaceSource = transform('../../../host/apiproxy/src/api/rpc.ts', sourceMapPath)
expect(workspaceSource).toBe('../../../packages/host/apiproxy/src/api/rpc.ts')
const resolved = new URL(workspaceSource, 'https://dsh.test/plugins/@deepseek-ai/dsh-client-connection/client.js.map')
expect(resolved.pathname).toBe('/packages/host/apiproxy/src/api/rpc.ts')
const dependencySource = '../../../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/index.js'
expect(transform(dependencySource, sourceMapPath)).toBe(dependencySource)
})
})
describe('client bundle CSS Modules watch graph', () => {
it('registers the physical stylesheet read behind a virtual module', async () => {
const plugin = cssModulePlugin()

View File

@@ -279,8 +279,6 @@ export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
WebRoute: 'route registration contract is owned by packages/host/webserver/src/index.ts',
ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts',
Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts',
TuiOverlayRequest: 'service-local extension contract is owned by packages/ui/tui/README.md',
TuiOverlaySession: 'service-local extension contract is owned by packages/ui/tui/README.md',
InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md',
PresetOption: 'deployment menu metadata is owned by packages/ui/permission/README.md',
PresetSpec: 'deployment preset composition is owned by packages/ui/permission/README.md',

View File

@@ -218,7 +218,6 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'session-reference',
title: 'Cross-session snapshot preparation',
mode: 'core',
consumers: ['tui'],
note: 'Projects bounded current-surface conversation snapshots into durable untrusted message context; host adapters own mention syntax.',
},
{
@@ -250,8 +249,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'user-interaction',
title: 'Human question/answer seam',
mode: 'seam',
implementations: ['tui'],
consumers: ['tool-ask-user', 'tui'],
consumers: ['tool-ask-user'],
note: 'UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise.',
},
{
@@ -266,8 +264,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'commands',
title: 'Human command registry',
mode: 'core',
consumers: ['tui'],
note: 'Plugins register direct human commands; TUI consumes the effective per-agent catalog without sending invocations to the model.',
note: 'Plugins register direct human commands without sending invocations to the model.',
},
{
key: 'sessionProjections',
@@ -285,13 +282,6 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['host-apiproxy'],
note: 'Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs.',
},
{
key: 'tui',
pkg: 'tui',
title: 'Mounted-terminal interaction service',
mode: 'bundle',
note: 'One TUI front door provides a FIFO overlay host; injected plugins receive caller-fiber ownership without access to pi-tui or terminal lifecycle state.',
},
{
key: 'skills',
pkg: 'skill',
@@ -306,7 +296,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'agent',
title: 'Agent service',
mode: 'core',
consumers: ['agent-loop', 'acp', 'cli-demo', 'subagent-inprocess', 'tui-demo'],
consumers: ['agent-loop', 'acp', 'cli-demo', 'subagent-inprocess'],
note: 'Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation.',
},
{
@@ -627,12 +617,12 @@ function stripYamlScalar(value: string): string {
const APP_EXAMPLES = [
{
id: 'tui',
id: 'dsh_base',
rel: 'apps/cli/composition.md',
title: 'TUI Agent App Composition',
label: 'apps/cli/config',
title: 'DSH Base Composition',
label: 'apps/cli/config/base.cordis.yml',
config: 'apps/cli/config/base.cordis.yml',
summary: 'The TUI surface combines the shared CLI base with its surface overlay and full-screen terminal package.',
summary: 'The raw CLI applies one required caller-selected patch list over this shared base; Web and headless apply their own shipped overlays.',
},
{
id: 'headless',
@@ -659,9 +649,7 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string
const jsonl = nodeId('bundle', 'jsonl')
lines.push(` ${appNode} --> ${agentCore}["@deepseek-ai/dsh-agent-spine-demo"]`)
lines.push(` ${appNode} --> ${jsonl}["@deepseek-ai/dsh-session-persistence-jsonl"]`)
if (pluginName === '@deepseek-ai/dsh-tui-demo') {
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'tui')}["@deepseek-ai/dsh-tui<br/>pre-created main agent"]`)
} else if (pluginName === '@deepseek-ai/dsh-cli-demo') {
if (pluginName === '@deepseek-ai/dsh-cli-demo') {
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'cli')}["one-shot driver<br/>format-pure stdout<br/>fresh top-level agent"]`)
} else if (pluginName === '@deepseek-ai/dsh-acp-demo') {
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'acp')}["@deepseek-ai/dsh-acp<br/>automation-only JSON-RPC stdio<br/>fresh sessions created by client"]`)
@@ -689,7 +677,7 @@ function renderAppComposition(example: AppExample): string {
const pluginNode = nodeId(`plugin_${example.id}`, plugin.id)
lines.push(` ${pluginNode}["${escLabel(plugin.id)}<br/>${escLabel(plugin.name)}"]`)
lines.push(` cfg --> ${pluginNode}`)
if (plugin.name === '@deepseek-ai/dsh-tui-demo' || plugin.name === '@deepseek-ai/dsh-cli-demo' || plugin.name === '@deepseek-ai/dsh-acp-demo') {
if (plugin.name === '@deepseek-ai/dsh-cli-demo' || plugin.name === '@deepseek-ai/dsh-acp-demo') {
renderAppExpansion(lines, pluginNode, plugin.name)
}
}
@@ -1304,8 +1292,8 @@ function renderDocs(): GraphDoc[] {
function renderIndex(docs: GraphDoc[]): string {
const labels: Record<string, string> = {
'docs/capability-seams.md': 'capability seams and core services',
'apps/cli/composition.md': 'dsh shared base composition',
'examples/headless-agent/composition.md': 'headless-agent app composition',
'examples/tui-agent/composition.md': 'tui-agent app composition',
'examples/cordis-agent/composition.md': 'cordis-agent app composition',
'examples/acp-agent/composition.md': 'acp-agent app composition',
'docs/event-producer-consumer.md': 'event producer/consumer matrix',
@@ -1314,8 +1302,8 @@ function renderIndex(docs: GraphDoc[]): string {
}
const modes: Record<string, string> = {
'docs/capability-seams.md': 'hybrid generated',
'apps/cli/composition.md': 'hybrid generated',
'examples/headless-agent/composition.md': 'hybrid generated',
'examples/tui-agent/composition.md': 'hybrid generated',
'examples/cordis-agent/composition.md': 'hybrid generated',
'examples/acp-agent/composition.md': 'hybrid generated',
'docs/event-producer-consumer.md': 'hybrid generated',

View File

@@ -10,9 +10,8 @@
# `pnpm install`, points the stable `~/.dsh/source/current` symlink
# at that staging worktree and symlinks `dsh` onto PATH at `current/bin/dsh`,
# records your API credentials in the Harness home (`~/.dsh`) dsh reads at boot,
# and lets you launch the Web UI or TUI. The Web choice builds the repository
# artifacts first; the TUI runs directly from TypeScript source through the
# repo's own tsx. Keeping every checkout under ~/.dsh/source keeps successive
# builds the repository artifacts, and launches the Web UI. Keeping every
# checkout under ~/.dsh/source keeps successive
# upgrades in one place instead of scattered sibling clones, and lets staging
# worktrees share the master clone's object store. The PATH symlink resolves through
# `current`, so an upgrade repoints one stable symlink instead of relinking PATH:
@@ -414,33 +413,15 @@ if [ "${SKIP_CREDS:-0}" != 1 ]; then
fi
fi
# --- 6. choose and launch an interface -----------------------------------------
# --- 6. build and launch the Web interface -------------------------------------
step "Done"
if [ "$HAS_TTY" = 1 ]; then
printf ' 1) Web UI (recommended)\n'
printf ' 2) TUI\n'
while :; do
LAUNCH_INTERFACE=$(ask "Choose an interface [1/2]:" 1)
case "$LAUNCH_INTERFACE" in
1|web|Web|WEB)
step "Building DeepSeek Harness for Web UI"
( cd "$DSH_STAGING" && pnpm run build )
info "launching Web UI — run 'dsh web' anytime to start again"
exec "$DSH_BIN_DIR/dsh" web </dev/tty
;;
2|tui|Tui|TUI)
info "launching TUI — run 'dsh' anytime to start again"
exec "$DSH_BIN_DIR/dsh" </dev/tty
;;
*)
warn "choose 1 for Web UI or 2 for TUI"
;;
esac
done
step "Building DeepSeek Harness for Web UI"
( cd "$DSH_STAGING" && pnpm run build )
info "launching Web UI — run 'dsh web' anytime to start again"
exec "$DSH_BIN_DIR/dsh" web </dev/tty
else
info "install complete. Build and start the Web UI with:"
printf ' (cd %s && pnpm run build)\n' "$DSH_STAGING"
printf ' %s web\n' "$DSH_BIN_DIR/dsh"
info "or start the TUI with:"
printf ' %s\n' "$DSH_BIN_DIR/dsh"
fi

View File

@@ -594,7 +594,7 @@ function builtBinSmokeGate(needs: string[] = ['build']): Gate {
'--config',
'vitest.e2e.config.ts',
'examples/headless-agent/tests/keyless-smoke.e2e.ts',
'apps/cli/tests/tui-keyless-smoke.e2e.ts',
'apps/cli/tests/built-bin.e2e.ts',
'packages/examples/cli-demo/tests/built-bin.e2e.ts',
'packages/examples/acp-demo/tests/built-bin.e2e.ts',
'packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts',

File diff suppressed because one or more lines are too long