Language-aware house style and a resizable chat split

House style now follows the app language: Chinese UI forces the new
bundled humanizer-zh skill into every run, English forces humanizer -
one variant per run, picked by preference with cross-language fallback
so an older catalogue never silently loses house style. Both variants
are protected from grant/revoke, list_skills labels each with its
language, the bundle guard requires both, and startup sync imports the
new skill into existing orgs on next launch. Covered by a unit test
that flips app_state.language and asserts which humanizer compiles.

The chat split is now adjustable: drag the conversation list's right
edge (200-480px, persisted in localStorage) and the composer textarea
grew a native vertical resize grip capped at 40vh.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-09 01:14:05 -04:00
1 parent c027508b97
commit eaaf36f859
16 files changed
+931 -45

No files matched your search

+1
View File
@@ -17,6 +17,7 @@ describe("bundled skill catalogue", () => {
const staged = stageBuiltinSkillsLibrary(resolve(process.cwd(), "built-in skills"), libraryRoot);
expect(staged.length).toBeGreaterThan(200);
expect(staged.some((unit) => unit.slug === "humanizer")).toBe(true);
expect(staged.some((unit) => unit.slug === "humanizer-zh")).toBe(true);
const withDescription = staged.filter((unit) => unit.manifest.description).length;
expect(withDescription / staged.length).toBeGreaterThan(0.9);
} finally {
+48 -4
View File
@@ -55,6 +55,12 @@ function harness(): Harness {
mkdirSync(join(sourceRoot, "humanizer"), { recursive: true });
writeFileSync(join(sourceRoot, "humanizer", "SKILL.md"), HUMANIZER_MD, "utf8");
writeFileSync(join(sourceRoot, "humanizer", "README.md"), "# About\n", "utf8");
mkdirSync(join(sourceRoot, "Humanizer-zh"), { recursive: true });
writeFileSync(
join(sourceRoot, "Humanizer-zh", "SKILL.md"),
"---\nname: humanizer-zh\ndescription: 去除文本中的 AI 生成痕迹。\n---\n\n像真人一样写作。\n",
"utf8",
);
mkdirSync(join(sourceRoot, "engineering"), { recursive: true });
writeFileSync(join(sourceRoot, "engineering", "engineering-backend-architect.md"), ARCHITECT_MD, "utf8");
writeFileSync(join(sourceRoot, "engineering", "README.md"), "docs, not a skill\n", "utf8");
@@ -75,8 +81,9 @@ describe("built-in skills", () => {
test("stages both bundle shapes into the library and imports them once per org", () => {
const h = harness();
const staged = stageBuiltinSkillsLibrary(h.sourceRoot, h.libraryRoot);
expect(staged.map((unit) => unit.slug).sort()).toEqual(["engineering-backend-architect", "humanizer"]);
expect(staged.map((unit) => unit.slug).sort()).toEqual(["engineering-backend-architect", "humanizer", "humanizer-zh"]);
expect(existsSync(join(h.libraryRoot, "humanizer", "SKILL.md"))).toBe(true);
expect(existsSync(join(h.libraryRoot, "humanizer-zh", "SKILL.md"))).toBe(true);
expect(existsSync(join(h.libraryRoot, "engineering-backend-architect", "SKILL.md"))).toBe(true);
const architect = staged.find((unit) => unit.slug === "engineering-backend-architect");
expect(architect?.manifest.name).toBe("Backend Architect");
@@ -86,12 +93,12 @@ describe("built-in skills", () => {
expect(humanizer?.manifest.description).toContain("AI-generated writing");
const first = h.connection.transaction(() => importStagedSkills(h.skills, h.orgId, staged));
expect(first).toMatchObject({ imported: 2, skipped: 0, retired: 0, failures: [] });
expect(first).toMatchObject({ imported: 3, skipped: 0, retired: 0, failures: [] });
const second = h.connection.transaction(() => importStagedSkills(h.skills, h.orgId, staged));
expect(second).toMatchObject({ imported: 0, skipped: 2, retired: 0 });
expect(second).toMatchObject({ imported: 0, skipped: 3, retired: 0 });
const slugs = (h.connection.db.prepare("select slug from skill_definitions where org_id = ? order by slug").all(h.orgId) as Array<{ slug: string }>)
.map((row) => row.slug);
expect(slugs).toEqual(["engineering-backend-architect", "humanizer"]);
expect(slugs).toEqual(["engineering-backend-architect", "humanizer", "humanizer-zh"]);
h.connection.close();
});
@@ -191,6 +198,43 @@ describe("built-in skills", () => {
h.connection.close();
});
test("the forced humanizer follows the app language, with cross-language fallback", () => {
const h = harness();
const staged = stageBuiltinSkillsLibrary(h.sourceRoot, h.libraryRoot);
h.connection.transaction(() => importStagedSkills(h.skills, h.orgId, staged));
let agentId = "";
h.connection.transaction(() => {
agentId = h.organization.createAgent({
orgId: h.orgId, name: "Ada", role: "Engineer", department: "Engineering",
modelProviderId: null, modelId: null, budgetCents: 1000, managerAgentId: null,
});
});
const promptFor = (runId: string) => {
const snapshotId = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId }));
return (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(snapshotId) as { compiled_prompt: string }).compiled_prompt;
};
// English UI (default): the English humanizer, and only that one.
const englishPrompt = promptFor("run_lang_en");
expect(englishPrompt).toContain("# Skill: humanizer");
expect(englishPrompt).not.toContain("humanizer-zh");
// Chinese UI: the Chinese variant replaces it.
h.connection.transaction(() => h.organization.updateUiPreferences({ language: "zh" }));
const chinesePrompt = promptFor("run_lang_zh");
expect(chinesePrompt).toContain("# Skill: humanizer-zh");
expect(chinesePrompt).not.toContain("# Skill: humanizer\n");
// Chinese UI against a catalogue without the zh variant falls back to
// English house style instead of silently dropping it.
h.connection.transaction(() => {
h.connection.db.prepare("update skill_definitions set status = 'retired' where org_id = ? and slug = 'humanizer-zh'").run(h.orgId);
});
const fallbackPrompt = promptFor("run_lang_fallback");
expect(fallbackPrompt).toContain("# Skill: humanizer");
h.connection.close();
});
test("frontmatter parsing reads inline values and block scalars", () => {
expect(parseFrontmatter(ARCHITECT_MD)).toMatchObject({
name: "Backend Architect",
+4
View File
@@ -68,6 +68,10 @@ function importSkill(h: Harness, name: string, slug: string): void {
insert into skill_revisions(id, skill_id, version_semver, manifest_json, source_type, source_ref, published_at, immutable_checksum)
values (?, ?, '1.0.0', '{}', 'local_directory', '/tmp', ?, 'checksum')
`).run(`skr_${slug}`, `skl_${slug}`, now);
h.connection.db.prepare(`
insert into skill_artifacts(id, revision_id, artifact_type, object_key, sha256, size_bytes)
values (?, ?, 'entrypoint', ?, 'sha', 1)
`).run(`ska_${slug}`, `skr_${slug}`, `/tmp/${slug}/SKILL.md`);
}
describe("skill grants", () => {