Wire skills into agent runs: org-scoped, published-only, org-preferred resolution

ISkillCatalog.GetByKeysAsync now takes the org id and resolves each key within that org's namespace
only — the org's own published skill, else a shared builtin (null org), never another org's. Org-owned
is preferred over the builtin; only Published (golden-tested) skills are injected; the resolved
skill@version is recorded in the prompt heading and run trace. AgentRunExecutor threads
context.OrganizationId. SeatsPage now loads the org library (builtins + authored + installed), dedupes
to one entry per key, and flags drafts (won't run until published).

Verified: ArchitectureTests 8/8, IntegrationTests 48/48 (new SkillRunScopingTests: a run assembles the
org's own skill over the builtin of the same key, and another org's same-key skill never leaks in),
client build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-13 13:35:53 +03:30
parent cca7c68da3
commit 2ebe2808be
6 changed files with 216 additions and 9 deletions
+8 -3
View File
@@ -105,7 +105,12 @@ export function SeatsPage() {
if (!organizationId) return
void run(async () => {
setTeams(await api.get<Team[]>(`/api/orgboard/teams?organizationId=${organizationId}`))
setSkills(await api.get<Skill[]>('/api/skills/'))
// The org's library = shared builtins + its own authored/installed skills. The API returns
// every version (newest first per key); collapse to one selectable entry per key.
const lib = await api.get<Skill[]>(`/api/skills/?organizationId=${organizationId}`)
const byKey = new Map<string, Skill>()
for (const s of lib) if (!byKey.has(s.skillKey)) byKey.set(s.skillKey, s)
setSkills([...byKey.values()])
await loadConfigs()
})
}, [organizationId, loadConfigs, run])
@@ -359,9 +364,9 @@ export function SeatsPage() {
)}
<div className="flex flex-wrap gap-2">
{skills.map((skill) => (
<button key={skill.skillKey} onClick={() => toggleSkill(skill.skillKey)}>
<button key={skill.skillKey} onClick={() => toggleSkill(skill.skillKey)} title={skill.status !== 'Published' ? 'Draft — add roles + a golden test to publish before an agent can run it' : undefined}>
<Badge variant={agent.skillKeys.includes(skill.skillKey) ? 'default' : 'outline'}>
{skill.name}
{skill.name}{skill.status !== 'Published' ? ' · draft' : ''}
</Badge>
</button>
))}