fix: branch reset on workdir change and Codex MCP server config merge

- Reset git branch to current when user changes working directory in create-agent modal
- Fix Codex MCP servers not being passed from extra.codex.mcp_servers config
- Remove macbook host from default daemons
- Update test expectations and structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-01-07 14:42:16 +07:00
parent 66881511b6
commit 0d9a69d00f
8 changed files with 52 additions and 45 deletions

View File

@@ -436,6 +436,7 @@ function AgentFlowModal({
(value: string) => {
setWorkingDirFromUser(value);
setErrorMessage("");
shouldSyncBaseBranchRef.current = true;
},
[setWorkingDirFromUser]
);

View File

@@ -119,6 +119,7 @@ function DiffLineView({ line }: { line: DiffLine }) {
function DiffFileSection({ file, defaultExpanded = true, testID }: DiffFileSectionProps) {
const { theme } = useUnistyles();
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
const [scrollViewWidth, setScrollViewWidth] = useState(0);
const horizontalScroll = useHorizontalScrollOptional();
const scrollId = useId();
@@ -190,8 +191,9 @@ function DiffFileSection({ file, defaultExpanded = true, testID }: DiffFileSecti
contentContainerStyle={styles.diffContentInner}
onScroll={handleScroll}
scrollEventThrottle={16}
onLayout={(e) => setScrollViewWidth(e.nativeEvent.layout.width)}
>
<View style={styles.linesContainer}>
<View style={[styles.linesContainer, scrollViewWidth > 0 && { minWidth: scrollViewWidth }]}>
{file.hunks.map((hunk, hunkIndex) =>
hunk.lines.map((line, lineIndex) => (
<DiffLineView key={`${hunkIndex}-${lineIndex}`} line={line} />
@@ -388,8 +390,6 @@ const styles = StyleSheet.create((theme) => ({
flexDirection: "column",
},
linesContainer: {
alignSelf: "flex-start",
minWidth: "100%",
backgroundColor: "#0d1117",
},
diffLineContainer: {

View File

@@ -8,7 +8,6 @@ const LEGACY_SETTINGS_KEY = "@paseo:settings";
const FALLBACK_DAEMON_URL = "ws://localhost:6767/ws";
const DEFAULT_DAEMONS: Array<{ label: string; wsUrl: string; restUrl?: string | null }> = [
{ label: "localhost", wsUrl: "ws://localhost:6767/ws" },
{ label: "macbook", wsUrl: "ws://mohameds-macbook-pro:6767/ws" },
];
const DAEMON_REGISTRY_QUERY_KEY = ["daemon-registry"];

View File

@@ -53,12 +53,14 @@ describe("create_agent MCP tool", () => {
const missingTitle = await tool.inputSchema.safeParseAsync({
cwd: "/tmp/repo",
initialMode: "default",
});
expect(missingTitle.success).toBe(false);
expect(missingTitle.error.issues[0].path).toEqual(["title"]);
const tooLong = await tool.inputSchema.safeParseAsync({
cwd: "/tmp/repo",
initialMode: "default",
title: "x".repeat(41),
});
expect(tooLong.success).toBe(false);
@@ -66,6 +68,7 @@ describe("create_agent MCP tool", () => {
const ok = await tool.inputSchema.safeParseAsync({
cwd: "/tmp/repo",
initialMode: "default",
title: "Short title",
});
expect(ok.success).toBe(true);

View File

@@ -509,7 +509,7 @@ describe("ClaudeAgentClient (SDK integration)", () => {
timeline.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "granted"
)
).toBe(true);
@@ -583,7 +583,7 @@ describe("ClaudeAgentClient (SDK integration)", () => {
timeline.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "denied"
)
).toBe(true);
@@ -662,7 +662,7 @@ describe("ClaudeAgentClient (SDK integration)", () => {
timeline.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "denied"
)
).toBe(true);

View File

@@ -530,7 +530,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
if (event.item.type === "reasoning") {
sawReasoning = true;
}
if (event.item.type === "tool_call" && event.item.server !== "permission") {
if (event.item.type === "tool_call" && event.item.name !== "permission") {
toolCalls.push(event.item);
}
}
@@ -569,7 +569,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
.reverse()
.find(
(item) =>
item.server === "command" && item.status !== "running"
item.name === "shell" && item.status !== "running"
);
expect(commandToolCall).toBeTruthy();
@@ -665,7 +665,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
if (
timelineItems.some(
(item) => item.type === "tool_call" && item.server === "file_change"
(item) => item.type === "tool_call" && item.name === "apply_patch"
)
) {
rawItemTypes.add("file_change");
@@ -674,8 +674,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "test" &&
item.tool === "echo"
item.name === "test.echo"
)
) {
rawItemTypes.add("mcp_tool_call");
@@ -684,8 +683,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "web_search" &&
item.tool === "web_search"
item.name === "web_search"
)
) {
rawItemTypes.add("web_search");
@@ -707,23 +705,21 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
expect(
timelineItems.some(
(item) => item.type === "tool_call" && item.server === "file_change"
(item) => item.type === "tool_call" && item.name === "apply_patch"
)
).toBe(true);
expect(
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "test" &&
item.tool === "echo"
item.name === "test.echo"
)
).toBe(true);
expect(
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "web_search" &&
item.tool === "web_search"
item.name === "web_search"
)
).toBe(true);
expect(
@@ -807,7 +803,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
permissionResolved = true;
}
if (event.type === "timeline" && providerFromEvent(event) === "codex") {
if (event.item.type === "tool_call" && event.item.server !== "permission") {
if (event.item.type === "tool_call" && event.item.name !== "permission") {
toolCalls.push(event.item);
}
}
@@ -820,7 +816,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
expect.soft(permissionResolved).toBe(true);
const commandCalls = toolCalls.filter(
(item) => item.server === "command" && item.status === "completed"
(item) => item.name === "shell" && item.status === "completed"
);
expect.soft(commandCalls.length).toBeGreaterThanOrEqual(2);
@@ -842,7 +838,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
expect.soft(extractExitCode(stderrCall?.output)).toBe(0);
const fileChangeCalls = toolCalls.filter(
(item) => item.server === "file_change" && item.tool === "apply_patch"
(item) => item.name === "apply_patch"
);
expect.soft(fileChangeCalls.length).toBeGreaterThanOrEqual(2);
expect.soft(
@@ -859,21 +855,21 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
).toBe(true);
const readCall = toolCalls.find(
(item) => item.tool === "read_file" && item.status === "completed"
(item) => item.name === "read_file" && item.status === "completed"
);
expect.soft(readCall).toBeTruthy();
expect.soft(stringifyUnknown(readCall?.input)).toContain("tool-create.txt");
expect.soft(stringifyUnknown(readCall?.output)).toContain("beta");
const mcpCall = toolCalls.find(
(item) => item.server === "test" && item.tool === "echo"
(item) => item.name === "test.echo"
);
expect.soft(mcpCall).toBeTruthy();
expect.soft(stringifyUnknown(mcpCall?.input)).toContain("mcp-ok");
expect.soft(stringifyUnknown(mcpCall?.output)).toContain("mcp-ok");
const webSearchCall = toolCalls.find(
(item) => item.server === "web_search" && item.tool === "web_search"
(item) => item.name === "web_search"
);
expect.soft(webSearchCall).toBeTruthy();
expect.soft(stringifyUnknown(webSearchCall?.input)).toContain("OpenAI Codex MCP");
@@ -894,11 +890,11 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
}
}
const commandCallIds = toolCalls
.filter((item) => item.server === "command")
.filter((item) => item.name === "shell")
.map((item) => item.callId)
.filter((callId): callId is string => typeof callId === "string");
const fileChangeCallIds = toolCalls
.filter((item) => item.server === "file_change")
.filter((item) => item.name === "apply_patch")
.map((item) => item.callId)
.filter((callId): callId is string => typeof callId === "string");
@@ -1147,13 +1143,13 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "granted"
)
).toBe(true);
expect(
timelineItems.some(
(item) => item.type === "tool_call" && item.server === "command"
(item) => item.type === "tool_call" && item.name === "shell"
)
).toBe(true);
expect(existsSync(filePath)).toBe(true);
@@ -1211,7 +1207,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "requested"
)
).toBe(true);
@@ -1282,7 +1278,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "denied"
)
).toBe(true);
@@ -1363,7 +1359,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "denied"
)
).toBe(true);
@@ -1419,7 +1415,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
event.type === "timeline" &&
providerFromEvent(event) === "codex" &&
event.item.type === "tool_call" &&
event.item.server === "command" &&
event.item.name === "shell" &&
isSleepCommandToolCall(event.item)
) {
sawSleepCommand = true;
@@ -1494,7 +1490,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
event.type === "timeline" &&
providerFromEvent(event) === "codex" &&
event.item.type === "tool_call" &&
event.item.server === "command"
event.item.name === "shell"
) {
const commandText = commandTextFromInput(event.item.input);
if (commandText && commandText.includes(marker)) {

View File

@@ -2523,7 +2523,17 @@ function buildCodexMcpConfig(
args: ["@playwright/mcp", "--headless", "--isolated"],
};
// Merge user-provided MCP servers (they take precedence)
// Merge MCP servers from extra.codex.mcp_servers (legacy location)
const extraCodex = config.extra?.codex as Record<string, unknown> | undefined;
if (extraCodex?.mcp_servers && typeof extraCodex.mcp_servers === "object") {
for (const [name, serverConfig] of Object.entries(extraCodex.mcp_servers as Record<string, unknown>)) {
if (typeof serverConfig === "object" && serverConfig !== null) {
mcpServers[name] = serverConfig as CodexMcpServerConfig;
}
}
}
// Merge user-provided MCP servers (they take highest precedence)
if (config.mcpServers) {
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
if (typeof serverConfig === "object" && serverConfig !== null) {
@@ -3098,7 +3108,7 @@ class CodexMcpAgentSession implements AgentSession {
type: "timeline",
provider: CODEX_PROVIDER,
item: createToolCallTimelineItem({
name: pending.request.name,
name: "permission",
status,
callId: pending.request.id,
input: pending.request.input,
@@ -3405,7 +3415,7 @@ class CodexMcpAgentSession implements AgentSession {
type: "timeline",
provider: CODEX_PROVIDER,
item: createToolCallTimelineItem({
name: request.name,
name: "permission",
status: "requested",
callId: request.id,
input: request.input,

View File

@@ -172,7 +172,7 @@ describe("daemon E2E", () => {
const hasGranted = timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "granted"
);
expect(hasGranted).toBe(true);
@@ -256,7 +256,7 @@ describe("daemon E2E", () => {
const hasDenied = timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "denied"
);
expect(hasDenied).toBe(true);
@@ -799,8 +799,7 @@ describe("daemon E2E", () => {
const hasCreateAgentCall = timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.tool === "create_agent" &&
item.server === "agent-control"
item.name === "agent-control.create_agent"
);
expect(hasCreateAgentCall).toBe(true);
@@ -827,8 +826,7 @@ describe("daemon E2E", () => {
const createAgentCall = timelineItems.find(
(item) =>
item.type === "tool_call" &&
item.tool === "create_agent" &&
item.server === "agent-control"
item.name === "agent-control.create_agent"
);
let childAgentId: string | null = null;
@@ -1372,7 +1370,7 @@ describe("daemon E2E", () => {
const hasGranted = timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "granted"
);
expect(hasGranted).toBe(true);
@@ -1463,7 +1461,7 @@ describe("daemon E2E", () => {
const hasDenied = timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.server === "permission" &&
item.name === "permission" &&
item.status === "denied"
);
expect(hasDenied).toBe(true);