返回 DeepSeek-Reasonix
protocol_recovery.go
根目录 / internal / control / protocol_recovery.go
1 package control
2
3 import (
4 "context"
5 "strings"
6
7 "reasonix/internal/agent"
8 "reasonix/internal/provider"
9 )
10
11 const ProtocolRecoveryAction = "protocol_recovery"
12 const RecoverContextCommand = "/recover-context"
13 const protocolRecoveryPrompt = "Continue the interrupted task from valid history. Preserve completed tool results. For calls with unknown outcomes, inspect workspace or external state before deciding whether a retry is needed; read-only or idempotent calls may be retried when useful."
14
15 func ParseProtocolRecoveryCommand(input string) (id, guidance string, ok bool) {
16 parts := strings.Fields(input)
17 if len(parts) == 0 || parts[0] != RecoverContextCommand {
18 return "", "", false
19 }
20 if len(parts) > 1 {
21 id = parts[1]
22 }
23 if len(parts) > 2 {
24 guidance = strings.Join(parts[2:], " ")
25 }
26 return id, guidance, true
27 }
28
29 func (c *Controller) protocolRecoveryContext(ctx context.Context, id string) (context.Context, error) {
30 if c.executor == nil {
31 return ctx, agent.ErrProtocolRecoveryUnavailable
32 }
33 action := c.executor.PendingProtocolRecovery()
34 if action == nil || (id != "" && id != action.ID) {
35 return ctx, agent.ErrProtocolRecoveryUnavailable
36 }
37 return agent.WithInputMessageOrigin(agent.WithProtocolRecovery(ctx, action.ID), provider.MessageOriginHost), nil
38 }
39
40 func (c *Controller) RunProtocolRecoveryWithAdmission(ctx context.Context, id, guidance string, admitted func()) error {
41 return c.runSynchronousTurn(ctx, nil, func(runCtx context.Context) error {
42 recoveryCtx, err := c.protocolRecoveryContext(runCtx, id)
43 if err != nil {
44 return err
45 }
46 if admitted != nil {
47 admitted()
48 }
49 return c.runTurn(recoveryCtx, protocolRecoveryPrompt+recoveryGuidance(guidance))
50 })
51 }
52
53 func recoveryGuidance(input string) string {
54 if strings.TrimSpace(input) == "" {
55 return ""
56 }
57 return "\n\nUser guidance: " + input
58 }
59
60 func (c *Controller) SubmitProtocolRecovery(id, guidance string) {
61 c.submissions.mu.Lock()
62 defer c.releaseSubmissionAdmission()
63 c.submitProtocolRecoveryLocked(id, guidance, turnAdmission{})
64 }
65
66 func (c *Controller) submitProtocolRecoveryLocked(id, guidance string, admission turnAdmission) {
67 // Bind a token before enqueueing; a later request cannot recover a different
68 // incident just because it used the tokenless CLI shortcut.
69 if id == "" && c.executor != nil {
70 if pending := c.executor.PendingProtocolRecovery(); pending != nil {
71 id = pending.ID
72 }
73 }
74 c.runGuardedWithAdmission(func(ctx context.Context) error {
75 if id == "" {
76 return agent.ErrProtocolRecoveryUnavailable
77 }
78 recoveryCtx, err := c.protocolRecoveryContext(ctx, id)
79 if err != nil {
80 return err
81 }
82 return c.runTurn(recoveryCtx, protocolRecoveryPrompt+recoveryGuidance(guidance))
83 }, admission)
84 }
85
86 // PendingProtocolRecovery exposes the same admission token on every transport.
87 func (c *Controller) PendingProtocolRecovery() *provider.ProtocolRecoveryAction {
88 if c.executor == nil || c.Running() {
89 return nil
90 }
91 return c.executor.PendingProtocolRecovery()
92 }
93
93 lines GO