feat(FN-2104): merge fusion/fn-2104

This commit is contained in:
gsxdsm
2026-04-19 02:36:37 -07:00
parent bee87329da
commit cace54db0b
7 changed files with 260 additions and 93 deletions

View File

@@ -34,7 +34,7 @@ import {
} from "./skill-resolver.js";
import { isContextLimitError } from "./context-limit-detector.js";
import { createFusionAuthStorage, getModelRegistryModelsPath } from "./auth-storage.js";
import { piLog } from "./logger.js";
import { piLog, extensionsLog } from "./logger.js";
export interface AgentResult {
session: AgentSession;
@@ -81,20 +81,20 @@ async function promptSessionAndCheck(session: AgentSession, prompt: string, opti
export async function promptWithFallback(session: AgentSession, prompt: string, options?: unknown): Promise<void> {
const maybePromptable = session as Partial<PromptableSession>;
if (typeof maybePromptable.promptWithFallback === "function") {
console.error(`[pi] promptWithFallback: delegating to session.promptWithFallback (prompt length=${prompt.length})`);
piLog.log(`promptWithFallback: delegating to session.promptWithFallback (prompt length=${prompt.length})`);
await maybePromptable.promptWithFallback(prompt, options);
console.error(`[pi] promptWithFallback: completed`);
piLog.log("promptWithFallback: completed");
return;
}
console.error(`[pi] promptWithFallback: calling session.prompt (prompt length=${prompt.length})`);
piLog.log(`promptWithFallback: calling session.prompt (prompt length=${prompt.length})`);
try {
await promptSessionAndCheck(session, prompt, options);
console.error(`[pi] promptWithFallback: prompt completed`);
piLog.log("promptWithFallback: prompt completed");
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
if (!isContextLimitError(errorMessage)) {
console.error(`[pi] promptWithFallback: non-context error — propagating: ${errorMessage}`);
piLog.error(`promptWithFallback: non-context error — propagating: ${errorMessage}`);
throw err;
}
@@ -110,21 +110,21 @@ export async function promptWithFallback(session: AgentSession, prompt: string,
}
}
console.error(`[pi] promptWithFallback: context limit error — attempting auto-compaction`);
piLog.warn("promptWithFallback: context limit error — attempting auto-compaction");
await flushMemoryBeforeSessionCompaction(session);
const compactResult = await compactSessionContext(session);
if (!compactResult) {
console.error(`[pi] promptWithFallback: compaction unavailable — propagating original error`);
piLog.error("promptWithFallback: compaction unavailable — propagating original error");
throw err;
}
console.error(`[pi] promptWithFallback: compaction succeeded (${compactResult.tokensBefore} tokens) — retrying prompt`);
piLog.log(`promptWithFallback: compaction succeeded (${compactResult.tokensBefore} tokens) — retrying prompt`);
try {
await promptSessionAndCheck(session, prompt, options);
console.error(`[pi] promptWithFallback: prompt completed after auto-compaction`);
piLog.log("promptWithFallback: prompt completed after auto-compaction");
} catch (retryErr: unknown) {
const retryErrorMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
console.error(`[pi] promptWithFallback: retry after auto-compaction failed: ${retryErrorMessage}`);
piLog.error(`promptWithFallback: retry after auto-compaction failed: ${retryErrorMessage}`);
throw err; // Throw original error to preserve original context
}
}
@@ -226,17 +226,17 @@ async function retryWithCompactedPromptMemory(
return { recovered: false };
}
console.error(
`[pi] promptWithFallback: retrying with compacted prompt memory (${prompt.length}${compactedPrompt.length} chars)`,
piLog.log(
`promptWithFallback: retrying with compacted prompt memory (${prompt.length}${compactedPrompt.length} chars)`,
);
try {
await promptSessionAndCheck(session, compactedPrompt, options);
console.error(`[pi] promptWithFallback: prompt completed after prompt-memory compaction`);
piLog.log("promptWithFallback: prompt completed after prompt-memory compaction");
return { recovered: true };
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.error(`[pi] promptWithFallback: retry after prompt-memory compaction failed: ${errorMessage}`);
piLog.error(`promptWithFallback: retry after prompt-memory compaction failed: ${errorMessage}`);
return { recovered: false, error: err };
}
}
@@ -257,7 +257,7 @@ async function flushMemoryBeforeSessionCompaction(session: AgentSession): Promis
await promptSessionAndCheck(session, flushPrompt);
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.error(`[pi] promptWithFallback: memory flush before compaction skipped: ${errorMessage}`);
piLog.warn(`promptWithFallback: memory flush before compaction skipped: ${errorMessage}`);
}
}
@@ -357,7 +357,7 @@ function resolveConfiguredModel(
const providerModels = modelRegistry.getAll().filter((m) => m.provider === provider);
if (providerModels.length > 0) {
const baseModel = providerModels[0]!;
console.error(`[pi] ${kind} model ${provider}/${modelId} not in registry; using provider base model as template`);
piLog.warn(`${kind} model ${provider}/${modelId} not in registry; using provider base model as template`);
return { ...baseModel, id: modelId, name: modelId };
}
@@ -476,7 +476,7 @@ async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegis
);
for (const { path, error } of extensionsResult.errors) {
console.error(`[extensions] Failed to load ${path}: ${error}`);
extensionsLog.warn(`Failed to load ${path}: ${error}`);
}
for (const { name, config, extensionPath } of extensionsResult.runtime.pendingProviderRegistrations) {
@@ -484,7 +484,7 @@ async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegis
modelRegistry.registerProvider(name, config);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`[extensions] Failed to register provider from ${extensionPath}: ${message}`);
extensionsLog.warn(`Failed to register provider from ${extensionPath}: ${message}`);
}
}
@@ -492,7 +492,7 @@ async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegis
modelRegistry.refresh();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`[extensions] Failed to discover extensions: ${message}`);
extensionsLog.error(`Failed to discover extensions: ${message}`);
createExtensionRuntime();
modelRegistry.refresh();
}
@@ -668,7 +668,7 @@ export function wrapToolsWithBoundary(
* Reuses the user's existing pi auth and model configuration.
*/
export async function createKbAgent(options: AgentOptions): Promise<AgentResult> {
console.error(`[pi] createKbAgent called (cwd=${options.cwd}, tools=${options.tools}, provider=${options.defaultProvider}, model=${options.defaultModelId})`);
piLog.log(`createKbAgent called (cwd=${options.cwd}, tools=${options.tools}, provider=${options.defaultProvider}, model=${options.defaultModelId})`);
const authStorage = createFusionAuthStorage();
const modelRegistry = new ModelRegistry(authStorage, getModelRegistryModelsPath());
await registerExtensionProviders(options.cwd, modelRegistry);
@@ -713,7 +713,7 @@ export async function createKbAgent(options: AgentOptions): Promise<AgentResult>
// Resolve skill selection: explicit skillSelection wins over convenience `skills`
let effectiveSkillSelection: SkillSelectionContext | undefined = options.skillSelection;
if (!effectiveSkillSelection && options.skills && options.skills.length > 0) {
console.error(`[pi] Using skills from convenience parameter: [${options.skills.join(", ")}]`);
piLog.log(`Using skills from convenience parameter: [${options.skills.join(", ")}]`);
effectiveSkillSelection = {
projectRootDir: options.cwd,
requestedSkillNames: options.skills,
@@ -728,7 +728,7 @@ export async function createKbAgent(options: AgentOptions): Promise<AgentResult>
if (selectionResult.diagnostics.length > 0) {
const purpose = effectiveSkillSelection.sessionPurpose ?? "skills";
for (const diag of selectionResult.diagnostics) {
console.error(`[pi] [skills] [${purpose}] ${diag.type}: ${diag.message}`);
piLog.warn(`[skills] [${purpose}] ${diag.type}: ${diag.message}`);
}
}
skillsOverrideFn = createSkillsOverrideFromSelection(selectionResult, {
@@ -766,16 +766,16 @@ export async function createKbAgent(options: AgentOptions): Promise<AgentResult>
let usingFallback = false;
try {
sessionResult = await createSessionWithModel(selectedModel);
console.error(`[pi] Session created successfully (model=${selectedModel ? `${selectedModel.provider}/${selectedModel.id}` : "default"})`);
piLog.log(`Session created successfully (model=${selectedModel ? `${selectedModel.provider}/${selectedModel.id}` : "default"})`);
} catch (err: any) {
if (!fallbackModel || !selectedModel || !isRetryableModelSelectionError(err?.message || "")) {
console.error(`[pi] Session creation failed: ${err.message}`);
piLog.error(`Session creation failed: ${err.message}`);
throw err;
}
console.error(`[pi] Primary model failed (${err.message}), trying fallback`);
piLog.warn(`Primary model failed (${err.message}), trying fallback`);
usingFallback = true;
sessionResult = await createSessionWithModel(fallbackModel);
console.error(`[pi] Fallback session created successfully`);
piLog.log("Fallback session created successfully");
}
const { session } = sessionResult;
@@ -801,22 +801,22 @@ export async function createKbAgent(options: AgentOptions): Promise<AgentResult>
}
}
console.error(`[pi] promptWithFallback: context limit error — attempting auto-compaction`);
piLog.warn("promptWithFallback: context limit error — attempting auto-compaction");
await flushMemoryBeforeSessionCompaction(session);
const compactResult = await compactSessionContext(session);
if (compactResult) {
console.error(`[pi] promptWithFallback: compaction succeeded (${compactResult.tokensBefore} tokens) — retrying prompt`);
piLog.log(`promptWithFallback: compaction succeeded (${compactResult.tokensBefore} tokens) — retrying prompt`);
try {
await promptSessionAndCheck(session, prompt, promptOptions);
return;
} catch (retryErr: any) {
const retryErrorMessage = retryErr?.message || "";
console.error(`[pi] promptWithFallback: retry after auto-compaction failed: ${retryErrorMessage}`);
piLog.error(`promptWithFallback: retry after auto-compaction failed: ${retryErrorMessage}`);
// Throw original error to preserve original context
throw err;
}
} else {
console.error(`[pi] promptWithFallback: compaction unavailable — propagating original error`);
piLog.error("promptWithFallback: compaction unavailable — propagating original error");
throw err;
}
}
@@ -880,21 +880,21 @@ export async function createKbAgent(options: AgentOptions): Promise<AgentResult>
}
}
console.error(`[pi] promptWithFallback: fallback session context limit error — attempting auto-compaction`);
piLog.warn("promptWithFallback: fallback session context limit error — attempting auto-compaction");
await flushMemoryBeforeSessionCompaction(fallbackSession);
const compactResult = await compactSessionContext(fallbackSession);
if (compactResult) {
console.error(`[pi] promptWithFallback: fallback compaction succeeded (${compactResult.tokensBefore} tokens) — retrying`);
piLog.log(`promptWithFallback: fallback compaction succeeded (${compactResult.tokensBefore} tokens) — retrying`);
try {
await promptSessionAndCheck(fallbackSession, prompt, promptOptions);
return;
} catch (retryErr: any) {
const retryErrorMessage = retryErr?.message || "";
console.error(`[pi] promptWithFallback: fallback retry after auto-compaction failed: ${retryErrorMessage}`);
piLog.error(`promptWithFallback: fallback retry after auto-compaction failed: ${retryErrorMessage}`);
throw fallbackErr; // Throw original fallback error
}
} else {
console.error(`[pi] promptWithFallback: fallback compaction unavailable — propagating original error`);
piLog.error("promptWithFallback: fallback compaction unavailable — propagating original error");
throw fallbackErr;
}
}