Skip to content

Commit 2ea8ceb

Browse files
committed
Release claims on preflight exits
1 parent 2fa4ce0 commit 2ea8ceb

3 files changed

Lines changed: 17 additions & 5 deletions

File tree

src/chrome/src/ui/sidepanel.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7103,16 +7103,21 @@ async function sendMessage(extraChatParams = {}) {
71037103
} catch { /* the durable lease still expires if release fails */ }
71047104
onContextMenuClaimRejected?.(rejection);
71057105
};
7106-
if (isConversationClearInProgress(tabId)) return false;
7106+
if (isConversationClearInProgress(tabId)) {
7107+
await releaseOwnedContextMenuClaim({ reason: 'conversation-clear', retryAfterMs: 1_000 });
7108+
return false;
7109+
}
71077110
const permissionSkipContext = permissionSkipCommandContextForDraft(tabId, text);
71087111
const requestId = createRunRequestId(tabId);
71097112
text = normalizeScreenshotCommandText(text);
71107113
if (isAwaitingPlanReviewForTab(tabId)) {
7114+
await releaseOwnedContextMenuClaim({ reason: 'plan-review-pending', retryAfterMs: 1_000 });
71117115
showComposerToast(t('sp.plan.awaiting_review'), { duration: 5000 });
71127116
syncSendButtonState();
71137117
return false;
71147118
}
71157119
if (!retryOptions && !sourceGrounding && !isProcessing && isAttachmentReadPendingForTab(tabId)) {
7120+
await releaseOwnedContextMenuClaim({ reason: 'attachment-read-pending', retryAfterMs: 1_000 });
71167121
syncSendButtonState();
71177122
return false;
71187123
}
@@ -7188,6 +7193,7 @@ async function sendMessage(extraChatParams = {}) {
71887193
// If the entire message was just the slash command, there's nothing
71897194
// left to send to the agent — bail out after the side effect.
71907195
if (!text) {
7196+
await releaseOwnedContextMenuClaim({ reason: 'preflight-empty', retryAfterMs: 1_000 });
71917197
scrollToBottom({ force: true });
71927198
inputEl.value = '';
71937199
autoResizeInput();

src/firefox/src/ui/sidepanel.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6845,16 +6845,21 @@ async function sendMessage(extraChatParams = {}) {
68456845
} catch { /* the durable lease still expires if release fails */ }
68466846
onContextMenuClaimRejected?.(rejection);
68476847
};
6848-
if (isConversationClearInProgress(tabId)) return false;
6848+
if (isConversationClearInProgress(tabId)) {
6849+
await releaseOwnedContextMenuClaim({ reason: 'conversation-clear', retryAfterMs: 1_000 });
6850+
return false;
6851+
}
68496852
const permissionSkipContext = permissionSkipCommandContextForDraft(tabId, text);
68506853
const requestId = createRunRequestId(tabId);
68516854
text = normalizeScreenshotCommandText(text);
68526855
if (isAwaitingPlanReviewForTab(tabId)) {
6856+
await releaseOwnedContextMenuClaim({ reason: 'plan-review-pending', retryAfterMs: 1_000 });
68536857
showComposerToast(t('sp.plan.awaiting_review'), { duration: 5000 });
68546858
syncSendButtonState();
68556859
return false;
68566860
}
68576861
if (!retryOptions && !sourceGrounding && !isProcessing && isAttachmentReadPendingForTab(tabId)) {
6862+
await releaseOwnedContextMenuClaim({ reason: 'attachment-read-pending', retryAfterMs: 1_000 });
68586863
syncSendButtonState();
68596864
return false;
68606865
}
@@ -6925,6 +6930,7 @@ async function sendMessage(extraChatParams = {}) {
69256930
return false;
69266931
}
69276932
if (!text) {
6933+
await releaseOwnedContextMenuClaim({ reason: 'preflight-empty', retryAfterMs: 1_000 });
69286934
scrollToBottom({ force: true });
69296935
inputEl.value = '';
69306936
autoResizeInput();

test/run.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15117,7 +15117,7 @@ test('sidepanel New conversation uses a Vivaldi-safe in-panel confirmation dialo
1511715117
`${label}: confirmed New conversation should discard queued prompts before stopping and clearing`,
1511815118
);
1511915119
assert.match(panel, /function syncSendButtonState\(\) \{[\s\S]*?isConversationClearInProgress\(\)[\s\S]*?sendBtn\.disabled = true;/, `${label}: the composer should stay disabled for the full clear transaction`);
15120-
assert.match(panel, /async function sendMessage\(extraChatParams = \{\}\) \{[\s\S]*?const tabId = currentTabId;[\s\S]*?if \(isConversationClearInProgress\(tabId\)\) return false;/, `${label}: Enter and programmatic sends should not bypass the pending-clear interlock`);
15120+
assert.match(panel, /async function sendMessage\(extraChatParams = \{\}\) \{[\s\S]*?const tabId = currentTabId;[\s\S]*?if \(isConversationClearInProgress\(tabId\)\) \{[\s\S]*?releaseOwnedContextMenuClaim\(\{ reason: 'conversation-clear', retryAfterMs: 1_000 \}\);[\s\S]*?return false;/, `${label}: Enter and programmatic sends should not bypass the pending-clear interlock`);
1512115121
assert.match(panel, /function suppressRunUpdatesForClearedConversation\(tabId\) \{[\s\S]*?localRunRequestIds\.get\(Number\(tabId\)\)[\s\S]*?clearedConversationRunRequestIds\.add\(requestId\)[\s\S]*?clearedConversationRunRequestIds\.size > 100/, `${label}: conversation clear should retain a bounded set of invalidated run requests`);
1512215122
assert.match(panel, /function handleAgentUpdateMessage\(msg\) \{[\s\S]*?if \(msg\.requestId && clearedConversationRunRequestIds\.has\(String\(msg\.requestId\)\)\) return;[\s\S]*?const eventAssistantEl = ensureCurrentRunAssistant\(msg\);/, `${label}: cleared-run updates should be rejected before they can recreate an assistant bubble`);
1512315123
assert.match(panel, /async function abortRun\(tabId = currentTabId\) \{[\s\S]*?sendToBackground\('abort', \{ tabId \}\)[\s\S]*?stopBtn\.addEventListener\('click', \(\) => abortRun\(\)\);/, `${label}: Stop should support a captured tab target without treating click events as tab ids`);
@@ -22329,7 +22329,7 @@ test('sidepanel long replies use reading-first turn navigation', () => {
2232922329
);
2233022330
assert.match(
2233122331
panel,
22332-
/if \(!text\) \{\s*scrollToBottom\(\{ force: true \}\);\s*inputEl\.value = '';/,
22332+
/if \(!text\) \{[\s\S]*?scrollToBottom\(\{ force: true \}\);\s*inputEl\.value = '';/,
2233322333
`${label}: fully consumed slash commands should reveal synchronous UI output`,
2233422334
);
2233522335
assert.match(
@@ -52480,7 +52480,7 @@ test('sidepanel: restored plan review cards rebind approve and cancel actions',
5248052480
assert.match(source, /sendToBackground\('agent_run_state'/, `${file} should ask background for active-run state`);
5248152481
assert.match(source, /const awaitingPlanReviewTabs = new Set\(\);/, `${file} should track tabs awaiting plan approval`);
5248252482
assert.match(source, /function isAwaitingPlanReviewForTab\(/, `${file} should detect awaiting plan-review tabs`);
52483-
assert.match(source, /if \(isAwaitingPlanReviewForTab\(tabId\)\) \{[\s\S]*?sp\.plan\.awaiting_review[\s\S]*?return false;/, `${file} should block normal sends while a plan awaits approval`);
52483+
assert.match(source, /if \(isAwaitingPlanReviewForTab\(tabId\)\) \{[\s\S]*?releaseOwnedContextMenuClaim\(\{ reason: 'plan-review-pending', retryAfterMs: 1_000 \}\);[\s\S]*?sp\.plan\.awaiting_review[\s\S]*?return false;/, `${file} should release claimed context-menu prompts before blocking sends for plan approval`);
5248452484
assert.match(source, /rebindPlanReviewCards\(\);/, `${file} should call the rebinder after chat restore`);
5248552485
assert.match(source, /plan-review-approve[\s\S]*submitPlanReview\(card, tabId, planId, 'approve'/, `${file} should rebind approve`);
5248652486
assert.match(source, /plan-review-change[\s\S]*setPlanReviewRawEditing\(card, enableRaw/, `${file} should toggle raw markdown via Edit as text`);

0 commit comments

Comments
 (0)