fix: eagerly warm extension host stores from engine TaskStores (#3340)
## Summary Warm the extension-host task stores **up front** at dashboard startup instead of letting the first `fn_task_*` call lazily boot a second PostgreSQL pool per project. ## What changed `packages/cli/src/commands/dashboard.ts`: - After the dashboard boots, iterate every registered project (from `centralCoreForEngine.listProjects()`) and call `setHostTaskStore(p.path, engine.getTaskStore())` for each non-cwd project that already has a running `ProjectEngine`. - Reuses each engine's **existing** `TaskStore` directly — no new backend connection, no schema advisory-lock contention, no extra connection-pool exhaustion. - `cwd` is skipped because its store is already injected at startup. - Per-project failures are non-fatal (warn) and a failed project listing logs a single warn — dashboard startup never blocks on this. - `.changeset/extension-host-store-warmup.md` (patch, fix). ## Why Left on its own, the first extension tool call (`fn_task_update`, `fn_task_archive`, `fn_agent_show`, …) for a non-cwd project falls through to `createTaskStoreForBackend`, which boots a **second** PostgreSQL connection pool on demand. On busy hosts that lazy boot can time out, or the call stalls behind pool/startup contention — the classic "first `fn_task_*` call is slow or errors" experience. Pre-populating from the already-running engines removes that lazy worst-case path entirely. ## Verification - `pnpm verify:fast` — PASS (13 steps, 115s): CLI `tsup` build green, scoped typecheck/build green, boot smoke green (`fn --help` + real `serve` with `GET /api/health` 200). - Cherry-picked cleanly onto current `origin/main` (`5532019fd`); branch is up-to-date with `origin/main` at PR time. ## Files - `packages/cli/src/commands/dashboard.ts` (+30) - `.changeset/extension-host-store-warmup.md` (new) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved dashboard startup reliability by reusing existing project task connections. * Prevented extension task tools from creating duplicate connection pools. * Added non-blocking warnings when individual project initialization or discovery fails. * Dashboard startup now reports how many project task stores were successfully prepared. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
This commit is contained in:
7
.changeset/extension-host-store-warmup.md
Normal file
7
.changeset/extension-host-store-warmup.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Warm extension-host task stores up front so fn_task_* tools never hit a lazy second-pool boot.
|
||||||
|
category: fix
|
||||||
|
dev: Populates setHostTaskStore for every registered project from the already-running ProjectEngine TaskStores at dashboard startup, so extension API tools (fn_task_update, fn_task_archive, fn_agent_show) find a cached store and never fall through to createTaskStoreForBackend, which could time out creating a second connection pool. cwd is skipped because its store is already injected. Non-fatal warnings on any per-project failure.
|
||||||
@@ -2272,6 +2272,36 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FNXC:ExtensionHostStoreWarmup 2026-07-18-19:20:
|
||||||
|
// Pre-populate setHostTaskStore for all registered projects from already-
|
||||||
|
// running ProjectEngine TaskStores, so extension API tools (fn_task_archive,
|
||||||
|
// fn_task_update, etc.) find a cached store and never fall through to
|
||||||
|
// createTaskStoreForBackend (which times out creating a second pool).
|
||||||
|
// Reuses each engine's existing TaskStore directly — no new PG boot needed.
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const projects = await centralCoreForEngine.listProjects();
|
||||||
|
// Skip cwd — its store is already injected at line 928.
|
||||||
|
const nonCwd = projects.filter((p) => p.path !== cwd);
|
||||||
|
for (const p of nonCwd) {
|
||||||
|
try {
|
||||||
|
const engine = engineManager.getEngine(p.id);
|
||||||
|
if (!engine) continue;
|
||||||
|
setHostTaskStore(p.path, engine.getTaskStore());
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
logSink.warn(`Failed to warm extension store for ${p.name} (${p.path}): ${msg}`, "extension");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nonCwd.length > 0) {
|
||||||
|
logSink.log(`Warmed extension host stores for ${nonCwd.length} project(s)`, "extension");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
logSink.warn(`Failed to list projects for store warmup: ${msg}`, "extension");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
disposeCallbacks.push(async () => {
|
disposeCallbacks.push(async () => {
|
||||||
if (hybridExecutor) {
|
if (hybridExecutor) {
|
||||||
await hybridExecutor.shutdown();
|
await hybridExecutor.shutdown();
|
||||||
|
|||||||
Reference in New Issue
Block a user