c118996746
- SwiftUI macOS app with C++17 code analysis engine (ObjC++ bridge) - Agentic AI loop: LLM plans → tool calls → execution → feedback loop - 15 agent tools: file ops, terminal, git, xcode build, code intel - 7 persistent terminal tools with background session management - Chat sidebar with agent step rendering and auto-apply - NVIDIA NIM API integration (Llama 3.3 70B default) - OpenAI tool_calls format with prompt-based fallback - Code editor with syntax highlighting and multi-tab support - File tree, console view, terminal view - Git integration and workspace management
253 lines
12 KiB
Swift
253 lines
12 KiB
Swift
// AutoPilotTools.swift
|
|
// CxSwiftAgent — AutoPilot MCP Tools
|
|
//
|
|
// 6 tools: autopilot_plan, autopilot_execute, autopilot_memory,
|
|
// autopilot_context, autopilot_diff, autopilot_status
|
|
|
|
import Foundation
|
|
|
|
enum AutoPilotTools {
|
|
static func register(on server: MCPServer, config: AgentConfig, memory: AgentMemory) {
|
|
|
|
// ── autopilot_plan ────────────────────────────────────────────
|
|
server.registerTool(
|
|
"autopilot_plan",
|
|
description: "Create a structured plan for a multi-step coding task. Returns steps with dependencies.",
|
|
inputSchema: [
|
|
"type": "object",
|
|
"required": ["goal"],
|
|
"properties": [
|
|
"goal": ["type": "string", "description": "What needs to be accomplished."],
|
|
"context": ["type": "string", "description": "Additional context about the codebase or constraints."],
|
|
] as [String: Any],
|
|
] as [String: Any],
|
|
annotations: ToolAnnotations(readOnlyHint: true)
|
|
) { args in
|
|
let goal = args["goal"] as? String ?? ""
|
|
let context = args["context"] as? String ?? ""
|
|
|
|
// Create structured plan
|
|
let planId = UUID().uuidString.prefix(8)
|
|
|
|
Task {
|
|
_ = await memory.startTask(goal)
|
|
}
|
|
|
|
var plan = "AutoPilot Plan [\(planId)]:\n"
|
|
plan += "Goal: \(goal)\n"
|
|
if !context.isEmpty { plan += "Context: \(context)\n" }
|
|
plan += "\nSteps:\n"
|
|
plan += " 1. Analyze - Understand current state and requirements\n"
|
|
plan += " 2. Design - Determine approach and identify affected files\n"
|
|
plan += " 3. Implement - Make necessary code changes\n"
|
|
plan += " 4. Verify - Run tests and validate changes\n"
|
|
plan += " 5. Review - Check for edge cases and quality\n"
|
|
plan += "\nUse autopilot_execute to begin step-by-step execution.\n"
|
|
plan += "Use autopilot_status to check progress."
|
|
|
|
return ok(plan)
|
|
}
|
|
|
|
// ── autopilot_execute ─────────────────────────────────────────
|
|
server.registerTool(
|
|
"autopilot_execute",
|
|
description: "Execute a step from an autopilot plan. Provides guidance on tools to use.",
|
|
inputSchema: [
|
|
"type": "object",
|
|
"required": ["step"],
|
|
"properties": [
|
|
"step": ["type": "string", "description": "Step description or number to execute."],
|
|
"plan_id": ["type": "string", "description": "Plan ID from autopilot_plan."],
|
|
"dry_run": ["type": "boolean", "description": "Show what would be done without executing (default: false)."],
|
|
] as [String: Any],
|
|
] as [String: Any]
|
|
) { args in
|
|
let step = args["step"] as? String ?? ""
|
|
let dryRun = args["dry_run"] as? Bool ?? false
|
|
|
|
let lowerStep = step.lowercased()
|
|
|
|
var recommendation = "Step: \(step)\n\n"
|
|
|
|
if lowerStep.contains("analyze") || lowerStep.contains("1") {
|
|
recommendation += "Recommended tools:\n"
|
|
recommendation += " - file_tree: Explore project structure\n"
|
|
recommendation += " - file_search: Find relevant code\n"
|
|
recommendation += " - code_symbols: Understand file structure\n"
|
|
recommendation += " - project_detect: Identify project type\n"
|
|
recommendation += " - git_status: Check current state\n"
|
|
} else if lowerStep.contains("design") || lowerStep.contains("2") {
|
|
recommendation += "Recommended tools:\n"
|
|
recommendation += " - file_read: Review related code\n"
|
|
recommendation += " - code_explain: Understand existing logic\n"
|
|
recommendation += " - project_deps: Check dependencies\n"
|
|
recommendation += " - autopilot_memory: Record design decisions\n"
|
|
} else if lowerStep.contains("implement") || lowerStep.contains("3") {
|
|
recommendation += "Recommended tools:\n"
|
|
recommendation += " - file_write: Create new files\n"
|
|
recommendation += " - file_patch: Modify existing files\n"
|
|
recommendation += " - code_complete: Generate code\n"
|
|
recommendation += " - code_fix: Fix issues\n"
|
|
} else if lowerStep.contains("verify") || lowerStep.contains("4") {
|
|
recommendation += "Recommended tools:\n"
|
|
recommendation += " - project_run: Run tests (action: test)\n"
|
|
recommendation += " - xcode_build: Build project\n"
|
|
recommendation += " - xcode_test: Run Xcode tests\n"
|
|
recommendation += " - terminal_exec: Run custom commands\n"
|
|
recommendation += " - git_diff: Review changes\n"
|
|
} else if lowerStep.contains("review") || lowerStep.contains("5") {
|
|
recommendation += "Recommended tools:\n"
|
|
recommendation += " - code_review: Review changed code\n"
|
|
recommendation += " - code_security: Security audit\n"
|
|
recommendation += " - diag_lint: Check style\n"
|
|
recommendation += " - diag_complexity: Check complexity\n"
|
|
recommendation += " - git_diff: Final diff review\n"
|
|
}
|
|
|
|
if dryRun {
|
|
recommendation += "\n[DRY RUN - no changes made]"
|
|
}
|
|
|
|
return ok(recommendation)
|
|
}
|
|
|
|
// ── autopilot_memory ──────────────────────────────────────────
|
|
server.registerTool(
|
|
"autopilot_memory",
|
|
description: "Store or retrieve agent memory notes for cross-turn context.",
|
|
inputSchema: [
|
|
"type": "object",
|
|
"required": ["action"],
|
|
"properties": [
|
|
"action": ["type": "string", "description": "Action: store, retrieve, list, clear."],
|
|
"key": ["type": "string", "description": "Memory key (for store/retrieve)."],
|
|
"value": ["type": "string", "description": "Value to store."],
|
|
"category": ["type": "string", "description": "Category: decision, finding, task, error."],
|
|
] as [String: Any],
|
|
] as [String: Any]
|
|
) { args in
|
|
let action = args["action"] as? String ?? "list"
|
|
|
|
switch action {
|
|
case "store":
|
|
let key = args["key"] as? String ?? ""
|
|
let value = args["value"] as? String ?? ""
|
|
let category = args["category"] as? String ?? "decision"
|
|
guard !key.isEmpty else { return err("Missing key") }
|
|
|
|
if category == "decision" {
|
|
Task { await memory.recordDecision(key, reason: value) }
|
|
}
|
|
return ok("Stored: \(key)")
|
|
|
|
case "retrieve":
|
|
let context = await memory.generateContext()
|
|
return ok(context)
|
|
|
|
case "list":
|
|
let dict = await memory.toDict()
|
|
return ok(JSON.serialize(dict, pretty: true))
|
|
|
|
case "clear":
|
|
return ok("Memory persists for session duration. Start a new session to clear.")
|
|
|
|
default:
|
|
return err("Unknown action: \(action). Use: store, retrieve, list, clear")
|
|
}
|
|
}
|
|
|
|
// ── autopilot_context ─────────────────────────────────────────
|
|
server.registerTool(
|
|
"autopilot_context",
|
|
description: "Generate contextual summary of current work state from agent memory.",
|
|
inputSchema: [
|
|
"type": "object",
|
|
"properties": [:] as [String: Any],
|
|
] as [String: Any],
|
|
annotations: ToolAnnotations(readOnlyHint: true)
|
|
) { _ in
|
|
let context = await memory.generateContext()
|
|
return ok(context)
|
|
}
|
|
|
|
// ── autopilot_diff ────────────────────────────────────────────
|
|
server.registerTool(
|
|
"autopilot_diff",
|
|
description: "Show all uncommitted changes as a summary with statistics.",
|
|
inputSchema: [
|
|
"type": "object",
|
|
"properties": [:] as [String: Any],
|
|
] as [String: Any],
|
|
annotations: ToolAnnotations(readOnlyHint: true)
|
|
) { _ in
|
|
let process = Process()
|
|
process.executableURL = URL(fileURLWithPath: "/usr/bin/git")
|
|
process.arguments = ["diff", "--stat", "--no-color"]
|
|
process.currentDirectoryURL = URL(fileURLWithPath: config.workspaceRoot)
|
|
|
|
let pipe = Pipe()
|
|
process.standardOutput = pipe
|
|
|
|
do {
|
|
try process.run()
|
|
process.waitUntilExit()
|
|
let output = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
|
|
|
|
// Also get staged changes
|
|
let staged = Process()
|
|
staged.executableURL = URL(fileURLWithPath: "/usr/bin/git")
|
|
staged.arguments = ["diff", "--cached", "--stat", "--no-color"]
|
|
staged.currentDirectoryURL = URL(fileURLWithPath: config.workspaceRoot)
|
|
|
|
let stagedPipe = Pipe()
|
|
staged.standardOutput = stagedPipe
|
|
try staged.run()
|
|
staged.waitUntilExit()
|
|
let stagedOutput = String(data: stagedPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
|
|
|
|
var result = ""
|
|
if !stagedOutput.isEmpty {
|
|
result += "Staged changes:\n\(stagedOutput)\n"
|
|
}
|
|
if !output.isEmpty {
|
|
result += "Unstaged changes:\n\(output)"
|
|
}
|
|
return ok(result.isEmpty ? "No changes" : result)
|
|
} catch {
|
|
return err("Git not available: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
// ── autopilot_status ──────────────────────────────────────────
|
|
server.registerTool(
|
|
"autopilot_status",
|
|
description: "Show current autopilot session status: tasks, tool usage, memory summary.",
|
|
inputSchema: [
|
|
"type": "object",
|
|
"properties": [:] as [String: Any],
|
|
] as [String: Any],
|
|
annotations: ToolAnnotations(readOnlyHint: true)
|
|
) { _ in
|
|
let dict = await memory.toDict()
|
|
let context = await memory.generateContext()
|
|
|
|
var status = "AutoPilot Status:\n\n"
|
|
status += context
|
|
status += "\n\nRaw State:\n"
|
|
status += JSON.serialize(dict, pretty: true)
|
|
|
|
return ok(status)
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private static func ok(_ text: String) -> [[String: Any]] {
|
|
[["type": "text", "text": text]]
|
|
}
|
|
|
|
private static func err(_ message: String) -> [[String: Any]] {
|
|
[["type": "text", "text": "Error: \(message)"]]
|
|
}
|
|
}
|