-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(swift-ios): make settle the primary thread swipe action #5974
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
90e9d25
62e9c96
5fe4945
9c7ea5e
2b197da
4e38726
460e40e
ca1feb9
1df2fbd
e2ea292
7388b78
3e25141
dc2494e
bb748e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,6 +217,7 @@ public struct FeatureThread: Identifiable, Sendable, Equatable, Hashable, Codabl | |
| public var supportsSnooze: Bool? | ||
| public var supportsPinning: Bool? | ||
| public var supportsTitleRegeneration: Bool? | ||
| public var settlementInput: FeatureSettlementProjectionInput? | ||
| public var attentionAt: Date? | ||
| public var workingStartedAt: Date? | ||
| public var latestTurnCompletedAt: Date? | ||
|
|
@@ -252,6 +253,7 @@ public struct FeatureThread: Identifiable, Sendable, Equatable, Hashable, Codabl | |
| supportsSnooze: Bool? = nil, | ||
| supportsPinning: Bool? = nil, | ||
| supportsTitleRegeneration: Bool? = nil, | ||
| settlementInput: FeatureSettlementProjectionInput? = nil, | ||
| attentionAt: Date? = nil, | ||
| workingStartedAt: Date? = nil, | ||
| latestTurnCompletedAt: Date? = nil, | ||
|
|
@@ -286,6 +288,7 @@ public struct FeatureThread: Identifiable, Sendable, Equatable, Hashable, Codabl | |
| self.supportsSnooze = supportsSnooze | ||
| self.supportsPinning = supportsPinning | ||
| self.supportsTitleRegeneration = supportsTitleRegeneration | ||
| self.settlementInput = settlementInput | ||
| self.attentionAt = attentionAt | ||
| self.workingStartedAt = workingStartedAt | ||
| self.latestTurnCompletedAt = latestTurnCompletedAt | ||
|
|
@@ -308,6 +311,78 @@ public struct FeatureThread: Identifiable, Sendable, Equatable, Hashable, Codabl | |
| } | ||
| } | ||
|
|
||
| public struct FeatureSettlementProjectionInput: Sendable, Equatable, Hashable, Codable { | ||
| public var hasPendingApprovals: Bool | ||
| public var hasPendingUserInput: Bool | ||
| public var sessionStatus: String? | ||
| public var latestUserMessageAt: String? | ||
| public var latestTurnRequestedAt: String? | ||
| public var latestTurnStartedAt: String? | ||
| public var latestTurnCompletedAt: String? | ||
|
|
||
| public init( | ||
| hasPendingApprovals: Bool, | ||
| hasPendingUserInput: Bool, | ||
| sessionStatus: String?, | ||
| latestUserMessageAt: String?, | ||
| latestTurnRequestedAt: String?, | ||
| latestTurnStartedAt: String?, | ||
| latestTurnCompletedAt: String? | ||
| ) { | ||
| self.hasPendingApprovals = hasPendingApprovals | ||
| self.hasPendingUserInput = hasPendingUserInput | ||
| self.sessionStatus = sessionStatus | ||
| self.latestUserMessageAt = latestUserMessageAt | ||
| self.latestTurnRequestedAt = latestTurnRequestedAt | ||
| self.latestTurnStartedAt = latestTurnStartedAt | ||
| self.latestTurnCompletedAt = latestTurnCompletedAt | ||
| } | ||
|
|
||
| func withPendingRequests(approvals: Bool, userInput: Bool) -> Self { | ||
| var copy = self | ||
| copy.hasPendingApprovals = approvals | ||
| copy.hasPendingUserInput = userInput | ||
| return copy | ||
| } | ||
| } | ||
|
|
||
| enum FeatureSettlementProjection { | ||
| static let queuedTurnStartGrace: TimeInterval = 2 * 60 | ||
|
|
||
| static func canSettle( | ||
| _ input: FeatureSettlementProjectionInput, | ||
| now: Date | ||
| ) -> Bool { | ||
| if input.hasPendingApprovals || input.hasPendingUserInput { return false } | ||
| if input.sessionStatus == "starting" || input.sessionStatus == "running" { return false } | ||
| if input.sessionStatus == "error" { return true } | ||
| guard let rawMessageAt = input.latestUserMessageAt, | ||
| let messageAt = parseDate(rawMessageAt) else { | ||
| return true | ||
| } | ||
| let messageAge = now.timeIntervalSince(messageAt) | ||
| if messageAge < 0 { return false } | ||
| if messageAge > queuedTurnStartGrace { return true } | ||
| if let completedAt = input.latestTurnCompletedAt.flatMap(parseDate), | ||
| completedAt >= messageAt { | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Settlement projection misses turn adoptionMedium Severity
Reviewed by Cursor Bugbot for commit bb748e6. Configure here. |
||
|
|
||
| private static func parseDate(_ value: String) -> Date? { | ||
| fractionalDateFormatter.date(from: value) ?? dateFormatter.date(from: value) | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| private static let fractionalDateFormatter: ISO8601DateFormatter = { | ||
| let formatter = ISO8601DateFormatter() | ||
| formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] | ||
| return formatter | ||
| }() | ||
|
|
||
| private static let dateFormatter = ISO8601DateFormatter() | ||
| } | ||
|
|
||
| public enum FeatureMessageRole: String, Sendable, Codable { | ||
| case user | ||
| case assistant | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -635,23 +635,33 @@ extension FeatureThread { | |
| .first(where: { $0.id == projectID })? | ||
| .environmentID | ||
| let resolvedEnvironmentID = environmentID ?? projectEnvironmentID | ||
| let providers = resolvedEnvironmentID.flatMap { | ||
| snapshot.providersByEnvironment?[$0] | ||
| } ?? [] | ||
| let providers: [FeatureProvider] | ||
| if let providersByEnvironment = snapshot.providersByEnvironment { | ||
| providers = resolvedEnvironmentID.flatMap { providersByEnvironment[$0] } ?? [] | ||
| } else { | ||
| providers = snapshot.providers | ||
| } | ||
| return providers.first(where: { $0.id == providerID })?.name ?? providerID | ||
| } | ||
|
|
||
| var needsAttention: Bool { | ||
| state == .waitingForApproval || state == .waitingForInput || state == .failed | ||
| } | ||
|
|
||
| var canSettle: Bool { | ||
| isSettled || settlementInput.map { | ||
| FeatureSettlementProjection.canSettle($0, now: .now) | ||
| } == true | ||
| } | ||
|
|
||
| func isEffectivelySettled(at now: Date) -> Bool { | ||
| switch state { | ||
| case .queued, .working, .monitoring, .waitingForApproval, .waitingForInput: | ||
| return false | ||
| case .idle, .failed, .completed: | ||
| break | ||
| } | ||
| guard canSettle else { return false } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Settled rows ignore queued-turn blockerMedium Severity
Reviewed by Cursor Bugbot for commit bb748e6. Configure here. |
||
| if isSettled { | ||
| return true | ||
| } | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium
Shared/FeatureModels.swift:359canSettlereturnstruefor a fresh message whose turn has only been requested, allowing Settle while the turn is still waiting to start.latestTurnRequestedAtbeing newer thanlatestUserMessageAtmakesallSatisfyfalse, andabs(now.timeIntervalSince(messageAt))also treats a sufficiently future server timestamp as stale and returnstrue; keep future timestamps blocked and treat a request without started/completed timestamps as queued.🤖 Copy this AI Prompt to have your agent fix this: