Skip to content

release: v6.8.7 - #56

Merged
code-crusher merged 5 commits into
mainfrom
release/6.8.7
Sep 21, 2026
Merged

code-crusher merged 5 commits into
mainfrom
release/6.8.7

Conversation

@code-crusher

Copy link
Copy Markdown
Member

Release v6.8.7

Added

  • Force-send a queued message (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 or ctrl+s.
  • Organization-scoped dynamic model catalog: fetchDynamicModels sends X-KiloCode-OrganizationId and X-Org-Id headers so the gateway returns organization-specific model catalogs.

Fixed

  • Incremental & crash-safe session persistence: Session history is persisted after prompt submission and after every model step (instead of only turn completion), written atomically to temp files to prevent corruption, with stale-write protection against conflicting processes.
  • Ctrl+C interrupt and double-press exit: Ctrl+C interrupts running turns (like Esc) and prompts for exit confirmation when idle, preventing zombie background processes from rolling back sessions.

…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.
@matterai-app

matterai-app Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Summary By MatterAI MatterAI logo

🔄 What Changed

This release (v6.8.7) enhances CLI stability, session persistence reliability, and user interaction flows. Key additions include robust stale-write protection for session saving, atomic write-then-rename file persistence with JSON serialization fallback, an interactive TUI queue management component with ctrl+s force-send capabilities, and improved interrupt/exit keyboard handling (ctrl+c/ctrl+d).

🔍 Impact of the Change

Improves data integrity by preventing concurrent CLI instances from clobbering session history and eliminating mid-write file truncation. Enhances UX transparency during long-running agent streaming by allowing users to inspect, reorder, and force-send queued prompts instantly.

📁 Total Files Changed

Click to Expand
File ChangeLog
Version Bump package.json Updated project version to 6.8.7.
Model Headers src/api/models.ts Added organization ID resolution and header injection (X-Org-Id).
Agent State src/core/agent.ts Implemented stale-write detection and aggressive session persistence on turn start/step.
Session IO src/core/sessions.ts Added atomic file writes (.tmp rename) and robust circular/BigInt JSON serialization.
CLI TUI src/ui/App.tsx Added queue management, ctrl+c turn interruption, and ctrl+s prompt force-sending.
UI Header src/ui/components/Header.tsx Updated keyboard shortcut hint banner.
TUI Queue Component src/ui/components/QueuedMessages.tsx Created new interactive component for viewing and force-sending pending prompts.

🧪 Test Added/Recommended

Recommended

  • Unit tests for serializeSession handling circular references and BigInt values.
  • Integration tests for concurrent agent session persistence and stale-write race conditions.
  • TUI component tests for QueuedMessages force-send and hover interactions.

🔒Security Vulnerabilities

  • None detected.

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 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 pattern
  • package-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: forceSendQueued calls agent.abort() unconditionally whenever busy is true, but the new Ctrl+C handler in this same PR explicitly guards against aborting while pendingApproval, pendingFollowup, pendingHookTrust, or pendingMcpApproval is active. Aborting during one of those pending states means the awaited approval/followup promise never settles, turn-end never 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 forceSendQueued before 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 .tmp file behind whenever renameSync fails (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
+  	}

@code-crusher
code-crusher merged commit c4c1874 into main Sep 21, 2026
1 check passed
@code-crusher
code-crusher deleted the release/6.8.7 branch September 21, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant