| 1 | package control |
| 2 | |
| 3 | import ( |
| 4 | "reasonix/internal/event" |
| 5 | "reasonix/internal/provider" |
| 6 | ) |
| 7 | |
| 8 | func (c *Controller) cancelledTurnWasSilent(completion *guardedTurnCompletion) bool { |
| 9 | if c == nil || c.executor == nil || completion == nil || completion.checkpoint == nil { |
| 10 | return false |
| 11 | } |
| 12 | msgs := c.terminationMessages() |
| 13 | start := completion.checkpoint.messageIndex |
| 14 | if start < 0 || start > len(msgs) { |
| 15 | return false |
| 16 | } |
| 17 | for _, m := range msgs[start:] { |
| 18 | if m.LocalOnly || m.Role == provider.RoleUser || m.Role == provider.RoleSystem { |
| 19 | continue |
| 20 | } |
| 21 | if m.Role == provider.RoleAssistant && (m.Content != "" || m.ReasoningContent != "" || len(m.ToolCalls) > 0) { |
| 22 | return false |
| 23 | } |
| 24 | if m.Role == provider.RoleTool && m.Content != "" { |
| 25 | return false |
| 26 | } |
| 27 | } |
| 28 | return true |
| 29 | } |
| 30 | |
| 31 | func (c *Controller) applyToolRecoveryTurnStatus(done *event.Event, completion *guardedTurnCompletion) { |
| 32 | if c == nil || c.executor == nil { |
| 33 | return |
| 34 | } |
| 35 | if len(c.executor.PendingToolRecovery()) > 0 { |
| 36 | // Unknown effects are durable execution facts. They make the cancelled |
| 37 | // turn diagnosable, but must not create a separate terminal state or an |
| 38 | // admission requirement for the next turn. |
| 39 | done.Recovery = &event.RecoveryStatus{State: "unknown", Reason: "tool_effect_unconfirmed"} |
| 40 | return |
| 41 | } |
| 42 | if c.executor.SilentToolRecovery() || c.cancelledTurnWasSilent(completion) { |
| 43 | done.Recovery = &event.RecoveryStatus{State: "interrupted", Reason: "silent_interruption"} |
| 44 | } |
| 45 | } |
| 46 |