Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,21 @@ openclaw onboard

Arc has one runtime with two surfaces:

| Surface | Role |
| --- | --- |
| Surface | Role |
| ------------------- | ------------------------------------------------------ |
| **Swift macOS app** | Flagship review workstation — diffs, queues, decisions |
| **VPS TUI** | Fast remote operator console — queue, inspect, unblock |
| **VPS TUI** | Fast remote operator console — queue, inspect, unblock |

### The Layer Model

Arc only makes sense if the layers stay clean:

| Layer | Role |
| --- | --- |
| **Arc** | product, workflow, workstation, project cockpit |
| **OpenClaw** | runtime, gateway, worktrees, worker lifecycle, durable state |
| **Claude + Codex** | worker engines that do the coding work |
| **Obsidian** | planning, notes, specs, architecture, project memory |
| Layer | Role |
| ------------------ | ------------------------------------------------------------ |
| **Arc** | product, workflow, workstation, project cockpit |
| **OpenClaw** | runtime, gateway, worktrees, worker lifecycle, durable state |
| **Claude + Codex** | worker engines that do the coding work |
| **Obsidian** | planning, notes, specs, architecture, project memory |

Obsidian should hold thinking. Arc should hold execution.

Expand Down
43 changes: 43 additions & 0 deletions apps/macos/Sources/OpenClaw/CockpitData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ struct CockpitLaneSummary: Codable, Identifiable, Sendable {
}
}

struct CockpitReviewArtifacts: Codable, Sendable {
let workerId: String
let worktreePath: String
let baseBranch: String
let diff: String
let commitLog: String
let testOutput: String
let generatedAt: String
}

struct CockpitWorkerLogs: Codable, Sendable {
let workerId: String
let latestRun: CockpitRunSummary?
Expand Down Expand Up @@ -421,6 +431,39 @@ extension CockpitWorkspaceSummary {
])
}

extension CockpitReviewArtifacts {
static func preview(workerId: String) -> CockpitReviewArtifacts {
CockpitReviewArtifacts(
workerId: workerId,
worktreePath: "/Users/tessaro/openclaw/.worktrees/code/shell-lane",
baseBranch: "main",
diff: """
src/code-cockpit/runtime.ts | 42 ++++++++++++++++++++++++++++++++++++++++++
src/code-cockpit/store.ts | 9 +++++++++
2 files changed, 51 insertions(+)

diff --git a/src/code-cockpit/runtime.ts b/src/code-cockpit/runtime.ts
--- a/src/code-cockpit/runtime.ts
+++ b/src/code-cockpit/runtime.ts
@@ -1585,6 +1585,48 @@
+ async readWorkerReviewArtifacts(params: {
+ workerId: string;
+ }): Promise<CodeWorkerReviewArtifacts> {
+ // ... review artifacts collection
+ }
""",
commitLog: """
abc1234 feat: add review artifacts endpoint
def5678 feat: add diff/test/log review lane UI
""",
testOutput: """
Tests: 42 passed, 0 failed
Test Suites: 8 passed, 0 failed
""",
generatedAt: "2026-03-19T13:00:00.000Z")
}
}

