Files
hapi/ios/Packages/HapiKit/Sources/HapiProtocol/Models/SessionSummary.swift
T
weishu 5ef4304a4a fix: synchronize read state across web and native clients
Track readable content revisions and durable read cursors in the hub. Acknowledge only rendered foreground tails, persist offline read operations, and preserve newer manual unread choices across devices.

Migrate existing history to a read baseline and cover pagination receipts with shared native conformance fixtures.
2026-11-01 09:52:51 +08:00

179 lines
6.0 KiB
Swift

import Foundation
/// Classification of a pending request for list badges.
public enum PendingRequestKind: String, Codable, Sendable {
case permission
case input
}
/// A capped, list-friendly view of one pending tool request.
///
/// Mirrors `PendingRequest` (`shared/src/sessionSummary.ts`).
public struct PendingRequest: Codable, Equatable, Sendable {
public var id: String
public var kind: PendingRequestKind
public var tool: String
/// Epoch ms when the request was raised (falls back server-side to the
/// session's `updatedAt` for requests stored without `createdAt`).
public var since: Int
public init(id: String, kind: PendingRequestKind, tool: String, since: Int) {
self.id = id
self.kind = kind
self.tool = tool
self.since = since
}
}
/// Todo completion counters shown on the session row.
public struct TodoProgress: Codable, Equatable, Sendable {
public var completed: Int
public var total: Int
public init(completed: Int, total: Int) {
self.completed = completed
self.total = total
}
}
/// Metadata subset embedded in a session-list row.
///
/// Mirrors `SessionSummaryMetadata` (`shared/src/sessionSummary.ts`).
public struct SessionSummaryMetadata: Codable, Equatable, Sendable {
/// The `summary` field carries only the text in list rows.
public struct Summary: Codable, Equatable, Sendable {
public var text: String
public init(text: String) {
self.text = text
}
}
public var name: String?
public var path: String
public var machineId: String?
public var summary: Summary?
public var flavor: String?
public var worktree: WorktreeMetadata?
/// Native agent session id resolved per flavor by the hub.
public var agentSessionId: String?
public var lifecycleState: String?
public var hapiMcpUrl: String?
public init(
name: String? = nil,
path: String,
machineId: String? = nil,
summary: Summary? = nil,
flavor: String? = nil,
worktree: WorktreeMetadata? = nil,
agentSessionId: String? = nil,
lifecycleState: String? = nil,
hapiMcpUrl: String? = nil
) {
self.name = name
self.path = path
self.machineId = machineId
self.summary = summary
self.flavor = flavor
self.worktree = worktree
self.agentSessionId = agentSessionId
self.lifecycleState = lifecycleState
self.hapiMcpUrl = hapiMcpUrl
}
}
/// One row of `GET /api/sessions`.
///
/// Mirrors `SessionSummary` (`shared/src/sessionSummary.ts`). The summary is
/// hub-derived: badges must use `pendingRequestsCount` (authoritative total),
/// never `pendingRequests.count` (capped at 5, oldest first).
public struct SessionSummary: Codable, Equatable, Sendable {
public var contentRevision: Int?
public var readState: SessionReadState?
/// Server-side cap on `pendingRequests` entries
/// (`PENDING_REQUEST_SUMMARY_CAP`, `shared/src/sessionSummary.ts`).
public static let pendingRequestSummaryCap = 5
public var id: String
public var active: Bool
public var thinking: Bool
public var activeAt: Int
public var createdAt: Int?
public var lastUserMessageAt: Int?
public var updatedAt: Int
public var pinned: Bool?
public var globalPinned: Bool?
public var metadata: SessionSummaryMetadata?
/// Watermark mirrors of the detail record so structured SSE patches can
/// be gated without a detail fetch.
public var metadataVersion: Int
public var agentStateVersion: Int
public var todosUpdatedAt: Int
public var todoProgress: TodoProgress?
public var pendingRequestsCount: Int
public var pendingRequestKinds: [PendingRequestKind]
public var pendingRequests: [PendingRequest]
public var backgroundTaskCount: Int
public var futureScheduledMessageCount: Int
/// Epoch ms of the soonest uninvoked scheduled message, or `nil`.
public var nextScheduledAt: Int?
public var model: String?
public var modelReasoningEffort: String?
public var effort: String?
public init(
contentRevision: Int? = nil,
readState: SessionReadState? = nil,
id: String,
active: Bool,
thinking: Bool,
activeAt: Int,
updatedAt: Int,
createdAt: Int? = nil,
lastUserMessageAt: Int? = nil,
pinned: Bool? = nil,
globalPinned: Bool? = nil,
metadata: SessionSummaryMetadata? = nil,
metadataVersion: Int,
agentStateVersion: Int,
todosUpdatedAt: Int,
todoProgress: TodoProgress? = nil,
pendingRequestsCount: Int,
pendingRequestKinds: [PendingRequestKind] = [],
pendingRequests: [PendingRequest] = [],
backgroundTaskCount: Int = 0,
futureScheduledMessageCount: Int = 0,
nextScheduledAt: Int? = nil,
model: String? = nil,
modelReasoningEffort: String? = nil,
effort: String? = nil
) {
self.id = id
self.contentRevision = contentRevision
self.readState = readState
self.active = active
self.thinking = thinking
self.activeAt = activeAt
self.updatedAt = updatedAt
self.lastUserMessageAt = lastUserMessageAt
self.createdAt = createdAt
self.pinned = pinned
self.globalPinned = globalPinned
self.metadata = metadata
self.metadataVersion = metadataVersion
self.agentStateVersion = agentStateVersion
self.todosUpdatedAt = todosUpdatedAt
self.todoProgress = todoProgress
self.pendingRequestsCount = pendingRequestsCount
self.pendingRequestKinds = pendingRequestKinds
self.pendingRequests = pendingRequests
self.backgroundTaskCount = backgroundTaskCount
self.futureScheduledMessageCount = futureScheduledMessageCount
self.nextScheduledAt = nextScheduledAt
self.model = model
self.modelReasoningEffort = modelReasoningEffort
self.effort = effort
}
}