fix(chat): pin composer, set reply language, and track model loading
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user