Skip to content

Commit 782d95c

Browse files
fix(web): show project skills in selectors
1 parent 2daff8c commit 782d95c

24 files changed

Lines changed: 691 additions & 37 deletions

‎apps/server/src/orchestration/Layers/ProviderCommandReactor.ts‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -655,21 +655,28 @@ const make = Effect.gen(function* () {
655655
thread,
656656
projects: project ? [project] : [],
657657
});
658+
const refreshWorkspaceSnapshot = effectiveCwd
659+
? providerRegistry
660+
.refreshWorkspaceSnapshot({ instanceId: desiredInstanceId, cwd: effectiveCwd })
661+
.pipe(Effect.forkDetach)
662+
: Effect.void;
658663

659664
const startProviderSession = (input?: {
660665
readonly resumeCursor?: unknown;
661666
readonly provider?: ProviderDriverKind;
662667
}) =>
663-
providerService.startSession(threadId, {
664-
threadId,
665-
...(preferredProvider ? { provider: preferredProvider } : {}),
666-
providerInstanceId: desiredInstanceId,
667-
...(effectiveCwd ? { cwd: effectiveCwd } : {}),
668-
...(thread.title ? { title: thread.title } : {}),
669-
modelSelection: desiredModelSelection,
670-
...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}),
671-
runtimeMode: desiredRuntimeMode,
672-
});
668+
providerService
669+
.startSession(threadId, {
670+
threadId,
671+
...(preferredProvider ? { provider: preferredProvider } : {}),
672+
providerInstanceId: desiredInstanceId,
673+
...(effectiveCwd ? { cwd: effectiveCwd } : {}),
674+
...(thread.title ? { title: thread.title } : {}),
675+
modelSelection: desiredModelSelection,
676+
...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}),
677+
runtimeMode: desiredRuntimeMode,
678+
})
679+
.pipe(Effect.tap(() => refreshWorkspaceSnapshot));
673680

674681
const bindSessionToThread = (session: ProviderSession) =>
675682
Effect.gen(function* () {
@@ -727,6 +734,7 @@ const make = Effect.gen(function* () {
727734
!shouldRestartForModelChange &&
728735
!shouldRestartForModelSelectionChange
729736
) {
737+
yield* refreshWorkspaceSnapshot;
730738
return existingSessionThreadId;
731739
}
732740

‎apps/server/src/provider/Drivers/CodexDriver.ts‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ import { ServerConfig } from "../../config.ts";
3636
import { ServerSettingsService } from "../../serverSettings.ts";
3737
import { ProviderDriverError } from "../Errors.ts";
3838
import { makeCodexAdapter } from "../Layers/CodexAdapter.ts";
39-
import { checkCodexProviderStatus, makePendingCodexProvider } from "../Layers/CodexProvider.ts";
39+
import {
40+
checkCodexProviderStatus,
41+
makePendingCodexProvider,
42+
probeCodexSkillsForCwd,
43+
} from "../Layers/CodexProvider.ts";
44+
import { resolveCodexLaunchArgs } from "../Layers/codexLaunchArgs.ts";
4045
import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
4146
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
4247
import * as ModelManifest from "../ModelManifest.ts";
@@ -216,6 +221,34 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = {
216221
}),
217222
),
218223
);
224+
const snapshotForCwd = (cwd: string) =>
225+
!effectiveConfig.enabled
226+
? snapshot.getSnapshot
227+
: Effect.all([
228+
snapshot.getSnapshot,
229+
probeCodexSkillsForCwd({
230+
binaryPath: effectiveConfig.binaryPath,
231+
homePath: effectiveConfig.homePath,
232+
launchArgs: resolveCodexLaunchArgs(effectiveConfig.launchArgs, processEnv),
233+
cwd,
234+
environment: processEnv,
235+
}).pipe(
236+
Effect.scoped,
237+
Effect.timeout("20 seconds"),
238+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
239+
),
240+
]).pipe(
241+
Effect.map(([machineSnapshot, skills]) => ({ ...machineSnapshot, skills })),
242+
Effect.mapError(
243+
(cause) =>
244+
new ProviderDriverError({
245+
driver: DRIVER_KIND,
246+
instanceId,
247+
detail: `Failed to probe Codex skills for '${cwd}'`,
248+
cause,
249+
}),
250+
),
251+
);
219252

