diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts index e39a3eea6d..ed858e6240 100644 --- a/packages/cli/src/commands/validate.ts +++ b/packages/cli/src/commands/validate.ts @@ -21,6 +21,7 @@ import type { ScriptValidationResult, } from '@archon/workflows/validator'; import { loadConfig, loadRepoConfig } from '@archon/core'; +import { getArchonHome } from '@archon/paths'; /** * Build ValidationConfig from the repo's .archon/config.yaml @@ -89,7 +90,8 @@ export async function validateWorkflowsCommand( const defaultProvider = mergedConfig.assistant; const { workflows: workflowEntries, errors: loadErrors } = await discoverWorkflowsWithConfig( cwd, - loadConfig + loadConfig, + { globalSearchPath: getArchonHome() } ); // Build results from load errors (Level 1-2 failures) diff --git a/packages/core/src/handlers/command-handler.test.ts b/packages/core/src/handlers/command-handler.test.ts index de6516cb98..c02afeb10d 100644 --- a/packages/core/src/handlers/command-handler.test.ts +++ b/packages/core/src/handlers/command-handler.test.ts @@ -194,6 +194,7 @@ const mockLogger = createMockLogger(); mock.module('@archon/paths', () => ({ createLogger: mock(() => mockLogger), getArchonWorkspacesPath: mock(() => '/home/test/.archon/workspaces'), + getArchonHome: mock(() => '/home/test/.archon'), getCommandFolderSearchPaths: mock(() => ['.archon/commands']), expandTilde: mock((p: string) => p.replace(/^~/, '/home/test')), ensureProjectStructure: mock(() => Promise.resolve()), @@ -1040,8 +1041,14 @@ describe('CommandHandler', () => { await handleCommand(conversationWithCodebase, '/workflow list'); - // Verify loadConfig function is passed as the second argument - expect(spyDiscoverWorkflows).toHaveBeenCalledWith(expect.any(String), expect.any(Function)); + // Verify loadConfig function and globalSearchPath are passed + expect(spyDiscoverWorkflows).toHaveBeenCalledWith( + expect.any(String), + expect.any(Function), + { + globalSearchPath: '/home/test/.archon', + } + ); }); }); @@ -1104,6 +1111,18 @@ describe('CommandHandler', () => { expect(result.message).toContain('Discovered 1 workflow(s)'); expect(result.message).not.toContain('failed to load'); }); + + test('should pass globalSearchPath to discoverWorkflowsWithConfig on reload', async () => { + spyDiscoverWorkflows.mockResolvedValueOnce({ workflows: [], errors: [] }); + + await handleCommand(conversationWithCodebase, '/workflow reload'); + + expect(spyDiscoverWorkflows).toHaveBeenCalledWith( + expect.any(String), + expect.any(Function), + { globalSearchPath: '/home/test/.archon' } + ); + }); }); describe('/workflow run with load errors', () => { @@ -1546,6 +1565,21 @@ describe('CommandHandler', () => { expect(result.message).toContain('/workflow list'); }); + test('should pass globalSearchPath to discoverWorkflowsWithConfig on run', async () => { + spyDiscoverWorkflows.mockResolvedValueOnce({ + workflows: [makeTestWorkflowWithSource({ name: 'assist', description: 'test' })], + errors: [], + }); + + await handleCommand(conversationWithCodebase, '/workflow run nonexistent'); + + expect(spyDiscoverWorkflows).toHaveBeenCalledWith( + expect.any(String), + expect.any(Function), + { globalSearchPath: '/home/test/.archon' } + ); + }); + test('should return error when workflow is not found', async () => { spyDiscoverWorkflows.mockResolvedValueOnce({ workflows: [ diff --git a/packages/core/src/handlers/command-handler.ts b/packages/core/src/handlers/command-handler.ts index 94227e54b9..d90d82b595 100644 --- a/packages/core/src/handlers/command-handler.ts +++ b/packages/core/src/handlers/command-handler.ts @@ -16,7 +16,7 @@ import { cleanupStaleWorktrees, getWorktreeStatusBreakdown, } from '../services/cleanup-service'; -import { getArchonWorkspacesPath } from '@archon/paths'; +import { getArchonWorkspacesPath, getArchonHome } from '@archon/paths'; import { loadConfig } from '../config/config-loader'; import { discoverWorkflowsWithConfig } from '@archon/workflows/workflow-discovery'; import { resolveWorkflowName } from '@archon/workflows/router'; @@ -563,7 +563,9 @@ async function handleWorkflowCommand( let workflowEntries: readonly WorkflowWithSource[]; let errors: readonly WorkflowLoadError[]; try { - const result = await discoverWorkflowsWithConfig(workflowCwd, loadConfig); + const result = await discoverWorkflowsWithConfig(workflowCwd, loadConfig, { + globalSearchPath: getArchonHome(), + }); workflowEntries = result.workflows; errors = result.errors; } catch (error) { @@ -609,7 +611,9 @@ async function handleWorkflowCommand( case 'reload': { try { const { workflows: reloadedWorkflows, errors: reloadErrors } = - await discoverWorkflowsWithConfig(workflowCwd, loadConfig); + await discoverWorkflowsWithConfig(workflowCwd, loadConfig, { + globalSearchPath: getArchonHome(), + }); let msg = `Discovered ${String(reloadedWorkflows.length)} workflow(s).`; if (reloadErrors.length > 0) { msg += `\n\n**${String(reloadErrors.length)} failed to load:**\n`; @@ -800,11 +804,12 @@ async function handleWorkflowCommand( 'cmd.workflow_run_invoked' ); - // Discover workflows with error handling let workflowEntries: readonly WorkflowWithSource[]; let loadErrors: readonly WorkflowLoadError[]; try { - const result = await discoverWorkflowsWithConfig(workflowCwd, loadConfig); + const result = await discoverWorkflowsWithConfig(workflowCwd, loadConfig, { + globalSearchPath: getArchonHome(), + }); workflowEntries = result.workflows; loadErrors = result.errors; } catch (error) { diff --git a/packages/core/src/orchestrator/orchestrator-agent.ts b/packages/core/src/orchestrator/orchestrator-agent.ts index d5eb9397b3..36c8459289 100644 --- a/packages/core/src/orchestrator/orchestrator-agent.ts +++ b/packages/core/src/orchestrator/orchestrator-agent.ts @@ -1431,7 +1431,9 @@ async function handleWorkflowRunCommand( let discovery; try { - discovery = await discoverWorkflowsWithConfig(workflowCwd, loadConfig); + discovery = await discoverWorkflowsWithConfig(workflowCwd, loadConfig, { + globalSearchPath: getArchonHome(), + }); } catch (error) { const err = error as Error; getLog().error({ err, cwd: workflowCwd }, 'workflow_discovery_failed'); diff --git a/packages/core/src/orchestrator/orchestrator.test.ts b/packages/core/src/orchestrator/orchestrator.test.ts index de4618ed15..c314c056c9 100644 --- a/packages/core/src/orchestrator/orchestrator.test.ts +++ b/packages/core/src/orchestrator/orchestrator.test.ts @@ -555,7 +555,8 @@ describe('orchestrator-agent handleMessage', () => { expect(mockDiscoverWorkflows).toHaveBeenCalledWith( '/workspace/test-project', - expect.any(Function) + expect.any(Function), + { globalSearchPath: expect.any(String) } ); expect(platform.sendMessage).toHaveBeenCalledWith( 'chat-456', diff --git a/packages/server/src/routes/api.ts b/packages/server/src/routes/api.ts index 1684a9b773..2f5562c26f 100644 --- a/packages/server/src/routes/api.ts +++ b/packages/server/src/routes/api.ts @@ -1686,7 +1686,9 @@ export function registerApiRoutes( return c.json({ workflows: [] }); } - const result = await discoverWorkflowsWithConfig(workingDir, loadConfig); + const result = await discoverWorkflowsWithConfig(workingDir, loadConfig, { + globalSearchPath: getArchonHome(), + }); return c.json({ workflows: result.workflows.map(ws => ({ workflow: ws.workflow, source: ws.source })), errors: result.errors.length > 0 ? result.errors : undefined, diff --git a/packages/server/src/routes/api.workflows.test.ts b/packages/server/src/routes/api.workflows.test.ts index e50b252640..21c905e878 100644 --- a/packages/server/src/routes/api.workflows.test.ts +++ b/packages/server/src/routes/api.workflows.test.ts @@ -116,7 +116,9 @@ describe('GET /api/workflows', () => { expect(body.workflows[0]?.workflow.name).toBe('deploy'); expect(body.workflows[0]?.source).toBe('bundled'); expect(body.workflows.workflows).toBeUndefined(); - expect(mockDiscoverWorkflows).toHaveBeenCalledWith('/tmp/project', expect.any(Function)); + expect(mockDiscoverWorkflows).toHaveBeenCalledWith('/tmp/project', expect.any(Function), { + globalSearchPath: expect.any(String), + }); expect(body.errors).toBeDefined(); expect(Array.isArray(body.errors)).toBe(true); });