mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
feat(bash): resolve executor config through the bash settings namespace
The capability's namespace is owned by the seam because it names the capability, not an implementation: a host composes exactly one provider of ctx.bash, so both executor families register the same namespace with their own schema and composition entry without ever colliding, and a settings document carried between platforms keeps resolving on both. Both executors read their config through a source thunk, so a stored change reaches the next command. The constructor checks the schema cannot express become the section validator, refusing a bad value at the write instead of at the next command. pwsh re-resolves its executable only when the declared path changed, so an unrelated settings change never re-probes the filesystem.
This commit is contained in:
@@ -11,9 +11,10 @@
|
||||
|
||||
import { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import { BashExecutor } from '@deepseek-ai/dsh-bash'
|
||||
import { BASH_SETTINGS_NAMESPACE, BashExecutor } from '@deepseek-ai/dsh-bash'
|
||||
import type { BashExecRequest, BashExecSpec, BashProcess, BashProcessRead, BashRunResult, CollectedOutput } from '@deepseek-ai/dsh-bash'
|
||||
import type { SubprocessCollect, SubprocessHandle, SubprocessOutputReader, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
|
||||
import { installSettingsSection } from '@deepseek-ai/dsh-settings'
|
||||
import { clampTimeout, deadline, MAX_TIMER_DELAY_MS, timeoutOf } from '@deepseek-ai/dsh-timeout'
|
||||
|
||||
/**
|
||||
@@ -71,6 +72,26 @@ function assertPositiveFinite(name: string, value: number): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject a resolved section this executor could not run with. The schema
|
||||
* expresses neither "positive and finite" nor the timer bound `graceMs` has to
|
||||
* fit, so a stored value is refused where it is written instead of failing at
|
||||
* the next command.
|
||||
* @param config - the resolved section, schema-valid by construction.
|
||||
* @throws Error naming the field that cannot be used.
|
||||
*/
|
||||
export function assertServiceableBashConfig(config: Config): void {
|
||||
const resolved = config as ResolvedConfig
|
||||
assertPositiveFinite('timeoutMs', resolved.timeoutMs)
|
||||
assertPositiveFinite('maxTimeoutMs', resolved.maxTimeoutMs)
|
||||
assertPositiveFinite('maxOutputBytes', resolved.maxOutputBytes)
|
||||
assertPositiveFinite('maxSpillBytes', resolved.maxSpillBytes)
|
||||
assertPositiveFinite('graceMs', resolved.graceMs)
|
||||
if (resolved.graceMs > MAX_TIMER_DELAY_MS) {
|
||||
throw new Error(`bash-local: graceMs must be no greater than ${MAX_TIMER_DELAY_MS}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local bash executor over `ctx.subprocess`. Bounded output, spill files, and
|
||||
* process-group SIGTERM→SIGKILL escalation are the subprocess service's
|
||||
@@ -90,21 +111,29 @@ export class LocalBashExecutor extends BashExecutor {
|
||||
graceMs: z.number().default(DEFAULT_GRACE_MS),
|
||||
})
|
||||
|
||||
/** The currently authoritative config: the settings section, or the composition entry. */
|
||||
private source: () => ResolvedConfig
|
||||
|
||||
/** Validated config (schemastery applied the defaults before construction). */
|
||||
readonly config: ResolvedConfig
|
||||
get config(): ResolvedConfig {
|
||||
return this.source()
|
||||
}
|
||||
|
||||
constructor(ctx: Context, config: Config) {
|
||||
super(ctx)
|
||||
// Schemastery fills these fields before construction; the type does not encode that step.
|
||||
this.config = config as ResolvedConfig
|
||||
assertPositiveFinite('timeoutMs', this.config.timeoutMs)
|
||||
assertPositiveFinite('maxTimeoutMs', this.config.maxTimeoutMs)
|
||||
assertPositiveFinite('maxOutputBytes', this.config.maxOutputBytes)
|
||||
assertPositiveFinite('maxSpillBytes', this.config.maxSpillBytes)
|
||||
assertPositiveFinite('graceMs', this.config.graceMs)
|
||||
if (this.config.graceMs > MAX_TIMER_DELAY_MS) {
|
||||
throw new Error(`bash-local: graceMs must be no greater than ${MAX_TIMER_DELAY_MS}`)
|
||||
}
|
||||
const entry = config as ResolvedConfig
|
||||
assertServiceableBashConfig(entry)
|
||||
this.source = () => entry
|
||||
installSettingsSection(ctx, BASH_SETTINGS_NAMESPACE, LocalBashExecutor.Config, entry, {
|
||||
validate: assertServiceableBashConfig,
|
||||
setSource: (current) => {
|
||||
this.source = current as () => ResolvedConfig
|
||||
},
|
||||
// Every field is read through the getter at each command, so nothing
|
||||
// derived from the source needs rebuilding when the document changes.
|
||||
onChange: () => {},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user