Git panel: share checkout actions + fix push loading (#20)

* Update files

* Git panel: share checkout actions

Hoist checkout-scoped async git actions into a shared store and keep primary Push CTA loading in-place. Also stabilize e2e git flows.

* Stabilize git panel async actions and fix e2e flakes
This commit is contained in:
Mohamed Boudra
2026-02-06 13:18:30 +07:00
committed by GitHub
parent d724988542
commit cf9ff07be6
27 changed files with 858 additions and 433 deletions
@@ -158,7 +158,6 @@ describe("Codex app-server provider (integration)", () => {
thinkingOptionId: CODEX_TEST_THINKING_OPTION_ID,
});
await session.run("Reply with OK.");
const info = await session.getRuntimeInfo();
await session.close();
@@ -730,7 +729,20 @@ describe("Codex app-server provider (integration)", () => {
if (captured) {
expect(sawPermissionResolved).toBe(true);
}
expect(readFileSync(targetPath, "utf8").trim()).toBe("ok");
const sawPatch = timelineItems.some((item) => hasApplyPatchFile(item, "approval-test.txt"));
expect(sawPatch).toBe(true);
const text = await waitForFileToContainText(targetPath, "ok", { timeoutMs: 10000 });
if (!text) {
const toolNames = timelineItems
.filter((item) => item.type === "tool_call")
.map((item) => item.name)
.join(", ");
throw new Error(
`approval-test.txt was not written after file change approval flow (saw tools: ${toolNames || "none"})`
);
}
expect(text.trim()).toBe("ok");
} finally {
cleanup();
rmSync(cwd, { recursive: true, force: true });
@@ -1466,6 +1466,12 @@ class CodexAppServerAgentSession implements AgentSession {
async getRuntimeInfo(): Promise<AgentRuntimeInfo> {
if (this.cachedRuntimeInfo) return { ...this.cachedRuntimeInfo };
if (!this.connected) {
await this.connect();
}
if (!this.currentThreadId) {
await this.ensureThread();
}
const info: AgentRuntimeInfo = {
provider: CODEX_PROVIDER,
sessionId: this.currentThreadId,
+1
View File
@@ -33,6 +33,7 @@ async function main() {
if (process.argv.includes("--no-mcp")) {
config.mcpEnabled = false;
}
const daemon = await createPaseoDaemon(config, logger);
try {
@@ -90,6 +90,25 @@ function buildPersistence(
function buildToolCallForPrompt(provider: string, prompt: string) {
const text = prompt.toLowerCase();
const createFileMatch =
/create a file named\s+"([^"]+)"\s+with the content\s+"([^"]*)"/i.exec(prompt) ??
/create a file named\s+"([^"]+)"\s+with the content\s+'([^']*)'/i.exec(prompt);
if (createFileMatch) {
const fileName = createFileMatch[1] ?? "test.txt";
const content = createFileMatch[2] ?? "";
if (provider === "codex") {
return {
name: "shell",
input: { command: `printf "%s" "${content}" > ${fileName}` },
output: { ok: true },
};
}
return {
name: "Bash",
input: { command: `printf "%s" "${content}" > ${fileName}` },
output: { ok: true },
};
}
if (provider === "claude") {
if (text.includes("read") && text.includes("/etc/hosts")) {
return { name: "Read", input: { path: "/etc/hosts" }, output: undefined };
@@ -374,6 +393,7 @@ class FakeAgentSession implements AgentSession {
{ id: "default", label: "Default", description: "Ask for permissions" },
{ id: "full-access", label: "Full access", description: "No prompts" },
{ id: "auto", label: "Auto", description: "Ask/allow based on policy" },
{ id: "always-ask", label: "Always Ask", description: "Always prompt" },
];
}
@@ -475,6 +495,20 @@ class FakeAgentSession implements AgentSession {
private buildAssistantText(prompt: string): string {
const lower = prompt.toLowerCase();
// Special-case for tests that ask the agent to run pwd but use a placeholder in the
// "respond with exactly" instruction.
if (lower.includes("run `pwd`") && lower.includes("respond with exactly: cwd:")) {
const cwd = this.config.cwd ?? process.cwd();
return `CWD: ${cwd}`;
}
const respondExactlyMatch =
/respond with exactly:\s*([^\n\r]+)\s*$/i.exec(prompt) ??
/respond with exactly:\s*([^\n\r]+)/i.exec(prompt);
if (respondExactlyMatch) {
return (respondExactlyMatch[1] ?? "").trim();
}
if (lower.includes("state saved")) return "state saved";
if (lower.includes("timeline test")) return "timeline test";
if (lower.includes("quick brown fox") && lower.includes("lazy dog")) {
@@ -499,6 +533,9 @@ class FakeAgentSession implements AgentSession {
prompt: string
): Promise<void> {
const lower = prompt.toLowerCase();
const createFileMatch =
/create a file named\s+"([^"]+)"\s+with the content\s+"([^"]*)"/i.exec(prompt) ??
/create a file named\s+"([^"]+)"\s+with the content\s+'([^']*)'/i.exec(prompt);
if (toolName === "Read" || toolName === "read_file") {
const p = typeof toolInput.path === "string" ? toolInput.path : "/etc/hosts";
@@ -512,6 +549,17 @@ class FakeAgentSession implements AgentSession {
if (toolName === "Bash" || toolName === "shell") {
const command = typeof toolInput.command === "string" ? toolInput.command : "";
// Deterministic file-create behavior for permission prompt tests:
// Prompt: Create a file named "X" with the content "Y"
if (createFileMatch) {
const fileName = createFileMatch[1] ?? "test.txt";
const content = createFileMatch[2] ?? "";
const dest = path.join(this.config.cwd ?? process.cwd(), fileName);
writeFileSync(dest, content);
return;
}
if (lower.includes("rm -f permission.txt") || command.includes("rm -f permission.txt")) {
const dest = path.join(this.config.cwd ?? process.cwd(), "permission.txt");
try {
@@ -660,9 +708,18 @@ class FakeAgentClient implements AgentClient {
}
async listModels(_options?: ListModelsOptions): Promise<AgentModelDefinition[]> {
return [
{ provider: this.provider, id: "test-model", label: "Test Model", isDefault: true },
];
if (this.provider === "claude") {
return [
{ provider: this.provider, id: "haiku", label: "Haiku", isDefault: true },
{ provider: this.provider, id: "sonnet", label: "Sonnet", isDefault: false },
];
}
if (this.provider === "codex") {
return [
{ provider: this.provider, id: "gpt-5.1-codex-mini", label: "gpt-5.1-codex-mini", isDefault: true },
];
}
return [{ provider: this.provider, id: "test-model", label: "Test Model", isDefault: true }];
}
async isAvailable(): Promise<boolean> {