mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
feat(pi-subagents): 重新启用子 Agent 并使用独立的子 Agent 提示词
- 根包恢复 file:./pi-subagents 依赖与 extensions/subagents.ts 加载 (排在 permission-system 之后、Tool Search 之前),并刷新根锁文件; Agent / get_subagent_result / steer_subagent 权限设为 allow, 子 Agent 内的实际操作仍逐个经权限系统并转发主会话。 - 新增 config/prompt/subagent/:执行者角色 preamble 与组合包自带的 general-purpose / Explore / Plan 定义(替换上游内置,去掉 bash find 冲突规则与写死的 Haiku 模型)。 - pi-subagents 新增 basePrompt / bundledAgentsDir / excludedExtensionPaths 选项:子 Agent 不再嵌入父会话提示词,身份标记置于开头;子会话排除 pi-ask-user。未传选项时保持上游行为。 - system-prompt.ts:customPrompt 自带开场白时不再覆盖(修复子 Agent 角色 与 <active_agent> 身份被抹掉);仅凭 customPrompt 上的 <active_agent> 识别子会话,routing 规则支持 scope,sections 支持 subagentExclude (子会话去掉 memory_policy 与 chrome_profile_bridge)。 - pi-hermes-memory:子会话只读,记忆/skill 写入工具返回错误,后台整理、 纠错检测、会话 flush 与索引写入全部跳过。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
b817d66a11
commit
360d8cffc8
@@ -20,6 +20,8 @@ export interface ToolCatalog {
|
||||
export interface RoutingRule {
|
||||
whenAll?: string[];
|
||||
whenAny?: string[];
|
||||
/** Which sessions the rule renders for; absent means both. */
|
||||
scope?: "main" | "subagent";
|
||||
text: string;
|
||||
}
|
||||
|
||||
@@ -31,6 +33,8 @@ export interface RoutingConfig {
|
||||
export interface SectionsConfig {
|
||||
version: 1;
|
||||
order: string[];
|
||||
/** Contributed sections removed from subagent (customPrompt) sessions. */
|
||||
subagentExclude: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +68,14 @@ export function resolveSectionsPath(): string {
|
||||
return fileURLToPath(new URL("sections.jsonc", PROMPT_CONFIG_DIR));
|
||||
}
|
||||
|
||||
export function resolveSubagentPreamblePath(): string {
|
||||
return fileURLToPath(new URL("subagent/preamble.md", PROMPT_CONFIG_DIR));
|
||||
}
|
||||
|
||||
export function resolveSubagentAgentsDir(): string {
|
||||
return fileURLToPath(new URL("subagent/agents/", PROMPT_CONFIG_DIR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip `//` and `/* ... *\/` comments while respecting string literals, so a
|
||||
* `//` inside a quoted value (for example `"https://…"`) is never removed.
|
||||
@@ -185,7 +197,7 @@ function validateToolCatalog(raw: unknown): ToolCatalog {
|
||||
return { version: 1, tools };
|
||||
}
|
||||
|
||||
const ROUTING_RULE_KEYS = new Set(["whenAll", "whenAny", "text"]);
|
||||
const ROUTING_RULE_KEYS = new Set(["whenAll", "whenAny", "scope", "text"]);
|
||||
|
||||
function validateRoutingRule(index: number, raw: unknown): RoutingRule {
|
||||
if (!isPlainObject(raw)) {
|
||||
@@ -205,10 +217,13 @@ function validateRoutingRule(index: number, raw: unknown): RoutingRule {
|
||||
if ("whenAny" in raw && !isStringArray(raw.whenAny)) {
|
||||
fail("routing.jsonc", `rule #${index}.whenAny must be an array of strings`);
|
||||
}
|
||||
if ("scope" in raw && raw.scope !== "main" && raw.scope !== "subagent") {
|
||||
fail("routing.jsonc", `rule #${index}.scope must be "main" or "subagent"`);
|
||||
}
|
||||
return raw as RoutingRule;
|
||||
}
|
||||
|
||||
function validateRoutingConfig(raw: unknown): RoutingConfig {
|
||||
export function validateRoutingConfig(raw: unknown): RoutingConfig {
|
||||
if (!isPlainObject(raw) || raw.version !== 1 || !Array.isArray(raw.rules)) {
|
||||
fail("routing.jsonc", 'expected { "version": 1, "rules": [ ... ] }');
|
||||
}
|
||||
@@ -220,7 +235,7 @@ function validateRoutingConfig(raw: unknown): RoutingConfig {
|
||||
|
||||
const SECTION_NAME_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
||||
|
||||
function validateSectionsConfig(raw: unknown): SectionsConfig {
|
||||
export function validateSectionsConfig(raw: unknown): SectionsConfig {
|
||||
if (!isPlainObject(raw) || raw.version !== 1 || !isStringArray(raw.order)) {
|
||||
fail("sections.jsonc", 'expected { "version": 1, "order": [ ... ] }');
|
||||
}
|
||||
@@ -229,7 +244,19 @@ function validateSectionsConfig(raw: unknown): SectionsConfig {
|
||||
fail("sections.jsonc", `"${name}" is not a valid section name`);
|
||||
}
|
||||
}
|
||||
return { version: 1, order: raw.order };
|
||||
if ("subagentExclude" in raw && !isStringArray(raw.subagentExclude)) {
|
||||
fail("sections.jsonc", `"subagentExclude" must be an array of section names`);
|
||||
}
|
||||
for (const name of raw.subagentExclude ?? []) {
|
||||
if (!SECTION_NAME_PATTERN.test(name) || name === "preamble") {
|
||||
fail("sections.jsonc", `"subagentExclude" entry "${name}" is not a valid section name`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
version: 1,
|
||||
order: raw.order,
|
||||
subagentExclude: isStringArray(raw.subagentExclude) ? raw.subagentExclude : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function loadToolCatalog(): ToolCatalog {
|
||||
@@ -262,6 +289,34 @@ export function readPreamble(): string | undefined {
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the bundle's subagent base preamble (`config/prompt/subagent/preamble.md`),
|
||||
* re-read on each call so the file is never cached between spawns.
|
||||
* Blank or missing → `undefined`, which keeps pi-subagents' upstream composition.
|
||||
*/
|
||||
export function readSubagentPreamble(): string | undefined {
|
||||
let raw: string;
|
||||
try {
|
||||
raw = readFileSync(resolveSubagentPreamblePath(), "utf8");
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = raw.replace(/\s+$/, "");
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `<active_agent name="..."/>` tag pi-subagents stamps into every child
|
||||
* prompt — tested ONLY against `systemPromptOptions.customPrompt`, never the
|
||||
* rendered prompt: a main session may contain the literal tag via project
|
||||
* docs and must still be treated as main.
|
||||
*/
|
||||
const ACTIVE_AGENT_TAG = /<active_agent\s+name=["'][^"']+["'][^>]*>/i;
|
||||
|
||||
export function isSubagentCustomPrompt(customPrompt: unknown): boolean {
|
||||
return typeof customPrompt === "string" && ACTIVE_AGENT_TAG.test(customPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the tool catalog to the mutable prompt options: a `snippet` string
|
||||
* replaces the tool's `<tools>` line (`null` hides it), `guidelines` replaces
|
||||
@@ -302,11 +357,19 @@ export function applyToolCatalog(options: MutablePromptOptions, catalog: ToolCat
|
||||
* `whenAny` tool is active (a rule with neither always applies). An absent
|
||||
* `selectedTools` means every tool counts as active, matching the previous
|
||||
* tool-routing behavior.
|
||||
*
|
||||
* `audience` filters `scope`: rules without a scope render for both "main"
|
||||
* and "subagent" sessions, scoped rules only for their own audience.
|
||||
*/
|
||||
export function renderRouting(rules: RoutingRule[], selectedTools: string[] | undefined): string {
|
||||
export function renderRouting(
|
||||
rules: RoutingRule[],
|
||||
selectedTools: string[] | undefined,
|
||||
audience: "main" | "subagent" = "main",
|
||||
): string {
|
||||
const isActive = (name: string) => selectedTools === undefined || selectedTools.includes(name);
|
||||
const lines: string[] = [];
|
||||
for (const rule of rules) {
|
||||
if (rule.scope !== undefined && rule.scope !== audience) continue;
|
||||
if (rule.whenAll !== undefined && !rule.whenAll.every(isActive)) continue;
|
||||
if (rule.whenAny !== undefined && !rule.whenAny.some(isActive)) continue;
|
||||
lines.push(`- ${rule.text}`);
|
||||
|
||||
Reference in New Issue
Block a user