fix(codex): use app-server native skills (#1289)

* fix(codex): use native skill catalog

* fix(codex): refresh changed skill inventory

* fix(codex): preserve skill fallback on discovery error

* fix(web): refresh skills when completion opens
This commit is contained in:
SSU-WEI HUANG
2026-08-02 08:49:35 +08:00
committed by GitHub
parent 82da539d32
commit 2556729ba8
7 changed files with 231 additions and 9 deletions
+41 -1
View File
@@ -17,10 +17,11 @@ import { AppServerEventConverter } from './utils/appServerEventConverter';
import { detectImageMimeType, registerGeneratedImage } from '@/modules/common/generatedImages';
import { registerAppServerPermissionHandlers } from './utils/appServerPermissionAdapter';
import { buildThreadStartParams, buildTurnStartParams } from './utils/appServerConfig';
import type { ThreadGoal, ThreadGoalStatus } from './appServerTypes';
import type { SkillMetadata, ThreadGoal, ThreadGoalStatus } from './appServerTypes';
import { shouldIgnoreTerminalEvent } from './utils/terminalEventGuard';
import { parseCodexSpecialCommand } from './codexSpecialCommands';
import { extractErrorInfo } from '@/utils/errorUtils';
import { RPC_METHODS } from '@hapi/protocol/rpcMethods';
import {
RemoteLauncherBase,
type RemoteLauncherDisplayContext,
@@ -3086,7 +3087,38 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
}
});
let nativeSkills: SkillMetadata[] = [];
let nativeSkillsAvailable = false;
const refreshNativeSkills = async (forceReload: boolean): Promise<void> => {
const response = await appServerClient.listSkills({
cwds: [session.path],
forceReload
});
const inventory = response.data?.find(entry => entry.cwd === session.path)
?? response.data?.[0];
if (!inventory || (inventory.skills.length === 0 && (inventory.errors?.length ?? 0) > 0)) {
throw new Error('skills/list returned no usable inventory');
}
nativeSkills = inventory.skills.filter(skill => skill.enabled);
if (!nativeSkillsAvailable) {
nativeSkillsAvailable = true;
session.client.rpcHandlerManager.registerHandler(RPC_METHODS.ListSkills, async () => ({
success: true,
skills: nativeSkills.map(skill => ({
name: skill.name,
description: skill.description
}))
}));
}
};
appServerClient.setNotificationHandler((method, params) => {
if (method === 'skills/changed') {
void refreshNativeSkills(true).catch((error) => {
logger.debug(`[Codex] failed to refresh skills: ${errorMessage(error)}`);
});
return;
}
const events = appServerEventConverter.handleNotification(method, params);
for (const event of events) {
const eventRecord = asRecord(event) ?? { type: undefined };
@@ -3165,6 +3197,13 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
experimentalApi: true
}
});
try {
await refreshNativeSkills(false);
} catch (error) {
logger.debug(`[Codex] skills/list failed: ${errorMessage(error)}; keeping filesystem fallback`);
}
let supportsTurnCollaborationMode = true;
let supportsGoals = true;
try {
@@ -3695,6 +3734,7 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
cwd: session.path,
mode,
cliOverrides: session.codexCliOverrides,
skills: nativeSkills,
overrides: suppressCollaborationMode
? { suppressCollaborationMode: true }
: undefined