Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/core/src/orchestrator/orchestrator-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,10 +902,12 @@ describe('discoverAllWorkflows — remote sync', () => {
mockSendQuery.mockClear();
mockGetCodebaseEnvVars.mockReset();
mockLoadConfig.mockReset();
mockListCodebases.mockReset();
// Reset mocks between tests in this suite and restore safe defaults
mockGetOrCreateConversation.mockImplementation(() => Promise.resolve(null));
mockGetCodebase.mockImplementation(() => Promise.resolve(null));
mockGetCodebaseEnvVars.mockImplementation(() => Promise.resolve({}));
mockListCodebases.mockImplementation(() => Promise.resolve([]));
mockLoadConfig.mockImplementation(() =>
Promise.resolve({
assistants: { claude: {}, codex: {} },
Expand Down Expand Up @@ -1043,6 +1045,61 @@ describe('discoverAllWorkflows — remote sync', () => {
const requestOptions = mockSendQuery.mock.calls[0][3] as Record<string, unknown>;
expect(requestOptions.env).toEqual({ FILE_SECRET: 'file-value' });
});

describe('provider cwd resolution', () => {
function getCwdPassedToProvider(): string {
expect(mockSendQuery).toHaveBeenCalled();
return mockSendQuery.mock.calls[0][1] as string;
}

test('passes codebase.default_cwd to provider when conversation is codebase-scoped', async () => {
const codebase = makeCodebaseForSync();
const conversation = makeConversation({ codebase_id: 'codebase-1', cwd: null });
mockGetOrCreateConversation.mockReturnValueOnce(Promise.resolve(conversation));
mockGetCodebase.mockReturnValueOnce(Promise.resolve(codebase));
mockListCodebases.mockReturnValueOnce(Promise.resolve([codebase]));

await handleMessage(makePlatform(), 'conv-1', 'What files are in src/?');

expect(getCwdPassedToProvider()).toBe('/repos/test-repo');
});

test('prefers conversation.cwd over codebase.default_cwd when set (worktree)', async () => {
const codebase = makeCodebaseForSync();
const conversation = makeConversation({
codebase_id: 'codebase-1',
cwd: '/home/test/.archon/workspaces/owner/repo/worktrees/feature-branch',
});
mockGetOrCreateConversation.mockReturnValueOnce(Promise.resolve(conversation));
mockGetCodebase.mockReturnValueOnce(Promise.resolve(codebase));
mockListCodebases.mockReturnValueOnce(Promise.resolve([codebase]));

await handleMessage(makePlatform(), 'conv-1', 'What files are in src/?');

expect(getCwdPassedToProvider()).toBe(
'/home/test/.archon/workspaces/owner/repo/worktrees/feature-branch'
);
});

test('falls back to getArchonWorkspacesPath when conversation has no codebase', async () => {
const conversation = makeConversation({ codebase_id: null, cwd: null });
mockGetOrCreateConversation.mockReturnValueOnce(Promise.resolve(conversation));

await handleMessage(makePlatform(), 'conv-1', 'Hello');

expect(getCwdPassedToProvider()).toBe('/home/test/.archon/workspaces');
});

test('falls back to getArchonWorkspacesPath when codebase_id is set but codebase not in list', async () => {
const conversation = makeConversation({ codebase_id: 'codebase-1', cwd: null });
mockGetOrCreateConversation.mockReturnValueOnce(Promise.resolve(conversation));
mockListCodebases.mockReturnValueOnce(Promise.resolve([]));

await handleMessage(makePlatform(), 'conv-1', 'Hello');

expect(getCwdPassedToProvider()).toBe('/home/test/.archon/workspaces');
});
});
});

// ─── Workflow dispatch routing — interactive flag ─────────────────────────────
Expand Down
17 changes: 16 additions & 1 deletion packages/core/src/orchestrator/orchestrator-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,22 @@ export async function handleMessage(
attachedFiles,
workflowContext
);
const cwd = getArchonWorkspacesPath();
// For codebase-scoped chat, use the worktree path (conversation.cwd) if set,
// otherwise the codebase's default working directory.
// Non-scoped chat falls back to the Archon workspaces root.
let cwd = getArchonWorkspacesPath();
if (conversation.codebase_id) {
const attachedCodebase = codebases.find(c => c.id === conversation.codebase_id);
if (attachedCodebase) {
cwd = conversation.cwd ?? attachedCodebase.default_cwd;
} else {
// Intentional fallback: codebase may have been deleted; run with workspaces root.
getLog().warn(
{ codebaseId: conversation.codebase_id, conversationId },
'orchestrator.codebase_not_found_cwd_fallback'
);
}
}

// 4. Update activity and get/create session
await db.touchConversation(conversation.id);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/services/cleanup-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ describe('runScheduledCleanup', () => {
expect(report.removed).toHaveLength(0);
});

test('continues processing after error on one environment', async () => {
test('processes all environments in batch (both paths missing)', async () => {
mockListAllActiveWithCodebase.mockResolvedValueOnce([
{
id: 'env-error',
Expand Down
Loading