diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 456e88609..c32a93df4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed the todo completion reminder still firing while the agent was waiting on a user question asked in a non-English language. The `isAwaitingUserAnswer` guard only recognized English question words and pronouns, so a `?`/`?`-terminated Chinese, Japanese, Korean, or Spanish prompt (e.g. `我应该继续吗?`) went undetected and the `` interrupted the pause — which the model then misread as the user's answer and acted on. A trailing question mark plus any non-ASCII character in the line now counts as a pending question ([#7803](https://github.com/can1357/oh-my-pi/issues/7803)). + ## [17.2.9] - 2026-08-05 ### Breaking Changes diff --git a/packages/coding-agent/src/session/todo-tracker.ts b/packages/coding-agent/src/session/todo-tracker.ts index 8174cc100..0a0875ee9 100644 --- a/packages/coding-agent/src/session/todo-tracker.ts +++ b/packages/coding-agent/src/session/todo-tracker.ts @@ -27,6 +27,15 @@ const QUESTION_PROMPT_RE = const USER_DIRECTED_PROMPT_RE = /\b(?:you|your|we|our)\b/i; const USER_RESPONSE_CUE_RE = /^(?:please\s+)?(?:confirm|reply|choose|pick|decide|advise)\b|^(?:please\s+)?answer\b|^(?:please\s+)?(?:let\s+me\s+know|tell\s+me)\b/i; +/** + * A trailing question mark is the universal signal that a line is a question, but + * the English word/pronoun gates above exist to filter incidental "?" out of prose + * (e.g. a TypeScript `foo?: string` tail). Non-English text has no cheap word list, + * yet any non-ASCII character in a "?"/"?"-terminated line reliably marks it as + * genuine prose — CJK/Japanese/Korean, Spanish `¿…?`, accented Latin — so treat it + * as a real user-directed question. Fixes non-Latin prompts going undetected (#7803). + */ +const NON_ASCII_TEXT_RE = /[^\x00-\x7F]/; interface PromptLine { text: string; @@ -361,7 +370,8 @@ function isQuestionPromptLine(line: string): boolean { return ( candidate.hadPromptLabel || QUESTION_PROMPT_RE.test(candidate.text) || - USER_DIRECTED_PROMPT_RE.test(candidate.text) + USER_DIRECTED_PROMPT_RE.test(candidate.text) || + NON_ASCII_TEXT_RE.test(candidate.text) ); } diff --git a/packages/coding-agent/test/agent-session-todo-reminder-loop.test.ts b/packages/coding-agent/test/agent-session-todo-reminder-loop.test.ts index 8912742df..9f8e056fa 100644 --- a/packages/coding-agent/test/agent-session-todo-reminder-loop.test.ts +++ b/packages/coding-agent/test/agent-session-todo-reminder-loop.test.ts @@ -186,6 +186,17 @@ describe("AgentSession todo reminder self-continuation suppression", () => { expect(continueSpy).not.toHaveBeenCalled(); }); + it("does not remind or continue when the assistant yields with a non-English (Chinese) question", async () => { + const continueSpy = vi.spyOn(session.agent, "continue").mockResolvedValue(); + + emitTextOnlyStop("我遇到一个需要你决定的问题:是否应该继续删除旧的配置文件?"); + await session.waitForIdle(); + + expect(reminderAttempts).toEqual([]); + expect(todoReminderTranscriptEntry()).toBeUndefined(); + expect(continueSpy).not.toHaveBeenCalled(); + }); + it("still reminds when the assistant answers its own prompt-shaped question", async () => { const continueSpy = vi.spyOn(session.agent, "continue").mockResolvedValue();