Skip to content

Commit 5e1752b

Browse files
committed
fix(skills): keep catalog bundles consistent and surface partial installs
Stop treating a failed sandbox as a full success, avoid writing a SHA when the catalog archive is missing, and pick the newest stored fork on migrate so files and chips match the catalog users actually see.
1 parent cd3f8b4 commit 5e1752b

22 files changed

Lines changed: 397 additions & 75 deletions

frontend/src/api/skill/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface SkillCatalogInstall {
1515
status: string;
1616
enabled: boolean;
1717
error?: string;
18+
bundle_sha256?: string;
1819
updated_at: string;
1920
}
2021

@@ -66,7 +67,7 @@ export function registerSkillCatalogFromFile(
6667
}
6768

6869
export function installSkillCatalog(catalogId: string, sandboxConfigIds: string[]) {
69-
return post<{ data: { installs: Record<string, string> } }>(
70+
return post<{ data: { installs: Record<string, string>; errors?: Record<string, string> } }>(
7071
`/api/v1/skills/catalog/${catalogId}/install`,
7172
{ sandbox_config_ids: sandboxConfigIds },
7273
);

frontend/src/components/SandboxSkillsPanel.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const manageBlock = source.slice(
1111
test('focused skill management expands env vars and transcript without a mid-page save button', () => {
1212
assert.match(manageBlock, /skillHasDeclaredEnvs\(managedSkill\)/)
1313
assert.match(manageBlock, /onEnvFieldBlur/)
14+
assert.match(source, /isBusy\(skill\) \|\| !hasEnvEdits\(skill\)/)
1415
assert.match(manageBlock, /SkillInstallTimeline/)
1516
assert.match(manageBlock, /settings\.skills\.manageUninstall/)
1617
assert.match(manageBlock, /skill-manage__progress/)

frontend/src/components/SandboxSkillsPanel.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
:value="managedSkill.enabled"
8888
:disabled="isBusy(managedSkill)"
8989
:loading="togglingId === managedSkill.id"
90-
@change="(v: any) => toggleEnabled(managedSkill, Boolean(v))"
90+
@change="(v: any) => managedSkill && toggleEnabled(managedSkill, Boolean(v))"
9191
/>
9292
<t-tooltip
9393
v-if="managedSkill.status === 'failed'"
@@ -157,15 +157,15 @@
157157
type="password"
158158
autocomplete="new-password"
159159
:name="`wk-se-focus-${envIdx}`"
160-
:readonly="!isEnvInputUnlocked(managedSkill.id, env.name)"
160+
:readonly="isBusy(managedSkill) || !isEnvInputUnlocked(managedSkill.id, env.name)"
161161
spellcheck="false"
162162
:placeholder="
163163
env.is_set
164164
? $t('settings.sandbox.skillEnv.placeholderSet')
165165
: $t('settings.sandbox.skillEnv.placeholderUnset')
166166
"
167167
@focus="unlockEnvInput(managedSkill.id, env.name)"
168-
@update:value="(v: string) => setEnvDraft(managedSkill.id, env.name, v)"
168+
@update:value="(v: string) => managedSkill && setEnvDraft(managedSkill.id, env.name, v)"
169169
@enter="saveEnvs(managedSkill, true)"
170170
@blur="onEnvFieldBlur(managedSkill)"
171171
/>
@@ -841,6 +841,7 @@ function envSaveInFlight(skill: ConfigSkill): boolean {
841841
}
842842
843843
async function saveEnvs(skill: ConfigSkill, silent = false) {
844+
if (isBusy(skill)) return
844845
const envs = envPayload(skill)
845846
if (Object.keys(envs).length === 0) return
846847
if (Object.values(envs).some((value) => !isValidEnvValueLength(value))) {
@@ -853,7 +854,7 @@ async function saveEnvs(skill: ConfigSkill, silent = false) {
853854
}
854855
855856
function onEnvFieldBlur(skill: ConfigSkill) {
856-
if (!hasEnvEdits(skill) || envSaveInFlight(skill)) return
857+
if (isBusy(skill) || !hasEnvEdits(skill) || envSaveInFlight(skill)) return
857858
void saveEnvs(skill, true)
858859
}
859860

frontend/src/i18n/locales/en-US.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ export default {
10231023
selectSkills: 'Select skills',
10241024
selectSkillsDesc: 'Choose which skills to enable. Uninstalled or not-ready skills cannot be checked.',
10251025
skillsAllListHint: 'Ready skills on this sandbox will be enabled. Uninstalled ones are not included until you install them.',
1026-
skillsListSummary: '{ready} ready, {pending} not installed on this sandbox',
1026+
skillsListSummary: '{ready} ready, {pending} not ready on this sandbox',
10271027
skillsListSummaryReadyOnly: '{ready} ready',
10281028
noSkillsAvailable: 'The workspace catalog has no skills yet.',
10291029
skillsNeedSandbox: 'Select a sandbox first.',
@@ -1432,6 +1432,8 @@ export default {
14321432
deleteSuccess: 'Removed from catalog',
14331433
registerAccepted: 'Added to catalog',
14341434
installAccepted: 'Install started',
1435+
installPartial: 'Started on some sandboxes. {failed} could not start.',
1436+
installOutdated: 'Differs from catalog',
14351437
loadFailed: 'Failed to load',
14361438
},
14371439
mcpService: 'MCP Service',

frontend/src/i18n/locales/ko-KR.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5329,6 +5329,8 @@ export default {
53295329
deleteSuccess: '카탈로그에서 삭제했습니다',
53305330
registerAccepted: '카탈로그에 추가했습니다',
53315331
installAccepted: '설치를 시작했습니다',
5332+
installPartial: '일부 샌드박스에서 설치를 시작했습니다. {failed}곳은 시작하지 못했습니다.',
5333+
installOutdated: '카탈로그와 버전이 다름',
53325334
loadFailed: '불러오지 못했습니다',
53335335
},
53345336
mcpService: 'MCP 서비스',
@@ -5733,7 +5735,7 @@ export default {
57335735
selectSkills: '스킬 선택',
57345736
selectSkillsDesc: '사용할 스킬을 선택하세요. 설치되지 않았거나 아직 준비되지 않은 스킬은 선택할 수 없습니다.',
57355737
skillsAllListHint: '이 샌드박스에서 준비된 스킬이 사용됩니다. 설치되지 않은 스킬은 설치해야 「전체」에 포함됩니다.',
5736-
skillsListSummary: '준비됨 {ready}개, 이 샌드박스에 미설치 {pending}개',
5738+
skillsListSummary: '준비됨 {ready}개, 이 샌드박스에서 아직 사용 불가 {pending}개',
57375739
skillsListSummaryReadyOnly: '준비됨 {ready}개',
57385740
noSkillsAvailable: '워크스페이스 카탈로그에 스킬이 없습니다.',
57395741
skillsNeedSandbox: '먼저 실행 샌드박스를 선택하세요.',

frontend/src/i18n/locales/ru-RU.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5329,6 +5329,8 @@ export default {
53295329
deleteSuccess: 'Удалено из каталога',
53305330
registerAccepted: 'Добавлено в каталог',
53315331
installAccepted: 'Установка начата',
5332+
installPartial: 'Установка начата на части песочниц. {failed} не удалось запустить.',
5333+
installOutdated: 'Отличается от каталога',
53325334
loadFailed: 'Не удалось загрузить',
53335335
},
53345336
mcpService: 'Сервис MCP',
@@ -5733,7 +5735,7 @@ export default {
57335735
selectSkills: 'Выбрать навыки',
57345736
selectSkillsDesc: 'Отметьте навыки, которые нужно включить. Неустановленные или неготовые навыки выбрать нельзя.',
57355737
skillsAllListHint: 'Будут включены готовые навыки в этой песочнице. Неустановленные не попадут в «Все», пока их не установить.',
5736-
skillsListSummary: '{ready} готовы, {pending} не установлены в эту песочницу',
5738+
skillsListSummary: '{ready} готовы, {pending} ещё нельзя включить в этой песочнице',
57375739
skillsListSummaryReadyOnly: '{ready} готовы',
57385740
noSkillsAvailable: 'В каталоге пространства ещё нет навыков.',
57395741
skillsNeedSandbox: 'Сначала выберите песочницу.',

frontend/src/i18n/locales/zh-CN.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5331,6 +5331,8 @@ export default {
53315331
deleteSuccess: '已从目录删除',
53325332
registerAccepted: '已添加到目录',
53335333
installAccepted: '已开始安装',
5334+
installPartial: '部分沙箱已开始安装,{failed} 个未能开始。',
5335+
installOutdated: '与目录版本不同',
53345336
loadFailed: '加载失败',
53355337
},
53365338
mcpService: 'MCP服务',
@@ -5735,7 +5737,7 @@ export default {
57355737
selectSkills: '选择技能',
57365738
selectSkillsDesc: '勾选要启用的技能。未安装或未就绪的不能勾选。',
57375739
skillsAllListHint: '将启用此沙箱上已就绪的技能。未安装的不会自动带上,装好后才会纳入「全部」。',
5738-
skillsListSummary: '{ready} 个已就绪,{pending} 个未安装到此沙箱',
5740+
skillsListSummary: '{ready} 个已就绪,{pending} 个尚未可在此沙箱启用',
57395741
skillsListSummaryReadyOnly: '{ready} 个已就绪',
57405742
noSkillsAvailable: '空间目录里还没有技能。',
57415743
skillsNeedSandbox: '请先选择运行沙箱。',

frontend/src/views/agent/AgentEditorModal.test.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ test('agent skill picker uses the catalog and only enables ready installs', () =
135135
assert.match(source, /showCatalogSkillList/)
136136
assert.match(source, /skillsSelectionMode\.value !== 'none'/)
137137
assert.match(source, /:disabled="!skill\.selectable"/)
138+
assert.match(source, /catalogPendingCount/)
139+
assert.match(source, /installPartial/)
138140
assert.match(source, /installCatalogToCurrent/)
139141
assert.match(source, /agent\.editor\.installToThisSandbox/)
140142
assert.match(source, /skill-pick-list/)

frontend/src/views/agent/AgentEditorModal.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,7 +2044,7 @@ const catalogReadyCount = computed(() =>
20442044
)
20452045
20462046
const catalogPendingCount = computed(() =>
2047-
catalogSkillRows.value.filter((skill) => !skill.installed || skill.installStatus === 'failed').length,
2047+
catalogSkillRows.value.filter((skill) => !skill.selectable).length,
20482048
)
20492049
20502050
const skillListSummary = computed(() => {
@@ -2117,8 +2117,13 @@ async function installCatalogToCurrent(skill: CatalogSkillRow) {
21172117
if (!configId || installingCatalogId.value) return
21182118
installingCatalogId.value = skill.id
21192119
try {
2120-
await installSkillCatalog(skill.id, [configId])
2121-
MessagePlugin.success(t('settings.skills.installAccepted'))
2120+
const res = await installSkillCatalog(skill.id, [configId])
2121+
const failed = Object.keys(res?.data?.errors || {}).length
2122+
if (failed > 0) {
2123+
MessagePlugin.warning(t('settings.skills.installPartial', { failed }))
2124+
} else {
2125+
MessagePlugin.success(t('settings.skills.installAccepted'))
2126+
}
21222127
await syncInstalledSkills(true)
21232128
} catch (e: any) {
21242129
MessagePlugin.error(e?.message || t('settings.sandbox.skillUploadFailed'))

frontend/src/views/settings/SkillSettings.test.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ test('catalog cards can install onto sandboxes and manage one install', () => {
2626
assert.match(source, /skill-card__icon-btn/)
2727
assert.match(source, /openCatalogFiles/)
2828
assert.match(source, /SkillFilesDrawer/)
29-
assert.match(source, /skill-card__chip-status/)
29+
assert.match(source, /installPartial/)
30+
assert.match(source, /installOutdated/)
31+
assert.match(source, /skill-card__chip--stale/)
32+
assert.match(source, /catalogInstallFailedCount/)
3033
assert.match(source, /chevron-right/)
3134
assert.match(source, /skill-card__heading/)
3235
assert.match(source, /align-items: center/)

0 commit comments

Comments
 (0)