docs: align one-shot demo prose

This commit is contained in:
Tianyi Cui
2026-07-19 13:25:30 +08:00
parent dbe9e307b0
commit 89987f0ad4
15 changed files with 38 additions and 53 deletions

View File

@@ -1,6 +1,6 @@
# @deepseek-ai/dsh-cli-demo
Headless one-shot app and bin for running one agent task without a readline or editor client. The app composes [`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md), JSONL persistence, and one fresh `main` agent; the bin submits one task, waits through all model and tool steps, emits the selected result, disposes to quiescence, and exits.
Headless one-shot app and bin for running one agent task without a readline or editor client. It composes [`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md), JSONL persistence, and exactly one fresh top-level agent. The bin submits the task, waits for its durable turn ending, renders the selected output, disposes to quiescence, and exits.
The package mounts no console logger, readline UI, user-interaction service, or `ask_user_question` tool. Stdout is reserved for the selected output format; diagnostics use stderr.
@@ -8,8 +8,8 @@ The package mounts no console logger, readline UI, user-interaction service, or
| Key | Default | Routed to |
|---|---|---|
| `provider` | required | the pre-created `main` agent's provider route |
| `model` | required | the pre-created `main` agent's model |
| `provider` | required | the configured agent's provider route |
| `model` | required | the configured agent's model |
| `persona` | — | the deployment persona in `dsh-system-prompt` |
| `toolOrder` | lexicographic | explicit model-facing tool order in `dsh-system-prompt` |
| `tools` | `{ mode: 'native' }` | tool-registry presentation config through `dsh-agent-spine-demo` |
@@ -17,8 +17,6 @@ The package mounts no console logger, readline UI, user-interaction service, or
| `persistenceRoot` | `./.sessions` | JSONL session root |
| `workspaceContext` | required | workspace-instruction byte budget, or `false` to disable loading |
Each process creates a new session whose workspace cwd is the launch directory. The app has no resume setting.
## CLI contract
```sh
@@ -39,7 +37,7 @@ Loader configs with bare package specifiers require `node --expose-internals` or
- `text` writes the last assistant message containing text, followed by one newline.
- `json` writes one DSH-native result record: `{ type: "result", success, sessionId, turn, result, reason, usage? }`. `usage` sums every model step in the task turn.
- `stream-json` writes each canonical event from the `main` session's task turn as `{ type: "session_event", sessionId, event }`, then the same result record. Child-agent activity appears only through the parent tool events and results.
- `stream-json` writes each canonical event from the top-level session's task turn as `{ type: "session_event", sessionId, event }`, then the same result record. Child-agent activity appears only through the parent tool events and results.
Only `reason.kind === "completed"` exits successfully. Other durable turn endings still emit partial text or a result record, add a stderr diagnostic, and exit nonzero. Argument and boot failures leave stdout empty. SIGINT and SIGTERM cancel active work, await disposal, and exit 130 and 143 respectively.
@@ -53,12 +51,12 @@ The headless-agent leaf supplies local bash, filesystem, skill, subagent, workfl
### One-shot task turn
**What the model sees**: The positional task becomes one user message. Through `dsh-agent-spine-demo`, the `main` agent also receives configured workspace instructions and persona, the skill catalog, visible tool schemas, and retained tool results needed for later steps in the same turn.
**What the model sees**: The positional task becomes one user message. Through `dsh-agent-spine-demo`, the top-level agent also receives configured workspace instructions and persona, the skill catalog, visible tool schemas, and retained tool results needed for later steps in the same turn.
**Token effect**: The task, prompt sections, tool schemas, assistant output, and tool results consume tokens on each model step. JSON event streaming and final rendering add no model tokens; delegated child work has its own model usage and is not included in the parent result's `usage` total.
## Known Limitations and Deferred Work
- **One fresh main session per process** — there is no resume, second prompt, stdin context, or concurrent top-level session in this app.
- **One fresh top-level session per process** — its workspace cwd is the launch directory; there is no resume, second prompt, stdin context, or concurrent top-level session in this app.
- **No interactive question or approval provider** — tools that require a human answer cannot complete unless a different leaf composes a non-interactive provider with explicit policy.
- **Streaming is main-session-only** — child sessions are not flattened into the stream, and aggregate usage covers only model steps recorded on the parent task turn.
- **Streaming is top-level-session-only** — child sessions are not flattened into the stream, and aggregate usage covers only model steps recorded on the parent task turn.

View File

@@ -1,6 +1,6 @@
/**
* Covered command parser and one-turn driver for `dsh-cli-demo`. The executable
* entry only installs process signal handlers and delegates here.
* Command parser and one-turn driver for `dsh-cli-demo`. The executable wrapper
* owns process signals; this module owns output, durability, and cleanup.
* @module @deepseek-ai/dsh-cli-demo/cli
*/
@@ -44,9 +44,9 @@ export interface CliResult {
export interface OneShotOptions {
/** Exactly one nonblank user task. */
readonly task: string
/** Optional cancellation signal owned by the process wrapper. */
/** Optional signal that cancels the selected agent. */
readonly signal?: AbortSignal
/** Synchronous observer for each canonical event in the selected task turn. */
/** Synchronous task-turn observer; a throw cancels the agent and fails the run after flush. */
readonly onEvent?: (sessionId: string, event: SessionEvent) => void
}
@@ -91,12 +91,10 @@ class CliInterruptedError extends Error {
}
}
/** Convert an unknown thrown value to an Error without losing its text. */
function toError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error))
}
/** Render the reason carried by an AbortSignal. */
function interruptionReason(signal: AbortSignal): string {
return signal.reason === undefined ? 'interrupted' : String(signal.reason)
}
@@ -145,7 +143,6 @@ export function parseCliArgs(args: readonly string[]): CliCommand {
}
}
/** Add one model step's usage into a detached turn total. */
function addUsage(total: TokenUsage | undefined, step: TokenUsage): TokenUsage {
const next: TokenUsage = {
inputTokens: (total?.inputTokens ?? 0) + step.inputTokens,
@@ -157,7 +154,6 @@ function addUsage(total: TokenUsage | undefined, step: TokenUsage): TokenUsage {
return next
}
/** Select the text blocks from an assistant message, or undefined when it has none. */
function assistantText(event: Extract<SessionEvent, { type: 'assistant/message' }>): string | undefined {
const blocks = event.data.content.filter(block => block.type === 'text')
return blocks.length === 0 ? undefined : blocks.map(block => block.text).join('')
@@ -189,8 +185,11 @@ async function waitForStartupIdle(agent: Agent, signal?: AbortSignal): Promise<v
* Run one message-triggered turn on the configured top-level agent, aggregate its
* final text and model usage, wait for idle plus an explicit persistence flush,
* and return its durable ending. Only the selected agent's task turn reaches
* `onEvent`; startup injections and unrelated sessions are ignored.
* @param ctx - settled Loader root containing `ctx.agents` and `ctx.sessions`.
* `onEvent`; startup injections and unrelated sessions are ignored. The context
* must contain exactly one top-level agent. Signal abort cancels that agent; an
* abort before the correlated task turn rejects. An observer throw cancels the
* turn and is rethrown after the agent reaches idle and the session flushes.
* @param ctx - settled Loader root containing one agent plus `ctx.sessions`.
* @param options - task, optional cancellation, and optional stream observer.
* @returns the DSH-native result envelope after durable quiescence.
*/
@@ -291,7 +290,6 @@ export async function runOneShot(ctx: Context, options: OneShotOptions): Promise
}
}
/** Render one final result in the selected output encoding. */
function renderResult(outputFormat: OutputFormat, result: CliResult): string {
return outputFormat === 'text' ? `${result.result}\n` : `${JSON.stringify(result)}\n`
}
@@ -315,9 +313,8 @@ export function formatTurnFailure(reason: TurnEndReason): string {
}
/**
* Parse, boot, run, render, diagnose, and dispose one CLI invocation. Argument
* and boot failures never write stdout; all booted contexts are disposed before
* this promise resolves.
* Execute one CLI invocation. Argument and boot failures never write stdout;
* every booted context is disposed before this promise resolves.
* @param args - arguments after the executable name.
* @param runtime - optional injected process boundaries for tests and embedding.
* @returns the ordinary process exit code; the thin bin overrides it for Unix signals.

View File

@@ -1,6 +1,6 @@
/**
* Headless one-shot app composition: the default agent spine, JSONL session
* persistence, and one pre-created `main` agent. The CLI driver owns task
* persistence, and one fresh top-level agent. The CLI driver owns task
* submission and output; the app deliberately mounts no interactive or logging
* front door so stdout remains protocol-pure.
* @module @deepseek-ai/dsh-cli-demo
@@ -18,11 +18,11 @@ const DEFAULT_PERSISTENCE_ROOT = './.sessions'
export const name = 'cli-demo'
/** App config forwarded to the spine, pre-created agent, and JSONL backend. */
/** App config forwarded to the spine, configured agent, and JSONL backend. */
export interface Config {
/** Provider route for the `main` agent. */
/** Provider route for the configured agent. */
provider: string
/** Model name for the `main` agent; a matching adapter must be registered. */
/** Model name for the configured agent; a matching adapter must be registered. */
model: string
/** Deployment persona forwarded to the system-prompt plugin. */
persona?: string
@@ -51,7 +51,7 @@ export const Config: z<Config> = z.object({
})
/**
* Compose the UI-less spine, a fresh `main` agent rooted at the process cwd,
* Compose the UI-less spine, a fresh top-level agent rooted at the process cwd,
* and JSONL persistence. Swappable adapters, executors, and product tools stay
* in the leaf `cordis.yml`.
* @param ctx - app context that owns the composed child plugins.