fix(chat): pin composer, set reply language, and track model loading

This commit is contained in:
Jeffrey Wu
2026-09-07 12:00:27 +08:00
parent 5c61371d7e
commit f4e3f41c2c
9 changed files with 299 additions and 36 deletions
+9
View File
@@ -3,6 +3,14 @@ import path from "node:path";
import { DefaultResourceLoader, SettingsManager, type InlineExtension, type ResourceDiagnostic, type Skill } from "@earendil-works/pi-coding-agent";
import type { SkillCatalogItem } from "@agent-studio/shared";
const languagePolicy = `会话回复语言规则:
默认使用中文回复,包括中文请求。
仅当首条用户输入中的自然语言请求明确为英文时,当前 Session 才使用英文回复。
判断时忽略 Skill 名称、Skill XML/正文、命令、路径、代码和技术标识;这些内容不作为英文发起会话的依据。
首条输入仅包含 Skill 内容或 Skill 名称,或自然语言无法明确判断时,选择中文;不要等待后续请求再选择语言。
选定的回复语言在当前 Session 内保持一致;Reset 创建新 Session 后重新判断。
保留代码、路径、命令和技术标识的原文。`;
export interface SkillIndexEntry extends SkillCatalogItem { canonicalPath: string }
export class SkillCatalog {
@@ -68,6 +76,7 @@ export class SkillCatalog {
settingsManager: SettingsManager.inMemory(),
additionalSkillPaths: this.canonicalDirectories,
extensionFactories,
appendSystemPromptOverride: (base) => [...base, languagePolicy],
noPromptTemplates: true,
noThemes: true,
noContextFiles: true,
+28
View File
@@ -103,6 +103,34 @@ describe("SkillCatalog", () => {
it("discovers .agents and .pi skills without reading bodies into the catalog", async () => {
const root = await mkdtemp(path.join(tmpdir(), "studio-skills-")); await createSkill(root, ".agents/skills/one", "one", "First skill"); await createSkill(root, ".pi/skills/two", "two", "Second skill"); const catalog = new SkillCatalog(root, path.join(root, "agent-dir")); await catalog.reload(); expect(catalog.list().filter((item) => ["one", "two"].includes(item.name))).toHaveLength(2); expect(JSON.stringify(catalog.list())).not.toContain("SECRET BODY");
});
it("appends the Session language policy while preserving default prompts, skills and extensions", async () => {
const root = await mkdtemp(path.join(tmpdir(), "studio-language-"));
const agentDir = path.join(root, "agent-dir");
await createSkill(root, ".agents/skills/one", "one", "First skill");
await mkdir(path.join(root, ".pi/skills"), { recursive: true });
await writeFile(path.join(root, ".pi/APPEND_SYSTEM.md"), "Existing appended instruction");
const catalog = new SkillCatalog(root, agentDir);
await catalog.reload();
const extension = vi.fn();
const loader = await catalog.createSessionLoader(root, agentDir, [extension]);
expect(loader.getSystemPrompt()).toBeUndefined(); // Pi still builds its default Tool/Skill prompt.
expect(loader.getSystemPromptSource()).toBeUndefined();
const appended = loader.getAppendSystemPrompt();
expect(appended[0]).toBe("Existing appended instruction");
const policy = appended.at(-1)!;
expect(policy).toContain("默认使用中文回复,包括中文请求");
expect(policy).toContain("仅当首条用户输入中的自然语言请求明确为英文时");
expect(policy).toContain("Skill 名称、Skill XML/正文、命令、路径、代码和技术标识");
expect(policy).toContain("首条输入仅包含 Skill 内容或 Skill 名称,或自然语言无法明确判断时,选择中文");
expect(policy).toContain("当前 Session 内保持一致");
expect(policy).toContain("Reset 创建新 Session 后重新判断");
expect(loader.getSkills().skills.some((skill) => skill.name === "one")).toBe(true);
expect(extension).toHaveBeenCalledOnce();
await writeFile(path.join(root, ".pi/SYSTEM.md"), "Existing system prompt");
const next = await catalog.createSessionLoader(root, agentDir, []);
expect(next.getSystemPrompt()).toBe("Existing system prompt");
expect(next.getAppendSystemPrompt()).toEqual(appended);
});
it("loads path-delimited AGENT_SKILLS_DIRS and de-duplicates canonical paths", async () => {
const root = await mkdtemp(path.join(tmpdir(), "studio-skills-")); await mkdir(path.join(root, ".agents/skills"), { recursive: true }); await mkdir(path.join(root, ".pi/skills"), { recursive: true }); const extra = path.join(root, "extra"); await createSkill(root, "extra/custom", "custom", "Extra skill"); const catalog = new SkillCatalog(root, path.join(root, "agent-dir"), [extra, extra].join(path.delimiter)); await catalog.reload(); expect(catalog.list().filter((item) => item.name === "custom")).toHaveLength(1);
});