fix: wait for worktree bootstrap before prompt dispatch; resolve session directory for send/fork (#2708)

This commit is contained in:
Bruno Fantauzzi
2026-08-06 23:47:30 +03:00
committed by GitHub
parent 27c46aa0b9
commit e8caed45c9
4 changed files with 170 additions and 3 deletions
@@ -240,10 +240,32 @@ export const createOpenChamberControlService = (dependencies) => {
}
};
// session.send/fork default the directory to the caller's context directory,
// which is wrong for sessions living in other worktrees: prompt_async then
// targets an instance that does not hold the session and the run dies with
// UnknownError. Resolve the target session's directory from the global
// session list when the caller did not scope explicitly.
const resolveSessionDirectory = async (sessionID) => {
try {
const client = await getClient();
const response = await client.experimental?.session?.list?.({});
const sessions = Array.isArray(response?.data) ? response.data : [];
const session = sessions.find((item) => item?.id === sessionID);
return asNonEmptyString(session?.directory) || null;
} catch {
return null;
}
};
const executeSessionAction = async (action, input, contextDirectory, signal) => {
if (input.timeout !== undefined && input.wait !== true) throw new OpenChamberControlError('timeout requires wait', 400);
if (input.lastAssistant === true && input.wait !== true) throw new OpenChamberControlError('lastAssistant requires wait', 400);
const directory = asNonEmptyString(input.directory) || (!input.projectId ? asNonEmptyString(contextDirectory) : null);
const sessionID = asNonEmptyString(input.sessionId);
let directory = asNonEmptyString(input.directory) || (!input.projectId ? asNonEmptyString(contextDirectory) : null);
if (sessionID && action !== 'session.create' && !asNonEmptyString(input.directory) && !input.projectId) {
const resolvedSessionDirectory = await resolveSessionDirectory(sessionID);
if (resolvedSessionDirectory) directory = resolvedSessionDirectory;
}
const payload = {
...(directory ? { directory } : {}),
...(asNonEmptyString(input.projectId) ? { projectId: input.projectId.trim() } : {}),
@@ -262,7 +284,6 @@ export const createOpenChamberControlService = (dependencies) => {
...(typeof input.setUpstream === 'boolean' ? { setUpstream: input.setUpstream } : {}),
...(asNonEmptyString(input.messageId) ? { messageId: input.messageId.trim() } : {}),
};
const sessionID = asNonEmptyString(input.sessionId);
const startedAt = now();
let result;
if (action === 'session.create') {
@@ -132,6 +132,43 @@ describe('OpenChamber control service', () => {
expect(sessionService[method]).toHaveBeenCalledWith('ses_1', { directory: '/repo', prompt: 'Continue' });
});
it('resolves the target session directory from the global session list when send omits it', async () => {
const { service, sessionService, client } = createService({
createClient: () => ({
...client,
experimental: {
session: {
list: vi.fn(async () => ({
data: [
{ id: 'ses_other', directory: '/repo/worktrees/other' },
{ id: 'ses_target', directory: '/repo/worktrees/target' },
],
})),
},
},
}),
});
sessionService.send.mockResolvedValue({ sessionId: 'ses_target', directory: '/repo/worktrees/target', promptDispatched: true });
await service.execute('session.send', { sessionId: 'ses_target', prompt: 'Continue' }, '/repo');
expect(sessionService.send).toHaveBeenCalledWith('ses_target', { directory: '/repo/worktrees/target', prompt: 'Continue' });
});
it('falls back to the context directory when the session is not in the global list', async () => {
const { service, sessionService, client } = createService({
createClient: () => ({
...client,
experimental: { session: { list: vi.fn(async () => ({ data: [] })) } },
}),
});
sessionService.send.mockResolvedValue({ sessionId: 'ses_unknown', directory: '/repo', promptDispatched: true });
await service.execute('session.send', { sessionId: 'ses_unknown', prompt: 'Continue' }, '/repo');
expect(sessionService.send).toHaveBeenCalledWith('ses_unknown', { directory: '/repo', prompt: 'Continue' });
});
it('waits past initial idle until a completed assistant result appears', async () => {
let timestamp = 1000;
const { service, client, sessionService } = createService({