nexus/.planning/milestones/v1.4-phases/29-default-provider/29-01-PLAN.md
Nexus Dev 8ae8e526d9 chore: complete v1.4 Hermes Default Provider milestone
3 phases, 6 plans, 16 requirements. Archives copied to milestones/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:55:49 +00:00

11 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
29-default-provider 01 execute 1
server/src/routes/agents.ts
ui/src/api/agents.ts
ui/src/components/NexusOnboardingWizard.tsx
ui/src/components/NewAgentDialog.tsx
true
DFLT-01
DFLT-02
truths artifacts key_links
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
path provides contains
server/src/routes/agents.ts Board-auth adapter probe route adapters/:type/probe
path provides contains
ui/src/api/agents.ts probeAdapter client method probeAdapter
path provides contains
ui/src/components/NexusOnboardingWizard.tsx Hermes fallback in onboarding wizard hermes_local
path provides
ui/src/components/NewAgentDialog.tsx Adapter-neutral templates
from to via pattern
ui/src/components/NexusOnboardingWizard.tsx /api/adapters/hermes_local/probe agentsApi.probeAdapter or fetch probe.*hermes_local
from to via pattern
ui/src/components/NewAgentDialog.tsx /agents/new navigate with role only, no adapterType 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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.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):

// 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):

// Existing API client pattern:
detectModel: (companyId: string, type: string) =>
  api.get<DetectedAdapterModel | null>(
    `/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/detect-model`,
  ),
testEnvironment: (companyId: string, type: string, data: {...}) =>
  api.post<AdapterEnvironmentTestResult>(...),

From ui/src/components/NexusOnboardingWizard.tsx:

// 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):

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:
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" });
  }
});
  1. In ui/src/api/agents.ts, add a probeAdapter method to the agentsApi object. It does NOT require companyId:
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):

const [defaultAdapter, setDefaultAdapter] = useState<"claude_local" | "hermes_local">("claude_local");
const [probing, setProbing] = useState(false);
  1. Add a useEffect that probes for Hermes when the wizard opens (after the existing useEffects, around line 60):
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]);
  1. In handleSubmit (line 67), replace the hardcoded adapterConfig and adapterType:
  • Change const adapterConfig = { cwd: rootDir.trim() }; to:
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.
  1. Update the form validation: the submit button disable condition (line 192) currently requires !rootDir.trim(). Change to:
disabled={loading || probing || (defaultAdapter === "claude_local" && !rootDir.trim())}
  1. Update the directory input label and required state. When hermes_local is selected, the directory is optional. Change the label (line 168):
<label htmlFor="nexus-root-dir" className="text-sm font-medium leading-none">
  Project root directory{defaultAdapter === "hermes_local" ? " (optional)" : ""}
</label>
  1. Update the description text (line 157) to mention local AI when hermes is detected:
<p className="text-sm text-muted-foreground">
  {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.`}
</p>

NewAgentDialog.tsx changes (DFLT-02):

  1. Remove adapterType from AGENT_TEMPLATES. Change lines 97-99:
const AGENT_TEMPLATES = [
  { id: "pm", label: "Project Manager", role: "pm" as const },
  { id: "engineer", label: "Engineer", role: "engineer" as const },
];
  1. Update handleTemplateSelect (around line 136). Remove adapterType from URL:
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 <acceptance_criteria> - 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) </acceptance_criteria> - 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

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/29-default-provider/29-01-SUMMARY.md`