Skip to content

Commit 97c02a8

Browse files
authored
Merge pull request #79 from esokullu/codex/skip-planner-short-followups
Skip planner for short follow-up turns
2 parents f7f1793 + 51425cf commit 97c02a8

3 files changed

Lines changed: 347 additions & 2 deletions

File tree

src/chrome/src/agent/agent.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class Agent {
7575
this._progressSessionCounter = 0;
7676
this.conversationModes = new Map(); // tabId -> 'ask' | 'act' | 'dev'
7777
this.conversationIds = new Map(); // tabId -> stable conversationId (regenerated on clearConversation)
78+
this.plannerFollowUpSkipTabs = new Set(); // tabIds allowed one short follow-up after an approved try-mode plan
7879
this.hydratedTabs = new Set(); // tabIds we've already pulled from storage
7980
this.persistTimers = new Map(); // tabId -> debounce handle
8081
this.abortFlags = new Map(); // tabId -> boolean
@@ -287,6 +288,7 @@ export class Agent {
287288
}
288289

289290
clearScratchpad(tabId) {
291+
this.plannerFollowUpSkipTabs.delete(tabId);
290292
const messages = this.conversations.get(tabId);
291293
if (!messages) {
292294
return { success: true, existed: false, note: 'scratchpad already empty' };
@@ -3896,6 +3898,44 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
38963898
return this._plannerMode() !== 'off';
38973899
}
38983900

3901+
_messageHasPlannerFollowUpAttachmentBlocks(message) {
3902+
const content = message?.content ?? message;
3903+
if (!Array.isArray(content)) return false;
3904+
return content.some(block => {
3905+
if (!block || typeof block !== 'object') return false;
3906+
if (block.type !== 'text') return true;
3907+
const text = String(block.text || '');
3908+
return text.startsWith('[UNTRUSTED USER ATTACHMENTS') || text.startsWith('[UNTRUSTED DOCUMENT');
3909+
});
3910+
}
3911+
3912+
_plannerFollowUpText(message) {
3913+
const text = this._stripInjectedTaskContext(userMessageToText(message));
3914+
return text.replace(/\s+/g, ' ').trim();
3915+
}
3916+
3917+
_plannerFollowUpHasExplicitUrl(text) {
3918+
return /(?:https?:\/\/|www\.)\S+|\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}(?:\/[^\s]*)?/i.test(String(text || ''));
3919+
}
3920+
3921+
_hasApprovedPlannerHandoff(messages) {
3922+
const idx = this._findScratchpadIndex(messages || []);
3923+
if (idx < 0) return false;
3924+
const body = this._extractScratchpadBody(messages[idx].content);
3925+
return /\[Approved plan\b[^\]]*pinned by planner\]/.test(body);
3926+
}
3927+
3928+
_shouldSkipPlannerForShortFollowUp(tabId, priorMessages, enriched, plannerMode) {
3929+
if (plannerMode !== 'try') return false;
3930+
if (this._normalizePlanReviewMode(this.planReviewMode) === 'always') return false;
3931+
if (!this.plannerFollowUpSkipTabs.has(tabId)) return false;
3932+
if (!this._hasApprovedPlannerHandoff(priorMessages)) return false;
3933+
if (this._messageHasPlannerFollowUpAttachmentBlocks(enriched)) return false;
3934+
const text = this._plannerFollowUpText(enriched);
3935+
if (!text || text.length > 100) return false;
3936+
return !this._plannerFollowUpHasExplicitUrl(text);
3937+
}
3938+
38993939
/**
39003940
* Plan-before-Act gate: push user message, pin approved plan after it, or stop early.
39013941
*/
@@ -3910,7 +3950,15 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
39103950
const priorMessages = runPlanner ? messages.slice() : null;
39113951
messages.push(enriched);
39123952
this._persist(tabId);
3913-
if (!runPlanner) return { proceed: true };
3953+
if (!runPlanner) {
3954+
this.plannerFollowUpSkipTabs.delete(tabId);
3955+
return { proceed: true };
3956+
}
3957+
if (this._shouldSkipPlannerForShortFollowUp(tabId, priorMessages, enriched, plannerMode)) {
3958+
this.plannerFollowUpSkipTabs.delete(tabId);
3959+
return { proceed: true };
3960+
}
3961+
this.plannerFollowUpSkipTabs.delete(tabId);
39143962

39153963
const historyDigest = this._buildPlannerHistoryDigest(priorMessages);
39163964
const gate = await this._runPlannerGate(tabId, enriched, onUpdate, costState, runId, historyDigest, tabInfo, plannerMode);
@@ -3935,6 +3983,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
39353983
// Note: the "Plan approved — running…" confirmation is rendered locally
39363984
// by submitPlanReview in the sidepanel, so there's no plan_approved
39373985
// agent_update to emit here (no handler consumed it).
3986+
if (plannerMode === 'try') this.plannerFollowUpSkipTabs.add(tabId);
39383987
}
39393988
this._persist(tabId);
39403989
}
@@ -4962,6 +5011,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
49625011
this._cancelClarifications(tabId, 'conversation cleared');
49635012
this._cancelPendingPlans(tabId, 'conversation cleared');
49645013
this.conversations.delete(tabId);
5014+
this.plannerFollowUpSkipTabs.delete(tabId);
49655015
this.progressLedgers.delete(tabId);
49665016
this.progressPageScopes.delete(tabId);
49675017
this.progressSessions.delete(tabId);