220253
return {
221254
instanceId,
@@ -225,6 +258,7 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = {
225258
accentColor,
226259
enabled,
227260
snapshot,
261+
snapshotForCwd,
228262
adapter,
229263
textGeneration,
230264
} satisfies ProviderInstance;

‎apps/server/src/provider/Drivers/OpenCodeDriver.ts‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { makeOpenCodeAdapter } from "../Layers/OpenCodeAdapter.ts";
3030
import {
3131
checkOpenCodeProviderStatus,
3232
makePendingOpenCodeProvider,
33+
openCodeSkillsToServerProviderSkills,
3334
} from "../Layers/OpenCodeProvider.ts";
3435
import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
3536
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
@@ -163,6 +164,34 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv>
163164
Effect.provideService(OpenCodeServerOwner.OpenCodeServerOwner, serverOwner),
164165
Effect.provideService(OpenCodeRuntime, openCodeRuntime),
165166
);
167+
const loadSkillsForCwd = (cwd: string) =>
168+
effectiveConfig.serverUrl.trim().length > 0
169+
? Effect.scoped(
170+
Effect.gen(function* () {
171+
const server = yield* openCodeRuntime.connectToOpenCodeServer({
172+
binaryPath: effectiveConfig.binaryPath,
173+
directory: cwd,
174+
serverUrl: effectiveConfig.serverUrl,
175+
...(effectiveConfig.serverPassword
176+
? { serverPassword: effectiveConfig.serverPassword }
177+
: {}),
178+
environment: processEnv,
179+
});
180+
const client = openCodeRuntime.createOpenCodeSdkClient({
181+
baseUrl: server.url,
182+
directory: cwd,
183+
...(effectiveConfig.serverPassword
184+
? { serverPassword: effectiveConfig.serverPassword }
185+
: {}),
186+
});
187+
return yield* openCodeRuntime.loadOpenCodeSkills(client);
188+
}),
189+
)
190+
: openCodeRuntime.loadSkillsFromCli({
191+
binaryPath: effectiveConfig.binaryPath,
192+
cwd,
193+
environment: processEnv,
194+
});
166195

167196
const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
168197
const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<OpenCodeSettings>>(
@@ -204,6 +233,27 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv>
204233
accentColor,
205234
enabled,
206235
snapshot,
236+
snapshotForCwd: (cwd) =>
237+
!effectiveConfig.enabled
238+
? snapshot.getSnapshot
239+
: Effect.all([
240+
snapshot.getSnapshot,
241+
loadSkillsForCwd(cwd).pipe(Effect.timeout("20 seconds")),
242+
]).pipe(
243+
Effect.map(([machineSnapshot, skills]) => ({
244+
...machineSnapshot,
245+
skills: openCodeSkillsToServerProviderSkills(skills),
246+
})),
247+
Effect.mapError(
248+
(cause) =>
249+
new ProviderDriverError({
250+
driver: DRIVER_KIND,
251+
instanceId,
252+
detail: `Failed to probe OpenCode skills for '${cwd}'`,
253+
cause,
254+
}),
255+
),
256+
),
207257
adapter,
208258
textGeneration,
209259
} satisfies ProviderInstance;

‎apps/server/src/provider/Layers/CodexProvider.ts‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,53 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
414414
} satisfies CodexAppServerProviderSnapshot;
415415
});
416416

