mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 08:00:45 +08:00
Add Host-global thinking and reply language controls.
Inject systemPrompt sections from Netx Ops settings so every preset can switch CoT and final-reply language without changing API/alarm lang. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
53c47fbc6a
commit
d52c05e774
14 changed files with 652 additions and 25 deletions
128
lib/client.js
128
lib/client.js
|
|
@ -667,11 +667,14 @@ var CSS = `
|
|||
.dsh-nx-field-head>label,.dsh-nx-field-head>.dsh-nx-field-label{flex:1;min-width:0}
|
||||
.dsh-nx-settings-field input[type=text],
|
||||
.dsh-nx-settings-field input[type=password],
|
||||
.dsh-nx-settings-field input:not([type]){
|
||||
.dsh-nx-settings-field input:not([type]),
|
||||
.dsh-nx-settings-field select{
|
||||
box-sizing:border-box;width:100%;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#dfe1e5);border-radius:6px;font:inherit;font-size:13px;line-height:20px;background:var(--dsw-alias-bg-module-platform,#f4f5f7);color:inherit
|
||||
}
|
||||
.dsh-nx-settings-field input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3370ff);outline-offset:1px}
|
||||
.dsh-nx-settings-field input:disabled{opacity:.55;cursor:default}
|
||||
.dsh-nx-settings-field input:focus-visible,
|
||||
.dsh-nx-settings-field select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3370ff);outline-offset:1px}
|
||||
.dsh-nx-settings-field input:disabled,
|
||||
.dsh-nx-settings-field select:disabled{opacity:.55;cursor:default}
|
||||
.dsh-nx-inputInvalid{border-color:var(--dsw-alias-state-error-primary,#d54941)!important}
|
||||
.dsh-nx-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#8f959e)}
|
||||
.dsh-nx-invalid{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary,#d54941)}
|
||||
|
|
@ -967,6 +970,63 @@ function ValueField(props) {
|
|||
]
|
||||
});
|
||||
}
|
||||
function SelectField(props) {
|
||||
const known = props.options.some((row) => row.value === props.field.text);
|
||||
return /* @__PURE__ */ jsx_runtime.jsxs("div", {
|
||||
className: "dsh-nx-settings-field",
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsxs("div", {
|
||||
className: "dsh-nx-field-head",
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsx("label", {
|
||||
htmlFor: props.id,
|
||||
children: props.label
|
||||
}),
|
||||
props.field.overridden ? /* @__PURE__ */ jsx_runtime.jsxs("span", {
|
||||
className: "dsh-nx-badges",
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsx("span", {
|
||||
className: "dsh-nx-badge",
|
||||
children: props.overriddenLabel
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx("button", {
|
||||
type: "button",
|
||||
className: "dsh-nx-reset",
|
||||
disabled: props.disabled,
|
||||
onClick: props.onReset,
|
||||
children: props.resetLabel
|
||||
})
|
||||
]
|
||||
}) : null
|
||||
]
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsxs("select", {
|
||||
id: props.id,
|
||||
className: props.field.invalid ? "dsh-nx-inputInvalid" : undefined,
|
||||
value: props.field.text,
|
||||
disabled: props.disabled,
|
||||
"aria-invalid": props.field.invalid || undefined,
|
||||
onChange: (event) => {
|
||||
props.onEdit(event.target.value);
|
||||
},
|
||||
children: [
|
||||
!known && props.field.text ? /* @__PURE__ */ jsx_runtime.jsx("option", {
|
||||
value: props.field.text,
|
||||
children: props.field.text
|
||||
}) : null,
|
||||
props.options.map((row) => /* @__PURE__ */ jsx_runtime.jsx("option", {
|
||||
value: row.value,
|
||||
children: row.label
|
||||
}, row.value))
|
||||
]
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx("p", {
|
||||
className: props.field.invalid ? "dsh-nx-invalid" : "dsh-nx-hint",
|
||||
children: props.field.invalid ? props.invalidLabel : props.hint
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
||||
function ImDeliveryPicker(props) {
|
||||
const options = props.catalog.options;
|
||||
const selected = resolveImTargets({
|
||||
|
|
@ -1275,6 +1335,48 @@ function NetxopsCard(props) {
|
|||
props.resetField("lang");
|
||||
}
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(SelectField, {
|
||||
id: "netxops-thinking-language",
|
||||
label: t("thinkingLanguage"),
|
||||
hint: t("thinkingLanguageHint"),
|
||||
field: state.thinkingLanguage,
|
||||
options: [
|
||||
{ value: "auto", label: "auto" },
|
||||
{ value: "zh-CN", label: "zh-CN" },
|
||||
{ value: "en", label: "en" }
|
||||
],
|
||||
overriddenLabel: t("overridden"),
|
||||
resetLabel: t("reset"),
|
||||
invalidLabel: t("invalid"),
|
||||
disabled,
|
||||
onEdit: (text) => {
|
||||
props.edit("thinkingLanguage", text);
|
||||
},
|
||||
onReset: () => {
|
||||
props.resetField("thinkingLanguage");
|
||||
}
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(SelectField, {
|
||||
id: "netxops-reply-language",
|
||||
label: t("replyLanguage"),
|
||||
hint: t("replyLanguageHint"),
|
||||
field: state.replyLanguage,
|
||||
options: [
|
||||
{ value: "follow-user", label: "follow-user" },
|
||||
{ value: "zh", label: "zh" },
|
||||
{ value: "en", label: "en" }
|
||||
],
|
||||
overriddenLabel: t("overridden"),
|
||||
resetLabel: t("reset"),
|
||||
invalidLabel: t("invalid"),
|
||||
disabled,
|
||||
onEdit: (text) => {
|
||||
props.edit("replyLanguage", text);
|
||||
},
|
||||
onReset: () => {
|
||||
props.resetField("replyLanguage");
|
||||
}
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(ValueField, {
|
||||
id: "netxops-nms-provider",
|
||||
label: t("nmsProvider"),
|
||||
|
|
@ -1860,6 +1962,8 @@ class NetxopsCardController {
|
|||
this.form = new CardForm(scope, [
|
||||
textField("apiUrl"),
|
||||
textField("lang"),
|
||||
textField("thinkingLanguage"),
|
||||
textField("replyLanguage"),
|
||||
textField("nmsProvider"),
|
||||
textField("kbRoot"),
|
||||
booleanFieldPersistFalse("groupOpsInPreset"),
|
||||
|
|
@ -2127,6 +2231,8 @@ class NetxopsCardController {
|
|||
...this.form.shell(),
|
||||
apiUrl: this.form.field("apiUrl"),
|
||||
lang: this.form.field("lang"),
|
||||
thinkingLanguage: this.form.field("thinkingLanguage"),
|
||||
replyLanguage: this.form.field("replyLanguage"),
|
||||
nmsProvider: this.form.field("nmsProvider"),
|
||||
kbRoot: this.form.field("kbRoot"),
|
||||
groupOpsInPreset: this.form.field("groupOpsInPreset"),
|
||||
|
|
@ -2281,8 +2387,12 @@ var en = {
|
|||
sectionExport: "Session export",
|
||||
apiUrl: "API URL",
|
||||
apiUrlHint: "e.g. http://127.0.0.1:8890",
|
||||
lang: "Language",
|
||||
langHint: "zh / en",
|
||||
lang: "API / alarm language",
|
||||
langHint: "zh / en — netx API query + alarm prompt copy only (not model reply)",
|
||||
thinkingLanguage: "Thinking language",
|
||||
thinkingLanguageHint: "auto / zh-CN / en — model chain-of-thought for all presets",
|
||||
replyLanguage: "Reply language",
|
||||
replyLanguageHint: "follow-user / zh / en — final answers for all presets",
|
||||
capabilityGroups: "Capability groups",
|
||||
nmsProvider: "NMS provider",
|
||||
nmsProviderHint: "zte-ume",
|
||||
|
|
@ -2354,8 +2464,12 @@ var zh = {
|
|||
sectionExport: "会话导出",
|
||||
apiUrl: "API 地址",
|
||||
apiUrlHint: "例如 http://127.0.0.1:8890",
|
||||
lang: "语言",
|
||||
langHint: "zh / en",
|
||||
lang: "API / 告警语言",
|
||||
langHint: "zh / en — 仅影响 netx API 与告警文案,不控制模型回复",
|
||||
thinkingLanguage: "思考语言",
|
||||
thinkingLanguageHint: "auto / zh-CN / en — 全预设模型思考过程语言",
|
||||
replyLanguage: "回复语言",
|
||||
replyLanguageHint: "follow-user / zh / en — 全预设最终回答语言",
|
||||
capabilityGroups: "能力组",
|
||||
nmsProvider: "NMS 提供方",
|
||||
nmsProviderHint: "zte-ume",
|
||||
|
|
|
|||
142
lib/index.js
142
lib/index.js
|
|
@ -1245,6 +1245,112 @@ function resetKbContext() {
|
|||
applyKbEnv(unconfiguredKbSnapshot());
|
||||
}
|
||||
|
||||
// src/netx/model-language.ts
|
||||
var THINKING_LANGUAGE_DEFAULT = "auto";
|
||||
var REPLY_LANGUAGE_DEFAULT = "follow-user";
|
||||
var THINKING_LANGUAGE_IDS = ["zh-CN", "en"];
|
||||
var REPLY_LANGUAGE_IDS = ["zh", "en"];
|
||||
var THINKING_META = {
|
||||
"zh-CN": { id: "zh-CN", name: "Simplified Chinese", native: "简体中文" },
|
||||
en: { id: "en", name: "English", native: "English" }
|
||||
};
|
||||
var REPLY_META = {
|
||||
zh: { id: "zh", name: "Simplified Chinese", native: "简体中文" },
|
||||
en: { id: "en", name: "English", native: "English" }
|
||||
};
|
||||
function normalizeThinkingLanguage(raw) {
|
||||
const value = String(raw ?? "").trim();
|
||||
if (!value || value === THINKING_LANGUAGE_DEFAULT)
|
||||
return THINKING_LANGUAGE_DEFAULT;
|
||||
const lower = value.toLowerCase().replace(/_/g, "-");
|
||||
if (lower === "zh" || lower === "zh-cn" || lower === "zh-hans" || lower === "chinese")
|
||||
return "zh-CN";
|
||||
if (lower === "en" || lower.startsWith("en-") || lower === "english")
|
||||
return "en";
|
||||
if (THINKING_LANGUAGE_IDS.includes(value)) {
|
||||
return value;
|
||||
}
|
||||
return THINKING_LANGUAGE_DEFAULT;
|
||||
}
|
||||
function normalizeReplyLanguage(raw) {
|
||||
const value = String(raw ?? "").trim();
|
||||
if (!value || value === REPLY_LANGUAGE_DEFAULT || value === "auto" || value === "default") {
|
||||
return REPLY_LANGUAGE_DEFAULT;
|
||||
}
|
||||
const lower = value.toLowerCase().replace(/_/g, "-");
|
||||
if (lower === "zh" || lower === "zh-cn" || lower === "zh-hans" || lower === "chinese")
|
||||
return "zh";
|
||||
if (lower === "en" || lower.startsWith("en-") || lower === "english")
|
||||
return "en";
|
||||
if (REPLY_LANGUAGE_IDS.includes(value)) {
|
||||
return value;
|
||||
}
|
||||
return REPLY_LANGUAGE_DEFAULT;
|
||||
}
|
||||
function resolveThinkingLanguage(setting, systemLocale) {
|
||||
const normalized = normalizeThinkingLanguage(setting);
|
||||
if (normalized !== THINKING_LANGUAGE_DEFAULT)
|
||||
return normalized;
|
||||
return languageFromLocale(systemLocale) ?? "en";
|
||||
}
|
||||
function languageFromLocale(locale) {
|
||||
const tag = String(locale ?? "").trim().toLowerCase().replace(/_/g, "-");
|
||||
if (!tag)
|
||||
return;
|
||||
if (tag === "zh" || tag.startsWith("zh-hans") || tag.startsWith("zh-cn") || tag === "zh-sg") {
|
||||
return "zh-CN";
|
||||
}
|
||||
if (tag.startsWith("zh"))
|
||||
return "zh-CN";
|
||||
if (tag === "en" || tag.startsWith("en-"))
|
||||
return "en";
|
||||
return;
|
||||
}
|
||||
function readSystemLocalePreference(settings) {
|
||||
if (!settings || typeof settings.get !== "function")
|
||||
return;
|
||||
let section;
|
||||
try {
|
||||
section = settings.get("locale");
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (section === null || typeof section !== "object" || Array.isArray(section))
|
||||
return;
|
||||
const preference = section.preference;
|
||||
return typeof preference === "string" && preference.trim() ? preference.trim() : undefined;
|
||||
}
|
||||
function thinkingInstruction(language) {
|
||||
const meta = THINKING_META[language];
|
||||
return [
|
||||
`The operator configured the language of your thinking/reasoning process: ${meta.native} (${meta.name}).`,
|
||||
`Write your entire internal reasoning — chain-of-thought, analysis, planning, and deliberation — in ${meta.name}.`,
|
||||
"Keep code, identifiers, file paths, commands, and technical terms as they are.",
|
||||
"Your final answer to the user follows the reply-language setting (or the user language when reply language is follow-user); this thinking setting never alone changes the final answer language."
|
||||
].join(" ");
|
||||
}
|
||||
function thinkingReminder(language) {
|
||||
const meta = THINKING_META[language];
|
||||
return [
|
||||
`[Thinking language] The operator wants your reasoning/thinking process written in ${meta.native} (${meta.name}).`,
|
||||
`Continue your entire chain-of-thought in ${meta.name}.`,
|
||||
"Final answer language follows the reply-language setting / user language."
|
||||
].join(" ");
|
||||
}
|
||||
function replyInstruction(setting) {
|
||||
const normalized = normalizeReplyLanguage(setting);
|
||||
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."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
// src/netx/runtime.ts
|
||||
var STORE_KEY3 = Symbol.for("dsh-netxops.connection-store");
|
||||
function store3() {
|
||||
|
|
@ -3837,6 +3943,8 @@ var DEFAULT_TOKEN_REF = "NETX_API_TOKEN";
|
|||
var Config = z.object({
|
||||
apiUrl: z.string().default("http://127.0.0.1:8890"),
|
||||
lang: z.string().default("zh"),
|
||||
thinkingLanguage: z.string().default("auto"),
|
||||
replyLanguage: z.string().default("follow-user"),
|
||||
tokenCredentialRef: z.string().role("credential-ref").default(DEFAULT_TOKEN_REF),
|
||||
toolCallTimeoutMs: z.number().step(1).min(1000).default(120000),
|
||||
installAgentPreset: z.boolean().default(true),
|
||||
|
|
@ -4009,6 +4117,40 @@ function apply(ctx, config = Config({})) {
|
|||
if (String(ref) === source().tokenCredentialRef)
|
||||
publish();
|
||||
});
|
||||
ctx.inject(["systemPrompt", "settings"], (promptCtx) => {
|
||||
const systemPrompt = promptCtx.systemPrompt;
|
||||
const effectiveThinking = () => {
|
||||
const settings = promptCtx.get("settings");
|
||||
const current = source();
|
||||
return resolveThinkingLanguage(normalizeThinkingLanguage(current.thinkingLanguage), readSystemLocalePreference(settings));
|
||||
};
|
||||
promptCtx.effect(() => {
|
||||
const disposers = [];
|
||||
const push = (dispose) => {
|
||||
if (typeof dispose === "function")
|
||||
disposers.push(dispose);
|
||||
};
|
||||
push(systemPrompt.section({
|
||||
name: "netxops:thinking-language",
|
||||
order: 85,
|
||||
text: () => thinkingInstruction(effectiveThinking())
|
||||
}));
|
||||
push(systemPrompt.context({
|
||||
name: "netxops:thinking-language-reminder",
|
||||
order: 1000,
|
||||
text: () => thinkingReminder(effectiveThinking())
|
||||
}));
|
||||
push(systemPrompt.section({
|
||||
name: "netxops:reply-language",
|
||||
order: 890,
|
||||
text: () => replyInstruction(normalizeReplyLanguage(source().replyLanguage))
|
||||
}));
|
||||
return () => {
|
||||
for (const dispose of disposers.splice(0))
|
||||
dispose();
|
||||
};
|
||||
}, "netxops: model language prompts");
|
||||
});
|
||||
ctx.inject(["tools"], (toolsCtx) => {
|
||||
let unregisterTools;
|
||||
const remountPublicTools = () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue