refactor(bash): centralize managed env prefix
This commit is contained in:
@@ -70,8 +70,8 @@ import type {} from '@deepseek-ai/dsh-system-prompt'
|
||||
// stays optional at runtime, same pattern as dsh-tools' ask routing).
|
||||
import type {} from '@deepseek-ai/dsh-user-approval'
|
||||
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
|
||||
import { BashTaskId, OwnerToken, effectiveSandboxMode } from '@deepseek-ai/dsh-bash'
|
||||
import type { BashRunResult, BashTask, CollectedOutput, DshEnvironment } from '@deepseek-ai/dsh-bash'
|
||||
import { BashTaskId, DSH_ENV_PREFIX, OwnerToken, effectiveSandboxMode } from '@deepseek-ai/dsh-bash'
|
||||
import type { BashRunResult, BashTask, CollectedOutput, DshEnvironment, DshEnvironmentKey } from '@deepseek-ai/dsh-bash'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
@@ -108,13 +108,13 @@ export interface BashEnvContributor {
|
||||
/** Stable contributor name used in diagnostics and duplicate detection. */
|
||||
name: string
|
||||
/** Complete set of `DSH_*` keys this contributor may return. */
|
||||
variables: Readonly<Record<`DSH_${string}`, BashEnvVariable>>
|
||||
variables: Readonly<Record<DshEnvironmentKey, BashEnvVariable>>
|
||||
/**
|
||||
* Resolve this contributor's available values for one tool execution.
|
||||
* @param execution - the bash tool execution and its optional calling agent.
|
||||
* @returns a partial map containing only keys declared in {@link variables}.
|
||||
*/
|
||||
resolve(execution: ToolExecution): Readonly<Partial<Record<`DSH_${string}`, string>>>
|
||||
resolve(execution: ToolExecution): Readonly<Partial<Record<DshEnvironmentKey, string>>>
|
||||
}
|
||||
|
||||
/** An enumerable declaration returned by {@link BashEnvRegistry.list}. */
|
||||
@@ -122,15 +122,19 @@ export interface BashEnvVariableInfo extends BashEnvVariable {
|
||||
/** Contributor that owns the variable. */
|
||||
contributor: string
|
||||
/** Declared `DSH_*` environment variable name. */
|
||||
key: `DSH_${string}`
|
||||
key: DshEnvironmentKey
|
||||
}
|
||||
|
||||
const RESERVED_BASH_ENV_KEYS = new Set<`DSH_${string}`>([
|
||||
'DSH_HOME',
|
||||
'DSH_SHELL',
|
||||
'DSH_SESSION_ID',
|
||||
const DSH_HOME_KEY = `${DSH_ENV_PREFIX}HOME` as const
|
||||
const DSH_SHELL_KEY = `${DSH_ENV_PREFIX}SHELL` as const
|
||||
const DSH_SESSION_ID_KEY = `${DSH_ENV_PREFIX}SESSION_ID` as const
|
||||
const DSH_SESSION_JSONL_KEY = `${DSH_ENV_PREFIX}SESSION_JSONL` as const
|
||||
const RESERVED_BASH_ENV_KEYS = new Set<DshEnvironmentKey>([
|
||||
DSH_HOME_KEY,
|
||||
DSH_SHELL_KEY,
|
||||
DSH_SESSION_ID_KEY,
|
||||
])
|
||||
const BASH_ENV_KEY = /^DSH_[A-Z][A-Z0-9_]*$/
|
||||
const BASH_ENV_KEY_SUFFIX = /^[A-Z][A-Z0-9_]*$/
|
||||
|
||||
/**
|
||||
* Registry (`ctx.bashEnv`) for trusted, per-execution `DSH_*` variables.
|
||||
@@ -142,7 +146,7 @@ const BASH_ENV_KEY = /^DSH_[A-Z][A-Z0-9_]*$/
|
||||
*/
|
||||
export class BashEnvRegistry extends Service {
|
||||
private readonly contributors = new Map<string, BashEnvContributor>()
|
||||
private readonly keyOwners = new Map<`DSH_${string}`, string>()
|
||||
private readonly keyOwners = new Map<DshEnvironmentKey, string>()
|
||||
private readonly dshHome: string
|
||||
|
||||
/**
|
||||
@@ -152,7 +156,7 @@ export class BashEnvRegistry extends Service {
|
||||
*/
|
||||
constructor(ctx: Context, config: Config = {}) {
|
||||
super(ctx, 'bashEnv')
|
||||
this.dshHome = resolvePath(config.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'))
|
||||
this.dshHome = resolvePath(config.dshHome ?? process.env[DSH_HOME_KEY] ?? join(homedir(), '.dsh'))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,9 +174,10 @@ export class BashEnvRegistry extends Service {
|
||||
throw new Error(`bash env contributor "${contributor.name}" is already registered`)
|
||||
}
|
||||
|
||||
const variables = Object.entries(contributor.variables) as [`DSH_${string}`, BashEnvVariable][]
|
||||
const variables = Object.entries(contributor.variables) as [DshEnvironmentKey, BashEnvVariable][]
|
||||
for (const [key, variable] of variables) {
|
||||
if (!BASH_ENV_KEY.test(key)) {
|
||||
if (!key.startsWith(DSH_ENV_PREFIX)
|
||||
|| !BASH_ENV_KEY_SUFFIX.test(key.slice(DSH_ENV_PREFIX.length))) {
|
||||
throw new Error(`bash env contributor "${contributor.name}" declared invalid key "${key}"`)
|
||||
}
|
||||
if (RESERVED_BASH_ENV_KEYS.has(key)) {
|
||||
@@ -203,18 +208,18 @@ export class BashEnvRegistry extends Service {
|
||||
* @returns an immutable environment overlay containing built-ins and current contributions.
|
||||
*/
|
||||
collect(execution: ToolExecution): DshEnvironment {
|
||||
const values: Record<`DSH_${string}`, string> = {
|
||||
DSH_HOME: this.dshHome,
|
||||
DSH_SHELL: '1',
|
||||
const values: Record<DshEnvironmentKey, string> = {
|
||||
[DSH_HOME_KEY]: this.dshHome,
|
||||
[DSH_SHELL_KEY]: '1',
|
||||
}
|
||||
if (execution.agent !== undefined) {
|
||||
values.DSH_SESSION_ID = execution.agent.session.header.id
|
||||
values[DSH_SESSION_ID_KEY] = execution.agent.session.header.id
|
||||
}
|
||||
|
||||
for (const contributor of [...this.contributors.values()].sort((left, right) => left.name.localeCompare(right.name))) {
|
||||
const resolved = contributor.resolve(execution)
|
||||
for (const [rawKey, value] of Object.entries(resolved)) {
|
||||
const key = rawKey as `DSH_${string}`
|
||||
const key = rawKey as DshEnvironmentKey
|
||||
if (!Object.hasOwn(contributor.variables, key)) {
|
||||
throw new Error(`bash env contributor "${contributor.name}" returned undeclared key "${key}"`)
|
||||
}
|
||||
@@ -237,7 +242,7 @@ export class BashEnvRegistry extends Service {
|
||||
.flatMap(contributor => Object.entries(contributor.variables).map(([key, variable]) => ({
|
||||
contributor: contributor.name,
|
||||
description: variable.description,
|
||||
key: key as `DSH_${string}`,
|
||||
key: key as DshEnvironmentKey,
|
||||
})))
|
||||
.sort((left, right) => left.key.localeCompare(right.key))
|
||||
}
|
||||
@@ -337,7 +342,7 @@ function bashDescription(escalationModes: readonly SandboxMode[]): string {
|
||||
const base = 'Execute a bash command (`bash -c`) and return its stdout/stderr. '
|
||||
+ 'Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — '
|
||||
+ 'pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. '
|
||||
+ 'Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. '
|
||||
+ `Current harness environment facts are exposed through managed \`$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. `
|
||||
+ 'Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]` — a policy denial, not a bug in the command; do not retry another way (a background task reports the same marker via bash_output once it has finished). '
|
||||
+ 'Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. '
|
||||
+ 'Set `run_in_background: true` for long-running commands: the call returns a task id immediately; '
|
||||
@@ -579,7 +584,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
||||
bashEnv.register({
|
||||
name: 'session-persistence',
|
||||
variables: {
|
||||
DSH_SESSION_JSONL: {
|
||||
[DSH_SESSION_JSONL_KEY]: {
|
||||
description: 'Absolute target path of the current session JSONL when the active persistence backend provides one.',
|
||||
},
|
||||
},
|
||||
@@ -587,7 +592,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
||||
const agent = execution.agent
|
||||
if (agent === undefined) return {}
|
||||
const location = ctx.get('sessionPersistence')?.locate(agent.session.header)
|
||||
return location?.kind === 'jsonl' ? { DSH_SESSION_JSONL: location.path } : {}
|
||||
return location?.kind === 'jsonl' ? { [DSH_SESSION_JSONL_KEY]: location.path } : {}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user