docs: trim generated prose
This commit is contained in:
@@ -1,14 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* The `dsh-stdio-agent` bin: boot a Cordis app from a leaf `cordis.yml` that
|
||||
* loads the {@link @deepseek-ai/dsh-stdio-agent} app plugin (plus a backend LLM
|
||||
* adapter and a bash executor). The boot glue — `.env` loading, the fail-loud
|
||||
* Loader guards, the settle-the-tree boot sequence — lives in
|
||||
* {@link @deepseek-ai/dsh-app-boot}, shared with the ACP bin.
|
||||
*
|
||||
* Usage: `dsh-stdio-agent [path-to-cordis.yml]` (default `./cordis.yml`). The
|
||||
* `demo:echo` / `demo:repl` scripts invoke it with the example's config.
|
||||
*
|
||||
* The `dsh-stdio-agent` bin: boot a Cordis app from a leaf `cordis.yml` that loads the {@link
|
||||
* @deepseek-ai/dsh-stdio-agent} app plugin (plus a backend LLM adapter and a bash executor).
|
||||
* @module @deepseek-ai/dsh-stdio-agent/bin
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,40 +1,8 @@
|
||||
/**
|
||||
* The stdio chat app: the default agent spine ({@link
|
||||
* @deepseek-ai/dsh-agent-core}) plus the coupled front-door cluster a terminal
|
||||
* chat needs — a console logger, the readline UI (the in-package `stdio-chat`
|
||||
* module), JSONL session
|
||||
* persistence, and a pre-created `main` agent the UI drives.
|
||||
*
|
||||
* The cluster is BAKED IN, not left to the leaf: a stdio app always logs to the
|
||||
* console (stdout is just the terminal) and always pre-creates the `main` agent
|
||||
* the readline UI sends to. The leaf supplies the swappable backends (the LLM
|
||||
* adapter, the bash executor), optional product tools, the optional `hmr`
|
||||
* dev-reload plugin, and this app's {@link Config} (model, prompt, persistence
|
||||
* root, welcome banner).
|
||||
*
|
||||
* `hmr` is deliberately a LEAF entry, not baked in here: it is a Loader-only,
|
||||
* subprocess-only dev plugin (its constructor throws without `--expose-internals`
|
||||
* + a live `loader`, and the in-process test tier cannot even import it), so a
|
||||
* package whose `apply` statically pulled it in could never be unit-tested or
|
||||
* carry the per-file coverage gate. Unlike the console logger, a stray `hmr` is
|
||||
* not a stdout-purity footgun — so leaving it at the leaf costs no safety, while
|
||||
* baking the LOGGER in (the real coupling) keeps stdout-vs-no-stdout a property
|
||||
* of the artifact.
|
||||
*
|
||||
* Counterpart to {@link @deepseek-ai/dsh-acp-agent}, which bakes in the OPPOSITE
|
||||
* cluster (no stdout logger, no pre-created agents — the ACP bridge reserves
|
||||
* stdout for JSON-RPC and creates agents on demand). Splitting the two front
|
||||
* doors into two packages makes each cluster a property of the artifact: there
|
||||
* is no logger entry in the ACP leaf to get wrong.
|
||||
*
|
||||
* Plugin export shape: named `name`/`Config`/`apply`, NO default export — the
|
||||
* cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray
|
||||
* default would collapse the module to the bare `apply` and drop the `Config`
|
||||
* namespace (see docs/postmortem/0001). This app carries no `inject`, so a
|
||||
* collapsed shape would BOOT rather than crash a smoke — the shape is pinned by
|
||||
* the explicit `unwrapExports` assertion in this package's unit suite, and the
|
||||
* keyless echo smoke proves the composed tree runs through the real Loader.
|
||||
*
|
||||
* The stdio chat app: the default agent spine ({@link @deepseek-ai/dsh-agent-core}) plus the
|
||||
* coupled front-door cluster a terminal chat needs — a console logger, the readline UI (the
|
||||
* in-package `stdio-chat` module), JSONL session persistence, and a pre-created `main` agent
|
||||
* the UI drives.
|
||||
* @module @deepseek-ai/dsh-stdio-agent
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/**
|
||||
* The stdio app's readline UI: reads lines from stdin → `agent.send()`/
|
||||
* `steer()`, and renders the durable transcript to stdout. A UI is "just a
|
||||
* plugin" — it consumes the `session/event` feed (the assistant token stream,
|
||||
* turn/step boundaries, tool activity, todos) plus a few `agent/*` control
|
||||
* events (`agent/status`, `agent/created`/`agent/disposed`) and the `agents`
|
||||
* service. Dimmed chain-of-thought rendering plus robust piped-stdin EOF→idle
|
||||
* exit handling, configured via {@link Config}.
|
||||
*
|
||||
* An internal module of the stdio app, not a package of its own: the app's
|
||||
* front-door cluster always includes this UI, and nothing else composes it.
|
||||
* The export shape stays named `name`/`inject`/`Config`/`apply` — the plugin
|
||||
* contract the app's `ctx.plugin(uiStdio, …)` mount consumes.
|
||||
*
|
||||
* The stdio app's readline UI: reads lines from stdin → `agent.send()`/ `steer()`, and renders
|
||||
* the durable transcript to stdout.
|
||||
* @module @deepseek-ai/dsh-stdio-agent/stdio-chat
|
||||
*/
|
||||
|
||||
@@ -80,15 +69,10 @@ type OptionSelection =
|
||||
| { kind: 'invalid' }
|
||||
|
||||
/**
|
||||
* The plugin body, parameterized over its I/O runtime. `apply` is the thin
|
||||
* production wrapper that binds the real `process` streams; tests call this
|
||||
* directly with fakes. Returns nothing — all registration is via `ctx.on`/
|
||||
* `ctx.effect`, so fiber disposal tears every listener and the readline
|
||||
* interface down.
|
||||
* @param ctx - the context supplying the `agents` service and the event feeds.
|
||||
* @param config - the plugin config; defaults are re-applied here for direct
|
||||
* callers that bypass Loader validation.
|
||||
* @param runtime - the process-I/O seam (line source, render sink, exit hook).
|
||||
* Register stdio chat against an injectable I/O runtime.
|
||||
* @param ctx - agent and event context.
|
||||
* @param config - plugin config, defaulted for direct callers.
|
||||
* @param runtime - line source, render sink, and exit hook.
|
||||
*/
|
||||
export function createStdioChat(ctx: Context, config: Config, runtime: StdioRuntime): void {
|
||||
// Default here too (not just via schemastery's `.default()`): this helper is
|
||||
@@ -99,26 +83,16 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
|
||||
const agentId = AgentId(config.agent ?? 'main')
|
||||
const { input, output, exit } = runtime
|
||||
|
||||
// Render label lookup: the `turn/start` session event carries only the turn
|
||||
// number, so to print the short agent id (`[main turn 1]`) we map the
|
||||
// session's id to its agent's id. The session id is not reliably the agent id
|
||||
// (a session can be created with an explicit/client-supplied id), so build the
|
||||
// map from `agent/created` rather than parsing the id string. Seed from the
|
||||
// registry's current agents first: an agent registered before this plugin
|
||||
// installed (e.g. the pre-created `main` agent, or any agent surviving an HMR
|
||||
// reload of just this fiber) already fired its `agent/created`, so the live
|
||||
// listener alone would miss it and its turns would fall back to the raw
|
||||
// session id.
|
||||
// Render label lookup: the `turn/start` session event carries only the turn number, so to
|
||||
// print the short agent id (`[main turn 1]`) we map the session's id to its agent's id.
|
||||
const labelBySession = new Map<string, string>()
|
||||
for (const agent of ctx.agents.list()) labelBySession.set(agent.session.header.id, agent.id)
|
||||
ctx.on('agent/created', (agent) => { labelBySession.set(agent.session.header.id, agent.id) })
|
||||
ctx.on('agent/disposed', (agent) => { labelBySession.delete(agent.session.header.id) })
|
||||
|
||||
// Transcript rendering off the durable `session/event` feed — the assistant
|
||||
// token stream, turn/step boundaries, tool activity, and todos all come from
|
||||
// the one canonical stream (no agent/* mirrors). A single listener over the
|
||||
// append order keeps `inReasoning` transitions deterministic across chunk and
|
||||
// boundary events.
|
||||
// Transcript rendering off the durable `session/event` feed — the assistant token stream,
|
||||
// turn/step boundaries, tool activity, and todos all come from the one canonical stream (no
|
||||
// agent/* mirrors).
|
||||
let inReasoning = false
|
||||
ctx.on('session/event', (session, event) => {
|
||||
if (event.type === 'assistant/chunk') {
|
||||
@@ -161,16 +135,9 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
|
||||
|
||||
ctx.effect(() => {
|
||||
const reader = createInterface({ input, output, terminal: isTTYPair(input, output) })
|
||||
// Piped-input exit, once stdin reaches EOF:
|
||||
// - If no line ever submitted work (empty stdin, blank-only lines), exit
|
||||
// immediately — no turn will ever start, so there is nothing to wait
|
||||
// for. (Gating on an observed 'running' here would hang forever.)
|
||||
// - If work WAS submitted, exit the next time the agent settles to idle
|
||||
// AFTER having run. Two subtleties this handles: the loop batches
|
||||
// several queued messages into ONE turn (one idle), so we don't count
|
||||
// sends; and agent.send() does NOT synchronously flip status to
|
||||
// 'running', so requiring an observed 'running' first (`sawRunning`)
|
||||
// avoids exiting in the gap before the turn starts and dropping work.
|
||||
// Piped-input exit, once stdin reaches EOF: - If no line ever submitted work (empty stdin,
|
||||
// blank-only lines), exit immediately — no turn will ever start, so there is nothing to
|
||||
// wait for.
|
||||
let stdinClosed = false
|
||||
let disposed = false
|
||||
let submittedWork = false
|
||||
@@ -188,10 +155,7 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
|
||||
const agent = ctx.agents.get(agentId)
|
||||
if (agent && agent.status !== 'idle') return // a turn is still running
|
||||
}
|
||||
// Let any final output flush, then exit. The handle is tracked so the
|
||||
// disposer can cancel it — a dispose within the flush window must not let
|
||||
// the process exit out from under HMR. Re-entrant `maybeExit` calls (e.g.
|
||||
// repeated idle signals) coalesce onto the one pending timer.
|
||||
// Let any final output flush, then exit.
|
||||
if (exitTimer !== undefined) {
|
||||
return // exit already scheduled — coalesce re-entrant calls
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user