Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/daemon/src/session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ const FIRE_AND_FORGET_METHODS = new Set([
'notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text',
]);

const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped'];
const TERMINAL_PREFIXES = [
'auto-mode stopped',
'step-mode stopped',
'auto-mode complete',
'no active milestone',
'auto-mode idle',
];

function isTerminalNotification(event: Record<string, unknown>): boolean {
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false;
Expand Down
8 changes: 7 additions & 1 deletion packages/mcp-server/src/session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ const FIRE_AND_FORGET_METHODS = new Set([
'notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text',
]);

const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped'];
const TERMINAL_PREFIXES = [
'auto-mode stopped',
'step-mode stopped',
'auto-mode complete',
'no active milestone',
'auto-mode idle',
];

function findExecutableOnPath(command: string): string | null {
const pathValue = getPathEnvValue();
Expand Down
8 changes: 7 additions & 1 deletion src/headless-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ export function mapStatusToExitCode(status: string): number {
*
* Blocked detection is separate — checked via isBlockedNotification.
*/
export const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped']
export const TERMINAL_PREFIXES = [
'auto-mode stopped',
'step-mode stopped',
'auto-mode complete',
'no active milestone',
'auto-mode idle',
]
export const IDLE_TIMEOUT_MS = 15_000
// new-milestone is a long-running creative task where the LLM may pause
// between tool calls (e.g. after mkdir, before writing files). Use a
Expand Down
20 changes: 19 additions & 1 deletion src/tests/headless-detection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import assert from "node:assert/strict";

// ─── Extracted detection logic (mirrors headless.ts) ────────────────────────

const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped']
const TERMINAL_PREFIXES = [
'auto-mode stopped',
'step-mode stopped',
'auto-mode complete',
'no active milestone',
'auto-mode idle',
]

function isTerminalNotification(event: Record<string, unknown>): boolean {
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false
Expand Down Expand Up @@ -69,6 +75,18 @@ test("detects 'Step-mode stopped.' as terminal", () => {
assert.ok(isTerminalNotification(makeNotify("Step-mode stopped.")))
})

test("detects 'Auto-mode complete...' as terminal", () => {
assert.ok(isTerminalNotification(makeNotify('Auto-mode complete — all milestones complete.')))
})

test("detects 'No active milestone...' as terminal", () => {
assert.ok(isTerminalNotification(makeNotify('No active milestone in registry.')))
})

test("detects 'Auto-mode idle...' as terminal", () => {
assert.ok(isTerminalNotification(makeNotify('Auto-mode idle: no roadmap work items found.')))
})

// ─── False positives that previously triggered early exit (#879) ────────────

test("does NOT match 'All slices are complete — nothing to discuss.'", () => {
Expand Down
22 changes: 21 additions & 1 deletion src/tests/headless-v2-migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ function mapStatusToExitCode(status: string): number {

// ─── Extracted terminal detection (mirrors headless-events.ts) ──────────────

const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped']
const TERMINAL_PREFIXES = [
'auto-mode stopped',
'step-mode stopped',
'auto-mode complete',
'no active milestone',
'auto-mode idle',
]

function isTerminalNotification(event: Record<string, unknown>): boolean {
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false
Expand Down Expand Up @@ -240,6 +246,20 @@ test('v1 fallback: terminal notification still triggers completion', () => {
assert.equal(state.exitCode, EXIT_SUCCESS)
})

test('v1 fallback: auto-mode complete notification triggers completion', () => {
const client = new MockRpcClient()
const state: EventHandlerState = { completed: false, blocked: false, exitCode: -1, v2Enabled: false }

handleEvent(
{ type: 'extension_ui_request', method: 'notify', id: 'n2', message: 'Auto-mode complete — all milestones complete.' },
state,
client,
)

assert.equal(state.completed, true)
assert.equal(state.exitCode, EXIT_SUCCESS)
})

test('v1 fallback: blocked notification sets blocked flag', () => {
const client = new MockRpcClient()
const state: EventHandlerState = { completed: false, blocked: false, exitCode: -1, v2Enabled: false }
Expand Down
Loading