Skip to content

Commit 1f30548

Browse files
committed
fix(johannesjo#43): surface MCP restore failures with error state and retry UI
1 parent 77b5c7d commit 1f30548

6 files changed

Lines changed: 370 additions & 51 deletions

File tree

src/App.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ import {
4949
setDockerAvailable,
5050
toggleTaskFocusMode,
5151
initMCPListeners,
52+
markTaskMcpPending,
5253
markTaskMcpReady,
54+
markTaskMcpError,
5355
} from './store/store';
5456
import { isGitHubUrl } from './lib/github-url';
5557
import type { PersistedWindowState } from './store/types';
@@ -346,6 +348,7 @@ function App() {
346348
task.dockerMode && task.agentIds[0]
347349
? `parallel-code-${task.agentIds[0].slice(0, 12)}`
348350
: undefined;
351+
markTaskMcpPending(taskId);
349352
mcpRestorePromises.push(
350353
invoke(IPC.StartMCPServer, {
351354
coordinatorTaskId: task.id,
@@ -358,10 +361,11 @@ function App() {
358361
agentArgs: agentDef?.args ?? [],
359362
dockerContainerName,
360363
})
364+
.then(() => markTaskMcpReady(taskId))
361365
.catch((err) => {
362366
console.warn(`[MCP] Failed to restore MCP server for coordinator task ${taskId}:`, err);
363-
})
364-
.finally(() => markTaskMcpReady(taskId)),
367+
markTaskMcpError(taskId, String(err));
368+
}),
365369
);
366370
}
367371
// Wait for all coordinators to register before hydrating their children —
@@ -375,6 +379,7 @@ function App() {
375379
if (!task?.coordinatedBy) continue;
376380
const projectRoot = store.projects.find((p) => p.id === task.projectId)?.path;
377381
if (!projectRoot) continue;
382+
markTaskMcpPending(task.id);
378383
invoke(IPC.MCP_HydrateCoordinatedTask, {
379384
id: task.id,
380385
name: task.name,
@@ -391,10 +396,11 @@ function App() {
391396
mcpConfigPath: task.mcpConfigPath,
392397
preambleFileExistedBefore: task.preambleFileExistedBefore,
393398
})
399+
.then(() => markTaskMcpReady(task.id))
394400
.catch((err) => {
395401
console.warn(`[MCP] Failed to hydrate coordinated task ${taskId}:`, err);
396-
})
397-
.finally(() => markTaskMcpReady(task.id));
402+
markTaskMcpError(task.id, String(err));
403+
});
398404
}
399405

400406
// Restore plan content for tasks that had a plan file before restart

src/components/TerminalView.tsx

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { onMount, onCleanup, createEffect } from 'solid-js';
1+
import { onMount, onCleanup, createEffect, Show } from 'solid-js';
22
import { Terminal, type IMarker } from '@xterm/xterm';
33
import { FitAddon } from '@xterm/addon-fit';
44
import { WebglAddon } from '@xterm/addon-webgl';
@@ -12,7 +12,7 @@ import { matchesGlobalShortcut } from '../lib/shortcuts';
1212
import { isMac } from '../lib/platform';
1313
import { resolvedBindings } from '../store/keybindings';
1414
import { matchesKeyEvent } from '../lib/keybindings';
15-
import { store, setTaskLastInputAt } from '../store/store';
15+
import { store, setTaskLastInputAt, retryTaskMcpStartup } from '../store/store';
1616
import { warn as logWarn } from '../lib/log';
1717
import { registerTerminal, unregisterTerminal, markDirty } from '../lib/terminalFitManager';
1818
import { dataTransferToShellArgs, escapePath } from '../lib/terminalDrop';
@@ -645,16 +645,18 @@ export function TerminalView(props: TerminalViewProps) {
645645
// For coordinator and coordinated sub-tasks, defer spawn until MCP is ready.
646646
// Coordinator tasks wait for StartMCPServer to complete; sub-tasks wait for hydrateTask.
647647
const task = store.tasks[taskId];
648-
if ((task?.coordinatedBy || task?.coordinatorMode) && !task?.mcpReady) {
648+
if (task?.mcpStartupStatus === 'pending') {
649649
let spawned = false;
650650
createEffect(() => {
651651
if (spawned) return;
652-
if (store.tasks[taskId]?.mcpReady) {
652+
const status = store.tasks[taskId]?.mcpStartupStatus;
653+
if (status === 'ready') {
653654
spawned = true;
654655
spawnNow();
655656
}
657+
// 'error' is handled by the overlay rendered outside onMount
656658
});
657-
} else {
659+
} else if (task?.mcpStartupStatus !== 'error') {
658660
spawnNow();
659661
}
660662

@@ -695,16 +697,62 @@ export function TerminalView(props: TerminalViewProps) {
695697
markDirty(props.agentId);
696698
});
697699

700+
const mcpError = () => store.tasks[props.taskId]?.mcpStartupError;
701+
const mcpStatus = () => store.tasks[props.taskId]?.mcpStartupStatus;
702+
698703
return (
699-
<div
700-
ref={containerRef}
701-
style={{
702-
width: '100%',
703-
height: '100%',
704-
overflow: 'hidden',
705-
padding: '4px 0 0 4px',
706-
contain: 'strict',
707-
}}
708-
/>
704+
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
705+
<div
706+
ref={containerRef}
707+
style={{
708+
width: '100%',
709+
height: '100%',
710+
overflow: 'hidden',
711+
padding: '4px 0 0 4px',
712+
contain: 'strict',
713+
}}
714+
/>
715+
<Show when={mcpStatus() === 'error'}>
716+
<div
717+
style={{
718+
position: 'absolute',
719+
inset: '0',
720+
display: 'flex',
721+
'flex-direction': 'column',
722+
'align-items': 'center',
723+
'justify-content': 'center',
724+
gap: '12px',
725+
background: 'rgba(0,0,0,0.85)',
726+
'font-family': 'var(--font-ui)',
727+
'z-index': '10',
728+
}}
729+
>
730+
<span
731+
style={{
732+
color: '#ff6b6b',
733+
'font-size': '13px',
734+
'text-align': 'center',
735+
padding: '0 16px',
736+
}}
737+
>
738+
MCP startup failed: {mcpError() ?? 'unknown error'}
739+
</span>
740+
<button
741+
style={{
742+
padding: '6px 16px',
743+
background: '#3b82f6',
744+
color: '#fff',
745+
border: 'none',
746+
'border-radius': '4px',
747+
'font-size': '13px',
748+
cursor: 'pointer',
749+
}}
750+
onClick={() => retryTaskMcpStartup(props.taskId)}
751+
>
752+
Retry
753+
</button>
754+
</div>
755+
</Show>
756+
</div>
709757
);
710758
}

src/store/store.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ export {
5959
initMCPListeners,
6060
getCoordinatorCloseWarning,
6161
setTaskControl,
62+
markTaskMcpPending,
6263
markTaskMcpReady,
64+
markTaskMcpError,
65+
retryTaskMcpStartup,
6366
} from './tasks';
6467
export {
6568
setActiveTask,

0 commit comments

Comments
 (0)