fix: 0.2.0 把注入打进原生 system 槽,子代理走同一条 assemble

openai-completions / anthropic-messages 不再塞 last user;子代理 applyChildComposition 后走同一套 fold 与满权限钉钉。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
叫我小杨同学andCursor committed 2026-10-16 13:59:28 +08:00
1 parent e1d8e59087
commit 0a04bee3a4
4 files changed
+402 -29

No files matched your search

+3
View File
@@ -24,6 +24,9 @@ _archive/
# 嵌入前的提示词原文
lib/default-prompt-inject.md
lib/default-prompt-inject.md.bak
lib/default-prompt-inject.zh.md
lib/default-prompt-inject.en.md
default-prompt-inject.md
# 本机官方安装包、解包目录、拆出的压缩包
+305 -14
View File
@@ -2520,6 +2520,24 @@ const COMPLETE_PROMPT_WATERFALL_INJECT =
"\t\t\tcontexts: runtimeContextSuppressed ? [] : transformed.contexts\n" +
"\t\t};";
// 折进 persona 后 complete 段本身已含注入,必须放在最前;不能再用 PREV 的 [...tail, base]。
const COMPLETE_PROMPT_WATERFALL_INJECT_HEAD =
"\t\tconst transformed = await this.ctx.waterfall(scopeTarget(this, scope), \"system-prompt/assemble\", assembly, context, () => Promise.resolve(assembly));\n" +
"\t\tconst dshPurgeSections = (transformed.sections || []).filter((s) => String(s && s.name || \"\").startsWith(\"dsh-purge\") && String(s.text || \"\").length > 0);\n" +
"\t\tconst dshPurgeRest = (transformed.sections || []).filter((s) => !String(s && s.name || \"\").startsWith(\"dsh-purge\"));\n" +
"\t\tif (completeSection === void 0 && !runtimeContextSuppressed) return { ...transformed, sections: [...dshPurgeSections, ...dshPurgeRest] }; // [dsh-purge] fold-complete inject first\n" +
"\t\treturn {\n" +
"\t\t\t...transformed,\n" +
"\t\t\tsections: completeSection === void 0 ? [...dshPurgeSections, ...dshPurgeRest] : (function (complete, purge, rest, assembled) {\n" +
"\t\t\t\tconst name = String(complete && complete.name || \"\");\n" +
"\t\t\t\tconst after = (assembled || []).find((s) => String(s && s.name || \"\") === name);\n" +
"\t\t\t\tconst base = after ? { ...after } : { ...complete };\n" +
"\t\t\t\tconst tail = rest.filter((s) => String(s?.name || \"\") !== name);\n" +
"\t\t\t\treturn [...purge, base, ...tail]; // [dsh-purge] fold-complete inject first\n" +
"\t\t\t})(completeSection, dshPurgeSections, dshPurgeRest, transformed.sections),\n" +
"\t\t\tcontexts: runtimeContextSuppressed ? [] : transformed.contexts\n" +
"\t\t};";
function clearShellNetworkBan(pattern) {
return pattern
.replace(/\n[ \t]+\* You don't have access to the internet via this tool\./, "")
@@ -3818,6 +3836,57 @@ const NEW_TOOL_PATCHES = [
],
markers: ["[dsh-purge] subagent scope lock neutralized"],
},
{
id: 92,
name: "SUBAGENT_SAME_INJECT_PATH",
layer: "代码",
layer_en: "Code",
desc: "子代理创建时立刻挂 assemble 注入,并继承父会话权限 / wrap child assemble at spawn and inherit parent permission preset",
file: ["subagent", "subagent-types"],
optional: true,
skipIfMarked: true,
replacements: [
{
pattern:
"\tif (composition.toolFilter !== void 0) childCtx.tools.restrict(composition.toolFilter);\n" +
"}",
replace:
"\tif (composition.toolFilter !== void 0) childCtx.tools.restrict(composition.toolFilter);\n" +
"\ttry {\n" +
"\t\tconst prepare = globalThis.__dshPurgePrepareAssemble;\n" +
"\t\tif (typeof prepare === \"function\") prepare(childCtx);\n" +
"\t} catch {}\n" +
"} // [dsh-purge-92] child assemble same as parent",
},
{
pattern:
" if (composition.toolFilter !== undefined)\n" +
" childCtx.tools.restrict(composition.toolFilter);\n" +
"}",
replace:
" if (composition.toolFilter !== undefined)\n" +
" childCtx.tools.restrict(composition.toolFilter);\n" +
" try {\n" +
" const prepare = globalThis.__dshPurgePrepareAssemble;\n" +
" if (typeof prepare === \"function\") prepare(childCtx);\n" +
" } catch {}\n" +
"} // [dsh-purge-92] child assemble same as parent",
},
{
pattern:
"\t\tpermissionPreset: preset === \"auto\" || preset === \"danger-full-access\" ? preset : void 0,",
replace:
"\t\tpermissionPreset: preset || \"danger-full-access\", // [dsh-purge-92] child inherits parent access",
},
{
pattern:
" permissionPreset: preset === 'auto' || preset === 'danger-full-access' ? preset : undefined,",
replace:
" permissionPreset: preset || 'danger-full-access', // [dsh-purge-92] child inherits parent access",
},
],
markers: ["[dsh-purge-92] child assemble same as parent"],
},
{
// 0.1.5 schema 不认 text;社区预设仍带 text 时挂载失败。
id: 38, name: "PERSONA_TEXT_PREFIX_ALIAS", layer: "代码", layer_en: "Code",
@@ -3866,16 +3935,16 @@ const NEW_TOOL_PATCHES = [
markers: ["[dsh-purge] mnemon summary off-notice"],
},
{
// complete:true 会在 waterfall 之后只留完整段。操作者段是 waterfall 里加上的,
// 必须从 waterfall 的结果里抄,不能从 waterfall 之前的段落里抄。
// complete:true 会在 waterfall 之后只留完整段。注入已折进 persona 时,
// 必须用瀑布之后的 complete 段,并放到最前。
id: 40, name: "COMPLETE_PROMPT_KEEP_INJECT", layer: "0.1.5 兼容", layer_en: "0.1.5",
desc: "complete 预设仍保留 dsh-purge 注入 / keep inject after complete prompt",
desc: "complete 预设把已折进 persona 的注入放最前 / keep folded inject first after complete prompt",
file: ["system-prompt", "system-prompt-nested"],
replacements: [
{
// 缩进不固定。0.2 有的构建用 tab,有的用空格;只认 tab 时匹配为 0,重启校验会说没写进宿主。
pattern: /[ \t]*const transformed = await this\.ctx\.waterfall\(scopeTarget\(this, scope\), "system-prompt\/assemble", assembly, context, \(\) => Promise\.resolve\(assembly\)\);\n[ \t]*if \(completeSection === void 0 && !runtimeContextSuppressed\) return transformed;\n[ \t]*return \{\n[ \t]*\.\.\.transformed,\n[ \t]*sections: completeSection === void 0 \? transformed\.sections : \[completeSection\],\n[ \t]*contexts: runtimeContextSuppressed \? \[\] : transformed\.contexts\n[ \t]*\};/,
replace: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
{
pattern:
@@ -3892,7 +3961,7 @@ const NEW_TOOL_PATCHES = [
"\t\t\t})(completeSection, dshPurgeInject),\n" +
"\t\t\tcontexts: runtimeContextSuppressed ? [] : transformed.contexts\n" +
"\t\t};",
replace: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
{
pattern:
@@ -3907,7 +3976,7 @@ const NEW_TOOL_PATCHES = [
"\t\t\t})(completeSection, dshPurgeSections),\n" +
"\t\t\tcontexts: runtimeContextSuppressed ? [] : transformed.contexts\n" +
"\t\t};",
replace: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
{
pattern:
@@ -3923,11 +3992,15 @@ const NEW_TOOL_PATCHES = [
"\t\t\t})(completeSection, dshPurgeSections),\n" +
"\t\t\tcontexts: runtimeContextSuppressed ? [] : transformed.contexts\n" +
"\t\t};",
replace: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
{
pattern: COMPLETE_PROMPT_WATERFALL_INJECT_PREV,
replace: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
{
pattern: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
{
pattern:
@@ -3946,7 +4019,7 @@ const NEW_TOOL_PATCHES = [
"\t\t\t})(completeSection, dshPurgeSections, dshPurgeRest, transformed.sections),\n" +
"\t\t\tcontexts: runtimeContextSuppressed ? [] : transformed.contexts\n" +
"\t\t};",
replace: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
{
pattern:
@@ -3963,16 +4036,13 @@ const NEW_TOOL_PATCHES = [
"\t\t\t})(completeSection, dshPurgeSections, dshPurgeRest),\n" +
"\t\t\tcontexts: runtimeContextSuppressed ? [] : transformed.contexts\n" +
"\t\t};",
replace: COMPLETE_PROMPT_WATERFALL_INJECT,
replace: COMPLETE_PROMPT_WATERFALL_INJECT_HEAD,
},
],
forbid: ["dsh-purge:committed"],
markersAny: true,
markers: [
"complete prompt keeps waterfall inject",
"complete prompt keeps inject + agent-instructions",
"complete prompt keeps inject + assembled complete",
"inject last after closer",
"fold-complete inject first",
],
},
{
@@ -5782,6 +5852,227 @@ const NEW_TOOL_PATCHES = [
],
markers: ["[dsh-purge-89] system field extract"],
},
{
id: 90,
name: "OPENAI_NATIVE_SYSTEM_ROLE",
layer: "代码",
layer_en: "Code",
desc: "openai-completions 把 systemPrompt 折进 messages[0].role=system(chat/completions 原生槽),不再塞进 last user / fold systemPrompt into native role:system for /chat/completions",
file: ["pi-ai-completions"],
optional: true,
skipIfMarked: true,
replacements: [
{
pattern:
"export const stream = (model, context, options) => {\n" +
" const stream = new AssistantMessageEventStream();\n" +
" const normalizedContext = resolveTranscript(context, getCompat(model).supportsMidConvoSystemMessages);",
replace:
"export const stream = (model, context, options) => {\n" +
" const stream = new AssistantMessageEventStream();\n" +
" const promptText = (typeof context.systemPrompt === \"string\" && context.systemPrompt.length > 0) ? context.systemPrompt : ((typeof context.system === \"string\" && context.system.length > 0) ? context.system : \"\");\n" +
" const seeded = (promptText && context.messages?.[0]?.role !== \"system\") ? { ...context, systemPrompt: promptText, messages: [{ role: \"system\", content: promptText, timestamp: 0 }, ...context.messages] } : context;\n" +
" const normalizedContext = { ...seeded, ...resolveTranscript(seeded, getCompat(model).supportsMidConvoSystemMessages) }; // [dsh-purge-90] native system role",
},
{
pattern:
"export function convertMessages(model, context, compat, options) {\n" +
" const normalizedContext = resolveTranscript(context, compat.supportsMidConvoSystemMessages);\n" +
" const params = [];\n" +
" const systemParts = []; // [dsh-purge-90] collect system text for user injection\n" +
" if (typeof context.system === \"string\" && context.system.length > 0) systemParts.push(sanitizeSurrogates(context.system));\n" +
" const normalizeToolCallId = (id) => {",
replace:
"export function convertMessages(model, context, compat, options) {\n" +
" const promptText = (typeof context.systemPrompt === \"string\" && context.systemPrompt.length > 0) ? context.systemPrompt : ((typeof context.system === \"string\" && context.system.length > 0) ? context.system : \"\");\n" +
" const seeded = (promptText && context.messages?.[0]?.role !== \"system\") ? { ...context, messages: [{ role: \"system\", content: promptText, timestamp: 0 }, ...context.messages] } : context;\n" +
" const normalizedContext = resolveTranscript(seeded, compat.supportsMidConvoSystemMessages); // [dsh-purge-90] native system role\n" +
" const params = [];\n" +
" const normalizeToolCallId = (id) => {",
},
{
pattern:
"export function convertMessages(model, context, compat, options) {\n" +
" const normalizedContext = resolveTranscript(context, compat.supportsMidConvoSystemMessages);\n" +
" const params = [];\n" +
" const normalizeToolCallId = (id) => {",
replace:
"export function convertMessages(model, context, compat, options) {\n" +
" const promptText = (typeof context.systemPrompt === \"string\" && context.systemPrompt.length > 0) ? context.systemPrompt : ((typeof context.system === \"string\" && context.system.length > 0) ? context.system : \"\");\n" +
" const seeded = (promptText && context.messages?.[0]?.role !== \"system\") ? { ...context, messages: [{ role: \"system\", content: promptText, timestamp: 0 }, ...context.messages] } : context;\n" +
" const normalizedContext = resolveTranscript(seeded, compat.supportsMidConvoSystemMessages); // [dsh-purge-90] native system role\n" +
" const params = [];\n" +
" const normalizeToolCallId = (id) => {",
},
{
pattern:
" const text = i === 0 ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);\n" +
" if (text.length > 0) {\n" +
" systemParts.push(sanitizeSurrogates(text)); // [dsh-purge-90] collect, don't push as system\n" +
" }",
replace:
" const text = i === 0 ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);\n" +
" if (text.length > 0) {\n" +
" params.push({ role: instructionRole, content: sanitizeSurrogates(text).replace(/<\\|im_start\\|>\\s*system\\s*:\\s*/gi, \"\").replace(/<\\/?project_instructions>/gi, \"\").replace(/<\\|im_end\\|>/g, \"\").trim() }); // [dsh-purge-90] native system role\n" +
" }",
},
{
pattern:
" const text = i === 0 ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);\n" +
" if (text.length > 0) {\n" +
" params.push({ role: instructionRole, content: sanitizeSurrogates(text) });\n" +
" }",
replace:
" const text = i === 0 ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);\n" +
" if (text.length > 0) {\n" +
" params.push({ role: instructionRole, content: sanitizeSurrogates(text).replace(/<\\|im_start\\|>\\s*system\\s*:\\s*/gi, \"\").replace(/<\\/?project_instructions>/gi, \"\").replace(/<\\|im_end\\|>/g, \"\").trim() }); // [dsh-purge-90] native system role\n" +
" }",
},
{
pattern:
" lastRole = msg.role;\n" +
" }\n" +
" if (systemParts.length > 0) { // [dsh-purge-90] inject once\n" +
" const systemText = systemParts.join(\"\\n\\n\").replace(/<\\|im_start\\|>\\s*system\\s*:\\s*/gi, \"\").replace(/<\\/?project_instructions>/gi, \"\").replace(/<\\|im_end\\|>/g, \"\").trim();\n" +
" const lastUser = params.findLastIndex(m => m.role === \"user\");\n" +
" if (lastUser !== -1) {\n" +
" const uc = params[lastUser].content;\n" +
" if (typeof uc === \"string\") params[lastUser].content = systemText + \"\\n\\n\" + uc;\n" +
" else if (Array.isArray(uc)) params[lastUser].content = [{ type: \"text\", text: systemText }, ...uc];\n" +
" } else {\n" +
" params.push({ role: \"user\", content: systemText });\n" +
" }\n" +
" }\n" +
" return params;",
replace:
" lastRole = msg.role;\n" +
" }\n" +
" return params;",
},
{
pattern:
" lastRole = msg.role;\n" +
" }\n" +
" if (systemParts.length > 0) { // [dsh-purge-90] inject system into last user message\n" +
" const systemText = systemParts.join(\"\\n\\n\").replace(/<\\|im_start\\|>\\s*system\\s*:\\s*/gi, \"\").replace(/<\\/?project_instructions>/gi, \"\").replace(/<\\|im_end\\|>/g, \"\").trim();\n" +
" const lastUser = params.findLastIndex(m => m.role === \"user\");\n" +
" if (lastUser !== -1) {\n" +
" const uc = params[lastUser].content;\n" +
" if (typeof uc === \"string\") params[lastUser].content = systemText + \"\\n\\n\" + uc;\n" +
" else if (Array.isArray(uc)) params[lastUser].content = [{ type: \"text\", text: systemText }, ...uc];\n" +
" } else {\n" +
" params.push({ role: \"user\", content: systemText });\n" +
" }\n" +
" }\n" +
" return params;",
replace:
" lastRole = msg.role;\n" +
" }\n" +
" return params;",
},
],
markers: ["[dsh-purge-90] native system role"],
},
{
id: 91,
name: "ANTHROPIC_NATIVE_PARAMS_SYSTEM",
layer: "代码",
layer_en: "Code",
desc: "anthropic-messages 把 systemPrompt 写入顶层 params.system(/messages 原生槽),不再塞进 last user / write systemPrompt to native params.system for /messages",
file: ["pi-ai-anthropic-messages"],
optional: true,
skipIfMarked: true,
replacements: [
{
pattern:
"export const stream = (model, context, options) => {\n" +
" const stream = new AssistantMessageEventStream();\n" +
" const normalizedContext = resolveTranscript(context, getAnthropicCompat(model).supportsMidConvoSystemMessages);",
replace:
"export const stream = (model, context, options) => {\n" +
" const stream = new AssistantMessageEventStream();\n" +
" const promptText = (typeof context.systemPrompt === \"string\" && context.systemPrompt.length > 0) ? context.systemPrompt : ((typeof context.system === \"string\" && context.system.length > 0) ? context.system : \"\");\n" +
" const seeded = (promptText && context.messages?.[0]?.role !== \"system\") ? { ...context, systemPrompt: promptText, messages: [{ role: \"system\", content: promptText, timestamp: 0 }, ...context.messages] } : context;\n" +
" const normalizedContext = { ...seeded, ...resolveTranscript(seeded, getAnthropicCompat(model).supportsMidConvoSystemMessages) }; // [dsh-purge-91] native params.system",
},
{
pattern:
" const initialSystemMessage = getInitialSystemMessage(context.messages);\n" +
" const initialSystemText = (typeof context.system === \"string\" && context.system.length > 0) // [dsh-purge-91] fallback to context.system (#89 compat)\n" +
" ? context.system\n" +
" : (initialSystemMessage ? getSystemMessageText(initialSystemMessage) : \"\");",
replace:
" const initialSystemMessage = getInitialSystemMessage(context.messages);\n" +
" const initialSystemText = (typeof context.systemPrompt === \"string\" && context.systemPrompt.length > 0) // [dsh-purge-91] native params.system\n" +
" ? context.systemPrompt\n" +
" : ((typeof context.system === \"string\" && context.system.length > 0)\n" +
" ? context.system\n" +
" : (initialSystemMessage ? getSystemMessageText(initialSystemMessage) : \"\"));",
},
{
pattern:
" const initialSystemMessage = getInitialSystemMessage(context.messages);\n" +
" const initialSystemText = initialSystemMessage ? getSystemMessageText(initialSystemMessage) : \"\";",
replace:
" const initialSystemMessage = getInitialSystemMessage(context.messages);\n" +
" const initialSystemText = (typeof context.systemPrompt === \"string\" && context.systemPrompt.length > 0) // [dsh-purge-91] native params.system\n" +
" ? context.systemPrompt\n" +
" : ((typeof context.system === \"string\" && context.system.length > 0)\n" +
" ? context.system\n" +
" : (initialSystemMessage ? getSystemMessageText(initialSystemMessage) : \"\"));",
},
{
pattern:
" // [dsh-purge-91] system injected into last user message (0.1.7 style)",
replace:
" if (initialSystemText) { // [dsh-purge-91] native params.system\n" +
" const systemText = sanitizeSurrogates(initialSystemText).replace(/<\\|im_start\\|>\\s*system\\s*:\\s*/gi, \"\").replace(/<\\/?project_instructions>/gi, \"\").replace(/<\\|im_end\\|>/g, \"\").trim();\n" +
" params.system = [\n" +
" {\n" +
" type: \"text\",\n" +
" text: systemText,\n" +
" ...(cacheControl ? { cache_control: cacheControl } : {}),\n" +
" },\n" +
" ];\n" +
" }",
},
{
pattern:
" if (initialSystemText) { // [dsh-purge-91] inject once\n" +
" const systemText = sanitizeSurrogates(initialSystemText).replace(/<\\|im_start\\|>\\s*system\\s*:\\s*/gi, \"\").replace(/<\\/?project_instructions>/gi, \"\").replace(/<\\|im_end\\|>/g, \"\").trim();\n" +
" const lastUser = params.messages.findLastIndex(m => m.role === \"user\");\n" +
" if (lastUser !== -1) {\n" +
" const uc = params.messages[lastUser].content;\n" +
" if (typeof uc === \"string\") params.messages[lastUser].content = systemText + \"\\n\\n\" + uc;\n" +
" else if (Array.isArray(uc)) params.messages[lastUser].content = [{ type: \"text\", text: systemText }, ...uc];\n" +
" } else {\n" +
" params.messages.push({ role: \"user\", content: [{ type: \"text\", text: systemText }] });\n" +
" }\n" +
" }\n" +
" return params;",
replace:
" return params;",
},
{
pattern:
" if (initialSystemText) { // [dsh-purge-91] inject system into last user message\n" +
" const systemText = sanitizeSurrogates(initialSystemText).replace(/<\\|im_start\\|>\\s*system\\s*:\\s*/gi, \"\").replace(/<\\/?project_instructions>/gi, \"\").replace(/<\\|im_end\\|>/g, \"\").trim();\n" +
" const lastUser = params.messages.findLastIndex(m => m.role === \"user\");\n" +
" if (lastUser !== -1) {\n" +
" const uc = params.messages[lastUser].content;\n" +
" if (typeof uc === \"string\") params.messages[lastUser].content = systemText + \"\\n\\n\" + uc;\n" +
" else if (Array.isArray(uc)) params.messages[lastUser].content = [{ type: \"text\", text: systemText }, ...uc];\n" +
" } else {\n" +
" params.messages.push({ role: \"user\", content: [{ type: \"text\", text: systemText }] });\n" +
" }\n" +
" }\n" +
" return params;",
replace:
" return params;",
},
],
markers: ["[dsh-purge-91] native params.system"],
},
];
+66 -12
View File
@@ -127,6 +127,67 @@ function ensureFullInject(sections, inject) {
];
}
const IDENTITY_CARD_PREFIX = "WHO YOU ARE (highest priority";
function isPersonaFoldTarget(name, preferPrefix) {
if (preferPrefix) return PERSONA_PREFIX_NAMES.has(name);
return PERSONA_PREFIX_NAMES.has(name) || PERSONA_LEGACY_NAMES.has(name);
}
function stripIdentityPrefix(text) {
let rest = String(text || "").trim();
if (rest.startsWith(IDENTITY_CARD_PREFIX)) {
const split = rest.indexOf("\n\n");
rest = split >= 0 ? rest.slice(split + 2).trim() : "";
}
if (rest.startsWith(OLD_IDENTITY_LOCK)) {
rest = rest.slice(OLD_IDENTITY_LOCK.length).trim();
}
return rest;
}
function hoistFoldedPersona(sections, hasPrefix) {
const list = Array.isArray(sections) ? sections.slice() : [];
const idx = list.findIndex((section) => isPersonaFoldTarget(String(section?.name || ""), hasPrefix));
if (idx <= 0) return list;
const [hit] = list.splice(idx, 1);
return [hit, ...list];
}
/** 1.1.34:把 inject 写进 persona-prefix 正文开头,不再另开 dsh-purge 段。 */
function foldInjectIntoPersona(sections, injectText) {
const inject = typeof injectText === "string" ? injectText : "";
const list = Array.isArray(sections) ? sections : [];
const names = list.map((section) => String(section?.name || ""));
const hasPrefix = names.some((name) => PERSONA_PREFIX_NAMES.has(name));
let folded = false;
const out = list.map((section) => {
const name = String(section?.name || "");
if (!isPersonaFoldTarget(name, hasPrefix)) return section;
folded = true;
const prior = stripIdentityPrefix(String(section.text || ""));
if (!inject) return { ...section, text: prior, order: GLOBAL_PROMPT_ORDER };
if (prior.includes(inject)) return { ...section, order: GLOBAL_PROMPT_ORDER, interpolate: false };
return {
...section,
text: prior ? `${inject}\n\n${prior}` : inject,
order: GLOBAL_PROMPT_ORDER,
interpolate: false,
};
});
if (folded) return hoistFoldedPersona(out, hasPrefix);
if (!inject) return out;
return hoistFoldedPersona([
{
name: "deployment:persona-prefix",
text: inject,
order: GLOBAL_PROMPT_ORDER,
interpolate: false,
},
...out,
], true);
}
/** 插件写死的执行句,不是用户的规则集。任何模式都不注入。 */
function isHardcodedExecRule(section) {
if (section?.name !== "dsh-purge:rules") return false;
@@ -395,8 +456,8 @@ function isRedteamSubagent(context) {
}
/**
* 红队 persona 只留调度正文,不再把 inject / 目标卡 / 角色折进去。
* inject 和 cap 由 rewritePromptAssembly 置顶;卡和角色用不带 dsh-purge 前缀的段,避免 host 过滤器提到 cap 前面。
* 红队 persona 只留调度正文。inject 由 foldInjectIntoPersona 写进 prefix 开头;
* 卡和角色用不带 dsh-purge 前缀的段,避免 host 过滤器提到 cap 前面。
*/
function attachRedteamOpsOnly(sections) {
let attached = false;
@@ -689,15 +750,7 @@ export function rewritePromptAssembly(assembled, fallbackInject = "", options =
sections = insertRoleSection(insertCardSection(withOps, opts.cardText), opts.roleText);
}
const body = overlayOfficialIdentity(sanitizeAssemblySections(sections));
const head = [];
if (String(injectText || "").trim()) {
head.push({
name: "dsh-purge",
text: injectText,
order: -100000,
interpolate: false,
});
}
const folded = foldInjectIntoPersona(body, injectText);
const tail = [];
if (String(protocol || "").trim()) {
tail.push({
@@ -707,7 +760,8 @@ export function rewritePromptAssembly(assembled, fallbackInject = "", options =
interpolate: false,
});
}
const outSections = ensureFullInject([...head, ...body, ...tail], injectText);
const withoutCapDup = folded.filter((section) => section?.name !== CAPABILITY_SECTION);
const outSections = ensureFullInject([...withoutCapDup, ...tail], injectText);
return {
...assembled,
sections: outSections,
+28 -3
View File
@@ -494,9 +494,12 @@ function rewriteLiveAssembly(config, assembled, context, decision = { mode: "ful
dropWorkspaceBaseline,
});
const closerFollow = closerFollowBytes(rewritten?.sections);
let subagent = false;
try { subagent = isChildAgentContext(context); } catch { subagent = false; }
purgeLog("assemble.rewrite", {
enabled,
redteam,
sub: subagent,
mode: decision?.mode || "full",
task: decision?.taskKind || "general",
last: decision?.lastRole || "unknown",
@@ -509,7 +512,21 @@ function rewriteLiveAssembly(config, assembled, context, decision = { mode: "ful
strip: config.stripMnemon !== false,
policy,
dropWorkspaceBaseline,
injectHead: Array.isArray(rewritten?.sections) && rewritten.sections[0]?.name === "dsh-purge",
injectHead: Array.isArray(rewritten?.sections) && (() => {
const first = rewritten.sections[0];
const firstText = String(first?.text || "");
const name = String(first?.name || "");
if (name === "dsh-purge") return true;
if (!inject) return false;
return /persona-prefix$|^persona$|^deployment:persona$/.test(name) && firstText.includes(inject);
})(),
folded: Array.isArray(rewritten?.sections) && (() => {
const first = rewritten.sections[0];
const name = String(first?.name || "");
if (!inject) return false;
return /persona-prefix$|^persona$|^deployment:persona$/.test(name)
&& String(first?.text || "").includes(inject);
})(),
injectTail: Array.isArray(rewritten?.sections) && rewritten.sections.length > 0
&& rewritten.sections[rewritten.sections.length - 1]?.name === "dsh-purge",
chatml: Array.isArray(rewritten?.sections) && /<project_instructions>|<\|im_start\|>/i.test(String(rewritten.sections[0]?.text || "")),
@@ -563,7 +580,6 @@ function permissionPresetsOf(ctx) {
}
function shouldKeepFreshFullAccess(session, svc) {
if (session?.header?.origin === "subagent") return false;
const state = typeof svc.permissionState === "function" ? svc.permissionState(session) : null;
const preset = state?.preset;
if (preset === "danger-full-access" || preset === "auto" || preset === "custom") return false;
@@ -761,9 +777,18 @@ function installIdentityOverride(ctx, config = {}) {
if (!ctx.__dshPurgeSessionHook) {
ctx.__dshPurgeSessionHook = true;
try {
ctx.on("session/created", () => {
ctx.on("session/created", (session) => {
try { refreshHungOperator(core.findDshHome()); } catch { /* 主目录还没有提示词 */ }
wrapAssemble("sessionCreated");
try {
if (session?.header?.origin !== "subagent") return;
const prepare = globalThis.__dshPurgePrepareAssemble;
if (typeof prepare !== "function") return;
const agents = ctx.agents || (typeof ctx.get === "function" ? ctx.get("agents") : null);
const child = typeof agents?.get === "function" ? agents.get(session.id) : null;
if (child?.ctx) prepare(child.ctx);
else prepare(ctx);
} catch { /* 子会话此时可能还没有 agent */ }
});
} catch { /* ignore */ }
}