fix(dsh): recognize 0.2 profiles and open workspace composer (#24589)

This commit is contained in:
Neil
2026-10-02 04:26:00 -07:00
committed by GitHub
parent 99b2628aef
commit cd8d03bc06
6 changed files with 160 additions and 40 deletions
+44
View File
@@ -0,0 +1,44 @@
# DeepSeek Harness integration
Orca detects the community `@deepseek-harness-tui/dsh-tui` launcher (`dsh-tui`, alias
`dst`) and requires the official `@deepseek-ai/dsh` executable too. The launcher
boots the `dsh-tui` profile; Orca passes `.` to select the current workspace and
reach its composer on the first launch. The official Harness does not bundle this community TUI.
DSH Console and DeepSeek Build are separate products and are not interchangeable
with this launch contract.
The official DSH 0.2 CLI accepts both `dsh --profile headless` and `dsh headless`.
Orca excludes the known `web`, `headless`, `sdk`, `sdk-minimal`, `acp`, and `desktop`
profiles from interactive process recognition, along with plugin management and
configuration dumps. Custom profile names remain eligible because profiles are
user configurable. Only launcher arguments are inspected; app prompts, resume IDs,
and patch filenames cannot change the selected profile's identity.
Status hooks use the official `@deepseek-ai/dsh-hooks-claude-code` plugin, installed
as an owned block in `$DSH_HOME/cordis.patch.yml`. User entries outside the block are
preserved. Local installation respects `DSH_HOME`; the existing SSH installer uses
the execution host's default `~/.dsh` because SFTP cannot read its environment.
Hooks report session start, prompt submission, tool start/end, and stopping through
Orca's host status store. Approval has no dedicated hook; it is not inferred from
an uncaptured screen. Subagent lifecycle events are ignored for parent-pane status.
DSH 0.2 still emits an empty `transcript_path` in Claude-compatible hooks. Its
session persistence defaults to compressed JSONL under `$DSH_HOME/sessions`.
Orca can resume a hook-associated session through `dsh-tui --resume <id>`, but
currently does not discover DSH logs in Agent Session History. Resume support alone
does not establish transcript-history support.
## Reproduce the official launcher check
Install `@deepseek-ai/dsh@0.2.0-rc.2` into a disposable prefix, then run:
```sh
ORCA_BACKGROUND_LAUNCH=1 ORCA_REAL_DSH_CLI=/path/to/prefix/node_modules/.bin/dsh \
pnpm test src/shared/dsh-real-cli.test.ts
```
The opt-in test checks published version, composed profile configurations, and
headless help in an isolated home and working folder without a model request.
Interactive readiness is separately pinned to the captured community TUI transcript
in `src/main/runtime/__fixtures__/dsh-tui-ready-no-key.txt`; that older capture is
not proof of current TUI compatibility or paid generation.
+24
View File
@@ -9,6 +9,14 @@ function tokens(commandLine: string): string[] {
describe('isDshNonInteractiveCommand', () => {
it.each([
'dsh web',
'dsh headless "run the tests"',
'dsh sdk',
'dsh sdk-minimal',
'dsh acp',
'dsh desktop',
'dsh dsh-tui --dump-config',
'dsh dsh-tui --dump-default-config',
'dsh dsh-tui --dump-config-schema',
'dsh --profile web',
'dsh --profile=web --port 8080',
'dsh --profile headless "run the tests"',
@@ -23,6 +31,14 @@ describe('isDshNonInteractiveCommand', () => {
it.each([
'dsh --profile dsh-tui',
'dsh dsh-tui',
'dsh tui --resume web',
'dsh tui --patch web --resume sdk',
'dsh tui --patch=web --resume sdk',
'dsh --profile tui --patch headless --resume sdk',
'dsh tui --from-default-profile web --resume sdk',
'dsh tui --resume session --dump-config',
'dsh --profile tui --resume session --dump-config',
'dsh --profile=dsh-tui',
'dsh --profile dsh-tui --resume 9478e2d8-29bc-4009-ab32-657efa2bd763',
'dsh-tui',
@@ -66,6 +82,14 @@ describe('dsh foreground process recognition', () => {
it.each([
'dsh web',
'dsh headless "summarize the diff"',
'dsh sdk',
'dsh sdk-minimal',
'dsh acp',
'node /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js headless "go"',
'node /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js sdk',
'node --no-warnings /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js sdk',
'C:\\Tools\\dsh.cmd headless go',
'dsh --profile headless "summarize the diff"',
'node /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js --profile acp'
])('does not claim %s as an interactive agent pane', (commandLine) => {
+15 -30
View File
@@ -1,11 +1,6 @@
// DeepSeek Harness ships one binary, `dsh`, and boots a *profile* with it. Only the
// `dsh-tui` profile paints an interactive composer; `web` serves HTTP, `headless` answers
// one task and exits, `sdk`/`sdk-minimal`/`acp` speak JSON-RPC on stdio, and `plugin` is
// package management. All five are `dsh` in the process table, so foreground recognition
// has to read the profile to tell an agent pane from a server or a one-shot.
//
// `dsh-tui` (and its `dst` alias) forward straight to `dsh --profile dsh-tui`, so they are
// always interactive and never reach this matcher.
import { findInterpreterEntrypointToken } from './agent-command-line-entrypoint'
// DSH 0.2 accepts positional profiles; community dsh-tui/dst choose their own TUI profile.
/** Profiles that boot something other than the interactive terminal UI. */
const NON_INTERACTIVE_PROFILES = new Set([
@@ -17,9 +12,8 @@ const NON_INTERACTIVE_PROFILES = new Set([
'desktop'
])
/** Bare-word subcommands the launcher accepts: `plugin` boots no profile at all, and `web`
* is the documented alias for `--profile web`. Neither hosts an interactive pane. */
const SUBCOMMANDS = new Set(['plugin', 'web'])
/** Plugin management never boots an interactive pane. */
const SUBCOMMANDS = new Set(['plugin'])
/** Launcher flags that print a composed config and exit. */
const DUMP_FLAGS = new Set(['--dump-config', '--dump-default-config', '--dump-config-schema'])
@@ -69,30 +63,21 @@ function isLauncherToken(token: string): boolean {
* and a prompt or session id is free text that must never be read as a launcher flag.
*/
export function isDshNonInteractiveCommand(tokens: readonly string[]): boolean {
// Why first: `dsh-tui`/`dst` have already chosen the interactive profile, and everything
// after them is the TERMINAL APP's argv — a `--resume` id or a workspace target. A
// workspace folder named `web` or `plugin` is an ordinary directory name, and reading it
// as a `dsh` subcommand would mark a live agent pane non-interactive, costing it status
// hooks and prompt delivery. Index 1 as well as 0 because a node shim puts the launcher
// script path there.
if (
TUI_LAUNCHER_NAMES.has(programBasename(tokens[0])) ||
TUI_LAUNCHER_NAMES.has(programBasename(tokens[1]))
) {
const firstProgram = programBasename(tokens[0])
const entrypoint = findInterpreterEntrypointToken([...tokens], firstProgram)
const programIndex = entrypoint === null ? 0 : tokens.indexOf(entrypoint)
if (TUI_LAUNCHER_NAMES.has(programBasename(tokens[programIndex]))) {
return false
}
const indexOfArgs = programIndex + 1
let index = indexOfArgs
let profile: string | null = null
let index = 1
// Skip the leading non-flag tokens: an interpreter invocation puts the script path here.
while (index < tokens.length && !isLauncherToken(tokens[index])) {
const token = tokens[index]
if (SUBCOMMANDS.has(token)) {
const first = tokens[index]
if (first !== undefined && !first.startsWith('-')) {
if (SUBCOMMANDS.has(first)) {
return true
}
if (!token.startsWith('-') && index > 1) {
// A bare word that is neither a subcommand nor a flag: the app's arguments start here.
return false
}
profile = first
index += 1
}
for (; index < tokens.length; index += 1) {
+53
View File
@@ -0,0 +1,53 @@
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { runProcess } from './child-process/run-process'
import { recognizeAgentProcessFromCommandLine } from './agent-process-recognition'
const binary = process.env.ORCA_REAL_DSH_CLI
// No model call: published profile configuration and help establish the launcher contract.
describe.skipIf(!binary)('published DSH 0.2 CLI', () => {
it('keeps positional and explicit headless profiles outside interactive recognition', async () => {
if (!binary) {
throw new Error('Set ORCA_REAL_DSH_CLI to the official dsh executable')
}
const root = await mkdtemp(join(tmpdir(), 'orca-dsh-cli-'))
const env = { ...process.env, DSH_HOME: root, DEEPSEEK_API_KEY: '' }
try {
const version = await runProcess({ program: binary, args: ['--version'], cwd: root, env })
expect(version.code).toBe(0)
expect(version.stdout.trim()).toMatch(/^0\.2\./)
for (const profile of ['headless', 'sdk', 'sdk-minimal', 'acp']) {
for (const args of [[profile], ['--profile', profile]]) {
const result = await runProcess({
program: binary,
args: [...args, '--dump-default-config'],
cwd: root,
env,
timeoutMs: 30_000
})
expect(result.code).toBe(0)
expect(result.stdout).toContain('@deepseek-ai/dsh-')
const command = `"${binary}" ${args.join(' ')}`
expect(recognizeAgentProcessFromCommandLine(command)).toBeNull()
expect(
recognizeAgentProcessFromCommandLine(command, { includeHeadlessOneShot: true })?.agent
).toBe('dsh')
}
}
const help = await runProcess({
program: binary,
args: ['headless', '--help'],
cwd: root,
env,
timeoutMs: 30_000
})
expect(help.code).toBe(0)
expect(help.stdout).toContain('Answer one task and exit')
} finally {
await rm(root, { recursive: true, force: true })
}
}, 120_000)
})
+21
View File
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest'
import { buildAgentStartupPlan } from './tui-agent-startup'
import { TUI_AGENT_CONFIG } from './tui-agent-config'
describe('DSH first launch', () => {
it.each(['darwin', 'linux', 'win32'] as const)(
'selects the current workspace before pasting the task on %s',
(platform) => {
const plan = buildAgentStartupPlan({
agent: 'dsh',
prompt: 'Review this folder',
cmdOverrides: {},
platform
})
expect(plan?.launchCommand).toContain('dsh-tui .')
expect(plan?.launchCommand).not.toContain('Review this folder')
expect(TUI_AGENT_CONFIG.dsh.promptInjectionMode).toBe('stdin-after-start')
expect(TUI_AGENT_CONFIG.dsh.draftPasteReadySignal).toBe('dsh-composer-prompt')
}
)
})
+3 -10
View File
@@ -290,21 +290,14 @@ const TUI_AGENT_CONFIG_SOURCE: Record<TuiAgent, TuiAgentConfigSource> = {
promptInjectionMode: 'stdin-after-start'
},
dsh: {
// Why: DeepSeek Harness publishes one binary (`dsh`) that boots a profile, and only the
// `dsh-tui` profile paints a composer. `dsh-tui` (alias `dst`) is the launcher that
// selects it, so detect that and require `dsh` too — the launcher delegates to it and
// fails without it.
detectCmd: 'dsh-tui',
detectCmdAliases: ['dst'],
detectRequiredCommands: ['dsh'],
// Why: the launcher re-execs `dsh --profile dsh-tui`, so the pane's foreground process
// is `dsh`, never `dsh-tui`. Readiness and follow-up delivery key off this name.
// A first bare launch opens the session browser instead of the composer.
launchCmd: 'dsh-tui .',
expectedProcess: 'dsh',
// Why: the terminal app parses only `--resume`/`--continue` and a workspace target; it
// has no prompt flag, so the first prompt is pasted into the composer after startup.
promptInjectionMode: 'stdin-after-start',
// Why: DSH-TUI animates a whale intro continuously behind its composer, so the default
// quiet window never settles (the grok failure mode). See dsh-tui-ready-no-key.txt.
// The whale intro keeps repainting behind the ready composer.
draftPasteReadySignal: 'dsh-composer-prompt'
},
zcode: {