Watch: activity line on working cards, All threads browsing, a thread's subagents, Load earlier, and markdown messages
This commit is contained in:
1 parent
abd2fd0e13
commit
d916bfbab5
21 files changed
+928
-46
No files matched your search
@@ -154,6 +154,9 @@ struct FeedItem: Codable, Hashable, Sendable, Identifiable {
|
||||
/// Set when `request` and `phase` belong to a subagent rather than this thread. Older gateways
|
||||
/// leave it out, which decodes as nil.
|
||||
var via: Via? = nil
|
||||
/// What a working thread is doing right now. Nil when nothing is in progress, and from older
|
||||
/// gateways.
|
||||
var activity: Activity? = nil
|
||||
|
||||
var id: String { "\(hostId)/\(threadId)" }
|
||||
var route: Route { .thread(hostId: hostId, threadId: threadId) }
|
||||
@@ -164,6 +167,66 @@ struct FeedItem: Codable, Hashable, Sendable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
/// One step a thread is taking now, for the working card's activity line.
|
||||
struct Activity: Codable, Hashable, Sendable {
|
||||
struct Kind: RawRepresentable, Codable, Hashable, Sendable {
|
||||
let rawValue: String
|
||||
init(rawValue: String) { self.rawValue = rawValue }
|
||||
|
||||
static let command = Kind(rawValue: "command")
|
||||
static let edit = Kind(rawValue: "edit")
|
||||
static let search = Kind(rawValue: "search")
|
||||
static let web = Kind(rawValue: "web")
|
||||
static let subagent = Kind(rawValue: "subagent")
|
||||
static let thinking = Kind(rawValue: "thinking")
|
||||
static let tool = Kind(rawValue: "tool")
|
||||
}
|
||||
|
||||
var kind: Kind
|
||||
/// The command line, file name, search, subagent title or tool name, trimmed by the gateway.
|
||||
var text: String
|
||||
/// When the step started, for its elapsed time.
|
||||
var startedAt: Date?
|
||||
}
|
||||
|
||||
/// A thread in a list: a project's threads, or a thread's subagents.
|
||||
struct ThreadRow: Codable, Hashable, Sendable, Identifiable {
|
||||
var hostId: String
|
||||
var threadId: String
|
||||
var title: String
|
||||
/// Nil for a thread that never ran or was stopped.
|
||||
var phase: Phase?
|
||||
var settled: Bool
|
||||
var updatedAt: Date
|
||||
|
||||
var id: String { "\(hostId)/\(threadId)" }
|
||||
var route: Route { .thread(hostId: hostId, threadId: threadId) }
|
||||
|
||||
/// "working", "waiting", "settled", "failed" or "done", for a subagent's row.
|
||||
var statusWord: String {
|
||||
if phase?.isWorking == true { return "working" }
|
||||
if phase?.needsYou == true { return "waiting" }
|
||||
if settled { return "settled" }
|
||||
return phase == .failed ? "failed" : "done"
|
||||
}
|
||||
|
||||
/// False once it finished or settled, which dims its title.
|
||||
var isActive: Bool { !settled && (phase?.isWorking == true || phase?.needsYou == true) }
|
||||
}
|
||||
|
||||
/// `GET /v1/projects/:hostId/:projectId/threads`: a project's newest 50 threads, newest first.
|
||||
struct ThreadListResponse: Codable, Hashable, Sendable {
|
||||
var items: [ThreadRow]
|
||||
}
|
||||
|
||||
/// `GET /v1/threads/:hostId/:threadId/history`: the page before the messages the watch has.
|
||||
struct HistoryResponse: Codable, Hashable, Sendable {
|
||||
/// Oldest first.
|
||||
var messages: [Message]
|
||||
/// The cursor for the page before these. Nil at the start of the thread.
|
||||
var earlierCursor: String?
|
||||
}
|
||||
|
||||
struct Feed: Codable, Hashable, Sendable {
|
||||
var needsYou: [FeedItem]
|
||||
var working: [FeedItem]
|
||||
@@ -268,6 +331,29 @@ struct ThreadDetail: Codable, Hashable, Sendable {
|
||||
/// True when the thread asks before each action (T3's approval-required mode). Older gateways
|
||||
/// leave it out, which decodes as nil, and the screen hides the switch.
|
||||
var askFirst: Bool? = nil
|
||||
/// Pass to the history route for older messages. Nil when `messages` starts the thread, and from
|
||||
/// older gateways, which hides Load earlier.
|
||||
var earlierCursor: String? = nil
|
||||
/// Subagents and delegated threads this thread started, newest first.
|
||||
var subagents: [ThreadRow] = []
|
||||
}
|
||||
|
||||
extension ThreadDetail {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case item, messages, canStop, askFirst, earlierCursor, subagents
|
||||
}
|
||||
|
||||
/// `subagents` is newer than the rest, so a gateway that leaves it out lists none.
|
||||
init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.init(
|
||||
item: try container.decode(FeedItem.self, forKey: .item),
|
||||
messages: try container.decode([Message].self, forKey: .messages),
|
||||
canStop: try container.decode(Bool.self, forKey: .canStop),
|
||||
askFirst: try container.decodeIfPresent(Bool.self, forKey: .askFirst),
|
||||
earlierCursor: try container.decodeIfPresent(String.self, forKey: .earlierCursor),
|
||||
subagents: try container.decodeIfPresent([ThreadRow].self, forKey: .subagents) ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
/// `POST /v1/threads/:hostId/:threadId/mode`.
|
||||
|
||||
Reference in new issue
Block a user