--- phase: 29-default-provider plan: 01 type: execute wave: 1 depends_on: [] files_modified: - server/src/routes/agents.ts - ui/src/api/agents.ts - ui/src/components/NexusOnboardingWizard.tsx - ui/src/components/NewAgentDialog.tsx autonomous: true requirements: [DFLT-01, DFLT-02] must_haves: truths: - "GET /api/adapters/hermes_local/probe returns {available: true} when Hermes CLI is installed" - "GET /api/adapters/hermes_local/probe returns {available: false} when Hermes CLI is absent" - "NexusOnboardingWizard creates agents with adapterType hermes_local when Hermes is available and no cloud provider is detected" - "NexusOnboardingWizard makes directory input optional when hermes_local is selected" - "AGENT_TEMPLATES in NewAgentDialog no longer hardcode adapterType" artifacts: - path: "server/src/routes/agents.ts" provides: "Board-auth adapter probe route" contains: "adapters/:type/probe" - path: "ui/src/api/agents.ts" provides: "probeAdapter client method" contains: "probeAdapter" - path: "ui/src/components/NexusOnboardingWizard.tsx" provides: "Hermes fallback in onboarding wizard" contains: "hermes_local" - path: "ui/src/components/NewAgentDialog.tsx" provides: "Adapter-neutral templates" key_links: - from: "ui/src/components/NexusOnboardingWizard.tsx" to: "/api/adapters/hermes_local/probe" via: "agentsApi.probeAdapter or fetch" pattern: "probe.*hermes_local" - from: "ui/src/components/NewAgentDialog.tsx" to: "/agents/new" via: "navigate with role only, no adapterType" pattern: "role=.*name=" --- Add adapter detection probe route and update NexusOnboardingWizard + NewAgentDialog to fall back to Hermes when no cloud provider is available. Purpose: Users with only Hermes + Ollama installed get a working onboarding flow without needing any paid API keys. Output: Probe route, wizard Hermes fallback, adapter-neutral templates. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/29-default-provider/29-RESEARCH.md From server/src/routes/agents.ts (line ~667): ```typescript // Existing adapter routes pattern — company-scoped: router.get("/companies/:companyId/adapters/:type/models", async (req, res) => { ... }); router.get("/companies/:companyId/adapters/:type/detect-model", async (req, res) => { ... }); router.post("/companies/:companyId/adapters/:type/test-environment", ...); // findServerAdapter import: import { findServerAdapter, listAdapterModels, detectAdapterModel } from "../adapters/index.js"; ``` From ui/src/api/agents.ts (line ~168): ```typescript // Existing API client pattern: detectModel: (companyId: string, type: string) => api.get( `/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/detect-model`, ), testEnvironment: (companyId: string, type: string, data: {...}) => api.post(...), ``` From ui/src/components/NexusOnboardingWizard.tsx: ```typescript // Lines 94-119: Three agentsApi.create calls with hardcoded adapterType: "claude_local" // Line 80: const adapterConfig = { cwd: rootDir.trim() }; // Line 41: effectiveOnboardingOpen controls wizard visibility ``` From ui/src/components/NewAgentDialog.tsx (lines 97-99): ```typescript const AGENT_TEMPLATES = [ { id: "pm", label: "Project Manager", role: "pm" as const, adapterType: "claude_local" as const }, { id: "engineer", label: "Engineer", role: "engineer" as const, adapterType: "claude_local" as const }, ]; // Line 140: navigate with adapterType in URL ``` Task 1: Add board-auth adapter probe route and frontend client - server/src/routes/agents.ts (lines 658-718 for existing adapter route patterns, line 186 for board auth pattern) - ui/src/api/agents.ts (lines 168-180 for existing adapter API methods) - server/src/adapters/index.ts (findServerAdapter export) server/src/routes/agents.ts, ui/src/api/agents.ts 1. In server/src/routes/agents.ts, add a new route BEFORE the existing company-scoped adapter routes (before line 667). This route has NO companyId — it uses board auth only: ```typescript router.get("/adapters/:type/probe", async (req, res) => { if (req.actor.type !== "board") { res.status(403).json({ error: "Board authentication required" }); return; } const type = req.params.type as string; const adapter = findServerAdapter(type); if (!adapter?.testEnvironment) { res.json({ available: false, status: "unknown" }); return; } try { const result = await adapter.testEnvironment({ companyId: "", adapterType: type, config: {}, }); const hasCliNotFound = result.checks.some( (c: { level: string; code?: string }) => c.level === "error" && (c.code?.includes("not_found") || c.code?.includes("cli")) ); res.json({ available: !hasCliNotFound, status: result.status, checks: result.checks }); } catch { res.json({ available: false, status: "error" }); } }); ``` 2. In ui/src/api/agents.ts, add a `probeAdapter` method to the agentsApi object. It does NOT require companyId: ```typescript probeAdapter: (type: string) => api.get<{ available: boolean; status: string }>(`/adapters/${encodeURIComponent(type)}/probe`), ``` cd /opt/nexus && pnpm --filter server exec tsc --noEmit 2>&1 | head -20 && pnpm --filter ui exec tsc --noEmit 2>&1 | head -20 - grep -q 'adapters/:type/probe' server/src/routes/agents.ts - grep -q 'probeAdapter' ui/src/api/agents.ts - grep -q 'req.actor.type !== "board"' server/src/routes/agents.ts (within the probe handler context) Board-auth probe route returns {available, status} without companyId. Frontend client can call agentsApi.probeAdapter("hermes_local"). Task 2: Update NexusOnboardingWizard to fall back to Hermes and make NewAgentDialog templates adapter-neutral - ui/src/components/NexusOnboardingWizard.tsx (full file — 228 lines) - ui/src/components/NewAgentDialog.tsx (lines 90-145 for templates and handleTemplateSelect) ui/src/components/NexusOnboardingWizard.tsx, ui/src/components/NewAgentDialog.tsx **NexusOnboardingWizard.tsx changes (DFLT-01):** 1. Add import for agentsApi (already imported on line 14: `import { agentsApi } from "../api/agents"`). 2. Add state for adapter detection after existing state declarations (around line 50): ```typescript const [defaultAdapter, setDefaultAdapter] = useState<"claude_local" | "hermes_local">("claude_local"); const [probing, setProbing] = useState(false); ``` 3. Add a useEffect that probes for Hermes when the wizard opens (after the existing useEffects, around line 60): ```typescript useEffect(() => { if (!effectiveOnboardingOpen) return; setProbing(true); agentsApi.probeAdapter("hermes_local") .then((data) => { if (data.available) setDefaultAdapter("hermes_local"); }) .catch(() => {}) // graceful — keep claude_local .finally(() => setProbing(false)); }, [effectiveOnboardingOpen]); ``` 4. In handleSubmit (line 67), replace the hardcoded adapterConfig and adapterType: - Change `const adapterConfig = { cwd: rootDir.trim() };` to: ```typescript const adapterConfig = defaultAdapter === "hermes_local" ? (rootDir.trim() ? { cwd: rootDir.trim() } : {}) : { cwd: rootDir.trim() }; ``` - Replace all three `adapterType: "claude_local"` (lines 97, 106, 115) with `adapterType: defaultAdapter`. 5. Update the form validation: the submit button disable condition (line 192) currently requires `!rootDir.trim()`. Change to: ```typescript disabled={loading || probing || (defaultAdapter === "claude_local" && !rootDir.trim())} ``` 6. Update the directory input label and required state. When hermes_local is selected, the directory is optional. Change the label (line 168): ```typescript ``` 7. Update the description text (line 157) to mention local AI when hermes is detected: ```typescript

{defaultAdapter === "hermes_local" ? `${VOCAB.appName} will set up a local AI workspace with a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist — no API key needed.` : `Choose a project root directory. ${VOCAB.appName} will set up a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist to start working.`}

``` **NewAgentDialog.tsx changes (DFLT-02):** 1. Remove `adapterType` from AGENT_TEMPLATES. Change lines 97-99: ```typescript const AGENT_TEMPLATES = [ { id: "pm", label: "Project Manager", role: "pm" as const }, { id: "engineer", label: "Engineer", role: "engineer" as const }, ]; ``` 2. Update handleTemplateSelect (around line 136). Remove adapterType from URL: ```typescript function handleTemplateSelect(template: typeof AGENT_TEMPLATES[number]) { closeNewAgent(); setShowAdvancedCards(false); navigate( `/agents/new?role=${encodeURIComponent(template.role)}&name=${encodeURIComponent(template.label)}`, ); } ``` This makes templates adapter-neutral. The /agents/new form will use its own default adapter logic.
cd /opt/nexus && pnpm --filter ui exec tsc --noEmit 2>&1 | head -20 - grep -q 'hermes_local' ui/src/components/NexusOnboardingWizard.tsx - grep -q 'probeAdapter' ui/src/components/NexusOnboardingWizard.tsx - grep -q 'defaultAdapter' ui/src/components/NexusOnboardingWizard.tsx - grep -v 'adapterType' ui/src/components/NewAgentDialog.tsx | grep -q 'AGENT_TEMPLATES' (templates no longer have adapterType field) - ! grep -q 'claude_local' ui/src/components/NewAgentDialog.tsx (no hardcoded claude_local in templates) - NexusOnboardingWizard probes for Hermes on open and uses hermes_local when available - Directory input is optional when hermes_local is selected - All three agents created with the detected adapter type - NewAgentDialog templates no longer hardcode adapterType — navigate with role+name only
- TypeScript compiles for both server and ui packages - NexusOnboardingWizard contains "hermes_local" and "probeAdapter" - NewAgentDialog AGENT_TEMPLATES have no adapterType field - Probe route exists in agents.ts with board auth guard - Probe route responds to GET /api/adapters/:type/probe with board auth - Wizard auto-detects Hermes and pre-selects hermes_local when available - Templates navigate without hardcoded adapter type - TypeScript compiles cleanly After completion, create `.planning/phases/29-default-provider/29-01-SUMMARY.md`