417+
export const probeCodexSkillsForCwd = Effect.fn("probeCodexSkillsForCwd")(function* (input: {
418+
readonly binaryPath: string;
419+
readonly homePath?: string;
420+
readonly launchArgs?: string;
421+
readonly cwd: string;
422+
readonly environment?: NodeJS.ProcessEnv;
423+
}) {
424+
const resolvedHomePath = input.homePath ? expandHomePath(input.homePath) : undefined;
425+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
426+
const environment = {
427+
...input.environment,
428+
...(resolvedHomePath ? { CODEX_HOME: resolvedHomePath } : {}),
429+
};
430+
const spawnCommand = yield* resolveSpawnCommand(
431+
input.binaryPath,
432+
codexAppServerArgs(input.launchArgs),
433+
{ env: environment, extendEnv: true },
434+
);
435+
const child = yield* spawner
436+
.spawn(
437+
ChildProcess.make(spawnCommand.command, spawnCommand.args, {
438+
cwd: input.cwd,
439+
env: environment,
440+
extendEnv: true,
441+
forceKillAfter: CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER,
442+
shell: spawnCommand.shell,
443+
}),
444+
)
445+
.pipe(
446+
Effect.mapError(
447+
(cause) =>
448+
new CodexErrors.CodexAppServerSpawnError({
449+
command: `${input.binaryPath} app-server`,
450+
cause,
451+
}),
452+
),
453+
);
454+
const clientContext = yield* Layer.build(CodexClient.layerChildProcess(child));
455+
const client = yield* Effect.service(CodexClient.CodexAppServerClient).pipe(
456+
Effect.provide(clientContext),
457+
);
458+
yield* client.request("initialize", buildCodexInitializeParams());
459+
yield* client.notify("initialized", undefined);
460+
const skillsResponse = yield* client.request("skills/list", { cwds: [input.cwd] });
461+
return parseCodexSkillsListResponse(skillsResponse, input.cwd);
462+
});
463+
417464
const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] => {
418465
const models = new Set<string>();
419466
for (const model of codexSettings.customModels) {

‎apps/server/src/provider/Layers/OpenCodeAdapter.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {
391391
cause: null,
392392
}),
393393
),
394+
loadOpenCodeSkills: () => Effect.succeed([]),
394395
loadInventoryFromCli: () =>
395396
Effect.fail(
396397
new OpenCodeRuntimeError({
@@ -399,6 +400,7 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {
399400
cause: null,
400401
}),
401402
),
403+
loadSkillsFromCli: () => Effect.succeed([]),
402404
};
403405

404406
const providerSessionDirectoryTestLayer = Layer.succeed(ProviderSessionDirectory, {

‎apps/server/src/provider/Layers/OpenCodeProvider.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {
149149
)
150150
: Effect.succeed(runtimeMock.state.inventory as OpenCodeInventory);
151151
},
152+
loadOpenCodeSkills: () => Effect.succeed([]),
153+
loadSkillsFromCli: () => Effect.succeed([]),
152154
};
153155

154156
beforeEach(() => {

‎apps/server/src/provider/Layers/OpenCodeProvider.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,11 @@ function trimOptional(value: string | null | undefined): string | undefined {
260260
return trimmed && trimmed.length > 0 ? trimmed : undefined;
261261
}
262262

263-
function flattenOpenCodeSkills(input: OpenCodeInventory): ReadonlyArray<ServerProviderSkill> {
263+
export function openCodeSkillsToServerProviderSkills(
264+
input: OpenCodeInventory["skills"] | undefined,
265+
): ReadonlyArray<ServerProviderSkill> {
264266
const skills: ServerProviderSkill[] = [];
265-
for (const skill of input.skills ?? []) {
267+
for (const skill of input ?? []) {
266268
const name = trimOptional(skill.name);
267269
const path = trimOptional(skill.location);
268270
if (!name || !path) {
@@ -475,7 +477,7 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu
475477
customModels,
476478
DEFAULT_OPENCODE_MODEL_CAPABILITIES,
477479
);
478-
const skills = flattenOpenCodeSkills(inventoryExit.value.inventory);
480+
const skills = openCodeSkillsToServerProviderSkills(inventoryExit.value.inventory.skills);
479481
const connectedCount = inventoryExit.value.inventory.providerList.connected.length;
480482
return buildServerProvider({
481483
presentation: OPENCODE_PRESENTATION,

0 commit comments

Comments
 (0)