perf: speed up model/agent readiness on the initial draft
Load startup config under the owning project's directory key (resolving from a worktree directory when needed) so the auto-opened draft, which activates the project, finds a ready providers/agents snapshot instead of triggering a second load. Also dedupe app.agents: listAgents now takes the directory directly and shares an in-flight request, so the config store and agents store no longer issue duplicate agent fetches at startup.
This commit is contained in:
@@ -29,6 +29,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Sessions: fixed a bug where a running session would briefly flicker as idle (in the sidebar, the send/stop button, and the status row) when the app is protected by a password.
|
||||
- Desktop: you can now open developer tools from the Help menu.
|
||||
- Sessions: new draft sessions now start from the default model and agent instead of inheriting the previous session's selection, and fall back to OpenCode's own `default_agent` (and its model) when no OpenChamber default is set.
|
||||
- Startup: the model and agent now appear faster on the initial draft — config loads under the project key up front (no reload when the draft opens) and the agent list is fetched once instead of per consumer.
|
||||
|
||||
## [1.12.4] - 2026-06-11
|
||||
|
||||
|
||||
@@ -229,6 +229,7 @@ class OpencodeService {
|
||||
private currentDirectory: string | undefined = undefined;
|
||||
private directoryContextQueue: Promise<void> = Promise.resolve();
|
||||
private listDirectoryInFlight: Map<string, Promise<FilesystemEntry[]>> = new Map();
|
||||
private listAgentsInFlight: Map<string, Promise<Agent[]>> = new Map();
|
||||
private listDirectoryCache: Map<string, { entries: FilesystemEntry[]; expiresAt: number }> = new Map();
|
||||
|
||||
constructor(baseUrl: string = DEFAULT_BASE_URL) {
|
||||
@@ -1294,14 +1295,34 @@ class OpencodeService {
|
||||
* useAgentsStore) can observe failure and retry; silently returning an
|
||||
* empty list would defeat retries and clear the cached agent list.
|
||||
*/
|
||||
async listAgents(): Promise<Agent[]> {
|
||||
const response = await this.client.app.agents(
|
||||
this.currentDirectory ? { directory: this.currentDirectory } : undefined
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(`app.agents failed: ${formatSdkError(response.error)}`);
|
||||
async listAgents(directory?: string | null): Promise<Agent[]> {
|
||||
// Pass the directory explicitly so we don't depend on (and serialize behind)
|
||||
// withDirectory's shared context queue. Concurrent callers for the same
|
||||
// directory (e.g. config store + agents store at startup) share one request.
|
||||
const effectiveDirectory = this.normalizeCandidatePath(directory) ?? directory ?? this.currentDirectory ?? undefined;
|
||||
const key = effectiveDirectory ?? '';
|
||||
|
||||
const existing = this.listAgentsInFlight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const request = (async () => {
|
||||
const response = await this.client.app.agents(
|
||||
effectiveDirectory ? { directory: effectiveDirectory } : undefined
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(`app.agents failed: ${formatSdkError(response.error)}`);
|
||||
}
|
||||
return response.data || [];
|
||||
})();
|
||||
|
||||
this.listAgentsInFlight.set(key, request);
|
||||
try {
|
||||
return await request;
|
||||
} finally {
|
||||
this.listAgentsInFlight.delete(key);
|
||||
}
|
||||
return response.data || [];
|
||||
}
|
||||
|
||||
// SSE infrastructure removed — EventPipeline in sync/event-pipeline.ts handles
|
||||
|
||||
@@ -245,8 +245,10 @@ export const useAgentsStore = create<AgentsStore>()(
|
||||
try {
|
||||
const queryParams = configDirectory ? `?directory=${encodeURIComponent(configDirectory)}` : '';
|
||||
|
||||
// Ensure we list agents using the correct project context
|
||||
const agents = await opencodeClient.withDirectory(configDirectory, () => opencodeClient.listAgents());
|
||||
// Ensure we list agents using the correct project context. Pass the
|
||||
// directory directly so this shares the in-flight request with the config
|
||||
// store instead of issuing a duplicate agents fetch at startup.
|
||||
const agents = await opencodeClient.listAgents(configDirectory);
|
||||
|
||||
const agentsWithScope = await Promise.all(
|
||||
agents.map(async (agent) => {
|
||||
|
||||
@@ -12,6 +12,8 @@ import { useSelectionStore } from "@/sync/selection-store";
|
||||
import { getRegisteredRuntimeAPIs } from "@/contexts/runtimeAPIRegistry";
|
||||
import { updateDesktopSettings } from "@/lib/persistence";
|
||||
import { useDirectoryStore } from "@/stores/useDirectoryStore";
|
||||
import { useProjectsStore } from "@/stores/useProjectsStore";
|
||||
import { resolveProjectForSessionDirectory } from "@/lib/projectResolution";
|
||||
import { streamDebugEnabled } from "@/stores/utils/streamDebug";
|
||||
import { parseModelIdentifier } from "@/lib/modelIdentifier";
|
||||
import { runtimeFetch } from "@/lib/runtime-fetch";
|
||||
@@ -1710,7 +1712,7 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
const [agents, openChamberDefaults, opencodeConfig] = await Promise.all([
|
||||
measureStartupTrace(
|
||||
'loadAgents:api',
|
||||
() => opencodeClient.withDirectory(fromDirectoryKey(directoryKey), () => opencodeClient.listAgents()),
|
||||
() => opencodeClient.listAgents(fromDirectoryKey(directoryKey)),
|
||||
{ directoryKey, source, requestedDirectory, effectiveDirectory, attempt: attempt + 1 },
|
||||
),
|
||||
fetchOpenChamberDefaults(),
|
||||
@@ -2634,10 +2636,28 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
|
||||
get().invalidateProviderCache();
|
||||
|
||||
// Config (providers/agents/defaults) lives at the PROJECT level. If the
|
||||
// app starts on a worktree directory, load config under the owning
|
||||
// project's key so the initial draft — which activates the project — finds
|
||||
// a ready snapshot instead of triggering a second provider/agent load.
|
||||
const initialDirectory = opencodeClient.getDirectory()
|
||||
?? useDirectoryStore.getState().currentDirectory
|
||||
?? fromDirectoryKey(get().activeDirectoryKey);
|
||||
const resolvedProject = resolveProjectForSessionDirectory(
|
||||
useProjectsStore.getState().projects,
|
||||
useSessionUIStore.getState().availableWorktreesByProject,
|
||||
initialDirectory ?? null,
|
||||
);
|
||||
const configDirectory = resolvedProject?.path ?? initialDirectory ?? null;
|
||||
const configDirectoryKey = toDirectoryKey(configDirectory);
|
||||
if (get().activeDirectoryKey !== configDirectoryKey) {
|
||||
set({ activeDirectoryKey: configDirectoryKey });
|
||||
}
|
||||
|
||||
if (debug) console.log("Loading providers and agents...");
|
||||
await Promise.all([
|
||||
get().loadProviders({ source: 'initializeApp' }),
|
||||
get().loadAgents({ source: 'initializeApp' }),
|
||||
get().loadProviders({ directory: configDirectory, source: 'initializeApp' }),
|
||||
get().loadAgents({ directory: configDirectory, source: 'initializeApp' }),
|
||||
]);
|
||||
|
||||
set({ isInitialized: true, isConnected: true, hasEverConnected: true, connectionPhase: "connected" });
|
||||
|
||||
Reference in New Issue
Block a user