mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
fix: auto-continuation overly biased toward Claude-style output — breaks with non-Claude models (#1713)
* fix: auto-continuation overly biased toward Claude-style output When using non-Claude models (OpenAI, Gemini, local models, etc.), the continuation detection frequently fails, causing the agent to stop after each turn and wait for manual user input. Changes: - Expand CONTINUATION_SIGNALS verb list to include common verbs used by non-Claude models: process, download, upload, compile, train, evaluate, test, continue, generate, extract, merge, deploy, install, configure, refactor, optimize (plus their -ing forms) - Fix COMPLETION_MARKERS check to only block continuation when no continuation signal is present nearby (prevents false positives when words like 'complete' or 'done' appear mid-sentence) - Add soft fallback nudging when text has no terminal punctuation and no explicit completion signal (assumes model intends to continue by default) - Increase MAX_CONTINUATION_NUDGES from 3 to 20 for longer multi-step tasks - Pass 'interrupt' reason to abortController.abort() in the TUI control interrupt handler for consistent signal propagation Fixes #1707 * fix: add 429 retry, disable microcompact, disable command-queue abort - Add 429 rate limit retry with backoff for non-Claude providers (src/services/api/withRetry.ts) - Disable microcompact to prevent aggressive conversation trimming (src/query.ts) - Disable command-queue abort to prevent interrupting running tools (src/cli/print.ts) * fix: address PR review feedback - 429 retry: use separate counter instead of manipulating attempt - Soft fallback: remove aggressive fallback nudging all unpunctuated text - Microcompact: honor maxMessagesCompactionThreshold 'off' setting - Fix typecheck issues (pendingCacheEdits, imports) * fix: address second round of PR review - Remove 429 retry policy (out of scope for continuation fix) - Add presentProgressive check for 'complete. Now processing...' case - Restore command queue preemption (split to separate PR) - Fix PendingCacheEdits type for typecheck * fix: address third round of PR review - Fix completion guard condition (inverted logic — was blocking with continuation signals present) - Narrow presentProgressive to only 'Now [verb]ing' (avoid gerund false positives) - Restore original microcompact call (remove config gate) - Add 'interrupt' reason to permission deny abort calls * fix: address fourth round of PR review - [P1] Add 'continuing with' and 'proceeding to' to strongIntent regex so punctuated transition phrases survive terminal-punctuation gate - [P2] Pass 'interrupt' reason to abortController.abort() in PermissionContext.runHooks() for interactive permission hooks * fix: address fifth round of PR review - [P1] Add missing -ing gerund forms to line 15 (creating, writing, editing, running, checking, building, etc.) so the late gerund transition fix covers the original action verbs too - [P2] Add imperative/declarative signals for bare patterns: 'need to <verb>' ('Need to process files'), 'now <verb>' ('Now process files', with negative lookahead to avoid 'Now you' false positives), 'next (i|we) <verb>' ('Next I process files') * fix: address sixth round of PR review Addresses all 5 findings from jatmn's CHANGES_REQUESTED review: 1. (code-quality) Extract verb list to shared ACTION_VERBS array; build all continuation regexes from it via buildContinuationSignals() 2. (minor) Restrict presentProgressive to gerund forms of the same verb list instead of broad \w+ing 3. (nit) Remove accidental 'nul' entry from .gitignore 4. (medium) Add focused tests for new verbs, imperative patterns, present-progressive fallback, completion-marker guard, and verb-list deduplication 5. (minor) Add tests verifying MAX_CONTINUATION_NUDGES = 20 and the guard comparison * fix: address seventh round of PR review Addresses all 4 findings from jatmn's CHANGES_REQUESTED review: 1. [P1] Remove dead 'take' branch from VERB_ING gerund map 2. [P2] Pass 'interrupt' reason to abort() in print.ts SIGINT handler and PermissionContext.ts cancelAndAbort path 3. [P3] Fix tab indentation in query.ts, PermissionPromptToolResultSchema.ts, and permissions.ts 4. [P3] Update PR description to match actual code changes * fix: address PR review feedback (indentation, filter-based verb exclusion) - Fix indentation (2-space style) in PermissionPromptToolResultSchema.ts else-if block (jatmn review finding 1) - Use ACTION_VERBS.filter() instead of fragile v.replace(/^do\|/, '') for 'time to' regex (finding 2) - Clarify default fallback behavior in PR description (finding 4) - Note: queryLifecycle cleanup (finding 3) not applicable — field does not exist in this codebase * fix: punctuated imperative/declarative patterns now signal continuation intent The new imperative patterns (need to <verb>, now <verb>, next i/we <verb>) matched in the late-window signal check but were silently dropped when the text had terminal punctuation, because the punctuated branch only checked strongIntent / presentProgressive / endsWithColon. This left examples like 'Need to process files.' and 'Now create the component.' returning shouldNudge: false, even though the bare variants correctly returned true. Fix: add hasImperativeSignal to the punctuated gate, re-testing lowerText against the imperative patterns so punctuated action-intent signals are recognized. Also narrows the #1707 closure claim in the PR description: the default fallback remains shouldNudge: false (the broader inversion suggested in the issue introduced false positives in earlier rounds). Tests added for punctuated imperative variants. * fix: address ninth round of PR review - [Medium] Restore abortController.abort('interrupt') in onInterrupt() bridge/SDK callback to keep interrupt-reason fix consistent - [Minor] Normalize indent (tabs -> 2-space) in imperative/declarative test block in bugfixes.test.ts - [Nit] Use lateText (last 120 chars) for hasImperativeSignal check for consistency with surrounding late-window logic - queryLifecycle removal is intentional cleanup from earlier round; no longer referenced in callModel options * fix: tighten need-to pattern to exclude subject-led advice (You need to...) CodeRabbit review flagged that the `need to` continuation pattern was overmatching subject-led advice like "You need to update..." causing false-positive nudges. Added negative lookbehind `(?<!\b(?:you|i|we|...)\s+)` to both `CONTINUATION_SIGNALS` and `hasImperativeSignal` so only bare imperatives (no subject) trigger continuation. Added regression tests. Also confirmed three earlier findings were already fixed: - print.ts onInterrupt() already uses abort('interrupt') ✅ - Indentation in bugfixes.test.ts:172-200 already 2-space ✅ - hasImperativeSignal already uses lateText ✅ - queryLifecycle removal was intentional cleanup ✅
This commit is contained in:
@@ -144,6 +144,130 @@ describe('Agent loop continuation nudge', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fix 3b: Expanded continuation coverage (PR #1713 review feedback)
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('Expanded continuation coverage', () => {
|
||||
test('newly added verbs trigger continuation', async () => {
|
||||
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
|
||||
|
||||
// Verbs added in the PR: process, download, compile, train, evaluate, etc.
|
||||
expect(analyzeContinuationIntent("Now I will process the data").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Let me download the file").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Time to compile the source").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("I need to train the model").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("So now I will evaluate the results").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Now I'll test the endpoint").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Let me extract the archive").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("I will merge the changes").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Time to deploy to production").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Now I will install the package").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("I need to configure the server").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Let me refactor this component").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Time to optimize the query").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Now I will upload the artifact").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("I need to convert the format").shouldNudge).toBe(true)
|
||||
})
|
||||
|
||||
test('imperative / declarative patterns trigger continuation', async () => {
|
||||
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
|
||||
|
||||
// "Need to ..." pattern
|
||||
expect(analyzeContinuationIntent("Need to update the config").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Need to process these files").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Need to deploy the changes").shouldNudge).toBe(true)
|
||||
|
||||
// "Now ..." pattern (without subject)
|
||||
expect(analyzeContinuationIntent("Now create the component").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Now run the tests").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Now compile everything").shouldNudge).toBe(true)
|
||||
|
||||
// "Now ..." should NOT match "Now you ..." (excluded by negative lookahead)
|
||||
expect(analyzeContinuationIntent("Now you can run the app").shouldNudge).toBe(false)
|
||||
|
||||
// "Next I/We ..." pattern
|
||||
expect(analyzeContinuationIntent("Next I will fix the bug").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Next we need to add tests").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Next I should deploy").shouldNudge).toBe(true)
|
||||
|
||||
// Punctuated variants should also signal intent (Reviewer Feedback)
|
||||
expect(analyzeContinuationIntent("Need to process the files.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Need to deploy the changes.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Now create the component.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Now run the tests.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Next I will fix the bug.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Next we need to add tests.").shouldNudge).toBe(true)
|
||||
|
||||
// "Need to ..." should NOT match subject-led advice ("You need to...", "We need to...")
|
||||
// ("I need to..." is correctly caught by strongIntent as agent's own intent)
|
||||
expect(analyzeContinuationIntent("You need to update the config.").shouldNudge).toBe(false)
|
||||
expect(analyzeContinuationIntent("You need to process these files.").shouldNudge).toBe(false)
|
||||
expect(analyzeContinuationIntent("You need to update the config").shouldNudge).toBe(false)
|
||||
expect(analyzeContinuationIntent("We need to deploy the changes.").shouldNudge).toBe(false)
|
||||
})
|
||||
|
||||
test('present-progressive fallback triggers continuation', async () => {
|
||||
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
|
||||
|
||||
// "now processing", "now compiling", "now deploying" with restricted verb list
|
||||
expect(analyzeContinuationIntent("Task done. Now processing the next batch.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Finished step 1. Now compiling the assets.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Complete. Now deploying to staging.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("All set. Now testing the endpoint.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Done. Now installing dependencies.").shouldNudge).toBe(true)
|
||||
|
||||
// Should NOT match passive/non-action ing-words (regression guard for review feedback)
|
||||
expect(analyzeContinuationIntent("Now being processed by the system").shouldNudge).toBe(false)
|
||||
expect(analyzeContinuationIntent("Now waiting for user input").shouldNudge).toBe(false)
|
||||
expect(analyzeContinuationIntent("Now having some issues").shouldNudge).toBe(false)
|
||||
})
|
||||
|
||||
test('completion marker correctly suppressed by nearby continuation signal', async () => {
|
||||
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
|
||||
|
||||
// "complete" appears mid-sentence before continuation signal — should nudge
|
||||
expect(analyzeContinuationIntent("The download is complete. Now processing the files.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("The analysis is done. Let me update the report.").shouldNudge).toBe(true)
|
||||
expect(analyzeContinuationIntent("Compilation finished. Now deploying the build.").shouldNudge).toBe(true)
|
||||
|
||||
// "done" at the very end without continuation signal — no nudge
|
||||
expect(analyzeContinuationIntent("All tests pass. Task done.").shouldNudge).toBe(false)
|
||||
expect(analyzeContinuationIntent("The implementation is complete.").shouldNudge).toBe(false)
|
||||
})
|
||||
|
||||
test('shared verb list in continuation.ts avoids duplication', async () => {
|
||||
const content = await file('utils/continuation.ts').text()
|
||||
|
||||
// Should have ACTION_VERBS array and build regexes from it
|
||||
expect(content).toContain('ACTION_VERBS')
|
||||
expect(content).toContain('buildContinuationSignals')
|
||||
expect(content).toContain('VERB_ALT')
|
||||
expect(content).toContain('VERB_ING')
|
||||
|
||||
// The verb list should appear only once as an array definition,
|
||||
// not repeated across multiple inline regexes
|
||||
const verbDeclarations = content.match(/ACTION_VERBS\s*=\s*\[/g)
|
||||
expect(verbDeclarations).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('MAX_CONTINUATION_NUDGES limit', () => {
|
||||
test('MAX_CONTINUATION_NUDGES is set to 20', async () => {
|
||||
const content = await file('query.ts').text()
|
||||
|
||||
const match = content.match(/MAX_CONTINUATION_NUDGES\s*=\s*(\d+)/)
|
||||
expect(match).not.toBeNull()
|
||||
expect(Number(match![1])).toBe(20)
|
||||
})
|
||||
|
||||
test('nudge count is compared to MAX_CONTINUATION_NUDGES', async () => {
|
||||
const content = await file('query.ts').text()
|
||||
|
||||
// The guard must exist: continuationNudgeCount < MAX_CONTINUATION_NUDGES
|
||||
expect(content).toContain('continuationNudgeCount < MAX_CONTINUATION_NUDGES')
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fix 4: Web search result count improvements
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+1
-1
@@ -1041,7 +1041,7 @@ function runHeadlessStreaming(
|
||||
const sigintHandler = () => {
|
||||
logForDiagnosticsNoPII('info', 'shutdown_signal', { signal: 'SIGINT' })
|
||||
if (abortController && !abortController.signal.aborted) {
|
||||
abortController.abort()
|
||||
abortController.abort('interrupt')
|
||||
}
|
||||
void gracefulShutdown(0)
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ function createPermissionContext(
|
||||
logForDebugging(
|
||||
`Aborting: tool=${tool.name} isAbort=${isAbort} hasFeedback=${!!feedback} isSubagent=${sub}`,
|
||||
)
|
||||
toolUseContext.abortController.abort()
|
||||
toolUseContext.abortController.abort('interrupt')
|
||||
}
|
||||
return { behavior: 'ask', message, contentBlocks }
|
||||
},
|
||||
@@ -282,7 +282,7 @@ function createPermissionContext(
|
||||
logForDebugging(
|
||||
`Hook interrupt: tool=${tool.name} hookMessage=${decision.message}`,
|
||||
)
|
||||
toolUseContext.abortController.abort()
|
||||
toolUseContext.abortController.abort('interrupt')
|
||||
}
|
||||
return this.buildDeny(
|
||||
decision.message || 'Permission denied by hook',
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ function* yieldMissingToolResultBlocks(
|
||||
* rules, ye will be punished with an entire day of debugging and hair pulling.
|
||||
*/
|
||||
const MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3
|
||||
const MAX_CONTINUATION_NUDGES = 3
|
||||
const MAX_CONTINUATION_NUDGES = 20
|
||||
|
||||
function formatAutoCompactRetryDelay(delayMs: number): string {
|
||||
const totalSeconds = Math.max(1, Math.ceil(delayMs / 1000))
|
||||
|
||||
+107
-20
@@ -5,23 +5,100 @@ import { tokenCountWithEstimation } from './tokens.js'
|
||||
* but stopped (potentially due to truncation or missed tool calls).
|
||||
*/
|
||||
|
||||
export const CONTINUATION_SIGNALS = [
|
||||
// English: Action-transition phrases (requires intent + action)
|
||||
/\bso now (i|let me|we) (need to|have to|should|must|will) (do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|start|begin|apply|identify|inspect|analyze|review|search)\b/i,
|
||||
/\bnow i('ll| will) (do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|go|proceed|start|begin|apply|identify|inspect|analyze|review|search)\b/i,
|
||||
/\bi (will|shall|now|need to|have to|must|should) (now )?(do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|go|proceed|start|begin|apply|identify|inspect|analyze|review|search)\b/i,
|
||||
/\blet me (go ahead and |now )?(do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|proceed|start|begin|apply|update|create|identify|inspect|analyze|review|search|summarize)\b/i,
|
||||
/\btime to (do|create|write|edit|update|fix|implement|add|run|check|make|build|get started|begin|start|inspect|analyze|review|search)\b/i,
|
||||
/\b(moving on to|next step is to|starting to|proceeding to|applying (the|these) changes|inspecting|analyzing|reviewing|searching)\b/i,
|
||||
// French: Support for common continuation phrasing (relaxed boundaries for accents and apostrophes)
|
||||
/(^|\s)(je passe (à|au)|ensuite|l'étape suivante est de|je continue avec|au suivant|passons à|je reviens vers vous|je suis en train d'|je vais maintenant)(\s|$|[a-zà-ÿ])/i,
|
||||
/(^|\s)(je (vais|dois|dois maintenant|vais maintenant) (faire|créer|écrire|modifier|ajouter|tester|vérifier|lancer|exécuter|procéder|démarrer|commencer|identifier|analyser|inspecter|revoir|chercher))(\s|$|[a-zà-ÿ])/i,
|
||||
/(^|\s)((lancement|exécution|vérification|modification|mise à jour|analyse|inspection|recherche) de)(\s|$|[a-zà-ÿ])/i,
|
||||
// Universal: Sentence ending with a colon indicates intent to list/act
|
||||
/:\s*$/,
|
||||
// Universal: Open task marker indicates pending work
|
||||
/◻/,
|
||||
]
|
||||
// Shared verb list used across all continuation patterns.
|
||||
// Build regexes from this array so maintenance stays in one place.
|
||||
const ACTION_VERBS = [
|
||||
'do',
|
||||
'create',
|
||||
'write',
|
||||
'edit',
|
||||
'update',
|
||||
'fix',
|
||||
'implement',
|
||||
'add',
|
||||
'run',
|
||||
'check',
|
||||
'make',
|
||||
'build',
|
||||
'set up',
|
||||
'start',
|
||||
'begin',
|
||||
'go',
|
||||
'proceed',
|
||||
'apply',
|
||||
'identify',
|
||||
'inspect',
|
||||
'analyze',
|
||||
'review',
|
||||
'search',
|
||||
'process',
|
||||
'download',
|
||||
'upload',
|
||||
'convert',
|
||||
'compile',
|
||||
'train',
|
||||
'evaluate',
|
||||
'test',
|
||||
'continue',
|
||||
'generate',
|
||||
'extract',
|
||||
'merge',
|
||||
'deploy',
|
||||
'install',
|
||||
'configure',
|
||||
'refactor',
|
||||
'optimize',
|
||||
'summarize',
|
||||
] as const
|
||||
|
||||
// Base verb alternatives used across most regexes (no "summarize" in older patterns, but harmless)
|
||||
const VERB_ALT = ACTION_VERBS.join('|')
|
||||
|
||||
// Gerund forms for progressive/participle patterns
|
||||
const VERB_ING = ACTION_VERBS.map(v => {
|
||||
// Handle special cases: "set up" -> "setting up", "do" -> "doing"
|
||||
if (v === 'set up') return 'setting up'
|
||||
if (v === 'do') return 'doing'
|
||||
if (v === 'go') return 'going'
|
||||
if (v === 'run') return 'running'
|
||||
if (v === 'begin') return 'beginning'
|
||||
if (v === 'make') return 'making'
|
||||
if (v === 'write') return 'writing'
|
||||
// Default: add -ing
|
||||
return v.replace(/e$/, '') + 'ing'
|
||||
}).join('|')
|
||||
|
||||
// Build continuation-signal regexes from the shared verb list.
|
||||
// (Using function to keep construction readable.)
|
||||
function buildContinuationSignals(): RegExp[] {
|
||||
const v = VERB_ALT
|
||||
// "time to" needs "do" explicitly, but the rest of the verb list without "do"
|
||||
// (use filtered array instead of string.replace so reordering ACTION_VERBS doesn't break it)
|
||||
const vWithoutDo = ACTION_VERBS.filter(a => a !== 'do').join('|')
|
||||
return [
|
||||
// English: Action-transition phrases (requires intent + action)
|
||||
new RegExp(`\\bso now (i|let me|we) (need to|have to|should|must|will) (${v})\\b`, 'i'),
|
||||
new RegExp(`\\bnow i('ll| will) (${v})\\b`, 'i'),
|
||||
new RegExp(`\\bi (will|shall|now|need to|have to|must|should) (now )?(${v})\\b`, 'i'),
|
||||
new RegExp(`\\blet me (go ahead and |now )?(${v})\\b`, 'i'),
|
||||
new RegExp(`\\btime to (do|${vWithoutDo}|get started|begin|start)\\b`, 'i'),
|
||||
new RegExp(`\\b(moving on to|next step is to|starting to|proceeding to|continuing with|applying (the|these) changes|${VERB_ING})\\b`, 'i'),
|
||||
// French: Support for common continuation phrasing (relaxed boundaries for accents and apostrophes)
|
||||
/(^|\s)(je passe (à|au)|ensuite|l'étape suivante est de|je continue avec|au suivant|passons à|je reviens vers vous|je suis en train d'|je vais maintenant)(\s|$|[a-zà-ÿ])/i,
|
||||
/(^|\s)(je (vais|dois|dois maintenant|vais maintenant) (faire|créer|écrire|modifier|ajouter|tester|vérifier|lancer|exécuter|procéder|démarrer|commencer|identifier|analyser|inspecter|revoir|chercher))(\s|$|[a-zà-ÿ])/i,
|
||||
/(^|\s)((lancement|exécution|vérification|modification|mise à jour|analyse|inspection|recherche) de)(\s|$|[a-zà-ÿ])/i,
|
||||
// Universal: Sentence ending with a colon indicates intent to list/act
|
||||
/:\s*$/,
|
||||
// Universal: Open task marker indicates pending work
|
||||
/◻/,
|
||||
// Imperative/declarative patterns (no subject required)
|
||||
new RegExp(`(?<!\\b(?:you|i|we|they|he|she|it)\\s+)\\bneed to (${v})\\b`, 'i'),
|
||||
new RegExp(`\\bnow (${v})\\b(?!\\s+you\\b)`, 'i'),
|
||||
new RegExp(`\\bnext (i|we)\\s+(need to|will|shall|should|must)?\\s*(${v})\\b`, 'i'),
|
||||
]
|
||||
}
|
||||
|
||||
export const CONTINUATION_SIGNALS = buildContinuationSignals()
|
||||
|
||||
export const COMPLETION_MARKERS = /\b(done|finished|completed|complete|summary|that's all|that is all|all set|hope this helps|let me know if|no issues|lgtm)\b/i
|
||||
|
||||
@@ -99,10 +176,17 @@ export function analyzeContinuationIntent(
|
||||
// it's a strong 1st person intent or open tasks are present.
|
||||
const hasTerminalPunctuation = /[.!??"'`)\]]\s*$/.test(lastText) || lastText.endsWith('`')
|
||||
if (hasTerminalPunctuation) {
|
||||
const strongIntent = /\b(i (will|shall|need to|must|should|now)|let (me|us)|je (vais|reviens)|passons à|moving on to|next step is to)\b/i.test(lowerText) ||
|
||||
const strongIntent = /\b(i (will|shall|need to|must|should|now)|let (me|us)|je (vais|reviens)|passons à|moving on to|continuing with|proceeding to|next step is to)\b/i.test(lowerText) ||
|
||||
/je suis en train d'/i.test(lowerText) || /◻/.test(lastText)
|
||||
const presentProgressive = new RegExp(`\\bnow (?:${VERB_ING})\\b`, 'i').test(lateText)
|
||||
// Imperative/declarative patterns also signal intent when punctuated
|
||||
// (e.g. "Need to process files.", "Now create the component.", "Next we need to add tests.")
|
||||
// Use lateText (last 120 chars) for consistency with the late-window intent check above.
|
||||
const hasImperativeSignal = new RegExp(`(?<!\\b(?:you|i|we|they|he|she|it)\\s+)\\bneed to (?:${VERB_ALT})\\b`, 'i').test(lateText) ||
|
||||
new RegExp(`\\bnow (?:${VERB_ALT})\\b(?!\\s+you\\b)`, 'i').test(lateText) ||
|
||||
new RegExp(`\\bnext (?:i|we)\\s+(?:need to|will|shall|should|must)?\\s*(?:${VERB_ALT})\\b`, 'i').test(lateText)
|
||||
const endsWithColon = /:\s*$/.test(lastText)
|
||||
if (strongIntent || endsWithColon) {
|
||||
if (strongIntent || endsWithColon || presentProgressive || hasImperativeSignal) {
|
||||
return { shouldNudge: true, reason: 'continuation_signal' }
|
||||
}
|
||||
} else {
|
||||
@@ -111,7 +195,10 @@ export function analyzeContinuationIntent(
|
||||
}
|
||||
|
||||
// 3. Completion Marker Guard (Final check for sound, completed messages)
|
||||
if (COMPLETION_MARKERS.test(lowerText)) {
|
||||
// Only block continuation if no continuation signal is present (prevents false
|
||||
// positives when "complete" or "done" appears mid-sentence, e.g. "The download
|
||||
// is complete. Now processing the files...")
|
||||
if (COMPLETION_MARKERS.test(lowerText) && !hasLateContinuationSignal && !CONTINUATION_SIGNALS.some(re => re.test(lowerText))) {
|
||||
return { shouldNudge: false }
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export function permissionPromptToolResultToPermissionDecision(
|
||||
logForDebugging(
|
||||
`SDK permission prompt deny+interrupt: tool=${tool.name} message=${result.message}`,
|
||||
)
|
||||
toolUseContext.abortController.abort()
|
||||
toolUseContext.abortController.abort('interrupt')
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
|
||||
@@ -466,7 +466,7 @@ async function runPermissionRequestHooksForHeadlessAgent(
|
||||
logForDebugging(
|
||||
`Hook interrupt: tool=${tool.name} hookMessage=${decision.message}`,
|
||||
)
|
||||
context.abortController.abort()
|
||||
context.abortController.abort('interrupt')
|
||||
}
|
||||
return {
|
||||
behavior: 'deny',
|
||||
|
||||
Reference in New Issue
Block a user