Let task agents suggest discarding their task, with reasoning
This commit is contained in:
+3
-1
@@ -39,6 +39,8 @@ A task's summary is what its agent writes, before the task closes, about the who
|
||||
|
||||
An implementer says its task is ready to merge with the `ready_for_merge` MCP tool (`internal/agents/ready.go`), whose `overview` covers the whole task for a human switching between tasks: a summary, tradeoffs and compromises, what was not done, and future TODOs. Its description tells the agent to call it only once the review of its most recent changes has come back, never in the same turn as a change or while that review is pending. Each call records a `task.ready` event carrying it, which the lead gets as a signal and the transcript draws as a bubble (`ReadyBubble` in `mac/Sources/Byre/Chat.swift`) headed "ready for merge". The latest one, while the task is active and neither a question nor a snapshot other than an answer snapshot has come since it (a question, or newer work the overview doesn't cover), is drawn like an open question (the warn tint and border) and carries small Merge and Discard buttons that act like the header's, confirmation dialogs included; earlier ones are drawn like an answered question (the faint warn tint), without buttons. Recording it changes nothing else. byre asks for it (`readyPrompt`, sent as a byre message) when a task's state goes to `idle` (`Manager.Observe` calls `promptReady`) and the latest non-answer snapshot's review is `done` or `skipped`, once per snapshot. It checks and sends under the session's delivery lock (the one human messages and answers take), and only to an agent that isn't running, so the prompt never queues behind a turn that could add unreviewed work; `tasks.ready_prompt_n` holds the last snapshot asked about, set only once the prompt's turn started, so turns that change no files don't ask again and a new snapshot does. It doesn't ask for workflow stage tasks, a task with a merge after review set, or a review that failed, and it skips the `reviewing` → `idle` change of a review with findings, since those go to the agent; the turn that answers them asks once it ends without new work. The prompt tells the agent not to call the tool, and to say what's left, when the task isn't ready.
|
||||
|
||||
An implementer that thinks its task shouldn't land at all (the approach failed, the task turned out unnecessary or already done) says so with the `suggest_discard` MCP tool (also `internal/agents/ready.go`), whose `reason` explains why; `readyPrompt` offers it as the alternative to `ready_for_merge`. Each call records a `task.discard_suggested` event, which the lead gets as a signal and the transcript draws with the same `ReadyBubble`, headed "suggests discarding". It takes part in "latest" with the ready-for-merge overviews, so whichever of the two came last is the current one, drawn and cleared under the same rules, and it carries only the Discard button. It only suggests: the task stays open until the human discards it.
|
||||
|
||||
Agents find closed tasks with the `search_closed_tasks` MCP tool, which task agents get for their own project and the lead for one project (`project_id`) or all: a case-insensitive substring match of `query` on title, merge title, TODO item, summary and merge commit (`Store.SearchClosedTasks`), newest closed first, at most `limit` (default 20, at most 100), each with its id, outcome, merge commit, close time, creator, titles, TODO item, fork source and summary. The lead reads a closed task's transcript with `read_transcript`. There is no closed-task list in the UI; the human asks the lead.
|
||||
|
||||
**Knowledge base.** How byre features work that agents need only now and then lives in articles they fetch on demand instead of in their guidance: the Markdown files in `internal/agents/kb/`, embedded in the binary (`kb.go`). A file's name without `.md` is its topic, its first line a one-line summary and the rest the article. The `read_knowledge` MCP tool, which task agents and the lead get, lists every topic with its summary in its description and returns the article for `topic`. Adding an article is adding a file; the index follows. The articles are `signing` (turning on signed merge commits), `usage-commands` (the usage command's JSON format, see "Usage" under "Status"), `workflow-files` (a workflow's YAML, see "Workflows"; `read_workflow` and `save_workflow` point at it) and `todo-file` (the `TODO.md` format; the task guidance points at it).
|
||||
@@ -317,7 +319,7 @@ Agents ask questions with the `ask_human` MCP tool. Each question carries a `con
|
||||
|
||||
`GET /api/inbox` is one query over open items, oldest first; `project_id` and `task_id` narrow it to a project or task view, and `state` picks another state. The right pane shows a task's questions in its transcript, where they were asked, and they are answered there (below).
|
||||
|
||||
**Attention.** At the top right of a task's transcript, over its content, a column of buttons (`AttentionBar` in `mac/Sources/Byre/Chat.swift`, counted by `ChatModel.attention`) shows what in the transcript needs the user, one button per kind that has any, in the warn colours: "N questions" for its open questions, "N findings" for the findings of the task's latest review (the highest-numbered snapshot with a `review.*` event, once it is `done`) that have neither a state from the user nor a response from the agent, and "ready to merge" while the latest ready-for-merge overview is current (see "Ready for merge"). Each click scrolls to the next one of that kind, top-anchored, and after the last back to the first. Where the transcript would follow the bottom, it follows only until the first of these rows in transcript order (`ChatModel.held`) reaches the top, and stays there as rows arrive; so a transcript opens at that row, and one that a new question arrives in stops with the question at the top. Scrolling by hand or sending a message lets that row go (`ChatModel.release`), and the transcript follows the bottom again until a different row becomes the first. A button goes away once nothing of its kind is left. When one of these rows stops needing the user (a question answered, a finding answered, the overview no longer current) and others remain, the transcript scrolls to the next remaining one after it in transcript order, wrapping to the first (`ChatModel.closed`). Elsewhere, the switcher and the Merge confirmation's question warning jump to the task's oldest open question (`Store.showQuestion`), and the review warning to that snapshot's review (`Store.jumpTo` with `.review`): each selects the task's Transcript tab and scrolls there once the row is in the transcript.
|
||||
**Attention.** At the top right of a task's transcript, over its content, a column of buttons (`AttentionBar` in `mac/Sources/Byre/Chat.swift`, counted by `ChatModel.attention`) shows what in the transcript needs the user, one button per kind that has any, in the warn colours: "N questions" for its open questions, "N findings" for the findings of the task's latest review (the highest-numbered snapshot with a `review.*` event, once it is `done`) that have neither a state from the user nor a response from the agent, and "ready to merge" while the latest ready-for-merge overview is current (see "Ready for merge"), or "suggests discard" when that latest one is a discard suggestion. Each click scrolls to the next one of that kind, top-anchored, and after the last back to the first. Where the transcript would follow the bottom, it follows only until the first of these rows in transcript order (`ChatModel.held`) reaches the top, and stays there as rows arrive; so a transcript opens at that row, and one that a new question arrives in stops with the question at the top. Scrolling by hand or sending a message lets that row go (`ChatModel.release`), and the transcript follows the bottom again until a different row becomes the first. A button goes away once nothing of its kind is left. When one of these rows stops needing the user (a question answered, a finding answered, the overview no longer current) and others remain, the transcript scrolls to the next remaining one after it in transcript order, wrapping to the first (`ChatModel.closed`). Elsewhere, the switcher and the Merge confirmation's question warning jump to the task's oldest open question (`Store.showQuestion`), and the review warning to that snapshot's review (`Store.jumpTo` with `.review`): each selects the task's Transcript tab and scrolls there once the row is in the transcript.
|
||||
|
||||
`POST /api/inbox/responses` answers any number of questions in one submission, each becoming `answered`. Response types:
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
)
|
||||
|
||||
const leadGuidance = `You are the human's lead agent in byre, shown in the left pane of byre's app. The human talks to you there, and you coordinate every task agent on their behalf. You don't do task work yourself: task agents work in their own worktrees, and you read, direct and report on them.
|
||||
- Signals: byre sends you, as messages from byre, what happens across tasks: questions that task agents ask, errors and rate limits their sessions hit, turns they finish with their final message, a task's agent saying the task is ready to merge (with its overview of the whole task, which byre asks for once the review of its latest work has finished and its turn has ended), questions the human answered, tasks created, reviews started and finished, a merge held behind another task's or set to start after the review (and that cancelled or dropped), starting to merge, merged or discarded, and every change of a task's state, as "<task> state: <before> → <now>." listed first among what happened at the same moment. State changes, reviews starting, and tasks the human created, started merging, merged or discarded never wake you on their own; they arrive only alongside another signal. The states are running, idle, review_pending, reviewing, merging, discarding, merged, discarded and no_agent. A finished turn arrives with its state change: review_pending when byre is about to review the turn's work, reviewing while the review runs; neither is done, since the review can still find problems, so don't treat a task's work as finished until its state goes to idle. Several may arrive in one message, followed by the current task list (as list_tasks gives it) and what your right pane shows, so you rarely need to call list_tasks after a signal. The human doesn't see signals. Messages from the human arrive separately.
|
||||
- Signals: byre sends you, as messages from byre, what happens across tasks: questions that task agents ask, errors and rate limits their sessions hit, turns they finish with their final message, a task's agent saying the task is ready to merge (with its overview of the whole task, which byre asks for once the review of its latest work has finished and its turn has ended) or suggesting the human discard it (with its reasoning), questions the human answered, tasks created, reviews started and finished, a merge held behind another task's or set to start after the review (and that cancelled or dropped), starting to merge, merged or discarded, and every change of a task's state, as "<task> state: <before> → <now>." listed first among what happened at the same moment. State changes, reviews starting, and tasks the human created, started merging, merged or discarded never wake you on their own; they arrive only alongside another signal. The states are running, idle, review_pending, reviewing, merging, discarding, merged, discarded and no_agent. A finished turn arrives with its state change: review_pending when byre is about to review the turn's work, reviewing while the review runs; neither is done, since the review can still find problems, so don't treat a task's work as finished until its state goes to idle. Several may arrive in one message, followed by the current task list (as list_tasks gives it) and what your right pane shows, so you rarely need to call list_tasks after a signal. The human doesn't see signals. Messages from the human arrive separately.
|
||||
- Don't respond to what the human just did. A signal about their own action (a question they answered, a task they created, merged, started merging or discarded) tells them nothing new: don't report, confirm or comment on it. Act on it only if it changes what you are doing, and then end the turn with one short line at most. When a batch of signals needs nothing from you to the human, make your final message exactly NO_REPLY and nothing else; byre folds that turn away so the human doesn't see it.
|
||||
- "Your right pane is free" means the human finished what was in the right pane. Show them the next thing that needs them, most urgent first (questions, then errors on the task's transcript tab, then finished turns, then tasks ready to merge), with one line saying what it is. If nothing needs them, show that project's project tab, then post a short status: one line per active task (what it's doing or waiting on), then up to three TODO items you'd start next (from list_tasks), each with a one-line reason. Write each item as a link to #/todo/<project_id>/<title, URL-encoded with parentheses as %28 and %29>, for example [Fix login redirect](#/todo/3f2a9c1b7d4e/Fix%20login%20redirect): byre draws it with a Start button the human can click, and once the task exists, its state. Start one with start_todo only when the human picks it. If that says nothing new since your last status, make your final message NO_REPLY.
|
||||
- "Your right pane is free" means the human finished what was in the right pane. Show them the next thing that needs them, most urgent first (questions, then errors on the task's transcript tab, then finished turns, then tasks ready to merge or suggested for discarding), with one line saying what it is. If nothing needs them, show that project's project tab, then post a short status: one line per active task (what it's doing or waiting on), then up to three TODO items you'd start next (from list_tasks), each with a one-line reason. Write each item as a link to #/todo/<project_id>/<title, URL-encoded with parentheses as %28 and %29>, for example [Fix login redirect](#/todo/3f2a9c1b7d4e/Fix%20login%20redirect): byre draws it with a Start button the human can click, and once the task exists, its state. Start one with start_todo only when the human picks it. If that says nothing new since your last status, make your final message NO_REPLY.
|
||||
- Whenever you suggest new work that isn't in TODO.md (a task you think is worth doing, an improvement to byre or to your own role), write it as a link to #/suggest/<project_id>/<title>/<brief>, both URL-encoded with parentheses as %28 and %29, for example [Lead tool for admin status](#/suggest/3f2a9c1b7d4e/Lead%20tool%20for%20admin%20status/Add%20a%20lead%20tool%20that%20reads%20%2Fapi%2Fadmin%2Fstatus%20...). The brief is the new task agent's whole first message, so make it a complete brief, as for create_task. byre draws the link with two buttons: Start creates the task with that title and brief at once, without you, and Discuss sends you a message asking to talk the suggestion over. Once an active task has that title, it shows the task's state instead. Don't ask whether to start a suggestion; the buttons do that.
|
||||
- Steering the right pane is a core part of your job, as important as what you write. The right pane shows one task, or a project with no task; point it with show (the task or project, and the tab to select). By default, keep it on whatever you and the human are discussing, so they can see it while they read your reply; don't make them go looking for it. Call show in the same turn you bring something up:
|
||||
- Discussing, picking or starting TODO items, or a project's tasks as a whole: the project's project tab (show with project_id when no single task of that project is involved).
|
||||
@@ -327,7 +327,7 @@ func (m *Manager) Observe(events []api.Event) {
|
||||
go m.promptReady(*ev.TaskId, ev.Payload["from"])
|
||||
}
|
||||
switch ev.Kind {
|
||||
case "inbox.created", "inbox.updated", "task.created", "task.merge_queued", "task.merge_after_review", "task.merge_after_review_cancelled", "task.merge_after_review_dropped", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.started", "review.finished", "task.state", "task.ready":
|
||||
case "inbox.created", "inbox.updated", "task.created", "task.merge_queued", "task.merge_after_review", "task.merge_after_review_cancelled", "task.merge_after_review_dropped", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.started", "review.finished", "task.state", "task.ready", "task.discard_suggested":
|
||||
case "user.message":
|
||||
if ev.TaskId == nil || !humanActor(ev.Actor) {
|
||||
continue
|
||||
@@ -548,6 +548,9 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error)
|
||||
case "task.ready":
|
||||
return fmt.Sprintf("%s's agent says the task is ready to merge:\n%s", m.taskLabel(ctx, deref(ev.TaskId)), clip(fmt.Sprint(ev.Payload["overview"]), 3000)), nil
|
||||
|
||||
case "task.discard_suggested":
|
||||
return fmt.Sprintf("%s's agent suggests discarding the task:\n%s", m.taskLabel(ctx, deref(ev.TaskId)), clip(fmt.Sprint(ev.Payload["reason"]), 3000)), nil
|
||||
|
||||
case "task.workflow_stage":
|
||||
return fmt.Sprintf("%s started stage %q of workflow %q.", m.taskLabel(ctx, deref(ev.TaskId)), fmt.Sprint(ev.Payload["stage"]), ev.Payload["workflow"]), nil
|
||||
|
||||
|
||||
@@ -751,7 +751,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
|
||||
return nil, err
|
||||
}
|
||||
l.notes = "\n\n" + taskNotesGuidance + "\n\n" + notes
|
||||
tools = append(tools, m.suggestNoteTool(l), m.readyForMergeTool(l), m.respondToFindingTool(l))
|
||||
tools = append(tools, m.suggestNoteTool(l), m.readyForMergeTool(l), m.suggestDiscardTool(l), m.respondToFindingTool(l))
|
||||
}
|
||||
if sess.Role == api.Implementer && sess.Member != "" {
|
||||
sessions, err := m.store.ListSessions(ctx, task.Id)
|
||||
|
||||
@@ -14,7 +14,8 @@ import (
|
||||
)
|
||||
|
||||
const readyPrompt = "Your turn has ended and the review of your latest work has finished. If the task is ready to merge, call ready_for_merge with an overview of the whole task, then end your turn with one line. " +
|
||||
"If it isn't (work remains that you mean to do, or you're waiting on the human), don't call it: say what's left in one line and end your turn. " +
|
||||
"If the work shouldn't land at all (the approach failed, the task turned out unnecessary or already done), call suggest_discard with your reasoning instead, then end your turn with one line. " +
|
||||
"If it isn't ready (work remains that you mean to do, or you're waiting on the human), call neither: say what's left in one line and end your turn. " +
|
||||
"If you're waiting on the human for a decision or input, ask it with ask_human now, even if an earlier message already put it in prose, so it reaches their inbox; only an open ask_human question already counts as asked. Don't change any files this turn."
|
||||
|
||||
type readyArgs struct {
|
||||
@@ -57,6 +58,45 @@ func (m *Manager) readyForMergeTool(l *live) mcp.Tool {
|
||||
}
|
||||
}
|
||||
|
||||
type discardArgs struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func (m *Manager) suggestDiscardTool(l *live) mcp.Tool {
|
||||
return mcp.Tool{
|
||||
Name: "suggest_discard",
|
||||
Description: "Suggest that the human discard this task rather than merge it, with your reasoning: for example the approach didn't work, the task turned out unnecessary, or another task already did it. " +
|
||||
"Write the reasoning for someone jumping between tasks who hasn't followed this one: what was tried, why it shouldn't land, and anything worth keeping for a later attempt. " +
|
||||
"byre shows it in the task's transcript with a Discard button and passes it to the human's lead agent. It only suggests: the human decides, and the task stays open until they do.",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reason": {"type": "string", "description": "Markdown: why this task should be discarded rather than merged."}
|
||||
},
|
||||
"required": ["reason"]
|
||||
}`),
|
||||
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
|
||||
args := discardArgs{}
|
||||
err := json.Unmarshal(raw, &args)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("malformed suggest_discard call: %w", err)
|
||||
}
|
||||
reason := strings.TrimSpace(args.Reason)
|
||||
if reason == "" {
|
||||
return "", fmt.Errorf("%w: reason must not be empty", ErrInvalid)
|
||||
}
|
||||
err = m.store.SuggestDiscard(ctx, l.taskID, reason, agentActor)
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
return "", fmt.Errorf("%w: this task is closed", ErrConflict)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Recorded: the human sees your suggestion to discard this task, with your reasoning.", nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) promptReady(taskID string, from any) {
|
||||
ctx := context.Background()
|
||||
err := m.tryPromptReady(ctx, taskID, from == string(api.TaskStateReviewing))
|
||||
|
||||
@@ -761,6 +761,14 @@ func (s *Store) SetReadyPromptN(ctx context.Context, id string, snapshotN int) e
|
||||
}
|
||||
|
||||
func (s *Store) MarkReady(ctx context.Context, id, overview, actor string) error {
|
||||
return s.emitActive(ctx, id, actor, "task.ready", map[string]any{"id": id, "overview": overview})
|
||||
}
|
||||
|
||||
func (s *Store) SuggestDiscard(ctx context.Context, id, reason, actor string) error {
|
||||
return s.emitActive(ctx, id, actor, "task.discard_suggested", map[string]any{"id": id, "reason": reason})
|
||||
}
|
||||
|
||||
func (s *Store) emitActive(ctx context.Context, id, actor, kind string, payload map[string]any) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
projectID := ""
|
||||
err := tx.QueryRowContext(ctx, `SELECT project_id FROM tasks WHERE id = ? AND status = ?`, id, string(api.TaskStatusActive)).Scan(&projectID)
|
||||
@@ -770,7 +778,7 @@ func (s *Store) MarkReady(ctx context.Context, id, overview, actor string) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "task.ready", ProjectId: &projectID, TaskId: &id, Actor: actor, Payload: map[string]any{"id": id, "overview": overview}})
|
||||
return emit(api.Event{Kind: kind, ProjectId: &projectID, TaskId: &id, Actor: actor, Payload: payload})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ final class ChatModel {
|
||||
var questions: [Int] = []
|
||||
var open: [Int] = []
|
||||
var ready: [Int] = []
|
||||
var discard = false
|
||||
for row in builder.root.rows where !row.hidden {
|
||||
switch row.kind {
|
||||
case .question where row.question?.state == .open:
|
||||
@@ -318,8 +319,9 @@ final class ChatModel {
|
||||
if let r = row.review, r.id == latest?.id, findingOpen(r, i) {
|
||||
open.append(row.id)
|
||||
}
|
||||
case .ready where row.id == builder.lastReady && active:
|
||||
case .ready(_, _, let d) where row.id == builder.lastReady && active:
|
||||
ready.append(row.id)
|
||||
discard = d
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -332,7 +334,9 @@ final class ChatModel {
|
||||
groups.append(AttentionGroup(id: "findings", label: open.count == 1 ? "1 finding" : "\(open.count) findings", help: "Scroll to the next finding of the latest review that nobody has answered", rows: open))
|
||||
}
|
||||
if !ready.isEmpty {
|
||||
groups.append(AttentionGroup(id: "ready", label: "ready to merge", help: "Scroll to the agent's ready-for-merge overview", rows: ready))
|
||||
groups.append(discard
|
||||
? AttentionGroup(id: "ready", label: "suggests discard", help: "Scroll to the agent's reasons to discard the task", rows: ready)
|
||||
: AttentionGroup(id: "ready", label: "ready to merge", help: "Scroll to the agent's ready-for-merge overview", rows: ready))
|
||||
}
|
||||
return groups
|
||||
}
|
||||
@@ -1257,8 +1261,8 @@ struct RowView: View {
|
||||
case .findingResponse(let snapshot, let index, let response, let reason):
|
||||
FindingResponseView(row: row, model: model, snapshot: snapshot, index: index, response: response, reason: reason)
|
||||
|
||||
case .ready(let when, let blocks):
|
||||
ReadyBubble(when: when, blocks: blocks, taskId: row.taskId, latest: row.id == model.builder.lastReady, unmerged: model.hasUnmerged)
|
||||
case .ready(let when, let blocks, let discard):
|
||||
ReadyBubble(when: when, blocks: blocks, discard: discard, taskId: row.taskId, latest: row.id == model.builder.lastReady, unmerged: model.hasUnmerged)
|
||||
|
||||
case .handoff:
|
||||
DetailsBox(boxed: false) {
|
||||
@@ -1388,6 +1392,7 @@ private struct FindingResponseView: View {
|
||||
private struct ReadyBubble: View {
|
||||
let when: Date
|
||||
let blocks: [MDBlock]
|
||||
let discard: Bool
|
||||
let taskId: String?
|
||||
let latest: Bool
|
||||
let unmerged: Bool
|
||||
@@ -1398,7 +1403,7 @@ private struct ReadyBubble: View {
|
||||
let current = latest && task?.status == .active
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 8) {
|
||||
Text("ready for merge")
|
||||
Text(discard ? "suggests discarding" : "ready for merge")
|
||||
.font(Fonts.font(11, weight: current ? 700 : 400))
|
||||
.foregroundStyle(current ? Palette.warn : Palette.muted)
|
||||
WhenText(when: when)
|
||||
@@ -1410,7 +1415,9 @@ private struct ReadyBubble: View {
|
||||
let busy = store.taskBusy(task.id)
|
||||
HStack(spacing: 8) {
|
||||
Spacer(minLength: 0)
|
||||
MergeButton(task: task, busy: busy, unmerged: unmerged, small: true)
|
||||
if !discard {
|
||||
MergeButton(task: task, busy: busy, unmerged: unmerged, small: true)
|
||||
}
|
||||
DiscardButton(task: task, busy: busy, small: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ enum RowKind {
|
||||
case review(note: Note, md: [MDBlock])
|
||||
case finding(index: Int)
|
||||
case findingResponse(snapshot: Int, index: Int, response: String, reason: String)
|
||||
case ready(when: Date, md: [MDBlock])
|
||||
case ready(when: Date, md: [MDBlock], discard: Bool)
|
||||
case handoff
|
||||
case snapshot(Snapshot)
|
||||
case question
|
||||
@@ -575,7 +575,7 @@ final class TranscriptBuilder {
|
||||
if ev.kind == "task.merge_after_review" && sink == nil {
|
||||
lastMergeAfterReview = row.id
|
||||
}
|
||||
if ev.kind == "task.ready" && sink == nil {
|
||||
if (ev.kind == "task.ready" || ev.kind == "task.discard_suggested") && sink == nil {
|
||||
lastReady = row.id
|
||||
}
|
||||
if ev.kind == "inbox.created" && sink == nil {
|
||||
@@ -796,7 +796,10 @@ final class TranscriptBuilder {
|
||||
return note([.text("\(ev.actor) set the task summary")])
|
||||
|
||||
case "task.ready":
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["overview"]))), taskId: taskId)
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["overview"])), discard: false), taskId: taskId)
|
||||
|
||||
case "task.discard_suggested":
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["reason"])), discard: true), taskId: taskId)
|
||||
|
||||
case "task.discard_started":
|
||||
return note([.text("\(ev.actor) asked to discard the task; the agent writes the task summary first")])
|
||||
|
||||
Reference in New Issue
Block a user