Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions packages/pi-fff/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ export default function fffExtension(pi: ExtensionAPI) {
// deadlock at the native layer (issue #403).
let finderPromise: Promise<FileFinder> | null = null;
let activeCwd = process.cwd();
// Bumped on every session_start / session_shutdown so an async warmup
// started for a previous session can detect that it's stale and bail out
// before touching `finder` / notifying a destroyed UI.
let lifecycleId = 0;

// Mode resolution: flag > env > default
let currentMode: FffMode =
Expand Down Expand Up @@ -355,10 +359,13 @@ export default function fffExtension(pi: ExtensionAPI) {
if (!result.ok)
throw new Error(`Failed to create FFF file finder: ${result.error}`);

finder = result.value;
const created = result.value;
finder = created;
finderCwd = cwd;
await finder.waitForScan(15000);
return finder;
await created.waitForScan(15000);
// Return the local handle: a shutdown during warmup may null/replace
// `finder`, but the caller still needs the instance they were promised.
return created;
})().finally(() => {
finderPromise = null;
});
Expand Down Expand Up @@ -467,6 +474,7 @@ export default function fffExtension(pi: ExtensionAPI) {
pi.on("session_start", async (_event, ctx) => {
try {
activeCwd = ctx.cwd;
const sessionLifecycleId = ++lifecycleId;

// Restore persisted mode from session entries. This handles session
// resume after process restart where env vars are lost, and ensures
Expand All @@ -492,7 +500,20 @@ export default function fffExtension(pi: ExtensionAPI) {
}

registerAutocompleteProvider(ctx);
await ensureFinder(activeCwd);

// Warm the finder in the background — Pi /new and /resume must not
// wait on the initial scan. Subsequent tool calls / mention lookups
// share the same in-flight promise via ensureFinder().
setTimeout(() => {
if (sessionLifecycleId !== lifecycleId) return;
ensureFinder(activeCwd).catch((e: unknown) => {
if (sessionLifecycleId !== lifecycleId) return;
ctx.ui.notify(
`FFF init failed: ${e instanceof Error ? e.message : String(e)}`,
"error",
);
});
}, 0);
} catch (e: unknown) {
ctx.ui.notify(
`FFF init failed: ${e instanceof Error ? e.message : String(e)}`,
Expand All @@ -502,6 +523,7 @@ export default function fffExtension(pi: ExtensionAPI) {
});

pi.on("session_shutdown", async () => {
lifecycleId++;
destroyFinder();
});

Expand Down
4 changes: 4 additions & 0 deletions packages/pi-fff/test/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ async function start(mode?: string) {
const sessionStart = setup.events.get("session_start");
expect(sessionStart).toBeDefined();
await sessionStart?.({ reason: "startup" }, ctx);
// session_start now schedules the finder warmup via setTimeout(0); flush
// the queue so tests can observe FileFinder.create / waitForScan calls.
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));

return { ...setup, ctx };
}
Expand Down
Loading