fix(ui): route answer worktrees from source session
This commit is contained in:
@@ -6,6 +6,8 @@ const createSessionCalls: Array<{ title?: string; directory: string | null; pare
|
||||
const permissionAutoAcceptCalls: Array<[string, boolean]> = []
|
||||
const savedVariantCalls: Array<string | undefined> = []
|
||||
let configVariantOverride: string | null | undefined
|
||||
let projects: Array<{ id: string; path: string; label: string }> = []
|
||||
const createdWorktreeProjects: Array<{ id: string; path: string }> = []
|
||||
// Sync's session→directory index. `createSession` writes it, and directory
|
||||
// resolution reads it as the authoritative source, so the mock has to keep one.
|
||||
const sessionDirectoryRegistry = new Map<string, string>()
|
||||
@@ -111,7 +113,7 @@ mock.module("@/stores/useConfigStore", () => ({
|
||||
mock.module("@/stores/useProjectsStore", () => ({
|
||||
useProjectsStore: {
|
||||
getState: () => ({
|
||||
projects: [],
|
||||
projects,
|
||||
activeProjectId: null,
|
||||
getActiveProject: () => null,
|
||||
}),
|
||||
@@ -127,6 +129,12 @@ mock.module("@/stores/useDirectoryStore", () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
mock.module("@/stores/useSessionGoalArmStore", () => ({
|
||||
useSessionGoalArmStore: {
|
||||
getState: () => ({ setArmed: () => undefined }),
|
||||
},
|
||||
}))
|
||||
|
||||
mock.module("@/stores/useGlobalSessionsStore", () => ({
|
||||
useGlobalSessionsStore: {
|
||||
getState: () => ({
|
||||
@@ -304,6 +312,33 @@ mock.module("../session-actions", () => ({
|
||||
abortCurrentOperation: mock(async () => undefined),
|
||||
}))
|
||||
|
||||
mock.module("@/lib/git/branchNameGenerator", () => ({
|
||||
generateBranchName: () => "generated-branch",
|
||||
}))
|
||||
|
||||
mock.module("@/lib/openchamberConfig", () => ({
|
||||
getWorktreeSetupCommands: async () => [],
|
||||
getWorktreeSetupWaitEnabled: async () => false,
|
||||
}))
|
||||
|
||||
mock.module("@/lib/worktrees/worktreeBootstrap", () => ({
|
||||
waitForWorktreeBootstrap: async () => undefined,
|
||||
}))
|
||||
|
||||
mock.module("@/lib/worktrees/worktreeCreate", () => ({
|
||||
createWorktreeWithDefaults: async (project: { id: string; path: string }) => {
|
||||
createdWorktreeProjects.push(project)
|
||||
return {
|
||||
source: "sdk",
|
||||
name: "generated-branch",
|
||||
path: "/worktrees/generated-branch",
|
||||
projectDirectory: project.path,
|
||||
branch: "generated-branch",
|
||||
label: "generated-branch",
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
const { materializeOpenDraftSession, useSessionUIStore } = await import("../session-ui-store")
|
||||
|
||||
describe("issue 2039 draft auto-accept", () => {
|
||||
@@ -500,3 +535,94 @@ describe("issue 2039 draft auto-accept", () => {
|
||||
expect(useSessionUIStore.getState().getDirectoryForSession(sessionId)).toBe("/canonical/worktree")
|
||||
})
|
||||
})
|
||||
|
||||
describe("assistant answer worktree routing", () => {
|
||||
test("reports session creation failure instead of completing silently", async () => {
|
||||
const state = useSessionUIStore.getState()
|
||||
const createFromAssistantMessage = state.createSessionFromAssistantMessage
|
||||
const originalCreateSession = state.createSession
|
||||
|
||||
useSessionUIStore.setState({
|
||||
createSession: async () => null,
|
||||
})
|
||||
|
||||
try {
|
||||
await expect(createFromAssistantMessage({
|
||||
sessionId: "source-session",
|
||||
directory: "/repo",
|
||||
text: "Implement the plan",
|
||||
}, {
|
||||
providerID: "provider",
|
||||
modelID: "model",
|
||||
variant: "",
|
||||
agent: "build",
|
||||
instructions: "Follow the answer",
|
||||
})).rejects.toThrow("Failed to create session")
|
||||
} finally {
|
||||
useSessionUIStore.setState({ createSession: originalCreateSession })
|
||||
}
|
||||
})
|
||||
|
||||
test("creates a sibling worktree from the captured source worktree directory", async () => {
|
||||
projects = [
|
||||
{ id: "project", path: "/repo", label: "Repo" },
|
||||
{ id: "source-worktree", path: "/worktrees/source", label: "Source worktree" },
|
||||
]
|
||||
createdWorktreeProjects.length = 0
|
||||
const sourceWorktree = {
|
||||
path: "/worktrees/source",
|
||||
projectDirectory: "/repo",
|
||||
branch: "source",
|
||||
label: "source",
|
||||
}
|
||||
const state = useSessionUIStore.getState()
|
||||
const createFromAssistantMessage = state.createSessionFromAssistantMessage
|
||||
const originalCreateSession = state.createSession
|
||||
const originalSendMessage = state.sendMessage
|
||||
const originalWorktreeMetadata = state.worktreeMetadata
|
||||
let createdDirectory: string | null | undefined
|
||||
|
||||
useSessionUIStore.setState({
|
||||
availableWorktreesByProject: new Map([["/repo", [sourceWorktree]]]),
|
||||
worktreeMetadata: new Map([["source-session", sourceWorktree]]),
|
||||
createSession: async (_title, directory) => {
|
||||
createdDirectory = directory
|
||||
return {
|
||||
id: "created-session",
|
||||
slug: "created-session",
|
||||
projectID: "project",
|
||||
directory: directory ?? "",
|
||||
title: "Created session",
|
||||
version: "1",
|
||||
time: { created: 1, updated: 1 },
|
||||
}
|
||||
},
|
||||
sendMessage: async () => undefined,
|
||||
})
|
||||
|
||||
try {
|
||||
await createFromAssistantMessage({
|
||||
sessionId: "source-session",
|
||||
directory: "/worktrees/source",
|
||||
text: "Implement the plan",
|
||||
}, {
|
||||
providerID: "provider",
|
||||
modelID: "model",
|
||||
variant: "",
|
||||
agent: "build",
|
||||
instructions: "Follow the answer",
|
||||
createWorktree: true,
|
||||
})
|
||||
} finally {
|
||||
useSessionUIStore.setState({
|
||||
createSession: originalCreateSession,
|
||||
sendMessage: originalSendMessage,
|
||||
worktreeMetadata: originalWorktreeMetadata,
|
||||
})
|
||||
projects = []
|
||||
}
|
||||
|
||||
expect(createdWorktreeProjects).toEqual([{ id: "project", path: "/repo" }])
|
||||
expect(createdDirectory).toBe("/worktrees/generated-branch")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user