Guard against phantom "I'll be notified" dead-end finals at delivery
An agent could end a turn with "I'll wait for the build to complete — I'll be notified" without registering any watcher, so nothing ever resumed and the user was left staring at a dead-end (observed in the tts_site room). The prompt rule alone did not stop the model. At final-delivery time, when the final reads like such a phantom-notification dead-end AND no CI watcher is active for the chat, append an explicit notice telling the user no notification is coming and to send a follow-up — turning a silent hang into an actionable prompt. Pure detector + guard with unit tests; wired into the owner/reviewer final-delivery path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
76
src/phantom-notification-guard.test.ts
Normal file
76
src/phantom-notification-guard.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
applyPhantomNotificationGuard,
|
||||
isPhantomNotificationDeadEnd,
|
||||
PHANTOM_NOTIFICATION_NOTICE,
|
||||
} from './phantom-notification-guard.js';
|
||||
|
||||
describe('isPhantomNotificationDeadEnd', () => {
|
||||
it('detects the reported "I\'ll wait for the build … I\'ll be notified" final', () => {
|
||||
expect(
|
||||
isPhantomNotificationDeadEnd(
|
||||
"I'll wait for the build to complete — I'll be notified.",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('detects close variants of the notification promise', () => {
|
||||
expect(isPhantomNotificationDeadEnd('I will be notified when it finishes.')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
isPhantomNotificationDeadEnd('Waiting for the deploy to finish now.'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isPhantomNotificationDeadEnd("I'll be notified once CI is green."),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('does not flag ordinary status finals', () => {
|
||||
expect(
|
||||
isPhantomNotificationDeadEnd(
|
||||
'TASK_DONE\n빌드를 끝냈고 테스트 1597개가 모두 통과했습니다.',
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isPhantomNotificationDeadEnd('The build completed and I pushed the fix.'),
|
||||
).toBe(false);
|
||||
expect(isPhantomNotificationDeadEnd('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyPhantomNotificationGuard', () => {
|
||||
const deadEnd = "I'll wait for the build to complete — I'll be notified.";
|
||||
|
||||
it('appends the follow-up notice for a dead-end final with no active watcher', () => {
|
||||
const result = applyPhantomNotificationGuard(deadEnd, {
|
||||
hasActiveWatcher: false,
|
||||
});
|
||||
expect(result.startsWith(deadEnd)).toBe(true);
|
||||
expect(result).toContain(PHANTOM_NOTIFICATION_NOTICE.trim());
|
||||
});
|
||||
|
||||
it('leaves the final untouched when a watcher is actually active', () => {
|
||||
expect(
|
||||
applyPhantomNotificationGuard(deadEnd, { hasActiveWatcher: true }),
|
||||
).toBe(deadEnd);
|
||||
});
|
||||
|
||||
it('leaves ordinary finals untouched', () => {
|
||||
const ok = 'TASK_DONE\n작업을 마쳤습니다.';
|
||||
expect(applyPhantomNotificationGuard(ok, { hasActiveWatcher: false })).toBe(
|
||||
ok,
|
||||
);
|
||||
});
|
||||
|
||||
it('is idempotent — never appends the notice twice', () => {
|
||||
const once = applyPhantomNotificationGuard(deadEnd, {
|
||||
hasActiveWatcher: false,
|
||||
});
|
||||
const twice = applyPhantomNotificationGuard(once, {
|
||||
hasActiveWatcher: false,
|
||||
});
|
||||
expect(twice).toBe(once);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user