Revert "fix(dashboard): lazy-init useMobileKeyboard so remount has no stale render"
This reverts commit 3f5d01f4d4.
This commit is contained in:
@@ -993,36 +993,6 @@ describe("createFnAgent", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it("continues session creation when setting thinking level hits reasoning conflict", async () => {
|
||||
const { piLog } = await import("../logger.js");
|
||||
const warnSpy = vi.spyOn(piLog, "warn").mockImplementation(() => {});
|
||||
const setThinkingLevel = vi.fn(() => {
|
||||
throw new Error("400 cannot specify both 'thinking' and 'reasoning_effort'");
|
||||
});
|
||||
|
||||
createAgentSessionMock.mockResolvedValueOnce({
|
||||
session: {
|
||||
prompt: vi.fn(),
|
||||
subscribe: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
setThinkingLevel,
|
||||
},
|
||||
});
|
||||
|
||||
const { createFnAgent } = await import("../pi.js");
|
||||
|
||||
await expect(createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
defaultThinkingLevel: "high",
|
||||
})).resolves.toBeTruthy();
|
||||
|
||||
expect(setThinkingLevel).toHaveBeenCalledWith("high");
|
||||
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("Disabling explicit thinking level"));
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe("skill selection", () => {
|
||||
beforeEach(() => {
|
||||
// Reset modules to ensure fresh imports for each test
|
||||
|
||||
@@ -662,53 +662,11 @@ describe("session failure diagnostics", () => {
|
||||
await expect((session as any).promptWithFallback("Run task")).resolves.toBeUndefined();
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Failed to dispose session during swap: dispose failed"),
|
||||
expect.stringContaining("Failed to dispose session during model fallback swap: dispose failed"),
|
||||
);
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("retries prompt on thinking/reasoning conflict without switching fallback models", async () => {
|
||||
const createAgentSessionMock = vi.mocked(createAgentSession);
|
||||
|
||||
const firstSession = {
|
||||
model: { provider: "test", id: "primary-model" },
|
||||
prompt: vi.fn().mockRejectedValue(new Error("400 cannot specify both 'thinking' and 'reasoning_effort'")),
|
||||
subscribe: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
setThinkingLevel: vi.fn(),
|
||||
sessionFile: undefined,
|
||||
} as unknown as AgentSession;
|
||||
|
||||
const retrySession = {
|
||||
model: { provider: "test", id: "primary-model" },
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
subscribe: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
setThinkingLevel: vi.fn(),
|
||||
sessionFile: undefined,
|
||||
} as unknown as AgentSession;
|
||||
|
||||
createAgentSessionMock.mockReset();
|
||||
createAgentSessionMock
|
||||
.mockResolvedValueOnce({ session: firstSession } as any)
|
||||
.mockResolvedValueOnce({ session: retrySession } as any);
|
||||
|
||||
const { session } = await createFnAgent({
|
||||
cwd: "/test/project",
|
||||
systemPrompt: "Test thinking compatibility",
|
||||
defaultProvider: "test",
|
||||
defaultModelId: "primary-model",
|
||||
fallbackProvider: "test",
|
||||
fallbackModelId: "fallback-model",
|
||||
defaultThinkingLevel: "high",
|
||||
});
|
||||
|
||||
await expect((session as any).promptWithFallback("Run review")).resolves.toBeUndefined();
|
||||
|
||||
expect(createAgentSessionMock).toHaveBeenCalledTimes(2);
|
||||
expect((retrySession.setThinkingLevel as any).mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("piLog structured diagnostics", () => {
|
||||
|
||||
@@ -130,10 +130,6 @@ function clearSessionStateError(session: AgentSession): void {
|
||||
}
|
||||
}
|
||||
|
||||
function isThinkingReasoningConflictError(message: string): boolean {
|
||||
return /cannot specify both\s+['"]?thinking['"]?\s+and\s+['"]?reasoning_effort['"]?/i.test(message);
|
||||
}
|
||||
|
||||
async function promptSessionAndCheck(session: AgentSession, prompt: string, options?: unknown): Promise<void> {
|
||||
clearSessionStateError(session);
|
||||
if (options === undefined) {
|
||||
@@ -1287,83 +1283,21 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
piLog.log("Fallback session created successfully");
|
||||
}
|
||||
|
||||
let activeSession = sessionResult.session;
|
||||
installToolResultContentGuard(activeSession as AgentToolHookSession);
|
||||
installMessageContentGuard(activeSession as AgentToolHookSession, sessionManager as unknown as SessionManagerLike);
|
||||
(activeSession as any).__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
||||
const promptableSession = activeSession as PromptableSession;
|
||||
|
||||
let thinkingCompatibilityDisabled = false;
|
||||
const applyThinkingLevelIfSupported = (targetSession: AgentSession, sourceModel: string): void => {
|
||||
if (!options.defaultThinkingLevel || thinkingCompatibilityDisabled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
(targetSession as PromptableSession).setThinkingLevel(options.defaultThinkingLevel as any);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (!isThinkingReasoningConflictError(message)) {
|
||||
throw err;
|
||||
}
|
||||
thinkingCompatibilityDisabled = true;
|
||||
piLog.warn(`Disabling explicit thinking level for model ${sourceModel}: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const wireFallbackHooks = (targetSession: PromptableSession): void => {
|
||||
installToolResultContentGuard(targetSession as unknown as AgentToolHookSession);
|
||||
installMessageContentGuard(
|
||||
targetSession as unknown as AgentToolHookSession,
|
||||
sessionManager as unknown as SessionManagerLike,
|
||||
);
|
||||
(targetSession as any).__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
||||
targetSession.subscribe((event) => {
|
||||
if (event.type === "message_update") {
|
||||
const msgEvent = event.assistantMessageEvent;
|
||||
if (msgEvent.type === "text_delta") {
|
||||
options.onText?.(msgEvent.delta);
|
||||
} else if (msgEvent.type === "thinking_delta") {
|
||||
options.onThinking?.(msgEvent.delta);
|
||||
}
|
||||
}
|
||||
if (event.type === "tool_execution_start") {
|
||||
options.onToolStart?.(event.toolName, event.args as Record<string, unknown> | undefined);
|
||||
}
|
||||
if (event.type === "tool_execution_end") {
|
||||
options.onToolEnd?.(event.toolName, event.isError, event.result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const swapPromptSession = async (modelToUse: typeof selectedModel): Promise<PromptableSession> => {
|
||||
if (!modelToUse) {
|
||||
throw new Error("Cannot swap session without a resolved model");
|
||||
}
|
||||
try {
|
||||
activeSession.dispose();
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
piLog.warn(`Failed to dispose session during swap: ${msg}`);
|
||||
}
|
||||
const next = (await createSessionWithModel(modelToUse)).session as PromptableSession;
|
||||
wireFallbackHooks(next);
|
||||
applyThinkingLevelIfSupported(next, `${modelToUse.provider}/${modelToUse.id}`);
|
||||
Object.setPrototypeOf(promptableSession, Object.getPrototypeOf(next));
|
||||
Object.assign(promptableSession, next);
|
||||
promptableSession.promptWithFallback = next.promptWithFallback ?? promptableSession.promptWithFallback;
|
||||
activeSession = next;
|
||||
return next;
|
||||
};
|
||||
const { session } = sessionResult;
|
||||
installToolResultContentGuard(session as AgentToolHookSession);
|
||||
installMessageContentGuard(session as AgentToolHookSession, sessionManager as unknown as SessionManagerLike);
|
||||
(session as any).__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
||||
const promptableSession = session as PromptableSession;
|
||||
|
||||
promptableSession.promptWithFallback = async (prompt: string, promptOptions?: unknown) => {
|
||||
try {
|
||||
await promptSessionAndCheck(activeSession, prompt, promptOptions);
|
||||
await promptSessionAndCheck(session, prompt, promptOptions);
|
||||
return;
|
||||
} catch (err: any) {
|
||||
const errorMessage = err?.message || "";
|
||||
if (isContextLimitError(errorMessage)) {
|
||||
// Context limit error — attempt auto-compaction and retry once
|
||||
const promptMemoryRetry = await retryWithCompactedPromptMemory(activeSession, prompt, promptOptions);
|
||||
const promptMemoryRetry = await retryWithCompactedPromptMemory(session, prompt, promptOptions);
|
||||
if (promptMemoryRetry.recovered) {
|
||||
return;
|
||||
}
|
||||
@@ -1375,12 +1309,12 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
}
|
||||
|
||||
piLog.warn("promptWithFallback: context limit error — attempting auto-compaction");
|
||||
await flushMemoryBeforeSessionCompaction(activeSession);
|
||||
const compactResult = await compactSessionContext(activeSession);
|
||||
await flushMemoryBeforeSessionCompaction(session);
|
||||
const compactResult = await compactSessionContext(session);
|
||||
if (compactResult) {
|
||||
piLog.log(`promptWithFallback: compaction succeeded (${compactResult.tokensBefore} tokens) — retrying prompt`);
|
||||
try {
|
||||
await promptSessionAndCheck(activeSession, prompt, promptOptions);
|
||||
await promptSessionAndCheck(session, prompt, promptOptions);
|
||||
return;
|
||||
} catch (retryErr: any) {
|
||||
const retryErrorMessage = retryErr?.message || "";
|
||||
@@ -1394,21 +1328,52 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
}
|
||||
}
|
||||
|
||||
if (!usingFallback && options.defaultThinkingLevel && !thinkingCompatibilityDisabled && isThinkingReasoningConflictError(errorMessage)) {
|
||||
thinkingCompatibilityDisabled = true;
|
||||
piLog.warn(`Prompt failed with thinking/reasoning conflict; retrying without explicit thinking level: ${errorMessage}`);
|
||||
const recoveredSession = await swapPromptSession(selectedModel);
|
||||
await promptSessionAndCheck(recoveredSession, prompt, promptOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fallbackModel || usingFallback || !isRetryableModelSelectionError(errorMessage)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
usingFallback = true;
|
||||
const fallbackSession = await swapPromptSession(fallbackModel);
|
||||
try {
|
||||
session.dispose();
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
piLog.warn(`Failed to dispose session during model fallback swap: ${msg}`);
|
||||
}
|
||||
|
||||
const fallbackSessionResult = await createSessionWithModel(fallbackModel);
|
||||
await emitFallbackUsed("prompt-time");
|
||||
const fallbackSession = fallbackSessionResult.session as PromptableSession;
|
||||
installToolResultContentGuard(fallbackSession as unknown as AgentToolHookSession);
|
||||
installMessageContentGuard(
|
||||
fallbackSession as unknown as AgentToolHookSession,
|
||||
sessionManager as unknown as SessionManagerLike,
|
||||
);
|
||||
(fallbackSession as any).__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
||||
|
||||
if (options.defaultThinkingLevel) {
|
||||
fallbackSession.setThinkingLevel(options.defaultThinkingLevel as any);
|
||||
}
|
||||
|
||||
fallbackSession.subscribe((event) => {
|
||||
if (event.type === "message_update") {
|
||||
const msgEvent = event.assistantMessageEvent;
|
||||
if (msgEvent.type === "text_delta") {
|
||||
options.onText?.(msgEvent.delta);
|
||||
} else if (msgEvent.type === "thinking_delta") {
|
||||
options.onThinking?.(msgEvent.delta);
|
||||
}
|
||||
}
|
||||
if (event.type === "tool_execution_start") {
|
||||
options.onToolStart?.(event.toolName, event.args as Record<string, unknown> | undefined);
|
||||
}
|
||||
if (event.type === "tool_execution_end") {
|
||||
options.onToolEnd?.(event.toolName, event.isError, event.result);
|
||||
}
|
||||
});
|
||||
|
||||
Object.setPrototypeOf(promptableSession, Object.getPrototypeOf(fallbackSession));
|
||||
Object.assign(promptableSession, fallbackSession);
|
||||
promptableSession.promptWithFallback = fallbackSession.promptWithFallback ?? promptableSession.promptWithFallback;
|
||||
|
||||
// Retry with fallback model, also with auto-compaction support
|
||||
try {
|
||||
@@ -1451,11 +1416,10 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
}
|
||||
};
|
||||
|
||||
// Apply thinking level if specified (with compatibility fallback).
|
||||
applyThinkingLevelIfSupported(
|
||||
promptableSession,
|
||||
selectedModel ? `${selectedModel.provider}/${selectedModel.id}` : describeModel(promptableSession),
|
||||
);
|
||||
// Apply thinking level if specified
|
||||
if (options.defaultThinkingLevel) {
|
||||
promptableSession.setThinkingLevel(options.defaultThinkingLevel as any);
|
||||
}
|
||||
|
||||
// Wire up event listeners
|
||||
promptableSession.subscribe((event) => {
|
||||
|
||||
Reference in New Issue
Block a user