release: v6.8.7 - #56
Conversation
…t turn Messages typed while the agent is streaming are held in a FIFO queue and drained one per turn, so a queued message previously had to wait for the whole in-flight turn (including every tool call) to finish before it was even considered. - Extract the queue panel into a new QueuedMessages component that renders a clickable "[send now]" action beside each message, with hover highlighting via mouse events. - Add forceSendQueued() in App: it jumps the target message to the front of the queue, then either drains directly (nothing in flight) or aborts the running turn. The abort makes the agent's turn-end handler drain the queue through the same path a normal turn end takes, so conversation history stays consistent. - Bind ctrl+s to force-send the next message in line; the queue panel header advertises the shortcut. - Move queue preview truncation into QueuedMessages (previewText/fit) and drop the now-unused truncateForQueue helper from App.
Session persistence previously ran only in runTurn's finally block, so killing OrbCode or closing the terminal mid-turn (a long multi-step turn can stream for many minutes) lost the entire in-flight turn: the user prompt, every assistant response, and every tool call/result accumulated across all its steps were never written to disk, and resuming showed the state from before that turn. - Persist right after the user message is pushed and after every model step, so a hard kill loses at most the single in-flight tool call. - saveSession now writes to a pid-suffixed temp file and renames it into place, so a crash mid-write can no longer truncate or corrupt the last good session file. - serializeSession degrades gracefully when a message holds a value JSON cannot represent (BigInt, circular reference) instead of losing the whole session to a stringify throw. - Surface save failures as transcript errors instead of silently swallowing them. - Guard persist() against stale writes: track the session file's last-known mtime (baselined from the resumed file at startup) and refuse to write when another process has written newer turns, warning instead of clobbering.
Ctrl+C previously did nothing (no handler existed), so quitting left zombie processes alive with the old conversation in memory; their next save would overwrite the session file with stale history, erasing turns written by a resumed session. Together with the stale-write guard in persist() from the previous commit, this removes both the source of stale writers and the damage they could do. - While a turn is running, Ctrl+C aborts it like Esc, unless a prompt (approval, followup, hook trust, MCP approval) is pending. - When idle, Ctrl+C exits through the same double-press confirmation as Ctrl+D, so an accidental press cannot discard the session. - Update the shortcut hints in the header and the /help panel to "ctrl+d/c exit".
fetchDynamicModels now sends X-KiloCode-OrganizationId and X-Org-Id headers so the gateway can return the models available to the user's organization instead of the global registry. The organization ID comes from the new optional argument, falling back to settings.organizationId when omitted.
Force-send queued messages (ctrl+s / [send now]), crash-safe incremental session persistence with a stale-write guard, Ctrl+C interrupt/exit, and organization-scoped dynamic model catalog.
There was a problem hiding this comment.
🧪 PR Review is completed: Release bump with solid session-persistence hardening (stale-write guard, atomic write-then-rename saves, surfaced save errors) and a new queue force-send feature. Two findings: forceSendQueued aborts without the pending-approval guards that the new Ctrl+C path carefully enforces, and the atomic-save temp file can leak on failure. Reviewed src/api/models.ts, src/core/agent.ts, src/ui/components/QueuedMessages.tsx, src/ui/components/Header.tsx, package.json: no issues found.
Skipped files
CHANGELOG.md: Skipped file patternpackage-lock.json: Skipped file pattern
⬇️ Low Priority Suggestions (2)
src/ui/App.tsx (1 suggestion)
Location:
src/ui/App.tsx(Lines 874-880)🟠 Logic / State Consistency
Issue:
forceSendQueuedcallsagent.abort()unconditionally wheneverbusyis true, but the new Ctrl+C handler in this same PR explicitly guards against aborting whilependingApproval,pendingFollowup,pendingHookTrust, orpendingMcpApprovalis active. Aborting during one of those pending states means the awaited approval/followup promise never settles,turn-endnever fires, and the queue never drains — so the "Force-sending…" info message is misleading and the force-send silently doesn't happen until the user answers the pending prompt.Fix: Apply the same pending-state guard in the busy branch of
forceSendQueuedbefore aborting, and include those states in the callback's dependency array so the guard never reads stale values.Impact: Prevents a stuck/misleading force-send during approvals and keeps interrupt semantics consistent with the Ctrl+C path.
- pushRow({ - kind: "info", - text: `Force-sending queued message (${queueRef.current.length} in queue)…`, - }); - agent.abort(); - }, - [busy, drainQueue, getAgent, pushRow], + if ( + pendingApproval || + pendingFollowup || + pendingHookTrust || + pendingMcpApproval + ) { + pushRow({ + kind: "info", + text: "Answer or dismiss the pending approval before force-sending.", + }); + return; + } + pushRow({ + kind: "info", + text: `Force-sending queued message (${queueRef.current.length} in queue)…`, + }); + agent.abort(); + }, + [ + busy, + drainQueue, + getAgent, + pushRow, + pendingApproval, + pendingFollowup, + pendingHookTrust, + pendingMcpApproval, + ],
src/core/sessions.ts (1 suggestion)
Location:
src/core/sessions.ts(Lines 84-86)🔵 Resource Cleanup
Issue: The new write-then-rename atomic save leaves the
.tmpfile behind wheneverrenameSyncfails (or the process dies between write and rename). Since the temp name is only pid-unique, orphaned files accumulate in the sessions directory over time.Fix: Wrap the write/rename in try/catch and unlink the temp file on failure before re-throwing, so the error still surfaces via the agent's persist handler but no stray temp file remains.
Impact: Keeps the sessions directory clean; no behavioral change on the success path.
- const tmp = `${target}.${process.pid}.tmp` - fs.writeFileSync(tmp, serializeSession(data), { mode: 0o600 }) - fs.renameSync(tmp, target) + const tmp = `${target}.${process.pid}.tmp` + try { + fs.writeFileSync(tmp, serializeSession(data), { mode: 0o600 }) + fs.renameSync(tmp, target) + } catch (error) { + try { + fs.unlinkSync(tmp) + } catch {} + throw error + }
Release v6.8.7
Added
ctrl+s/[send now]): Messages typed while the agent is streaming can now be force-sent immediately ahead of an in-flight turn via the[send now]action in the queue panel orctrl+s.fetchDynamicModelssendsX-KiloCode-OrganizationIdandX-Org-Idheaders so the gateway returns organization-specific model catalogs.Fixed