fix(review): align runtime-context diagnostics and docs
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/bash/bash-sandbox/README.md
|
||||
README.md: e9b040e7af55192e8afb9abf8bb4b99358ef8342
|
||||
README.zh.md: f3956111f31ad85331caa47b1755667b096d2969
|
||||
README.md: 035a8ad2401ca608d264049d454359eda7b2b9a7
|
||||
README.zh.md: cee27a9baaa539ba07eb1d730ea9bef2004fbeeb
|
||||
|
||||
@@ -52,7 +52,7 @@ Small fixed schema increment on requests where `bash` is visible, plus the curre
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
Prefix-stable while the executor and standing policy are unchanged. Changing the policy updates the owner-rendered section; changing executor capabilities also alters the `bash` schema.
|
||||
A standing-policy change appends a complete owner-rendered context snapshot after retained history, preserving the existing system/history prefix byte-for-byte. Changing executor capabilities alters the `bash` schema.
|
||||
|
||||
### Bash tool result, indirectly
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
执行器与常驻策略不变时,前缀保持稳定。更改策略会更新归属方渲染的段落;更改执行器能力也会改变 `bash` schema。
|
||||
常驻策略变化会在保留的历史之后追加一份由归属方渲染的完整上下文快照,并使既有 system/history 前缀保持逐字节不变。更改执行器能力会改变 `bash` schema。
|
||||
|
||||
### 间接的 Bash 工具结果
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ type StepOutcome =
|
||||
| { kind: 'request-failed'; error: RequestError; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined }
|
||||
|
||||
const RUNTIME_CONTEXT_SOURCE = '@deepseek-ai/dsh-system-prompt'
|
||||
/** Clearing marker kept distinct from every prefixed {@link renderContextSnapshot} result. */
|
||||
const CLEARED_RUNTIME_CONTEXT = 'Current runtime context: none. Earlier runtime-context snapshots no longer apply.'
|
||||
|
||||
/** Whether one user message is owned by runtime-context materialization. */
|
||||
|
||||
@@ -194,7 +194,7 @@ export interface Config {
|
||||
*/
|
||||
export function renderPrompt(assembly: PromptAssembly): string {
|
||||
return assembly.sections
|
||||
.map(section => interpolate(section, assembly.variables))
|
||||
.map(section => interpolate(section, assembly.variables, 'section'))
|
||||
.filter(text => text.length > 0)
|
||||
.join('\n\n')
|
||||
}
|
||||
@@ -209,16 +209,20 @@ export function renderPrompt(assembly: PromptAssembly): string {
|
||||
*/
|
||||
export function renderContextSnapshot(assembly: PromptAssembly): string {
|
||||
const body = assembly.contexts
|
||||
.map(context => interpolate(context, assembly.variables))
|
||||
.map(context => interpolate(context, assembly.variables, 'context'))
|
||||
.filter(text => text.length > 0)
|
||||
.join('\n\n')
|
||||
if (body.length === 0) return ''
|
||||
return `Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\n${body}`
|
||||
}
|
||||
|
||||
/** Interpolate one section's `{{variable}}` references (see {@link renderPrompt}). */
|
||||
function interpolate(section: AssembledSection, variables: Record<string, string | undefined>): string {
|
||||
const text = section.text
|
||||
/** Interpolate one section or context and attribute diagnostics to its owning input. */
|
||||
function interpolate(
|
||||
input: AssembledSection | AssembledContext,
|
||||
variables: Record<string, string | undefined>,
|
||||
kind: 'section' | 'context',
|
||||
): string {
|
||||
const text = input.text
|
||||
let result = ''
|
||||
let last = 0
|
||||
for (let open = text.indexOf('{{'); open >= 0; open = text.indexOf('{{', last)) {
|
||||
@@ -226,7 +230,7 @@ function interpolate(section: AssembledSection, variables: Record<string, string
|
||||
if (group === null) {
|
||||
// A later closing brace makes this malformed; otherwise it is literal prose.
|
||||
if (text.indexOf('}}', open + 2) >= 0) {
|
||||
throw new Error(`malformed prompt variable reference at "${text.slice(open, open + 16)}…" in section "${section.name}" (references are complete simple {{name}} groups)`)
|
||||
throw new Error(`malformed prompt variable reference at "${text.slice(open, open + 16)}…" in ${kind} "${input.name}" (references are complete simple {{name}} groups)`)
|
||||
}
|
||||
result += text.slice(last, open + 2)
|
||||
last = open + 2
|
||||
@@ -235,16 +239,16 @@ function interpolate(section: AssembledSection, variables: Record<string, string
|
||||
// `{{}}` yields an empty name and follows the malformed-reference path.
|
||||
const name = group[0].slice(2, -2)
|
||||
if (!VARIABLE_NAME.test(name)) {
|
||||
throw new Error(`malformed prompt variable reference "{{${name}}}" in section "${section.name}" (variable names match ${String(VARIABLE_NAME)})`)
|
||||
throw new Error(`malformed prompt variable reference "{{${name}}}" in ${kind} "${input.name}" (variable names match ${String(VARIABLE_NAME)})`)
|
||||
}
|
||||
// Do not resolve unregistered names through Object.prototype.
|
||||
if (!Object.hasOwn(variables, name)) {
|
||||
const known = Object.keys(variables)
|
||||
throw new Error(`unknown prompt variable "{{${name}}}" in section "${section.name}"; registered variables: ${known.length > 0 ? known.join(', ') : '(none)'}`)
|
||||
throw new Error(`unknown prompt variable "{{${name}}}" in ${kind} "${input.name}"; registered variables: ${known.length > 0 ? known.join(', ') : '(none)'}`)
|
||||
}
|
||||
const value = variables[name]
|
||||
if (value === undefined) {
|
||||
throw new Error(`prompt variable "{{${name}}}" has no value for this assembly (section "${section.name}")`)
|
||||
throw new Error(`prompt variable "{{${name}}}" has no value for this assembly (${kind} "${input.name}")`)
|
||||
}
|
||||
result += text.slice(last, open) + value
|
||||
last = open + group[0].length
|
||||
|
||||
@@ -309,6 +309,15 @@ describe('SystemPrompt', () => {
|
||||
.toBe('Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nMode: read-only.')
|
||||
})
|
||||
|
||||
it('attributes context interpolation failures to the contributing context', () => {
|
||||
expect(() => renderContextSnapshot({
|
||||
sections: [],
|
||||
contexts: [{ name: 'policy', text: 'Mode: {{missing}}.' }],
|
||||
tools: [],
|
||||
variables: {},
|
||||
})).toThrow('unknown prompt variable "{{missing}}" in context "policy"; registered variables: (none)')
|
||||
})
|
||||
|
||||
it('emits system-prompt/change when a tool provider is registered and disposed', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
|
||||
@@ -74,6 +74,7 @@ function childEnvironment(spec: PtyBackendSpawnSpec): NodeJS.ProcessEnv {
|
||||
function spawnArgv(ctx: Context, config: ResolvedConfig, policy: SandboxExecutionPolicy): string[] {
|
||||
const argv = [config.shellPath, ...config.shellArgs]
|
||||
if (policy.mode === 'danger-full-access') return argv
|
||||
// Re-state the discriminant because object spread does not preserve its narrowed type.
|
||||
return ctx.sandbox.confine(argv, { ...policy, mode: policy.mode }).argv
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user