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:
Thomas Marchand
2026-01-15 08:21:35 +00:00
committed by GitHub
parent c32f98f57f
commit 7e74e77a66
6 changed files with 55 additions and 451 deletions

View File

@@ -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 */,

View File

@@ -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())
)
}

View File

@@ -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: []
)
}

View File

@@ -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")
}

View File

@@ -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()
}
}
}
}

View File

@@ -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 {