Fix reply language ignored when user writes Chinese.

Strengthen Host prompt injection with a per-step reply reminder, read live settings, and stop persona from forcing follow-user language.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-15 20:38:51 +08:00
parent d52c05e774
commit 91f2e9a5c8
7 changed files with 108 additions and 19 deletions

View file

@ -59,6 +59,7 @@ import {
normalizeThinkingLanguage,
readSystemLocalePreference,
replyInstruction,
replyReminder,
resolveThinkingLanguage,
thinkingInstruction,
thinkingReminder,
@ -412,14 +413,34 @@ export function apply(ctx: Context, config: Config = Config({})): void {
context: (spec: { name: string, order: number, text: string | (() => string) }) => (() => void) | void
}
const systemPrompt = (promptCtx as { systemPrompt: PromptSurface }).systemPrompt
/** Prefer live settings document over cordis base config (survives UI saves). */
const liveConfig = (): Config => {
const settings = promptCtx.get('settings') as {
get?: (ns: string) => unknown
} | undefined
const section = typeof settings?.get === 'function'
? (() => {
try {
return settings.get!(NETXOPS_SETTINGS_NAMESPACE)
} catch {
return undefined
}
})()
: undefined
if (section !== null && typeof section === 'object' && !Array.isArray(section)) {
return { ...source(), ...(section as Partial<Config>) }
}
return source()
}
const effectiveThinking = (): ReturnType<typeof resolveThinkingLanguage> => {
const settings = promptCtx.get('settings') as { get?: (ns: string) => unknown } | undefined
const current = source()
const current = liveConfig()
return resolveThinkingLanguage(
normalizeThinkingLanguage(current.thinkingLanguage),
readSystemLocalePreference(settings),
)
}
const replySetting = (): string => normalizeReplyLanguage(liveConfig().replyLanguage)
promptCtx.effect(() => {
const disposers: Array<() => void> = []
const push = (dispose: (() => void) | void): void => {
@ -435,11 +456,22 @@ export function apply(ctx: Context, config: Config = Config({})): void {
order: 1000,
text: () => thinkingReminder(effectiveThinking()),
}))
// Late section + per-step context so reply language beats persona / user language.
push(systemPrompt.section({
name: 'netxops:reply-language',
order: 890,
text: () => replyInstruction(normalizeReplyLanguage(source().replyLanguage)),
order: 10150,
text: () => replyInstruction(replySetting()),
}))
push(systemPrompt.context({
name: 'netxops:reply-language-reminder',
order: 1100,
text: () => replyReminder(replySetting()),
}))
promptCtx.logger.info(
'netxops: model language prompts registered (thinking=%s reply=%s)',
normalizeThinkingLanguage(liveConfig().thinkingLanguage),
replySetting(),
)
return () => {
for (const dispose of disposers.splice(0)) dispose()
}

View file

@ -135,10 +135,26 @@ export function replyInstruction(setting: string | undefined | null): string {
if (normalized === REPLY_LANGUAGE_DEFAULT) return ''
const meta = REPLY_META[normalized]
return [
'## Reply language requirement',
`Regardless of the language the user writes in, you must always write the entire reply in ${meta.native} (${meta.name}) —`,
'including explanations, summaries, body text, titles, and error descriptions.',
'Keep code, commands, file paths, API names, host names, and proper nouns in their original form; do not translate them.',
'This requirement outranks language hints in the user message or persona until the Netx Ops reply-language setting changes.',
'## Reply language requirement (mandatory)',
`The operator set Netx Ops reply language to ${meta.native} (${meta.name}).`,
`You MUST write the entire final reply in ${meta.name} only.`,
`Do not switch to the user's language even if they write in Chinese, English, or any other language.`,
'This includes titles, bullet labels (Result / Evidence / Next may stay as English keywords), explanations, summaries, warnings, and error text.',
'Keep code, commands, file paths, API names, host names, UUIDs, and proper nouns unchanged.',
'This rule outranks persona wording such as "follow the user language" and outranks language hints in the user message.',
].join(' ')
}
/**
* Per-step reminder for forced reply language.
* Empty when `follow-user`. Appended after the user message each model call.
*/
export function replyReminder(setting: string | undefined | null): string {
const normalized = normalizeReplyLanguage(setting)
if (normalized === REPLY_LANGUAGE_DEFAULT) return ''
const meta = REPLY_META[normalized]
return [
`[Reply language] Mandatory: write your entire final answer in ${meta.native} (${meta.name}).`,
'Do not answer in the user\'s language. Code/paths/host names stay original.',
].join(' ')
}