Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
70 changes: 39 additions & 31 deletions src/core/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
Loading