返回 DeepSeek-Reasonix
inbox_cancel.go
根目录 / internal / control / inbox_cancel.go
1 package control
2
3 import "strings"
4
5 // InboxCancelResult is the authoritative receipt for a cancel+withdraw
6 // operation. Only IDs in DiscardedItemIDs are safe for a frontend to restore
7 // into its draft.
8 type InboxCancelResult struct {
9 DiscardedItemIDs []string
10 Warning string
11 }
12
13 // CancelWithInboxItems stops the active turn and discards only the durable
14 // pending items explicitly owned by the cancelling frontend. Admission is
15 // paused around the batch deletion so TurnDone cannot race a cancelled item
16 // into a new provider turn. Unrelated inbox items remain intact.
17 func (c *Controller) CancelWithInboxItems(ids []string, source string) error {
18 _, err := c.CancelWithInboxItemsResult(ids, source)
19 return err
20 }
21
22 // CancelWithInboxItemsResult serializes withdrawal against every inbox
23 // admission path and returns exactly the durable messages that were removed.
24 // A consumed/running item is intentionally absent from the receipt.
25 func (c *Controller) CancelWithInboxItemsResult(ids []string, source string) (InboxCancelResult, error) {
26 result := InboxCancelResult{DiscardedItemIDs: []string{}}
27 // Stopping maintenance never withdraws queued messages. Those messages were
28 // not part of the summary input and remain durable for post-maintenance
29 // dispatch.
30 if operationID, present, _ := c.signalMaintenanceCancel(); present {
31 c.recordLifecycle("cancel_requested", source, operationID, 0, "")
32 c.recordLifecycle("cancel_acknowledged", source, operationID, 0, "")
33 return result, nil
34 }
35 c.inbox.admissionMu.Lock()
36 defer c.inbox.admissionMu.Unlock()
37 // Capture and signal the foreground owner before touching the inbox store.
38 // A blocked sidecar or filesystem cannot delay Stop reaching the model/tool
39 // context. Status persistence and Goal pausing run after the inbox mutation.
40 turnID, cancelled := c.cancelTurnLocked()
41 c.recordLifecycle("cancel_requested", source, turnID, 0, "")
42 defer c.recordLifecycle("cancel_acknowledged", source, turnID, 0, "")
43 defer c.finishCancel(turnID, cancelled)
44 st, err := c.ensureInbox()
45 if err != nil {
46 return result, err
47 }
48 wasPaused := st.Snapshot().Paused
49 if err := st.SetPaused(true); err != nil {
50 return result, err
51 }
52 discarded, err := st.DiscardPendingItemsOwnedResult(ids, strings.TrimSpace(source))
53 if err != nil {
54 // Keep the inbox paused for inspection if an item already crossed the
55 // admission boundary. Cancellation still stops that in-flight turn.
56 return result, err
57 }
58 result.DiscardedItemIDs = discarded
59 if !wasPaused {
60 if err := st.SetPaused(false); err != nil {
61 result.Warning = "The turn was stopped, but the message queue remains paused. Review it before resuming."
62 return result, nil
63 }
64 }
65 return result, nil
66 }
67
67 lines GO