Handle reused Codex approval request IDs
Treat app-server request IDs as connection-local by reopening reused approval rows when the thread, turn, or item context changes. Keep duplicate resolved approvals in the same context closed, and add focused approval-path diagnostics without changing the Telegram approval UI.
This commit is contained in:
+34
-2
@@ -496,9 +496,41 @@ INSERT INTO pending_approvals (
|
||||
message_chat_id, message_id, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')
|
||||
ON CONFLICT(telegram_user_id, codex_request_id) DO UPDATE SET
|
||||
codex_thread_id = excluded.codex_thread_id,
|
||||
turn_id = excluded.turn_id,
|
||||
item_id = excluded.item_id,
|
||||
kind = excluded.kind,
|
||||
payload_json = excluded.payload_json,
|
||||
message_chat_id = CASE WHEN pending_approvals.status = 'pending' THEN excluded.message_chat_id ELSE pending_approvals.message_chat_id END,
|
||||
message_id = CASE WHEN pending_approvals.status = 'pending' THEN excluded.message_id ELSE pending_approvals.message_id END`,
|
||||
message_chat_id = CASE
|
||||
WHEN pending_approvals.codex_thread_id = excluded.codex_thread_id
|
||||
AND pending_approvals.turn_id = excluded.turn_id
|
||||
AND pending_approvals.item_id = excluded.item_id
|
||||
THEN pending_approvals.message_chat_id
|
||||
ELSE excluded.message_chat_id
|
||||
END,
|
||||
message_id = CASE
|
||||
WHEN pending_approvals.codex_thread_id = excluded.codex_thread_id
|
||||
AND pending_approvals.turn_id = excluded.turn_id
|
||||
AND pending_approvals.item_id = excluded.item_id
|
||||
THEN pending_approvals.message_id
|
||||
ELSE excluded.message_id
|
||||
END,
|
||||
status = CASE
|
||||
WHEN pending_approvals.status <> 'pending'
|
||||
AND pending_approvals.codex_thread_id = excluded.codex_thread_id
|
||||
AND pending_approvals.turn_id = excluded.turn_id
|
||||
AND pending_approvals.item_id = excluded.item_id
|
||||
THEN pending_approvals.status
|
||||
ELSE 'pending'
|
||||
END,
|
||||
resolved_at = CASE
|
||||
WHEN pending_approvals.status <> 'pending'
|
||||
AND pending_approvals.codex_thread_id = excluded.codex_thread_id
|
||||
AND pending_approvals.turn_id = excluded.turn_id
|
||||
AND pending_approvals.item_id = excluded.item_id
|
||||
THEN pending_approvals.resolved_at
|
||||
ELSE ''
|
||||
END`,
|
||||
approval.TelegramUserID, approval.CodexRequestID, approval.CodexThreadID, approval.TurnID,
|
||||
approval.ItemID, approval.Kind, approval.PayloadJSON, approval.MessageChatID, approval.MessageID)
|
||||
if err != nil {
|
||||
|
||||
@@ -267,6 +267,62 @@ func TestUpsertPendingApprovalDoesNotReopenResolved(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsertPendingApprovalReopensReusedRequestIDForNewContext(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, err := Open(ctx, filepath.Join(t.TempDir(), "bot.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer st.Close()
|
||||
|
||||
first, err := st.UpsertPendingApproval(ctx, PendingApproval{
|
||||
TelegramUserID: 42,
|
||||
CodexRequestID: "i:0",
|
||||
CodexThreadID: "thread-1",
|
||||
TurnID: "turn-1",
|
||||
ItemID: "item-1",
|
||||
Kind: "item/commandExecution/requestApproval",
|
||||
PayloadJSON: `{"command":"go test ./..."}`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.UpdatePendingApprovalMessage(ctx, first.ID, 1001, 2002); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.ResolvePendingApproval(ctx, 42, first.ID, "accept"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
second, err := st.UpsertPendingApproval(ctx, PendingApproval{
|
||||
TelegramUserID: 42,
|
||||
CodexRequestID: "i:0",
|
||||
CodexThreadID: "thread-2",
|
||||
TurnID: "turn-2",
|
||||
ItemID: "item-2",
|
||||
Kind: "item/commandExecution/requestApproval",
|
||||
PayloadJSON: `{"command":"git push"}`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if second.ID != first.ID {
|
||||
t.Fatalf("reused request id row = %d, want %d", second.ID, first.ID)
|
||||
}
|
||||
if second.Status != "pending" {
|
||||
t.Fatalf("reused request status = %q, want pending", second.Status)
|
||||
}
|
||||
if second.CodexThreadID != "thread-2" || second.TurnID != "turn-2" || second.ItemID != "item-2" {
|
||||
t.Fatalf("reused request context = thread %q turn %q item %q", second.CodexThreadID, second.TurnID, second.ItemID)
|
||||
}
|
||||
if second.MessageChatID != 0 || second.MessageID != 0 {
|
||||
t.Fatalf("reused request kept stale message: chat=%d message=%d", second.MessageChatID, second.MessageID)
|
||||
}
|
||||
if second.ResolvedAt != "" {
|
||||
t.Fatalf("reused request resolved_at = %q, want empty", second.ResolvedAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspacePath(t *testing.T) {
|
||||
if _, err := ValidateWorkspacePath("relative/path"); err == nil {
|
||||
t.Fatal("relative path should be rejected")
|
||||
|
||||
Reference in New Issue
Block a user