6 phases, 13 plans, 21 requirements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
287 lines
11 KiB
Markdown
287 lines
11 KiB
Markdown
---
|
|
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="
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/29-default-provider/29-RESEARCH.md
|
|
|
|
<interfaces>
|
|
<!-- Key types and contracts the executor needs -->
|
|
|
|
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<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:
|
|
```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
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Add board-auth adapter probe route and frontend client</name>
|
|
<read_first>
|
|
- 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)
|
|
</read_first>
|
|
<files>server/src/routes/agents.ts, ui/src/api/agents.ts</files>
|
|
<action>
|
|
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`),
|
|
```
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm --filter server exec tsc --noEmit 2>&1 | head -20 && pnpm --filter ui exec tsc --noEmit 2>&1 | head -20</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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)
|
|
</acceptance_criteria>
|
|
<done>Board-auth probe route returns {available, status} without companyId. Frontend client can call agentsApi.probeAdapter("hermes_local").</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Update NexusOnboardingWizard to fall back to Hermes and make NewAgentDialog templates adapter-neutral</name>
|
|
<read_first>
|
|
- ui/src/components/NexusOnboardingWizard.tsx (full file — 228 lines)
|
|
- ui/src/components/NewAgentDialog.tsx (lines 90-145 for templates and handleTemplateSelect)
|
|
</read_first>
|
|
<files>ui/src/components/NexusOnboardingWizard.tsx, ui/src/components/NewAgentDialog.tsx</files>
|
|
<action>
|
|
**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
|
|
<label htmlFor="nexus-root-dir" className="text-sm font-medium leading-none">
|
|
Project root directory{defaultAdapter === "hermes_local" ? " (optional)" : ""}
|
|
</label>
|
|
```
|
|
|
|
7. Update the description text (line 157) to mention local AI when hermes is detected:
|
|
```typescript
|
|
<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:
|
|
```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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm --filter ui exec tsc --noEmit 2>&1 | head -20</automated>
|
|
</verify>
|
|
<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>
|
|
<done>
|
|
- 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
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- 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
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/29-default-provider/29-01-SUMMARY.md`
|
|
</output>
|