diff --git a/macos/ControlCenter/AetherflowControlCenter.xcodeproj/project.pbxproj b/macos/ControlCenter/AetherflowControlCenter.xcodeproj/project.pbxproj
index 30fa3ba..6d3e638 100644
--- a/macos/ControlCenter/AetherflowControlCenter.xcodeproj/project.pbxproj
+++ b/macos/ControlCenter/AetherflowControlCenter.xcodeproj/project.pbxproj
@@ -52,6 +52,13 @@
fileRef
B00000000000000000000007
+ A00000000000000000000007
+
+ isa
+ PBXBuildFile
+ fileRef
+ B00000000000000000000008
+
B00000000000000000000001
isa
@@ -118,6 +125,17 @@
sourceTree
<group>
+ B00000000000000000000008
+
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ MonitoringStore.swift
+ sourceTree
+ <group>
+
B00000000000000000000006
isa
@@ -156,6 +174,7 @@
A00000000000000000000004
A00000000000000000000005
A00000000000000000000006
+ A00000000000000000000007
runOnlyForDeploymentPostprocessing
0
@@ -221,6 +240,7 @@
B00000000000000000000004
B00000000000000000000005
B00000000000000000000007
+ B00000000000000000000008
path
AetherflowControlCenter
diff --git a/macos/ControlCenter/Sources/AetherflowControlCenter/ControlCenterRootView.swift b/macos/ControlCenter/Sources/AetherflowControlCenter/ControlCenterRootView.swift
index 326515e..4a54a02 100644
--- a/macos/ControlCenter/Sources/AetherflowControlCenter/ControlCenterRootView.swift
+++ b/macos/ControlCenter/Sources/AetherflowControlCenter/ControlCenterRootView.swift
@@ -453,7 +453,8 @@ private struct DetailColumn: View {
private var headerTitle: String {
switch selectedSection {
case .sessions:
- return monitoring.selectedDetail?.session.workRef.nonEmptyValue
+ return monitoring.selectedDetail?.agent.taskTitle.nonEmptyValue
+ ?? monitoring.selectedDetail?.session.workRef.nonEmptyValue
?? monitoring.selectedDetail?.workloadID
?? "Live session monitor"
case .queue:
@@ -466,9 +467,12 @@ private struct DetailColumn: View {
private var headerSummary: String {
switch selectedSection {
case .sessions:
- return monitoring.selectedDetail?.session.sessionID.nonEmptyValue.map {
- "Showing live status, tool activity, and recent events for \($0)."
- } ?? monitoring.note
+ if let detail = monitoring.selectedDetail {
+ let sessionRoute = detail.session.sessionID.nonEmptyValue ?? "pending session"
+ let lifecycle = detail.agent.lifecycleState.nonEmptyValue ?? detail.session.status.nonEmptyValue ?? "unknown"
+ return "Showing live status, tool activity, and recent events for \(sessionRoute) in \(lifecycle) state."
+ }
+ return monitoring.note
case .queue:
return monitoring.queue.isEmpty ? monitoring.note : "The queue lane mirrors the daemon's waiting work without leaving the monitoring shell."
case .overview, .diagnostics:
@@ -519,9 +523,11 @@ private struct DetailHighlightsPanel: View {
switch section {
case .sessions:
if let detail = monitoring.selectedDetail {
+ let pidLabel = detail.agent.pid > 0 ? String(detail.agent.pid) : "pending"
return [
- "Selected workload: \(detail.workloadID).",
+ "Selected workload: \(detail.workloadID) on pid \(pidLabel).",
"Session route: \(detail.session.serverRef.nonEmptyValue ?? "pending server") / \(detail.session.sessionID.nonEmptyValue ?? "pending session").",
+ "Task lane: \(detail.agent.taskID.nonEmptyValue ?? detail.session.workRef.nonEmptyValue ?? "manual spawn"). Lifecycle: \(detail.agent.lifecycleState.nonEmptyValue ?? detail.agent.state.nonEmptyValue ?? "unknown").",
"Recent tool calls: \(detail.toolCalls.count). Event lines cached: \(detail.eventLines.count).",
detail.errors.isEmpty ? "No daemon-reported errors on the selected workload." : "Daemon reported \(detail.errors.count) error\(detail.errors.count == 1 ? "" : "s") for this workload."
]
@@ -529,7 +535,7 @@ private struct DetailHighlightsPanel: View {
return [
"Monitoring phase: \(monitoring.phase.rawValue).",
"Visible workloads: \(monitoring.workloads.count).",
- "Selection will load session metadata, tool calls, and recent daemon events in place."
+ "Selection follows the same session when the daemon rekeys a workload after reconnect."
]
case .queue:
if monitoring.queue.isEmpty {
@@ -926,6 +932,12 @@ private struct MonitoringSummaryRow: View {
var body: some View {
HStack(spacing: 12) {
MiniChip(text: snapshot.phase.rawValue.capitalized, tone: phaseTone)
+ if let poolMode = snapshot.poolMode.nonEmptyValue {
+ MiniChip(text: "Pool \(poolMode)", tone: ShellPalette.brass)
+ }
+ if let spawnPolicy = snapshot.spawnPolicy.nonEmptyValue {
+ MiniChip(text: "Policy \(spawnPolicy)", tone: ShellPalette.moss)
+ }
MiniChip(text: "Workloads \(snapshot.workloads.count)", tone: ShellPalette.ember)
MiniChip(text: "Queue \(snapshot.queue.count)", tone: ShellPalette.moss)
Spacer(minLength: 0)
@@ -967,6 +979,9 @@ private struct MonitoringWorkloadRow: View {
Text(workload.kind == .poolAgent ? "POOL" : "SPAWN")
.font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundStyle(workload.kind == .poolAgent ? ShellPalette.ember : ShellPalette.moss)
+ Text(workload.workRef)
+ .font(.system(size: 10, weight: .bold, design: .monospaced))
+ .foregroundStyle(ShellPalette.mutedInk)
if workload.attentionNeeded {
Text("ATTN")
.font(.system(size: 10, weight: .bold, design: .monospaced))
@@ -990,7 +1005,8 @@ private struct MonitoringWorkloadRow: View {
HStack(spacing: 10) {
DetailPill(label: "role", value: workload.role)
- DetailPill(label: "session", value: workload.sessionID.nonEmptyValue ?? "pending")
+ DetailPill(label: "session", value: workload.sessionID.nonEmptyValue ?? "claiming")
+ DetailPill(label: "pid", value: workload.pid > 0 ? String(workload.pid) : "pending")
DetailPill(label: "activity", value: workload.lastActivityAt.map(Self.relativeTimestamp) ?? "waiting")
}
}
@@ -1077,10 +1093,26 @@ private struct SessionsDetailPanel: View {
if let detail = snapshot.selectedDetail {
VStack(alignment: .leading, spacing: 14) {
HStack(spacing: 12) {
- DetailPill(label: "work", value: detail.session.workRef.nonEmptyValue ?? detail.workloadID)
+ DetailPill(label: "work", value: detail.agent.taskID.nonEmptyValue ?? detail.session.workRef.nonEmptyValue ?? detail.workloadID)
DetailPill(label: "session", value: detail.session.sessionID.nonEmptyValue ?? "pending")
DetailPill(label: "origin", value: detail.session.originType.nonEmptyValue ?? "unknown")
- DetailPill(label: "status", value: detail.session.status.nonEmptyValue ?? "unknown")
+ DetailPill(label: "status", value: detail.agent.lifecycleState.nonEmptyValue ?? detail.session.status.nonEmptyValue ?? "unknown")
+ }
+
+ HStack(spacing: 12) {
+ DetailPill(label: "agent", value: detail.workloadID)
+ DetailPill(label: "pid", value: detail.agent.pid > 0 ? String(detail.agent.pid) : "pending")
+ DetailPill(label: "server", value: detail.session.serverRef.nonEmptyValue ?? "pending")
+ DetailPill(label: "attach", value: detail.session.attachable ? "ready" : "pending")
+ }
+
+ if let lastLog = detail.agent.lastLog.nonEmptyValue {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Latest daemon note")
+ .font(.system(size: 12, weight: .bold, design: .monospaced))
+ .foregroundStyle(ShellPalette.mutedInk)
+ EventLineRow(line: lastLog, tone: ShellPalette.brass)
+ }
}
if !detail.toolCalls.isEmpty {
@@ -1122,8 +1154,10 @@ private struct SessionsDetailPanel: View {
}
} else {
MonitoringEmptyRow(
- title: snapshot.workloads.isEmpty ? "No session selected" : "Select a workload",
- detail: snapshot.note
+ title: snapshot.workloads.isEmpty ? "No session selected" : "Select a session lane",
+ detail: snapshot.workloads.isEmpty
+ ? snapshot.note
+ : "The detail pane will hold session route, tool activity, and recent daemon events without changing selection."
)
}
}
diff --git a/macos/ControlCenter/Sources/AetherflowControlCenter/MonitoringStore.swift b/macos/ControlCenter/Sources/AetherflowControlCenter/MonitoringStore.swift
index cd3e9ff..835f211 100644
--- a/macos/ControlCenter/Sources/AetherflowControlCenter/MonitoringStore.swift
+++ b/macos/ControlCenter/Sources/AetherflowControlCenter/MonitoringStore.swift
@@ -17,11 +17,14 @@ struct MonitoringWorkloadSummary: Identifiable, Equatable, Sendable {
let id: String
let kind: MonitoringWorkloadKind
let role: String
+ let workRef: String
let title: String
let subtitle: String
let sessionID: String
let lifecycleState: String
+ let pid: Int
let attentionNeeded: Bool
+ let spawnedAt: Date
let lastActivityAt: Date?
}
@@ -34,6 +37,7 @@ struct MonitoringQueueItem: Identifiable, Equatable, Sendable {
struct MonitoringSelectionDetail: Equatable, Sendable {
let workloadID: String
let session: DaemonSessionMetadataPayload
+ let agent: DaemonAgentStatusPayload
let toolCalls: [DaemonToolCallPayload]
let eventLines: [String]
let lastEventTimestamp: Int64
@@ -74,6 +78,11 @@ struct MonitoringSnapshot: Equatable, Sendable {
final class MonitoringStore: ObservableObject {
@Published private(set) var snapshot: MonitoringSnapshot
+ private struct SelectionAnchor: Equatable {
+ let workloadID: String
+ let sessionID: String?
+ }
+
private let context: ShellBootstrapContext
private let controller: DaemonControlling
private let isDaemonAbsent: (Error) -> Bool
@@ -82,6 +91,7 @@ final class MonitoringStore: ObservableObject {
private var monitorTask: Task?
private var hasConnected = false
private var needsAuthoritativeReload = true
+ private var selectionAnchor: SelectionAnchor?
init(
context: ShellBootstrapContext,
@@ -132,6 +142,12 @@ final class MonitoringStore: ObservableObject {
return
}
needsAuthoritativeReload = true
+ selectionAnchor = id.flatMap { workloadID in
+ guard let workload = snapshot.workloads.first(where: { $0.id == workloadID }) else {
+ return SelectionAnchor(workloadID: workloadID, sessionID: nil)
+ }
+ return SelectionAnchor(workloadID: workloadID, sessionID: workload.sessionID.nonEmptyValue)
+ }
snapshot = MonitoringSnapshot(
phase: snapshot.phase,
project: snapshot.project,
@@ -150,8 +166,8 @@ final class MonitoringStore: ObservableObject {
func refresh() async {
do {
let status = try await controller.fetchStatus(daemonURL: context.daemonURL)
- let workloads = buildWorkloads(status: status)
- let selectedWorkloadID = resolvedSelectionID(workloads: workloads, preferredID: snapshot.selectedWorkloadID)
+ let workloads = orderedWorkloads(incoming: buildWorkloads(status: status), previous: snapshot.workloads)
+ let selectedWorkloadID = resolvedSelectionID(workloads: workloads)
var selectedDetail = snapshot.selectedDetail
let reconnected = needsAuthoritativeReload || snapshot.phase != .connected
@@ -177,6 +193,12 @@ final class MonitoringStore: ObservableObject {
lastError: status.errors.last?.nonEmptyValue,
updatedAt: .now
)
+ selectionAnchor = selectedWorkloadID.flatMap { workloadID in
+ guard let workload = workloads.first(where: { $0.id == workloadID }) else {
+ return SelectionAnchor(workloadID: workloadID, sessionID: selectedDetail?.session.sessionID.nonEmptyValue)
+ }
+ return SelectionAnchor(workloadID: workloadID, sessionID: workload.sessionID.nonEmptyValue)
+ }
hasConnected = true
needsAuthoritativeReload = false
} catch {
@@ -201,19 +223,13 @@ final class MonitoringStore: ObservableObject {
afterTimestamp: afterTimestamp
)
- let lines: [String]
- let lastEventTimestamp: Int64
- if previous == nil {
- lines = events.lines
- lastEventTimestamp = events.lastTS
- } else {
- lines = previous!.eventLines + events.lines
- lastEventTimestamp = max(previous!.lastEventTimestamp, events.lastTS)
- }
+ let lines = (previous?.eventLines ?? []) + events.lines
+ let lastEventTimestamp = max(previous?.lastEventTimestamp ?? 0, events.lastTS)
return MonitoringSelectionDetail(
workloadID: workloadID,
session: events.session.sessionID.nonEmptyValue == nil ? detail.session : events.session,
+ agent: detail.agent,
toolCalls: detail.toolCalls,
eventLines: lines,
lastEventTimestamp: lastEventTimestamp,
@@ -247,11 +263,14 @@ final class MonitoringStore: ObservableObject {
id: $0.id,
kind: .poolAgent,
role: $0.role,
+ workRef: $0.taskID.nonEmptyValue ?? $0.id,
title: $0.taskTitle.nonEmptyValue ?? $0.id,
subtitle: $0.lastLog.nonEmptyValue ?? $0.taskID.nonEmptyValue ?? "Pool agent",
sessionID: $0.sessionID,
lifecycleState: $0.lifecycleState.nonEmptyValue ?? $0.state,
+ pid: $0.pid,
attentionNeeded: $0.attentionNeeded,
+ spawnedAt: $0.spawnTime,
lastActivityAt: $0.lastActivityAt
)
}
@@ -260,25 +279,62 @@ final class MonitoringStore: ObservableObject {
id: $0.spawnID,
kind: .spawn,
role: "spawn",
+ workRef: $0.spawnID,
title: $0.prompt.nonEmptyValue ?? $0.spawnID,
- subtitle: $0.state.nonEmptyValue ?? "Spawned session",
+ subtitle: $0.sessionID.nonEmptyValue ?? ($0.state.nonEmptyValue ?? "Spawned session"),
sessionID: $0.sessionID,
lifecycleState: $0.lifecycleState.nonEmptyValue ?? $0.state,
+ pid: $0.pid,
attentionNeeded: $0.attentionNeeded,
+ spawnedAt: $0.spawnTime,
lastActivityAt: $0.lastActivityAt ?? $0.exitedAt
)
}
return agents + spawns
}
- private func resolvedSelectionID(
- workloads: [MonitoringWorkloadSummary],
- preferredID: String?
- ) -> String? {
- if let preferredID, workloads.contains(where: { $0.id == preferredID }) {
- return preferredID
+ private func orderedWorkloads(
+ incoming: [MonitoringWorkloadSummary],
+ previous: [MonitoringWorkloadSummary]
+ ) -> [MonitoringWorkloadSummary] {
+ let incomingByID = Dictionary(uniqueKeysWithValues: incoming.map { ($0.id, $0) })
+ let retained = previous.compactMap { incomingByID[$0.id] }
+ let retainedIDs = Set(retained.map(\.id))
+ let appended = incoming
+ .filter { !retainedIDs.contains($0.id) }
+ .sorted(by: compareNewWorkloads)
+ return retained + appended
+ }
+
+ private func compareNewWorkloads(
+ _ lhs: MonitoringWorkloadSummary,
+ _ rhs: MonitoringWorkloadSummary
+ ) -> Bool {
+ if lhs.attentionNeeded != rhs.attentionNeeded {
+ return lhs.attentionNeeded && !rhs.attentionNeeded
+ }
+ if lhs.kind != rhs.kind {
+ return lhs.kind == .poolAgent
+ }
+ if lhs.spawnedAt != rhs.spawnedAt {
+ return lhs.spawnedAt > rhs.spawnedAt
+ }
+ return lhs.id.localizedStandardCompare(rhs.id) == .orderedAscending
+ }
+
+ private func resolvedSelectionID(workloads: [MonitoringWorkloadSummary]) -> String? {
+ if let sessionID = selectionAnchor?.sessionID,
+ let match = workloads.first(where: { $0.sessionID == sessionID }) {
+ return match.id
+ }
+ if let workloadID = selectionAnchor?.workloadID,
+ workloads.contains(where: { $0.id == workloadID }) {
+ return workloadID
+ }
+ guard let preferred = workloads.first else {
+ return nil
}
- return workloads.first?.id
+ return preferred.id
}
private func monitoringNote(for status: DaemonStatusPayload, workloadCount: Int) -> String {
diff --git a/macos/ControlCenter/Tests/AetherflowControlCenterTests/MonitoringStoreTests.swift b/macos/ControlCenter/Tests/AetherflowControlCenterTests/MonitoringStoreTests.swift
index 3c28759..c43e6c4 100644
--- a/macos/ControlCenter/Tests/AetherflowControlCenterTests/MonitoringStoreTests.swift
+++ b/macos/ControlCenter/Tests/AetherflowControlCenterTests/MonitoringStoreTests.swift
@@ -171,6 +171,97 @@ final class MonitoringStoreTests: XCTestCase {
XCTAssertEqual(eventAgentNames, ["agent-1", "spawn-1"])
}
+ func testSelectionTracksSameSessionWhenWorkloadIDChanges() async throws {
+ let bootstrap = Self.bootstrap
+ let controller = RecordingDaemonController(
+ statusResults: [
+ .success(Self.status(taskTitle: "Initial task")),
+ .success(Self.status(agentID: "agent-2", taskTitle: "Recovered task", sessionID: "ses-1")),
+ ],
+ detailResults: [
+ .success(Self.detail(agentID: "agent-1", workRef: "ts-c9cdd2", sessionID: "ses-1")),
+ .success(Self.detail(agentID: "agent-2", workRef: "ts-c9cdd2", sessionID: "ses-1")),
+ ],
+ eventResults: [
+ .success(Self.events(lines: ["session.created"], sessionID: "ses-1", workRef: "ts-c9cdd2", agentID: "agent-1", lastTS: 101)),
+ .success(Self.events(lines: ["session.reclaimed"], sessionID: "ses-1", workRef: "ts-c9cdd2", agentID: "agent-2", lastTS: 202)),
+ ]
+ )
+ let store = MonitoringStore(
+ context: bootstrap,
+ controller: controller,
+ isDaemonAbsent: { _ in false },
+ autoStartMonitoring: false
+ )
+
+ await store.refresh()
+ await store.refresh()
+
+ XCTAssertEqual(store.snapshot.selectedWorkloadID, "agent-2")
+ XCTAssertEqual(store.snapshot.selectedDetail?.workloadID, "agent-2")
+ XCTAssertEqual(store.snapshot.selectedDetail?.session.sessionID, "ses-1")
+
+ let detailAgentNames = await controller.recordedDetailAgentNames()
+ XCTAssertEqual(detailAgentNames, ["agent-1", "agent-2"])
+ }
+
+ func testRefreshPreservesExistingOrderAndAppendsNewWorkloads() async throws {
+ let bootstrap = Self.bootstrap
+ let controller = RecordingDaemonController(
+ statusResults: [
+ .success(Self.statusWithAgentAndSpawn()),
+ .success(Self.statusWithAdditionalAgent()),
+ ],
+ detailResults: [
+ .success(Self.detail(agentID: "agent-1", workRef: "ts-c9cdd2", sessionID: "ses-1")),
+ .success(Self.detail(agentID: "agent-1", workRef: "ts-c9cdd2", sessionID: "ses-1")),
+ ],
+ eventResults: [
+ .success(Self.events(lines: ["session.created"], sessionID: "ses-1", workRef: "ts-c9cdd2", agentID: "agent-1", lastTS: 101)),
+ .success(Self.events(lines: ["task.updated"], sessionID: "ses-1", workRef: "ts-c9cdd2", agentID: "agent-1", lastTS: 120)),
+ ]
+ )
+ let store = MonitoringStore(
+ context: bootstrap,
+ controller: controller,
+ isDaemonAbsent: { _ in false },
+ autoStartMonitoring: false
+ )
+
+ await store.refresh()
+ await store.refresh()
+
+ XCTAssertEqual(store.snapshot.workloads.map(\.id), ["agent-1", "spawn-1", "agent-2"])
+ }
+
+ func testRefreshOrdersNewWorkloadsByAttentionKindSpawnTimeAndID() async throws {
+ let bootstrap = Self.bootstrap
+ let controller = RecordingDaemonController(
+ statusResults: [
+ .success(Self.statusForNewWorkloadOrdering()),
+ ],
+ detailResults: [
+ .success(Self.detail(agentID: "agent-attn", workRef: "ts-attn", sessionID: "ses-attn")),
+ ],
+ eventResults: [
+ .success(Self.events(lines: ["session.created"], sessionID: "ses-attn", workRef: "ts-attn", agentID: "agent-attn", lastTS: 101)),
+ ]
+ )
+ let store = MonitoringStore(
+ context: bootstrap,
+ controller: controller,
+ isDaemonAbsent: { _ in false },
+ autoStartMonitoring: false
+ )
+
+ await store.refresh()
+
+ XCTAssertEqual(
+ store.snapshot.workloads.map(\.id),
+ ["agent-attn", "agent-newer", "agent-older", "spawn-a", "spawn-b"]
+ )
+ }
+
private static var bootstrap: ShellBootstrapContext {
ShellBootstrapContext(
projectName: "aetherflow",
@@ -180,7 +271,11 @@ final class MonitoringStoreTests: XCTestCase {
)
}
- private static func status(taskTitle: String) -> DaemonStatusPayload {
+ private static func status(
+ agentID: String = "agent-1",
+ taskTitle: String,
+ sessionID: String = "ses-1"
+ ) -> DaemonStatusPayload {
DaemonStatusPayload(
poolSize: 1,
poolMode: "active",
@@ -188,14 +283,14 @@ final class MonitoringStoreTests: XCTestCase {
spawnPolicy: "manual",
agents: [
DaemonAgentStatusPayload(
- id: "agent-1",
+ id: agentID,
taskID: "ts-c9cdd2",
role: "worker",
pid: 42,
spawnTime: .now,
taskTitle: taskTitle,
lastLog: "working",
- sessionID: "ses-1",
+ sessionID: sessionID,
state: "running",
lifecycleState: "running",
lastActivityAt: .now,
@@ -210,6 +305,61 @@ final class MonitoringStoreTests: XCTestCase {
)
}
+ private static func statusWithAdditionalAgent() -> DaemonStatusPayload {
+ DaemonStatusPayload(
+ poolSize: 2,
+ poolMode: "active",
+ project: "aetherflow",
+ spawnPolicy: "manual",
+ agents: [
+ DaemonAgentStatusPayload(
+ id: "agent-1",
+ taskID: "ts-c9cdd2",
+ role: "worker",
+ pid: 42,
+ spawnTime: .now.addingTimeInterval(-120),
+ taskTitle: "Implement HTTP transport",
+ lastLog: "working",
+ sessionID: "ses-1",
+ state: "running",
+ lifecycleState: "running",
+ lastActivityAt: .now,
+ attentionNeeded: false
+ ),
+ DaemonAgentStatusPayload(
+ id: "agent-2",
+ taskID: "ts-next",
+ role: "worker",
+ pid: 43,
+ spawnTime: .now,
+ taskTitle: "Follow-up task",
+ lastLog: "queued for handoff",
+ sessionID: "ses-3",
+ state: "running",
+ lifecycleState: "running",
+ lastActivityAt: .now,
+ attentionNeeded: true
+ ),
+ ],
+ spawns: [
+ DaemonSpawnStatusPayload(
+ spawnID: "spawn-1",
+ pid: 99,
+ sessionID: "ses-2",
+ state: "running",
+ lifecycleState: "running",
+ lastActivityAt: .now,
+ attentionNeeded: false,
+ prompt: "Manual validation run",
+ spawnTime: .now.addingTimeInterval(-60),
+ exitedAt: nil
+ )
+ ],
+ queue: [],
+ errors: []
+ )
+ }
+
private static func statusWithAgentAndSpawn() -> DaemonStatusPayload {
DaemonStatusPayload(
poolSize: 1,
@@ -251,6 +401,89 @@ final class MonitoringStoreTests: XCTestCase {
)
}
+ private static func statusForNewWorkloadOrdering() -> DaemonStatusPayload {
+ let baseTime = Date(timeIntervalSince1970: 1_700_000_000)
+
+ return DaemonStatusPayload(
+ poolSize: 3,
+ poolMode: "active",
+ project: "aetherflow",
+ spawnPolicy: "manual",
+ agents: [
+ DaemonAgentStatusPayload(
+ id: "agent-attn",
+ taskID: "ts-attn",
+ role: "worker",
+ pid: 0,
+ spawnTime: baseTime.addingTimeInterval(-300),
+ taskTitle: "Needs attention",
+ lastLog: "waiting for reconnect",
+ sessionID: "ses-attn",
+ state: "pending",
+ lifecycleState: "starting",
+ lastActivityAt: nil,
+ attentionNeeded: true
+ ),
+ DaemonAgentStatusPayload(
+ id: "agent-newer",
+ taskID: "ts-newer",
+ role: "worker",
+ pid: 41,
+ spawnTime: baseTime.addingTimeInterval(-60),
+ taskTitle: "Newest agent",
+ lastLog: "running",
+ sessionID: "ses-newer",
+ state: "running",
+ lifecycleState: "running",
+ lastActivityAt: baseTime,
+ attentionNeeded: false
+ ),
+ DaemonAgentStatusPayload(
+ id: "agent-older",
+ taskID: "ts-older",
+ role: "worker",
+ pid: 40,
+ spawnTime: baseTime.addingTimeInterval(-120),
+ taskTitle: "Older agent",
+ lastLog: "running",
+ sessionID: "ses-older",
+ state: "running",
+ lifecycleState: "running",
+ lastActivityAt: baseTime.addingTimeInterval(-10),
+ attentionNeeded: false
+ ),
+ ],
+ spawns: [
+ DaemonSpawnStatusPayload(
+ spawnID: "spawn-b",
+ pid: 51,
+ sessionID: "ses-spawn-b",
+ state: "running",
+ lifecycleState: "running",
+ lastActivityAt: baseTime,
+ attentionNeeded: false,
+ prompt: "Spawn B",
+ spawnTime: baseTime.addingTimeInterval(-180),
+ exitedAt: nil
+ ),
+ DaemonSpawnStatusPayload(
+ spawnID: "spawn-a",
+ pid: 50,
+ sessionID: "ses-spawn-a",
+ state: "running",
+ lifecycleState: "running",
+ lastActivityAt: baseTime,
+ attentionNeeded: false,
+ prompt: "Spawn A",
+ spawnTime: baseTime.addingTimeInterval(-180),
+ exitedAt: nil
+ )
+ ],
+ queue: [],
+ errors: []
+ )
+ }
+
private static func detail(
agentID: String = "agent-1",
workRef: String = "ts-c9cdd2",