From db008fafd54f8d4236e85d5bde0069920793142a Mon Sep 17 00:00:00 2001 From: GaltRanch Date: Tue, 14 Apr 2026 18:10:04 -0300 Subject: [PATCH] fix(auto-launch): place phase 22 hook inside break branch (Bug #8) + v2.10.65 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The latest Orbital kcode.log proved phase 22 has NEVER actually executed in production. Zero mentions of "auto-launch" or "maybeAutoLaunchDevServer" in ~/.kcode/logs/kcode-2026-04-14.log, despite the feature shipping in v2.10.60 and being "fixed" in v2.10.63 (text_delta bug) and v2.10.64 (Bug #6 early-return). Root cause: the hook at conversation.ts:1905 was placed at the end of a while-loop iteration, but handlePostTurn at line 1517 returns `action: "break"` for every normal end_turn and the subsequent `break` statement at line 1555 exits the loop BEFORE control ever reaches line 1905. Phase 22 was dead code on the happy path. Fix: move the hook into the `if (postTurnResult.action === "break")` branch itself, just before the break statement. Guards remain the same (runtime intent + successful Write + detectDevServer + port free). The dead code at line 1905 is replaced with a comment pointing at the audit finding. This is the third audit-fix for phase 22 in 24 hours: - PR #29 Bug #1: yield type "text" → "text_delta" (UI render path) - PR #30 Bug #6: detectDevServer early-return + port extraction - PR #31 Bug #8: hook placement (THIS FIX) Without #8, the previous two fixes were also dead code. The feature should finally work end-to-end after this merge. 38 tests pass in auto-launch-dev-server.test.ts + conversation-streaming.test.ts. Full regression running. Co-Authored-By: Kulvex Code --- package.json | 2 +- src/core/conversation.ts | 70 ++++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 2289be8..2d7300e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kcode", - "version": "2.10.64", + "version": "2.10.65", "description": "AI-powered coding assistant for the terminal - by Astrolexis", "author": "Astrolexis", "module": "src/index.ts", diff --git a/src/core/conversation.ts b/src/core/conversation.ts index 2866139..bd893a5 100644 --- a/src/core/conversation.ts +++ b/src/core/conversation.ts @@ -1551,6 +1551,39 @@ export class ConversationManager { for (const msg of postTurnResult.injectMessages) this.state.messages.push(msg); if (postTurnResult.action === "break") { + // Phase 22: the model has finished its final response and the + // agent loop is about to exit. This is the correct firing + // point for auto-launch — the previous placement at the + // bottom of the while iteration was dead code because + // handlePostTurn breaks out of the loop BEFORE reaching it. + // The hook itself is guarded by runtime-intent + write-in-turn + // checks so it's safe to call on every normal break. + if (stopReason === "end_turn") { + try { + const { maybeAutoLaunchDevServer } = await import( + "./auto-launch-dev-server.js" + ); + const { getUserTexts } = await import("./session-tracker.js"); + const launchResult = await maybeAutoLaunchDevServer( + this.config.workingDirectory, + this.state.messages, + getUserTexts(), + ); + if (launchResult) { + this.state.messages.push({ + role: "assistant", + content: launchResult.notice, + }); + yield { type: "text_delta", text: launchResult.notice }; + log.info( + "auto-launch", + `phase 22 fired: ${launchResult.url ?? "no url"}`, + ); + } + } catch (err) { + log.debug("auto-launch", `hook failed (non-fatal): ${err}`); + } + } this.abortController = null; break; } @@ -1897,37 +1930,12 @@ export class ConversationManager { guardState.consecutiveDenials = 0; } - // Phase 22: if the model just finished its final response AND the - // user's original prompt had runtime intent AND a Write just - // completed, proactively launch the dev server and tell the user - // how to stop it. Fires only on "end_turn" — never during - // tool_use continuation, so we don't launch mid-reasoning. - if (stopReason === "end_turn") { - try { - const { maybeAutoLaunchDevServer } = await import( - "./auto-launch-dev-server.js" - ); - const { getUserTexts } = await import("./session-tracker.js"); - const launchResult = await maybeAutoLaunchDevServer( - this.config.workingDirectory, - this.state.messages, - getUserTexts(), - ); - if (launchResult) { - this.state.messages.push({ - role: "assistant", - content: launchResult.notice, - }); - // Must be text_delta — StreamEvent has no "text" variant. - // The UI renders text_delta events via print-mode and - // stream-handler. The bug from the initial phase 22 ship - // was yielding type: "text", which silently dropped. - yield { type: "text_delta", text: launchResult.notice }; - } - } catch (err) { - log.debug("auto-launch", `hook failed (non-fatal): ${err}`); - } - } + // Phase 22 moved: the correct firing point is inside the + // handlePostTurn break branch earlier in the loop. Placing it + // here was dead code — handlePostTurn's `action: "break"` path + // exits the loop BEFORE reaching this line for every normal + // end_turn, so the hook never ran in production. See Bug #8 + // in the v2.10.64 audit. yield { type: "turn_end", stopReason }; // Loop continues for next agent turn