extension CockpitWorkerLogs {
static func preview(workerId: String) -> CockpitWorkerLogs {
CockpitWorkerLogs(
Expand Down
51 changes: 51 additions & 0 deletions apps/macos/Sources/OpenClaw/CockpitStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,25 @@ typealias CockpitGatewayStatusLoader = @Sendable () async throws -> CockpitGatew
typealias CockpitWorkerLogsLoader = @Sendable (_ workerId: String) async throws -> CockpitWorkerLogs
typealias CockpitSupervisorTickPerformer = @Sendable (_ repoRoot: String?) async throws -> CockpitSupervisorTickResult
typealias CockpitWorkerActionPerformer = @Sendable (_ action: CockpitWorkerAction, _ workerId: String) async throws -> Void
typealias CockpitReviewArtifactsLoader = @Sendable (_ workerId: String) async throws -> CockpitReviewArtifacts
typealias CockpitRemoteReconnectAction = @Sendable () async throws -> Void

enum ReviewLaneTab: String, CaseIterable, Identifiable {
case diff = "Diff"
case tests = "Tests"
case logs = "Logs"

var id: String { self.rawValue }

var systemImage: String {
switch self {
case .diff: "doc.text.magnifyingglass"
case .tests: "checkmark.circle"
case .logs: "terminal"
}
}
}

enum CockpitLoadError: LocalizedError {
case gatewayUnavailable(String)

Expand All @@ -32,6 +49,7 @@ final class CockpitStore {
store.selectedWorkerId = CockpitWorkspaceSummary.preview.activeLanes.first?.workerId
if let workerId = store.selectedWorkerId {
store.selectedWorkerLogs = .preview(workerId: workerId)
store.selectedWorkerReviewArtifacts = .preview(workerId: workerId)
}
return store
}
Expand All @@ -43,6 +61,9 @@ final class CockpitStore {
var selectedWorkerId: String?
var selectedWorkerLogs: CockpitWorkerLogs?
var isLoadingWorkerLogs = false
var selectedWorkerReviewArtifacts: CockpitReviewArtifacts?
var isLoadingReviewArtifacts = false
var reviewLaneTab: ReviewLaneTab = .diff
var isStartingNextWorker = false
var isPerformingWorkerAction = false
var activeWorkerAction: CockpitWorkerAction?
Expand All @@ -53,6 +74,7 @@ final class CockpitStore {
private let loadGatewayStatus: CockpitGatewayStatusLoader
private let loadSummary: CockpitSummaryLoader
private let loadWorkerLogs: CockpitWorkerLogsLoader
private let loadReviewArtifacts: CockpitReviewArtifactsLoader
private let performSupervisorTickImpl: CockpitSupervisorTickPerformer
private let performWorkerActionImpl: CockpitWorkerActionPerformer
private let reconnectRemoteGatewayImpl: CockpitRemoteReconnectAction
Expand All @@ -78,6 +100,7 @@ final class CockpitStore {
loadGatewayStatus: CockpitGatewayStatusLoader? = nil,
loadSummary: CockpitSummaryLoader? = nil,
loadWorkerLogs: CockpitWorkerLogsLoader? = nil,
loadReviewArtifacts: CockpitReviewArtifactsLoader? = nil,
performSupervisorTick: CockpitSupervisorTickPerformer? = nil,
performWorkerAction: CockpitWorkerActionPerformer? = nil,
reconnectRemoteGateway: CockpitRemoteReconnectAction? = nil)
Expand All @@ -94,6 +117,9 @@ final class CockpitStore {
self.loadWorkerLogs = loadWorkerLogs ?? { workerId in
try await GatewayConnection.shared.codeWorkerLogs(workerId: workerId)
}
self.loadReviewArtifacts = loadReviewArtifacts ?? { workerId in
try await GatewayConnection.shared.codeWorkerReviewArtifacts(workerId: workerId)
}
self.performSupervisorTickImpl = performSupervisorTick ?? { repoRoot in
try await GatewayConnection.shared.codeSupervisorTick(repoRoot: repoRoot)
}
Expand Down Expand Up @@ -188,6 +214,30 @@ final class CockpitStore {
func selectWorker(_ workerId: String) async {
self.selectedWorkerId = workerId
await self.refreshSelectedWorkerLogs()
await self.refreshSelectedWorkerReviewArtifacts()
}

func refreshSelectedWorkerReviewArtifacts() async {
guard let workerId = self.selectedWorkerId else {
self.selectedWorkerReviewArtifacts = nil
return
}
if self.isPreview {
self.selectedWorkerReviewArtifacts = .preview(workerId: workerId)
return
}

self.isLoadingReviewArtifacts = true
defer { self.isLoadingReviewArtifacts = false }

do {
self.selectedWorkerReviewArtifacts = try await self.loadReviewArtifacts(workerId)
} catch {
let message = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription
self.logger.error("code cockpit review artifacts failed \(message, privacy: .public)")
// Don't overwrite lastError for review artifact failures; they're non-critical
self.selectedWorkerReviewArtifacts = nil
}
}

func performWorkerAction(_ action: CockpitWorkerAction, workerId: String) async {
Expand Down Expand Up @@ -251,6 +301,7 @@ final class CockpitStore {
guard let snapshot = self.snapshot else {
self.selectedWorkerId = nil
self.selectedWorkerLogs = nil
self.selectedWorkerReviewArtifacts = nil
return
}
if let selectedWorkerId = self.selectedWorkerId,
Expand Down
Loading
Loading