src/firefox/src/agent/agent.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export class Agent {
6767
this._progressSessionCounter = 0;
6868
this.conversationIds = new Map(); // tabId -> stable conversationId (regenerated on clearConversation)
6969
this.conversationModes = new Map(); // tabId -> 'ask' | 'act' | 'dev'
70+
this.plannerFollowUpSkipTabs = new Set(); // tabIds allowed one short follow-up after an approved try-mode plan
7071
this.hydratedTabs = new Set(); // tabIds we've already pulled from storage
7172
this.persistTimers = new Map(); // tabId -> debounce handle
7273
this.abortFlags = new Map(); // tabId -> boolean
@@ -281,6 +282,7 @@ export class Agent {
281282
}
282283

283284
clearScratchpad(tabId) {
285+
this.plannerFollowUpSkipTabs.delete(tabId);
284286
const messages = this.conversations.get(tabId);
285287
if (!messages) {
286288
return { success: true, existed: false, note: 'scratchpad already empty' };
@@ -3174,6 +3176,44 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
31743176
return this._plannerMode() !== 'off';
31753177
}
31763178

3179+
_messageHasPlannerFollowUpAttachmentBlocks(message) {
3180+
const content = message?.content ?? message;
3181+
if (!Array.isArray(content)) return false;
3182+
return content.some(block => {
3183+
if (!block || typeof block !== 'object') return false;
3184+
if (block.type !== 'text') return true;
3185+
const text = String(block.text || '');
3186+
return text.startsWith('[UNTRUSTED USER ATTACHMENTS') || text.startsWith('[UNTRUSTED DOCUMENT');
3187+
});
3188+
}
3189+
3190+
_plannerFollowUpText(message) {
3191+
const text = this._stripInjectedTaskContext(userMessageToText(message));
3192+
return text.replace(/\s+/g, ' ').trim();
3193+
}
3194+
3195+
_plannerFollowUpHasExplicitUrl(text) {
3196+
return /(?:https?:\/\/|www\.)\S+|\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}(?:\/[^\s]*)?/i.test(String(text || ''));
3197+
}
3198+
3199+
_hasApprovedPlannerHandoff(messages) {
3200+
const idx = this._findScratchpadIndex(messages || []);
3201+
if (idx < 0) return false;
3202+
const body = this._extractScratchpadBody(messages[idx].content);
3203+
return /\[Approved plan\b[^\]]*pinned by planner\]/.test(body);
3204+
}
3205+
3206+
_shouldSkipPlannerForShortFollowUp(tabId, priorMessages, enriched, plannerMode) {
3207+
if (plannerMode !== 'try') return false;
3208+
if (this._normalizePlanReviewMode(this.planReviewMode) === 'always') return false;
3209+
if (!this.plannerFollowUpSkipTabs.has(tabId)) return false;
3210+
if (!this._hasApprovedPlannerHandoff(priorMessages)) return false;
3211+
if (this._messageHasPlannerFollowUpAttachmentBlocks(enriched)) return false;
3212+
const text = this._plannerFollowUpText(enriched);
3213+
if (!text || text.length > 100) return false;
3214+
return !this._plannerFollowUpHasExplicitUrl(text);
3215+
}
3216+
31773217
async _maybeRunPlannerGate(tabId, messages, enriched, onUpdate, mode, costState, runId, tabInfo = null) {
31783218
const plannerMode = this._isActionMode(mode) ? this._plannerMode() : 'off';
31793219
const runPlanner = plannerMode !== 'off';
@@ -3185,7 +3225,15 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
31853225
const priorMessages = runPlanner ? messages.slice() : null;
31863226
messages.push(enriched);
31873227
this._persist(tabId);
3188-
if (!runPlanner) return { proceed: true };
3228+
if (!runPlanner) {
3229+
this.plannerFollowUpSkipTabs.delete(tabId);
3230+
return { proceed: true };
3231+
}
3232+
if (this._shouldSkipPlannerForShortFollowUp(tabId, priorMessages, enriched, plannerMode)) {
3233+
this.plannerFollowUpSkipTabs.delete(tabId);
3234+
return { proceed: true };
3235+
}
3236+
this.plannerFollowUpSkipTabs.delete(tabId);
31893237

31903238
const historyDigest = this._buildPlannerHistoryDigest(priorMessages);
31913239
const gate = await this._runPlannerGate(tabId, enriched, onUpdate, costState, runId, historyDigest, tabInfo, plannerMode);
@@ -3209,6 +3257,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
32093257
// Note: the "Plan approved — running…" confirmation is rendered locally
32103258
// by submitPlanReview in the sidepanel, so there's no plan_approved
32113259
// agent_update to emit here (no handler consumed it).
3260+
if (plannerMode === 'try') this.plannerFollowUpSkipTabs.add(tabId);
32123261
}
32133262
this._persist(tabId);
32143263
}
@@ -4071,6 +4120,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
40714120
this._cancelClarifications(tabId, 'conversation cleared');
40724121
this._cancelPendingPlans(tabId, 'conversation cleared');
40734122
this.conversations.delete(tabId);
4123+
this.plannerFollowUpSkipTabs.delete(tabId);
40744124
this.progressLedgers.delete(tabId);
40754125
this.progressPageScopes.delete(tabId);
40764126
this.progressSessions.delete(tabId);

0 commit comments

Comments
 (0)