Fix iOS build by adding MarkdownView.swift to Xcode project (#34)
* Fix iOS build by adding MarkdownView.swift to Xcode project The file existed on disk but was missing from the project file, causing 'cannot find MarkdownView in scope' compilation error. * Sync iOS Workspace model with backend and stub deprecated agents API - Add new fields to Workspace model: skills, tools, plugins, template, distro, envVars, initScript to match backend WorkspaceResponse - Add custom decoder to handle optional fields gracefully - Stub listAgents/createAgent methods since /api/agents endpoint no longer exists (agents are now library-managed) * Remove dead agents code from iOS app The agents management was moved to library configuration in the backend. This removes the orphaned iOS code: - Delete AgentConfig.swift model - Delete AgentsView.swift (was not in main navigation) - Remove stub API methods for /api/agents endpoint - Remove AgentConfig unit tests - Update Xcode project references Build: SUCCEEDED Tests: 21 passed (was 23, removed 2 AgentConfig tests)
This commit is contained in:
@@ -10,14 +10,12 @@
|
||||
02DB7F25245D03FF72DD8E2E /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84519FDE8FC75084938B292 /* ControlView.swift */; };
|
||||
03176DF3878C25A0B557462C /* ToolUIOptionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D419C8490A0C5FC4DCDF20 /* ToolUIOptionListView.swift */; };
|
||||
0620B298DEF91DFCAE050DAC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 66A48A20D2178760301256C9 /* Assets.xcassets */; };
|
||||
0672F3AC46E02746D9857565 /* AgentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA1A2E7115E4E4B93D41A66 /* AgentsView.swift */; };
|
||||
0B5E1A6153270BFF21A54C23 /* TerminalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DDF35DB8CD7D70F3CFC4A6 /* TerminalState.swift */; };
|
||||
1BBE749F3758FD704D1BFA0B /* ToolUIDataTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45213C3E550D451EDC566CDE /* ToolUIDataTableView.swift */; };
|
||||
29372E691F6A5C5D2CCD9331 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A09A33A3A1A99446C8A88DC /* HistoryView.swift */; };
|
||||
2E26F9659B38872F562C3B2B /* WorkspacesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91BF18B4AEAEB407887401AC /* WorkspacesView.swift */; };
|
||||
3361B14E949CB2A6E75B6962 /* ToolUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CBD2029F8CF6751AD7C4E2 /* ToolUIView.swift */; };
|
||||
3DD4D1D2E080C2F89C4881B7 /* ToolUIModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6128ECBCA632D9E2D415F2 /* ToolUIModels.swift */; };
|
||||
487AF98018941AF388DB5A80 /* AgentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E606D3ED9560CFEC056ADFD1 /* AgentConfig.swift */; };
|
||||
4B50B97618C0CC469FF64592 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504A1222CE8971417834D229 /* Theme.swift */; };
|
||||
4D0CF2666262F45370D000DF /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC6317C4EAD4DB9A8190209 /* TerminalView.swift */; };
|
||||
51436A7671B1E3C8478F81A2 /* RunningMissionsBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7FC053808661C9A0E21E83C /* RunningMissionsBar.swift */; };
|
||||
@@ -37,6 +35,7 @@
|
||||
CA70EC5A864C3D007D42E781 /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB591B632D3EF26AB217976 /* ChatMessage.swift */; };
|
||||
D64972881E36894950658708 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC90C32FEF604E025FFBF78 /* APIService.swift */; };
|
||||
DA4634D7424AF3FC985987E7 /* GlassButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5267DE67017A858357F68424 /* GlassButton.swift */; };
|
||||
E1A2B3C4D5E6F78901234567 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6789012345678 /* MarkdownView.swift */; };
|
||||
EFABDC95B65F6ED3420186FC /* NewMissionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A8191F935AB50463216395 /* NewMissionSheet.swift */; };
|
||||
FA7E68F22D16E1AC0B5F5E22 /* StatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6FB2E54DC07BE7A1EB08F8 /* StatusBadge.swift */; };
|
||||
FF9C447978711CBA9185B8B0 /* OpenAgentDashboardApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 139C740B7D55C13F3B167EF3 /* OpenAgentDashboardApp.swift */; };
|
||||
@@ -55,9 +54,9 @@
|
||||
/* Begin PBXFileReference section */
|
||||
02CBD2029F8CF6751AD7C4E2 /* ToolUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolUIView.swift; sourceTree = "<group>"; };
|
||||
0AC6317C4EAD4DB9A8190209 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
|
||||
0FA1A2E7115E4E4B93D41A66 /* AgentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentsView.swift; sourceTree = "<group>"; };
|
||||
139C740B7D55C13F3B167EF3 /* OpenAgentDashboardApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAgentDashboardApp.swift; sourceTree = "<group>"; };
|
||||
2B9834D4EE32058824F9DF00 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
|
||||
A1B2C3D4E5F6789012345678 /* MarkdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownView.swift; sourceTree = "<group>"; };
|
||||
3729F39FBF53046124D05BC1 /* NavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationState.swift; sourceTree = "<group>"; };
|
||||
3CB591B632D3EF26AB217976 /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = "<group>"; };
|
||||
43A2EBAE84C0FFDCA5E1D66E /* OpenAgentDashboard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OpenAgentDashboard.entitlements; sourceTree = "<group>"; };
|
||||
@@ -84,7 +83,6 @@
|
||||
CD8D224B6758B664864F3987 /* ANSIParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ANSIParser.swift; sourceTree = "<group>"; };
|
||||
D1A8191F935AB50463216395 /* NewMissionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewMissionSheet.swift; sourceTree = "<group>"; };
|
||||
D4AB47CF121ABA1946A4D879 /* Mission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mission.swift; sourceTree = "<group>"; };
|
||||
E606D3ED9560CFEC056ADFD1 /* AgentConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentConfig.swift; sourceTree = "<group>"; };
|
||||
E7C1198DDF17571DE85F5ABA /* Workspace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Workspace.swift; sourceTree = "<group>"; };
|
||||
E7FC053808661C9A0E21E83C /* RunningMissionsBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunningMissionsBar.swift; sourceTree = "<group>"; };
|
||||
EB5A4720378F06807FDE73E1 /* GlassCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassCard.swift; sourceTree = "<group>"; };
|
||||
@@ -140,6 +138,7 @@
|
||||
5267DE67017A858357F68424 /* GlassButton.swift */,
|
||||
EB5A4720378F06807FDE73E1 /* GlassCard.swift */,
|
||||
2B9834D4EE32058824F9DF00 /* LoadingView.swift */,
|
||||
A1B2C3D4E5F6789012345678 /* MarkdownView.swift */,
|
||||
E7FC053808661C9A0E21E83C /* RunningMissionsBar.swift */,
|
||||
CD6FB2E54DC07BE7A1EB08F8 /* StatusBadge.swift */,
|
||||
D09E84E812213CF7E52E4FEF /* ToolUI */,
|
||||
@@ -167,7 +166,6 @@
|
||||
73D80C56FA670F92E007E712 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F7F882A9E5E42DA9F9776160 /* Agents */,
|
||||
2EF415E84544334B25BD8E26 /* Components */,
|
||||
DABAA3652C0B0A54CFC3221B /* Control */,
|
||||
A688A831235D3E218A0A6783 /* Desktop */,
|
||||
@@ -199,7 +197,6 @@
|
||||
C786EDDB39D9D19A1A112CE9 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E606D3ED9560CFEC056ADFD1 /* AgentConfig.swift */,
|
||||
3CB591B632D3EF26AB217976 /* ChatMessage.swift */,
|
||||
BA70A2A73D3A386EAFD69FC4 /* FileEntry.swift */,
|
||||
D4AB47CF121ABA1946A4D879 /* Mission.swift */,
|
||||
@@ -256,14 +253,6 @@
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F7F882A9E5E42DA9F9776160 /* Agents */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0FA1A2E7115E4E4B93D41A66 /* AgentsView.swift */,
|
||||
);
|
||||
path = Agents;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -367,8 +356,6 @@
|
||||
files = (
|
||||
652A0AE498D69C9DB728B2DF /* ANSIParser.swift in Sources */,
|
||||
D64972881E36894950658708 /* APIService.swift in Sources */,
|
||||
487AF98018941AF388DB5A80 /* AgentConfig.swift in Sources */,
|
||||
0672F3AC46E02746D9857565 /* AgentsView.swift in Sources */,
|
||||
CA70EC5A864C3D007D42E781 /* ChatMessage.swift in Sources */,
|
||||
AA02567226057045DDD61CB1 /* ContentView.swift in Sources */,
|
||||
02DB7F25245D03FF72DD8E2E /* ControlView.swift in Sources */,
|
||||
@@ -380,6 +367,7 @@
|
||||
999ACAA94B0BD81A05288092 /* GlassCard.swift in Sources */,
|
||||
29372E691F6A5C5D2CCD9331 /* HistoryView.swift in Sources */,
|
||||
6865FE997D3E1D91D411F6BC /* LoadingView.swift in Sources */,
|
||||
E1A2B3C4D5E6F78901234567 /* MarkdownView.swift in Sources */,
|
||||
9BC40E40E1B5622B24328AEB /* Mission.swift in Sources */,
|
||||
83BB0F0AAFE4F2735FF76B87 /* NavigationState.swift in Sources */,
|
||||
EFABDC95B65F6ED3420186FC /* NewMissionSheet.swift in Sources */,
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// AgentConfig.swift
|
||||
// OpenAgentDashboard
|
||||
//
|
||||
// Agent configuration model
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An agent configuration defining model, tools, and capabilities.
|
||||
struct AgentConfig: Identifiable, Codable {
|
||||
let id: String
|
||||
var name: String
|
||||
var model_id: String
|
||||
var mcp_servers: [String]
|
||||
var skills: [String]
|
||||
var commands: [String]
|
||||
let created_at: String
|
||||
let updated_at: String
|
||||
}
|
||||
|
||||
// MARK: - Preview Data
|
||||
|
||||
extension AgentConfig {
|
||||
static let preview = AgentConfig(
|
||||
id: "00000000-0000-0000-0000-000000000001",
|
||||
name: "Default Agent",
|
||||
model_id: "claude-sonnet-4-20250514",
|
||||
mcp_servers: ["playwright", "supabase"],
|
||||
skills: ["coding", "research"],
|
||||
commands: ["review-pr"],
|
||||
created_at: ISO8601DateFormatter().string(from: Date()),
|
||||
updated_at: ISO8601DateFormatter().string(from: Date())
|
||||
)
|
||||
}
|
||||
@@ -57,15 +57,31 @@ struct Workspace: Codable, Identifiable {
|
||||
let status: WorkspaceStatus
|
||||
let errorMessage: String?
|
||||
let createdAt: String
|
||||
/// Skill names from library synced to this workspace
|
||||
let skills: [String]
|
||||
/// Tool names from library synced to this workspace
|
||||
let tools: [String]
|
||||
/// Plugin identifiers for hooks
|
||||
let plugins: [String]
|
||||
/// Workspace template name (if created from a template)
|
||||
let template: String?
|
||||
/// Preferred Linux distribution for container workspaces
|
||||
let distro: String?
|
||||
/// Environment variables always loaded in this workspace
|
||||
let envVars: [String: String]
|
||||
/// Init script to run when the workspace is built/rebuilt
|
||||
let initScript: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, path, status
|
||||
case id, name, path, status, skills, tools, plugins, template, distro
|
||||
case workspaceType = "workspace_type"
|
||||
case errorMessage = "error_message"
|
||||
case createdAt = "created_at"
|
||||
case envVars = "env_vars"
|
||||
case initScript = "init_script"
|
||||
}
|
||||
|
||||
init(id: String, name: String, workspaceType: WorkspaceType, path: String, status: WorkspaceStatus, errorMessage: String?, createdAt: String) {
|
||||
init(id: String, name: String, workspaceType: WorkspaceType, path: String, status: WorkspaceStatus, errorMessage: String?, createdAt: String, skills: [String] = [], tools: [String] = [], plugins: [String] = [], template: String? = nil, distro: String? = nil, envVars: [String: String] = [:], initScript: String? = nil) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.workspaceType = workspaceType
|
||||
@@ -73,6 +89,31 @@ struct Workspace: Codable, Identifiable {
|
||||
self.status = status
|
||||
self.errorMessage = errorMessage
|
||||
self.createdAt = createdAt
|
||||
self.skills = skills
|
||||
self.tools = tools
|
||||
self.plugins = plugins
|
||||
self.template = template
|
||||
self.distro = distro
|
||||
self.envVars = envVars
|
||||
self.initScript = initScript
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decode(String.self, forKey: .id)
|
||||
name = try container.decode(String.self, forKey: .name)
|
||||
workspaceType = try container.decode(WorkspaceType.self, forKey: .workspaceType)
|
||||
path = try container.decode(String.self, forKey: .path)
|
||||
status = try container.decode(WorkspaceStatus.self, forKey: .status)
|
||||
errorMessage = try container.decodeIfPresent(String.self, forKey: .errorMessage)
|
||||
createdAt = try container.decode(String.self, forKey: .createdAt)
|
||||
skills = try container.decodeIfPresent([String].self, forKey: .skills) ?? []
|
||||
tools = try container.decodeIfPresent([String].self, forKey: .tools) ?? []
|
||||
plugins = try container.decodeIfPresent([String].self, forKey: .plugins) ?? []
|
||||
template = try container.decodeIfPresent(String.self, forKey: .template)
|
||||
distro = try container.decodeIfPresent(String.self, forKey: .distro)
|
||||
envVars = try container.decodeIfPresent([String: String].self, forKey: .envVars) ?? [:]
|
||||
initScript = try container.decodeIfPresent(String.self, forKey: .initScript)
|
||||
}
|
||||
|
||||
/// Check if this is the default host workspace.
|
||||
@@ -105,7 +146,10 @@ extension Workspace {
|
||||
path: "/root",
|
||||
status: .ready,
|
||||
errorMessage: nil,
|
||||
createdAt: ISO8601DateFormatter().string(from: Date())
|
||||
createdAt: ISO8601DateFormatter().string(from: Date()),
|
||||
skills: [],
|
||||
tools: [],
|
||||
plugins: []
|
||||
)
|
||||
|
||||
static let previewChroot = Workspace(
|
||||
@@ -115,6 +159,9 @@ extension Workspace {
|
||||
path: "/var/lib/openagent/containers/project-sandbox",
|
||||
status: .ready,
|
||||
errorMessage: nil,
|
||||
createdAt: ISO8601DateFormatter().string(from: Date())
|
||||
createdAt: ISO8601DateFormatter().string(from: Date()),
|
||||
skills: ["code-review", "testing"],
|
||||
tools: ["pytest", "eslint"],
|
||||
plugins: []
|
||||
)
|
||||
}
|
||||
|
||||
@@ -284,36 +284,6 @@ final class APIService {
|
||||
|
||||
// MARK: - Workspaces
|
||||
|
||||
// MARK: - Agents
|
||||
|
||||
func listAgents(completion: @escaping (Result<[AgentConfig], Error>) -> Void) {
|
||||
Task {
|
||||
do {
|
||||
let agents: [AgentConfig] = try await get("/api/agents")
|
||||
completion(.success(agents))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createAgent(name: String, modelId: String, completion: @escaping (Result<AgentConfig, Error>) -> Void) {
|
||||
Task {
|
||||
do {
|
||||
struct CreateAgentRequest: Encodable {
|
||||
let name: String
|
||||
let model_id: String
|
||||
}
|
||||
let agent: AgentConfig = try await post("/api/agents", body: CreateAgentRequest(name: name, model_id: modelId))
|
||||
completion(.success(agent))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Workspaces
|
||||
|
||||
func listWorkspaces() async throws -> [Workspace] {
|
||||
try await get("/api/workspaces")
|
||||
}
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
//
|
||||
// AgentsView.swift
|
||||
// OpenAgentDashboard
|
||||
//
|
||||
// Agent configuration management view
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// AgentConfig model is now in Models/AgentConfig.swift
|
||||
|
||||
struct AgentsView: View {
|
||||
@State private var agents: [AgentConfig] = []
|
||||
@State private var isLoading = true
|
||||
@State private var errorMessage: String?
|
||||
@State private var selectedAgent: AgentConfig?
|
||||
@State private var showNewAgentSheet = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
} else if let error = errorMessage {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(.red.opacity(0.6))
|
||||
Text(error)
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
.multilineTextAlignment(.center)
|
||||
Button("Retry") {
|
||||
loadAgents()
|
||||
}
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
VStack(spacing: 0) {
|
||||
// Header
|
||||
HStack {
|
||||
Text("Agents")
|
||||
.font(.largeTitle.bold())
|
||||
.foregroundColor(.white)
|
||||
Spacer()
|
||||
Button(action: { showNewAgentSheet = true }) {
|
||||
Image(systemName: "plus")
|
||||
.font(.title3)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 40, height: 40)
|
||||
.background(Color.indigo.opacity(0.2))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
if agents.isEmpty {
|
||||
Spacer()
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "cpu")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(.white.opacity(0.2))
|
||||
Text("No agents yet")
|
||||
.foregroundColor(.white.opacity(0.4))
|
||||
Text("Create an agent to get started")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.3))
|
||||
}
|
||||
Spacer()
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 12) {
|
||||
ForEach(agents) { agent in
|
||||
AgentCard(agent: agent, onTap: {
|
||||
selectedAgent = agent
|
||||
})
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(item: $selectedAgent) { agent in
|
||||
AgentDetailView(agent: agent, onDismiss: {
|
||||
selectedAgent = nil
|
||||
loadAgents()
|
||||
})
|
||||
}
|
||||
.sheet(isPresented: $showNewAgentSheet) {
|
||||
NewAgentSheet(onDismiss: {
|
||||
showNewAgentSheet = false
|
||||
loadAgents()
|
||||
})
|
||||
}
|
||||
.onAppear {
|
||||
loadAgents()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAgents() {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
APIService.shared.listAgents { result in
|
||||
DispatchQueue.main.async {
|
||||
isLoading = false
|
||||
switch result {
|
||||
case .success(let agentList):
|
||||
agents = agentList
|
||||
case .failure(let error):
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AgentCard: View {
|
||||
let agent: AgentConfig
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Image(systemName: "cpu")
|
||||
.foregroundColor(.indigo)
|
||||
Text(agent.name)
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.4))
|
||||
}
|
||||
|
||||
Text(agent.model_id)
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
|
||||
if !agent.mcp_servers.isEmpty || !agent.skills.isEmpty || !agent.commands.isEmpty {
|
||||
HStack(spacing: 12) {
|
||||
if !agent.mcp_servers.isEmpty {
|
||||
Label("\(agent.mcp_servers.count)", systemImage: "server.rack")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
}
|
||||
if !agent.skills.isEmpty {
|
||||
Label("\(agent.skills.count)", systemImage: "doc.text")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
}
|
||||
if !agent.commands.isEmpty {
|
||||
Label("\(agent.commands.count)", systemImage: "terminal")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.white.opacity(0.02))
|
||||
.cornerRadius(12)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(Color.white.opacity(0.06), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AgentDetailView: View {
|
||||
let agent: AgentConfig
|
||||
let onDismiss: () -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Model")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.4))
|
||||
Text(agent.model_id)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
if !agent.mcp_servers.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("MCP Servers")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.4))
|
||||
ForEach(agent.mcp_servers, id: \.self) { server in
|
||||
HStack {
|
||||
Image(systemName: "server.rack")
|
||||
.font(.caption)
|
||||
.foregroundColor(.indigo.opacity(0.6))
|
||||
Text(server)
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !agent.skills.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Skills")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.4))
|
||||
ForEach(agent.skills, id: \.self) { skill in
|
||||
HStack {
|
||||
Image(systemName: "doc.text")
|
||||
.font(.caption)
|
||||
.foregroundColor(.indigo.opacity(0.6))
|
||||
Text(skill)
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !agent.commands.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Commands")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.4))
|
||||
ForEach(agent.commands, id: \.self) { command in
|
||||
HStack {
|
||||
Image(systemName: "terminal")
|
||||
.font(.caption)
|
||||
.foregroundColor(.indigo.opacity(0.6))
|
||||
Text("/\(command)")
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.navigationTitle(agent.name)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Done") {
|
||||
onDismiss()
|
||||
}
|
||||
.foregroundColor(.indigo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NewAgentSheet: View {
|
||||
let onDismiss: () -> Void
|
||||
@State private var name = ""
|
||||
@State private var isCreating = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 20) {
|
||||
TextField("Agent Name", text: $name)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
|
||||
Text("Note: Create basic agent here, configure details in web dashboard")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("New Agent")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: createAgent) {
|
||||
if isCreating {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
} else {
|
||||
Text("Create")
|
||||
}
|
||||
}
|
||||
.disabled(name.isEmpty || isCreating)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createAgent() {
|
||||
isCreating = true
|
||||
// Create with default model
|
||||
APIService.shared.createAgent(name: name, modelId: "claude-sonnet-4-20250514") { result in
|
||||
DispatchQueue.main.async {
|
||||
isCreating = false
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,54 +10,6 @@ import XCTest
|
||||
|
||||
final class ModelTests: XCTestCase {
|
||||
|
||||
// MARK: - AgentConfig Tests
|
||||
|
||||
func testAgentConfigDecoding() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": "test-id",
|
||||
"name": "Test Agent",
|
||||
"model_id": "claude-sonnet-4",
|
||||
"mcp_servers": ["playwright"],
|
||||
"skills": ["coding"],
|
||||
"commands": ["review"],
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
let config = try decoder.decode(AgentConfig.self, from: json)
|
||||
|
||||
XCTAssertEqual(config.id, "test-id")
|
||||
XCTAssertEqual(config.name, "Test Agent")
|
||||
XCTAssertEqual(config.model_id, "claude-sonnet-4")
|
||||
XCTAssertEqual(config.mcp_servers, ["playwright"])
|
||||
XCTAssertEqual(config.skills, ["coding"])
|
||||
XCTAssertEqual(config.commands, ["review"])
|
||||
}
|
||||
|
||||
func testAgentConfigEncoding() throws {
|
||||
let config = AgentConfig(
|
||||
id: "encode-test",
|
||||
name: "Encoded Agent",
|
||||
model_id: "claude-opus-4",
|
||||
mcp_servers: ["supabase", "playwright"],
|
||||
skills: ["research", "coding"],
|
||||
commands: ["commit"],
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z"
|
||||
)
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
let data = try encoder.encode(config)
|
||||
let decoded = try JSONDecoder().decode(AgentConfig.self, from: data)
|
||||
|
||||
XCTAssertEqual(decoded.id, config.id)
|
||||
XCTAssertEqual(decoded.name, config.name)
|
||||
XCTAssertEqual(decoded.mcp_servers.count, 2)
|
||||
}
|
||||
|
||||
// MARK: - Workspace Tests
|
||||
|
||||
func testWorkspaceDecoding() throws {
|
||||
|
||||
Reference in New Issue
Block a user