2026-07-14 03:37:51 +08:00
/ * *
* Model - facing result rendering for the bash tool .
*
* @module @deepseek - ai / dsh - tool - bash / render
* /
2026-08-13 00:36:22 +08:00
import type { ShellProcessRead , ShellRunResult , ShellSandboxInfo , CollectedOutput } from '@deepseek-ai/dsh-shell'
2026-07-14 03:37:51 +08:00
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
2026-07-15 21:39:02 +08:00
import { escalationHintMarker , sandboxDenialMarker } from '@deepseek-ai/dsh-sandbox'
2026-07-14 03:37:51 +08:00
/** Append the truncation notice (with the full-output spill path) to a stream's text. */
function streamText ( output : CollectedOutput ) : string {
if ( ! output . truncated ) return output . text
return ` ${ output . text } \ n[output truncated; full output: ${ output . spillPath ? ? '(unavailable)' } ] `
}
/ * *
* Shape one finished run into the text the model sees : stdout , then a marked
docs(tasks): condense background task prose
The background-task change repeated its lifecycle design across implemented RFCs, package READMEs, JSDoc, test commentary, and model-visible schemas. That repetition obscured the contracts that maintainers must preserve and added avoidable prompt tokens.
Rewrite the implemented RFCs around the current design, keep authorization, exact-owner cleanup, wait/abort ordering, producer quiescence, and teardown-failure guarantees at their owning surfaces, and remove peer surveys, review history, control-flow narration, and emphatic restatement.
Shorten the task and subagent schema wording, synchronize the bilingual tool cookbook, and regenerate the config, service, RFC, tool, and replay snapshot derivatives. Runtime behavior is unchanged; test edits update prose-only assertions and descriptions.
2026-07-15 21:08:58 +08:00
* stderr section , then exit - status markers . Non - zero exits are reported , not
2026-07-14 03:37:51 +08:00
* errored — the model decides how to react ; only infrastructure failures
* ( spawn errors , aborts ) surface as isError results .
* @param result - the completed foreground run from the executor .
* @param escalationModes - the escalation targets this composition advertises ;
* non - empty adds the same - turn escalation hint after a denial marker
* ( default ` [] ` : no hint ) .
* @returns the model - facing text : output body ( or ` (no output) ` ) , then any timeout / signal / exit markers , each on its own line .
* /
export function renderResult (
2026-08-13 00:36:22 +08:00
result : ShellRunResult ,
2026-07-14 03:37:51 +08:00
escalationModes : readonly SandboxMode [ ] = [ ] ,
) : string {
const out = streamText ( result . stdout )
const err = streamText ( result . stderr )
let body = out
if ( err . length > 0 ) {
// Single newline between sections (stdout usually ends with one already).
if ( body . length > 0 && ! body . endsWith ( '\n' ) ) body += '\n'
body += ` [stderr] \ n ${ err } `
}
if ( body . length === 0 ) body = '(no output)'
const markers : string [ ] = [ ]
docs(tasks): condense background task prose
The background-task change repeated its lifecycle design across implemented RFCs, package READMEs, JSDoc, test commentary, and model-visible schemas. That repetition obscured the contracts that maintainers must preserve and added avoidable prompt tokens.
Rewrite the implemented RFCs around the current design, keep authorization, exact-owner cleanup, wait/abort ordering, producer quiescence, and teardown-failure guarantees at their owning surfaces, and remove peer surveys, review history, control-flow narration, and emphatic restatement.
Shorten the task and subagent schema wording, synchronize the bilingual tool cookbook, and regenerate the config, service, RFC, tool, and replay snapshot derivatives. Runtime behavior is unchanged; test edits update prose-only assertions and descriptions.
2026-07-15 21:08:58 +08:00
// Keep the exit marker last because parseExitStatus anchors there.
2026-07-14 03:37:51 +08:00
if ( result . sandbox ? . denied ) {
2026-07-15 21:39:02 +08:00
markers . push ( sandboxDenialMarker ( result . sandbox . mode ) )
docs(tasks): condense background task prose
The background-task change repeated its lifecycle design across implemented RFCs, package READMEs, JSDoc, test commentary, and model-visible schemas. That repetition obscured the contracts that maintainers must preserve and added avoidable prompt tokens.
Rewrite the implemented RFCs around the current design, keep authorization, exact-owner cleanup, wait/abort ordering, producer quiescence, and teardown-failure guarantees at their owning surfaces, and remove peer surveys, review history, control-flow narration, and emphatic restatement.
Shorten the task and subagent schema wording, synchronize the bilingual tool cookbook, and regenerate the config, service, RFC, tool, and replay snapshot derivatives. Runtime behavior is unchanged; test edits update prose-only assertions and descriptions.
2026-07-15 21:08:58 +08:00
// Hint only when the composition exposes escalation, before the final exit marker.
2026-07-14 03:37:51 +08:00
if ( escalationModes . length > 0 ) {
2026-07-15 21:39:02 +08:00
markers . push ( escalationHintMarker ( 'command' ) )
2026-07-14 03:37:51 +08:00
}
}
docs(tasks): condense background task prose
The background-task change repeated its lifecycle design across implemented RFCs, package READMEs, JSDoc, test commentary, and model-visible schemas. That repetition obscured the contracts that maintainers must preserve and added avoidable prompt tokens.
Rewrite the implemented RFCs around the current design, keep authorization, exact-owner cleanup, wait/abort ordering, producer quiescence, and teardown-failure guarantees at their owning surfaces, and remove peer surveys, review history, control-flow narration, and emphatic restatement.
Shorten the task and subagent schema wording, synchronize the bilingual tool cookbook, and regenerate the config, service, RFC, tool, and replay snapshot derivatives. Runtime behavior is unchanged; test edits update prose-only assertions and descriptions.
2026-07-15 21:08:58 +08:00
// A command may trap SIGTERM and exit 0 after timeout; still report interruption.
2026-07-14 03:37:51 +08:00
if ( result . timedOut ) markers . push ( ` [timed out after ${ result . timeoutMs } ms] ` )
if ( result . signal !== null ) {
markers . push ( ` [killed by signal: ${ result . signal } ] ` )
} else if ( result . exitCode !== 0 ) {
markers . push ( ` [exit code: ${ result . exitCode } ] ` )
}
if ( markers . length === 0 ) return body
if ( ! body . endsWith ( '\n' ) ) body += '\n'
return body + markers . join ( '\n' )
}
2026-07-14 03:45:22 +08:00
2026-07-15 13:38:17 +08:00
/ * *
2026-08-13 00:36:22 +08:00
* Shape one background - process read into the ` job_output ` delta the model
2026-07-15 13:38:17 +08:00
* sees : the incremental delta , plus the lossy - read notice ( with full - stream
* spill paths ) when in - memory truncation dropped unread bytes . Empty - delta
2026-08-13 00:36:22 +08:00
* rendering ( ` (no new output) ` ) is the generic job controller ' s job .
2026-07-15 13:38:17 +08:00
* @param read - one incremental read from the process handle .
* @param sandbox - settled sandbox facts , when this was a confined process .
* @param escalationModes - escalation targets advertised by this composition .
* @returns the delta text with any loss or sandbox notice appended .
* /
export function renderProcessRead (
2026-08-13 00:36:22 +08:00
read : ShellProcessRead ,
sandbox? : ShellSandboxInfo ,
2026-07-15 13:38:17 +08:00
escalationModes : readonly SandboxMode [ ] = [ ] ,
) : string {
const notices : string [ ] = [ ]
if ( read . lossy ) {
const paths = [ read . stdoutSpillPath , read . stderrSpillPath ] . filter ( ( path ) : path is string = > path !== undefined )
notices . push ( ` [some output was dropped from memory; full output: ${ paths . length > 0 ? paths . join ( ', ' ) : '(unavailable)' } ] ` )
}
if ( sandbox ? . runnerFailed ) {
notices . push ( ` [sandbox: the sandbox runner itself failed under ${ sandbox . mode } mode — the command did not run; this is a sandbox problem, not a command failure] ` )
} else if ( sandbox ? . denied ) {
2026-07-16 23:31:51 +08:00
notices . push ( sandboxDenialMarker ( sandbox . mode ) )
2026-07-15 13:38:17 +08:00
if ( escalationModes . length > 0 ) {
2026-07-16 23:31:51 +08:00
notices . push ( escalationHintMarker ( 'command' ) )
2026-07-15 13:38:17 +08:00
}
}
if ( notices . length === 0 ) return read . delta
return ` ${ read . delta } ${ read . delta . length > 0 && ! read . delta . endsWith ( '\n' ) ? '\n' : '' } ${ notices . join ( '\n' ) } `
}
2026-07-14 03:45:22 +08:00
/ * *
2026-08-05 02:05:06 +08:00
* The exit - status parse is the shared marker - contract half of the shell - tool
2026-08-13 00:36:22 +08:00
* rendering story , owned by ` @deepseek-ai/dsh-shell ` so ` dsh-tool-pwsh ` reuses
2026-08-05 02:05:06 +08:00
* it ( its renderer emits the same markers ) . Re - exported here to keep
* ` ../src/render.ts ` a single import root for bash - tool consumers .
2026-07-29 10:00:29 +08:00
* /
2026-08-13 00:36:22 +08:00
export { parseExitStatus , type ParsedExitStatus } from '@deepseek-ai/dsh-shell'