From f3a09e43e416acde4e384423959d03f03ee8343b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B9=E5=9D=A4?= Date: Mon, 21 Sep 2026 09:06:45 +0800 Subject: [PATCH 1/2] fix(sdk): clear removed agent skill bindings --- .../src/internal/providers/bailian/mapper.ts | 25 +++++++++---------- .../src/internal/providers/qoder/mapper.ts | 13 +++++----- packages/sdk/tests/unit/bailian.test.ts | 5 ++++ .../sdk/tests/unit/qoder-examples.test.ts | 1 + 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/sdk/src/internal/providers/bailian/mapper.ts b/packages/sdk/src/internal/providers/bailian/mapper.ts index 9802bbe..462c234 100644 --- a/packages/sdk/src/internal/providers/bailian/mapper.ts +++ b/packages/sdk/src/internal/providers/bailian/mapper.ts @@ -290,19 +290,18 @@ export function mapAgent( } } - // Skills - if (refs.skill_ids.length) { - body.skills = refs.skill_ids.map((s) => ({ - // Bailian's SkillType enum is "customer" | "official"; map the - // resolver's generic "custom" sentinel to "customer". - type: s.type === "custom" ? "customer" : s.type, - skill_id: s.skill_id, - // Bailian composes `{skill_id}@{version}` internally and rejects - // entries without a version. Prefer explicit external references, then - // the latest active remote version, then the common initial version. - version: s.version ?? skillVersions?.[s.skill_id] ?? "1.0", - })); - } + // Skills are an explicit replacement set. Keep an empty array in the full update + // payload so removing the last binding has the same declarative meaning everywhere. + body.skills = refs.skill_ids.map((s) => ({ + // Bailian's SkillType enum is "customer" | "official"; map the + // resolver's generic "custom" sentinel to "customer". + type: s.type === "custom" ? "customer" : s.type, + skill_id: s.skill_id, + // Bailian composes `{skill_id}@{version}` internally and rejects + // entries without a version. Prefer explicit external references, then + // the latest active remote version, then the common initial version. + version: s.version ?? skillVersions?.[s.skill_id] ?? "1.0", + })); return body; } diff --git a/packages/sdk/src/internal/providers/qoder/mapper.ts b/packages/sdk/src/internal/providers/qoder/mapper.ts index b4f3bf0..3bf2113 100644 --- a/packages/sdk/src/internal/providers/qoder/mapper.ts +++ b/packages/sdk/src/internal/providers/qoder/mapper.ts @@ -466,13 +466,12 @@ export function mapAgent( } } - // Skills - if (refs.skill_ids.length) { - body.skills = refs.skill_ids.map((s) => ({ - type: s.type === "official" ? "qoder" : s.type, - skill_id: s.skill_id, - })); - } + // Skills are an explicit replacement set. Qoder agent updates use merge semantics, + // so omitting this field would retain remote bindings when the declaration is `skills: []`. + body.skills = refs.skill_ids.map((s) => ({ + type: s.type === "official" ? "qoder" : s.type, + skill_id: s.skill_id, + })); return body; } diff --git a/packages/sdk/tests/unit/bailian.test.ts b/packages/sdk/tests/unit/bailian.test.ts index b1312f0..2cbfe6e 100644 --- a/packages/sdk/tests/unit/bailian.test.ts +++ b/packages/sdk/tests/unit/bailian.test.ts @@ -222,6 +222,11 @@ describe("Bailian mapAgent", () => { expect(body.skills).toEqual([{ type: "customer", skill_id: "skill_abc", version: "1.0" }]); }); + test("keeps an explicit empty skill replacement set", () => { + const body = mapAgent("helper", minimalDecl, emptyRefs) as Record; + expect(body.skills).toEqual([]); + }); + test("injects agents metadata when projectName provided", () => { const body = mapAgent("helper", minimalDecl, emptyRefs, undefined, "my-project") as Record; const meta = body.metadata as Record; diff --git a/packages/sdk/tests/unit/qoder-examples.test.ts b/packages/sdk/tests/unit/qoder-examples.test.ts index 2691205..02d2655 100644 --- a/packages/sdk/tests/unit/qoder-examples.test.ts +++ b/packages/sdk/tests/unit/qoder-examples.test.ts @@ -97,6 +97,7 @@ test("qoder agent mapper preserves declared tool permission policies", () => { ], }, ]); + expect(body.skills).toEqual([]); }); test("qoder permission overrides are case- and separator-insensitive", () => { From e0291df9cb7cb2b1bbdf20b188a1e983599b6bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B9=E5=9D=A4?= Date: Mon, 21 Sep 2026 09:10:34 +0800 Subject: [PATCH 2/2] chore: add changeset for skill binding cleanup --- .changeset/clear-removed-agent-skills.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/clear-removed-agent-skills.md diff --git a/.changeset/clear-removed-agent-skills.md b/.changeset/clear-removed-agent-skills.md new file mode 100644 index 0000000..bed275a --- /dev/null +++ b/.changeset/clear-removed-agent-skills.md @@ -0,0 +1,7 @@ +--- +"@openagentpack/sdk": patch +"@openagentpack/cli": patch +"@openagentpack/playground": patch +--- + +Clear stale Bailian and Qoder agent skill bindings when the last declared skill is removed.