From 36fafe193ec87b33eeabeaf8261c95d101d75263 Mon Sep 17 00:00:00 2001 From: Thomas Marchand Date: Sun, 18 Jan 2026 14:58:04 +0000 Subject: [PATCH] feat: implement Claude Code backend and harness-aware UI - Implement ClaudeCodeClient with subprocess JSON streaming to Claude CLI - Implement ClaudeCodeBackend with Backend trait for mission execution - Update mission runner to support both OpenCode and Claude Code backends - Add harness tabs to Library Configs page (OpenCode/Claude Code) - Add CLI path configuration for Claude Code in Settings - Add comprehensive harness system documentation --- dashboard/src/app/config/settings/page.tsx | 171 ++++++++- dashboard/src/app/settings/page.tsx | 18 + docs/HARNESS_SYSTEM.md | 226 +++++++++++ src/api/control.rs | 11 + src/api/mission_runner.rs | 305 +++++++++++++-- src/api/routes.rs | 1 + src/backend/claudecode/client.rs | 420 ++++++++++++++++++++- src/backend/claudecode/mod.rs | 268 ++++++++++++- 8 files changed, 1376 insertions(+), 44 deletions(-) create mode 100644 docs/HARNESS_SYSTEM.md diff --git a/dashboard/src/app/config/settings/page.tsx b/dashboard/src/app/config/settings/page.tsx index 20aec81..1fc8848 100644 --- a/dashboard/src/app/config/settings/page.tsx +++ b/dashboard/src/app/config/settings/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState, useEffect, useCallback } from 'react'; +import useSWR from 'swr'; import { getLibraryOpenCodeSettings, saveLibraryOpenCodeSettings, @@ -10,8 +11,10 @@ import { saveOpenAgentConfig, listOpenCodeAgents, OpenAgentConfig, + listBackends, + getBackendConfig, } from '@/lib/api'; -import { Save, Loader, AlertCircle, Check, RefreshCw, RotateCcw, Eye, EyeOff, AlertTriangle, X, GitBranch, Upload } from 'lucide-react'; +import { Save, Loader, AlertCircle, Check, RefreshCw, RotateCcw, Eye, EyeOff, AlertTriangle, X, GitBranch, Upload, Info, FileCode, Terminal } from 'lucide-react'; import { cn } from '@/lib/utils'; import { ConfigCodeEditor } from '@/components/config-code-editor'; import { useLibrary } from '@/contexts/library-context'; @@ -39,6 +42,31 @@ export default function SettingsPage() { refreshStatus, } = useLibrary(); + // Harness tab state + const [activeHarness, setActiveHarness] = useState<'opencode' | 'claudecode'>('opencode'); + + // Fetch backends and their config to show enabled harnesses + const { data: backends = [] } = useSWR('backends', listBackends, { + revalidateOnFocus: false, + fallbackData: [ + { id: 'opencode', name: 'OpenCode' }, + { id: 'claudecode', name: 'Claude Code' }, + ], + }); + const { data: opencodeConfig } = useSWR('backend-opencode-config', () => getBackendConfig('opencode'), { + revalidateOnFocus: false, + }); + const { data: claudecodeConfig } = useSWR('backend-claudecode-config', () => getBackendConfig('claudecode'), { + revalidateOnFocus: false, + }); + + // Filter to only enabled backends + const enabledBackends = backends.filter((b) => { + if (b.id === 'opencode') return opencodeConfig?.enabled !== false; + if (b.id === 'claudecode') return claudecodeConfig?.enabled !== false; + return true; + }); + // OpenCode settings state const [settings, setSettings] = useState(''); const [originalSettings, setOriginalSettings] = useState(''); @@ -407,7 +435,27 @@ export default function SettingsPage() { )} - {/* OpenCode Settings Section */} + {/* Harness Tabs */} +
+ {enabledBackends.map((backend) => ( + + ))} +
+ + {activeHarness === 'opencode' ? ( + <> + {/* OpenCode Settings Section */}
@@ -626,6 +674,125 @@ export default function SettingsPage() {
+ + ) : ( + /* Claude Code Section */ +
+ {/* Claude Code Info Card */} +
+
+
+ +
+
+
+

Claude Code Configuration

+

+ Claude Code uses a workspace-centric configuration model that differs from OpenCode. +

+
+
+
+
+ + {/* How It Works */} +
+
+ +

How Configuration Works

+
+
+

+ Unlike OpenCode which uses a centralized oh-my-opencode.json configuration, + Claude Code generates configuration per-workspace from your Library. +

+
+

Generated files in each workspace:

+
    +
  • + CLAUDE.md — + System prompt and context from Library skills +
  • +
  • + .claude/settings.local.json — + MCP servers and tool permissions +
  • +
+
+
+

Configuration sources from Library:

+
    +
  • + skills/ — + Markdown files become context in CLAUDE.md +
  • +
  • + mcps/ — + MCP server definitions for tool access +
  • +
  • + tools/ — + Custom tool definitions +
  • +
+
+
+
+ + {/* Configuration Location */} +
+
+ +

Where to Configure

+
+ +
+ + {/* Backend Settings Link */} +
+
+ +
+

Backend Settings

+

+ To configure Claude Code API key, default model, or CLI path, visit the{' '} + Settings page → Backends → Claude Code. +

+
+
+
+
+ )} {/* Commit Dialog */} {showCommitDialog && ( diff --git a/dashboard/src/app/settings/page.tsx b/dashboard/src/app/settings/page.tsx index 7d5185d..cdd1241 100644 --- a/dashboard/src/app/settings/page.tsx +++ b/dashboard/src/app/settings/page.tsx @@ -116,6 +116,7 @@ export default function SettingsPage() { const [claudeForm, setClaudeForm] = useState({ api_key: '', default_model: '', + cli_path: '', api_key_configured: false, enabled: true, }); @@ -231,6 +232,7 @@ export default function SettingsPage() { setClaudeForm((prev) => ({ ...prev, default_model: typeof settings.default_model === 'string' ? settings.default_model : '', + cli_path: typeof settings.cli_path === 'string' ? settings.cli_path : '', api_key_configured: Boolean(settings.api_key_configured), enabled: claudecodeBackendConfig.enabled, })); @@ -350,6 +352,7 @@ export default function SettingsPage() { try { const settings: Record = { default_model: claudeForm.default_model || null, + cli_path: claudeForm.cli_path || null, }; if (claudeForm.api_key) { settings.api_key = claudeForm.api_key; @@ -829,6 +832,21 @@ export default function SettingsPage() { className="w-full rounded-lg border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-sm text-white focus:outline-none focus:border-indigo-500/50" />
+
+ + + setClaudeForm((prev) => ({ ...prev, cli_path: e.target.value })) + } + placeholder="claude (uses PATH) or /path/to/claude" + className="w-full rounded-lg border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-sm text-white focus:outline-none focus:border-indigo-500/50" + /> +

+ Path to the Claude CLI executable. Leave blank to use default from PATH. +

+