Explorar o código

fix: add doom loop detection (#3445)

Co-authored-by: Aiden Cline <[email protected]>
ElecTwix hai 3 meses
pai
achega
d983b9485d

+ 27 - 0
packages/opencode/src/session/prompt.ts

@@ -56,6 +56,7 @@ export namespace SessionPrompt {
   const log = Log.create({ service: "session.prompt" })
   export const OUTPUT_TOKEN_MAX = 32_000
   const MAX_RETRIES = 10
+  const DOOM_LOOP_THRESHOLD = 3
 
   export const Event = {
     Idle: Bus.event(
@@ -1068,6 +1069,32 @@ export namespace SessionPrompt {
                     metadata: value.providerMetadata,
                   })
                   toolcalls[value.toolCallId] = part as MessageV2.ToolPart
+
+                  const parts = await Session.getParts(assistantMsg.id)
+                  const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD)
+                  if (
+                    lastThree.length === DOOM_LOOP_THRESHOLD &&
+                    lastThree.every(
+                      (p) =>
+                        p.type === "tool" &&
+                        p.tool === value.toolName &&
+                        p.state.status !== "pending" &&
+                        JSON.stringify(p.state.input) === JSON.stringify(value.input),
+                    )
+                  ) {
+                    await Permission.ask({
+                      type: "doom-loop",
+                      pattern: value.toolName,
+                      sessionID: assistantMsg.sessionID,
+                      messageID: assistantMsg.id,
+                      callID: value.toolCallId,
+                      title: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
+                      metadata: {
+                        tool: value.toolName,
+                        input: value.input,
+                      },
+                    })
+                  }
                 }
                 break
               }

+ 8 - 4
packages/tui/internal/components/chat/message.go

@@ -504,7 +504,11 @@ func renderToolDetails(
 		base := styles.NewStyle().Background(backgroundColor)
 		text := base.Foreground(t.Text()).Bold(true).Render
 		muted := base.Foreground(t.TextMuted()).Render
-		permissionContent = "Permission required to run this tool:\n\n"
+		if permission.Type == "doom-loop" {
+			permissionContent = permission.Title + "\n\n"
+		} else {
+			permissionContent = "Permission required to run this tool:\n\n"
+		}
 		permissionContent += text(
 			"enter ",
 		) + muted(
@@ -642,9 +646,9 @@ func renderToolDetails(
 				for _, item := range todos.([]any) {
 					todo := item.(map[string]any)
 					content := todo["content"]
-          if content == nil {
-            continue
-          }
+					if content == nil {
+						continue
+					}
 					switch todo["status"] {
 					case "completed":
 						body += fmt.Sprintf("- [x] %s\n", content)