Start a new task from a closed task
This commit is contained in:
@@ -134,7 +134,17 @@ final class ChatModel {
|
||||
member != nil
|
||||
}
|
||||
|
||||
var closed: Bool {
|
||||
guard let taskId, let task = store.findTask(taskId) else {
|
||||
return false
|
||||
}
|
||||
return task.status != .active
|
||||
}
|
||||
|
||||
var placeholder: String {
|
||||
if closed {
|
||||
return "Continue in a new task, started from main, whose agent picks up this conversation. Paste or drop images to attach them."
|
||||
}
|
||||
if team {
|
||||
return "Post to the team's board; every agent of the team gets it."
|
||||
}
|
||||
@@ -468,6 +478,14 @@ final class ChatModel {
|
||||
case .lead:
|
||||
_ = try await api.messageLead(body: .json(.init(text: text, images: images, model: sessionId != nil ? model : firstModel, sendId: sendID))).accepted
|
||||
case .task(let id):
|
||||
if closed {
|
||||
guard let sessionId else {
|
||||
throw ApiError(status: 0, message: "This task has no agent conversation to continue.")
|
||||
}
|
||||
let task = try await api.continueSession(path: .init(sessionId: sessionId), body: .json(.init(text: text, images: images, model: team ? nil : model, sendId: sendID))).created.body.json
|
||||
store.focusTask(task.id)
|
||||
return
|
||||
}
|
||||
if team {
|
||||
if images != nil {
|
||||
throw ApiError(status: 0, message: "The board takes text only; drop the images to post.")
|
||||
|
||||
@@ -482,7 +482,7 @@ struct ComposerView: View {
|
||||
}
|
||||
|
||||
private func modelState() -> ModelState {
|
||||
let live = chat.liveSession
|
||||
let live = chat.closed ? chat.session : chat.liveSession
|
||||
let current = live.map { $0.model.isEmpty ? chat.builder.context[$0.id]?.model ?? "" : $0.model } ?? ""
|
||||
let offered = live.map { s in models.filter { vendorOf($0) == s.vendor.rawValue } } ?? models
|
||||
let options = !current.isEmpty && !offered.contains(current) ? [current] + offered : offered
|
||||
@@ -632,12 +632,13 @@ struct ComposerView: View {
|
||||
.buttonStyle(WebButtonStyle(danger: true))
|
||||
.disabled(!running)
|
||||
.fixedSize()
|
||||
Button("Send") {
|
||||
Button(chat.closed ? "Continue" : "Send") {
|
||||
submit(modelState())
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
.disabled(!shown)
|
||||
.fixedSize()
|
||||
.help(chat.closed ? "Start a new task from main whose agent continues this conversation with your message" : "Send the message")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +669,7 @@ struct ComposerView: View {
|
||||
text = ""
|
||||
let sent = drafts.startSend(draftKey, value, imgs.count)
|
||||
let sendID = newID("snd")
|
||||
let cancel = chat.team ? { true } : chat.builder.showPending(value, imgs, sendID)
|
||||
let cancel = chat.team || chat.closed ? { true } : chat.builder.showPending(value, imgs, sendID)
|
||||
let sessionId = m.live?.id
|
||||
Task {
|
||||
var ids: [String]? = nil
|
||||
@@ -686,7 +687,7 @@ struct ComposerView: View {
|
||||
return
|
||||
} catch {
|
||||
let refused = (rootError(error) as? ApiError).map { $0.status < 500 } ?? false
|
||||
if chat.team || refused {
|
||||
if (chat.team && !chat.closed) || refused {
|
||||
guard cancel() else {
|
||||
log.notice("send failed after the server confirmed it: \(errorText(error), privacy: .public)")
|
||||
drafts.endSend(sent, true)
|
||||
@@ -698,7 +699,7 @@ struct ComposerView: View {
|
||||
overlays.showAlert(errorText(error))
|
||||
return
|
||||
}
|
||||
guard chat.builder.unconfirmed(sendID) else {
|
||||
guard chat.closed || chat.builder.unconfirmed(sendID) else {
|
||||
drafts.endSend(sent, true)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -536,11 +536,16 @@ final class TranscriptBuilder {
|
||||
continue
|
||||
}
|
||||
if ev.kind == "task.forked", let fork = str(p, "fork") {
|
||||
if let row = ctx.snapshots[snapKey(int(p, "forked_at") ?? 0)] {
|
||||
let at = int(p, "forked_at") ?? 0
|
||||
if at == 0 {
|
||||
put(Row(.note(Note(parts: [.text("\(ev.actor) continued this task as "), .task(fork)], when: ev.createdAt)), taskId: rowTask))
|
||||
continue
|
||||
}
|
||||
if let row = ctx.snapshots[snapKey(at)] {
|
||||
row.forks.append(fork)
|
||||
continue
|
||||
}
|
||||
put(Row(.note(Note(parts: [.text("\(ev.actor) forked snapshot #\(jsString(p["forked_at"])) as "), .task(fork)], when: ev.createdAt)), taskId: rowTask))
|
||||
put(Row(.note(Note(parts: [.text("\(ev.actor) forked snapshot #\(at) as "), .task(fork)], when: ev.createdAt)), taskId: rowTask))
|
||||
continue
|
||||
}
|
||||
if ev.kind == "review.started" || ev.kind == "review.finished" || ev.kind == "review.finding" {
|
||||
@@ -913,6 +918,12 @@ final class TranscriptBuilder {
|
||||
return note([.text("\(jsString(p["error"])) (\(jsString(p["vendor"])) \(limit["rateLimitType"].map { jsString($0) } ?? "") limit resets \(resets))")], .error)
|
||||
|
||||
case "task.created":
|
||||
if let from = str(p, "forked_from"), p["forked_at"] == nil {
|
||||
if let task = rowTask, task != taskId {
|
||||
return note([.text("\(ev.actor) continued "), .task(from), .text(" as "), .task(task), .text(" here")])
|
||||
}
|
||||
return note([.text("\(ev.actor) continued "), .task(from), .text(" here in a new task, started from main; everything above happened in that task, and its agent continues that conversation")])
|
||||
}
|
||||
if let from = str(p, "forked_from"), let task = rowTask, task != taskId {
|
||||
return note([.text("\(ev.actor) forked "), .task(task), .text(" here from "), .task(from), .text(" at snapshot #\(jsString(p["forked_at"]))")])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user