Skip to content

release: v1.8.0#58

Merged
mohitsoni48 merged 23 commits into
mainfrom
release/v1.8.0
Jul 14, 2026
Merged

release: v1.8.0#58
mohitsoni48 merged 23 commits into
mainfrom
release/v1.8.0

Conversation

@mohitsoni48

Copy link
Copy Markdown
Owner

Release branch for v1.8.0 (minor bump from 1.7.7).

Summary of work since v1.7.7

  • Code agent workspace, graduated thinking-budget control, chat session persistence
  • Code session archive/filters, tool-label clarity, shared engine-gate deadlock fix
  • Code context-usage % fixes (aborted-turn drop, live updates, post-turn reset)
  • Code transcript copy action, composer auto-grow, popover/queue fixes
  • Code "Add context" file attachments, honest skill invocation, AGENTS.md support
  • Gate Code behind an API key on non-host devices, independent of Chat
  • Revert-to-message in Code, with optional real file-edit reversal
  • Shimmer/animation polish for Code
  • Real Code "Coding activity" stats, replacing mock data
  • Anti-fallback web tools + mechanical failure backstop; rail icon sizing
  • Dependency version/docs discipline for Code's coding behavior
  • Real LSP integration for TypeScript/JavaScript and Python
  • Persist true message/tool-call order for completed turns
  • Extend Chat's Customize tools (MCP servers + run_code) to Code
  • Settings -> Experimental: gate Code, Memory, and RunPod behind a real opt-in toggle
  • macOS engine parity: Metal builds, Rapid-MLX, cross-engine fixes (fix: macOS engine parity — Metal builds, Rapid-MLX, and cross-engine test fixes #50)

Prepared per docs/RELEASE.md. Release-prep commit (version bump, changelog, README sync, persona KB) will follow on this branch after review.

…ession persistence

Adds a real pi-SDK (@earendil-works/pi-coding-agent) Code workspace alongside Chat:
daemon-owned reconnectable runs, containment + mode-based approval (auto/plan/ask),
a lazy skill-catalog architecture (invoke_skill tool loads full skill instructions
only when used, cutting first-message cost by ~98%), cross-turn conversation
replay with a defense-in-depth retry + text fallback for an intermittent pi SDK
SessionManager seeding bug, manual /compact, and a real "/" slash-command picker
shared with skill invocation.

Also:
- Replaces the old on/off "thinking" toggle with a graduated 0-16k token budget
  slider (Chat + Code + Settings), using the real thinking_budget_tokens field
  (the old reasoning_budget field was never recognized by the server).
- Persists the last-opened Chat conversation and Code session so the sidebar's
  mode switch returns to where you left off.
- Removes generation.ts, a dead standalone-agent code path with zero importers,
  superseded by chat-routes.ts's own generation loop and the new Code workspace.
…ngine-gate deadlock

Code workspace fixes and features built on top of the initial Code agent commit:

- Delete/archive Code sessions with an All/Active/Archived filter, a working
  "+ New session" button (previously wired but hidden by a stale guard), and
  a /clear + /resume command pair that clears the visible chat + model context
  without touching repo/worktree/branch or deleting anything.
- Two mobile UI fixes: the composer's Send button could be pushed fully
  off-screen on a narrow phone; the header's repo/branch chips were hidden on
  mobile entirely.
- invoke_skill tool-call cards now show which skill is running, e.g.
  "invoke skill (skill-id)", instead of a generic label.
- Root-caused and fixed a real, app-wide deadlock: GenerationGate.acquire()
  (shared by every background-priority engine call — Code turns, invoke_skill,
  chat's memory extraction, chat's autoTitle) had no cancellation or timeout,
  so a single leaked release anywhere wedged every future acquire() forever.
  pi SDK's own session.abort() waits for the agent to reach idle, which never
  happens while a tool call is stuck on an unresolvable acquire() — so Stop
  couldn't recover it either, requiring a full daemon restart. acquire() now
  takes an AbortSignal (so Stop actually cancels a stuck wait) plus a default
  180s self-heal timeout, both rejecting rather than silently granting access.
- Replaced invoke_skill's isolated, tool-less text completion with a real,
  separately-scoped pi sub-session per skill invocation (runSkillSubSession)
  that shares the outer session's containment and auto/plan/ask mode gating:
  auto gets real bash/edit/write, ask routes mutating tool calls through the
  same approval popup UI as the main session, plan is read-only. This is what
  caused skills to falsely report having taken real actions — they never had
  tool access to begin with.

See docs/decisions/decision-log.md ADR-195 through ADR-197 for full detail
and live-verification evidence.
…turn

Root-caused the founder-reported "ctx size changed from 50% to 0%" regression:
code-run-manager.ts's pump() took an aborted turn's contextUsed at face value,
but two distinct paths both under-report on abort — a thrown AbortError used
to hardcode contextUsed to 0, and a normal return with aborted:true can carry
a genuinely-computed but much-too-low number from pi SDK's own context
estimator running against a partial/interrupted session snapshot. Live-
reproduced directly: a confirmed 4932-token turn was immediately followed by
an aborted turn reporting 1045 — lower than before, which is impossible since
a follow-up only ever adds tokens.

Context usage cannot really shrink within an ongoing session, so an aborted
turn's reported ctxUsed is now floored at the last confirmed real value for
that conversation; a genuinely higher value (real growth before the stop) is
still trusted, not clamped. 4 new tests. Verified live, before/after, against
the same repro on the real LAN daemon.

See docs/decisions/decision-log.md ADR-198 for full detail.
…fixes

Four small UX items from the Code punch list, all live-verified in browser:

- Copy-message action on both the user instruction card and assistant
  commentary in the Code transcript, matching Chat's existing Copy affordance
  (reuses the same shared CopyButton component).
- Composer textarea now genuinely grows with content up to its CSS max-height
  before scrolling internally, instead of staying pinned at its initial rows
  height — moved the auto-resize logic into the shared CodeComposer itself
  (driven by the real CSS cap, not a hardcoded pixel value) and removed the
  duplicate hand-rolled autoResize() that had drifted separately in both
  CodeHomeScreen.tsx and CodeSessionScreen.tsx.
- Thinking-budget popover now opens upward (bottom-full) instead of downward
  (top-full) — it lives in a composer toolbar docked at the bottom of the
  screen, so opening down could push it off-screen / behind the OS taskbar.
- Queued follow-ups no longer interleave into the transcript rail above the
  still-streaming current turn. A queued follow-up's user message is
  persisted the moment it's submitted (server-side queue), so it used to
  render as its own instruction card ahead of the live content, which always
  renders last. The transcript now cuts off at the current live turn's own
  assistant placeholder; anything queued behind it stays confined to the
  existing "Queued" chips strip until it actually starts running.

Verified live against a real running session: queued-message text appears
exactly once, as a chip pill, never as a transcript rail card.
…GENTS.md support

Three more items from the Code punch list, all live-verified against a real
running session:

- Add context: the composer's "Add context" button now opens a real file
  browser (FsBrowser, reused from the engines screen) scoped to the session's
  repoRoot. Picked files render as removable chips; on send their paths are
  stored as the message's textAttachments (shown as read-only chips in the
  transcript) and folded into that turn's prompt as a "read this file if
  relevant" nudge — the agent reads the actual content itself via its own
  already-containment-checked read tool, so no new content-fetch path or
  security surface was needed. Wired into both the launchpad's first-message
  composer and the in-session follow-up composer. Added FsBrowser a `startPath`
  prop so it opens at the repo instead of the home directory.

- Skill invocation no longer rewrites the user's message. Typing "/skillid
  task" used to REPLACE the stored/displayed message with a synthetic
  "Use the invoke_skill tool..." instruction — the founder flagged this as
  bad UX (their own typed message shouldn't be silently altered). Added a
  promptOverride field to the turn endpoint: `content` is always what's
  stored/shown verbatim; `promptOverride`, when given, is what's actually
  sent to the model this turn. History replay for future turns always uses
  the stored message, never the override, so past turns read back as what
  the user actually said.

- AGENTS.md / ~/.turbollm/agents.md system-prompt injection, like OpenCode.
  Read automatically on every turn (repo-level file takes precedence in
  ordering after the global one) and appended to the system prompt — no
  per-session setup. Verified live: a marker instruction in AGENTS.md was
  followed on every turn of a session, including follow-ups.

15 new unit tests (persona.ts's AGENTS.md block, code-routes.ts's
contextFilesBlock). All three verified live end-to-end against the real
running daemon, backend API and browser UI both.
… Chat

Chat (and most of the app) can stay open on the LAN with no key — that's the
existing, deliberate default (requireApiKey off). Code is different: it
executes real bash/edit/write against the user's own filesystem, so it now
ALWAYS requires a valid API key from any device other than the host machine,
regardless of the global requireApiKey toggle.

- auth.ts: new codeAuth middleware, registered scoped to /api/v1/code/* only
  (after the existing global lanAuth). Reuses isLocalRequest (already
  tunnel-safe per ADR-152) to decide "is this request local to the host";
  factored the key-hash-lookup logic both gates share into
  verifyPresentedKey. 9 new unit tests.
- Frontend: the existing AuthGate prompt only ever watched the global
  /status poll, which never 401s for this gate. Added a small shared signal
  (auth-signal.ts) that code-api.ts marks on a 401 from any Code endpoint;
  App.tsx subscribes via useSyncExternalStore and shows the SAME AuthGate —
  no new UI needed, just a second trigger for it.

Verified live against the real running LAN daemon (lanBind on, requireApiKey
off — the exact scenario this targets): loopback calls to Code succeed with
no key; the same calls via the LAN IP 401 with a clear message; a valid key
succeeds, a wrong one still 401s; meanwhile /status (Chat's surface) stays
open on the same LAN IP throughout, unaffected.
A revert control on any user message (except the session's own first one, or
while a run is live): rewinds the transcript back to just before it and
refills the composer with that message's ORIGINAL text. Reuses the existing
/clear + /resume mechanism (clearedUpToMessageId) — nothing is deleted,
/resume still un-hides it.

When the discarded turns touched real files, asks whether to also reverse-
apply those edits (revert.ts), walking each file back through every recorded
edit-tool patch, most recent first — entirely in memory, written to disk
only if the WHOLE chain for that file applies cleanly, so a file is never
left half-reverted. Every target path is containment-checked the same way
the live tool_call hook already does. Added the `diff` (jsdiff) dependency
for the actual reverse-patch math rather than hand-rolling a unified-diff
algorithm for something that mutates real files.

Prerequisite fix along the way: edit-tool call records' diff/patch were
never actually persisted to the DB (code-run-manager.ts dropped them when
building the stored ToolCallRecord) — only present on the live SSE stream
that produced them. This silently broke the diff panel for any completed
tool call after a page reload, and would have made file-revert impossible.
Now persisted (db.ts's ToolCallRecord).

16 new unit tests (revert.ts's reverse-patch logic, against real temp files
and real jsdiff-generated patches). Verified live end-to-end including a
real UI click-through: two edits to the same file, reverted via the actual
button + confirmation dialog, correctly walked the file back through BOTH
recorded edits (not just the last one) to its true pre-edit content.
…de-in

The last (explicitly lowest-priority, deferred-to-last) item on the Code
punch list. Two real, evidence-based gaps found against the app's own design
rule (spec 11 §8: never a bare spinner/blank void — show the shape of what's
coming) rather than invented busywork:

- The Code session sidebar showed a literal "Loading…" text label while
  fetching the session list. Replaced with skeleton rows matching
  CodeSessionItem's real layout (status dot + title + subtitle line).
- CodeSessionScreen's first load (before any data has arrived) rendered a
  completely blank transcript area. Added CodeTranscriptSkeleton, matching
  the transcript's own rail language (marker + content block), shown only
  for the genuine first load — not reconnects/refetches.
- A quiet one-shot fade + 4px rise (tllm-rise-in, respects
  prefers-reduced-motion) on each persisted transcript entry as it mounts —
  previously entries just popped in instantly. Deliberately NOT applied to
  the live-streaming entry, whose shape shifts constantly as tokens arrive;
  animating that would be flicker, not polish.

Verified live: the skeleton renders in the correct place with the right
shape, the fade-in class is present on all persisted entries in a real
session's transcript, no console errors.
…ck data

The launchpad's activity stats/heatmap were pure fake numbers dressed up as
real ("200 sessions", a hardcoded favorite model, a deterministic-but-fake
182-day heatmap), with a "Beta · illustrative" disclaimer most people would
never read. Now computed fresh from real data on every request:

- db.ts's new codeStats(range) mirrors tokenUsageStats()'s day-bucket/streak
  pattern (dayKey/addDays hoisted to module scope to be shared by both) —
  sessions/tasksShipped/streaks/heatmap from agent_runs, filesTouched + real
  diff-added/removed line counts from messages.tool_calls (only possible now
  that edit-tool diffs actually persist, a prerequisite fix from the revert-
  to-message work), favoriteModel from conversations.model_key. Nothing is
  cached in a running counter, so it can't drift from whatever data actually
  exists.
- New GET /api/v1/code/stats route, useCodeStats() hook, loading-skeleton
  StatTiles. The "Beta · illustrative" banner is gone — nothing left to
  caveat.

7 new unit tests. Verified live against the real running LAN daemon: 3
pre-existing sessions correctly showed diffAdded: 0 (their diffs predate the
persistence fix, genuinely unrecoverable — expected, not a bug), then a
fresh real edit turn moved sessions/filesTouched/diffAdded/diffRemoved by
exactly the right amounts in real time, confirmed via both the API and the
rendered launchpad UI.
…ink rail icons

Code previously had no web access at all - a weak local model that fails
twice at a task (real repro: Camera2 API) would silently substitute an
easier, different feature instead of persisting. Registers web_search/
fetch_url (reusing Chat's own ToolRegistry), adds anti-fallback prompt
guidance, and backstops it mechanically: consecutiveToolFailures counts
real tool failures and injects a system nudge directly into the failing
tool's own result from the 2nd failure on, since prompt text alone can't
be trusted to work reliably on local models.

Also shrinks the Code transcript's rail-marker icons (24px/12px icon ->
20px/10px), which looked oversized next to the transcript's compact text.
…CLI-install backstop

Second of six queued Code reliability fixes. Adds a dedicated, always-on
system-prompt rule requiring the agent to look up a dependency's latest
version and read its real docs before adding it on any platform, backed
by a mechanical nudge for CLI package-manager installs (npm/pip/cargo/go/
etc) that fires when no web_search/fetch_url has succeeded yet this turn.
Hand-edited manifest files (Gradle etc) have no mechanical check - too
false-positive-prone to detect reliably - and rely on the prompt rule alone.
Third of six queued Code reliability fixes. Adds a genuine JSON-RPC-over-
stdio LSP client (vscode-jsonrpc, the same library VS Code's own client
extensions use) that auto-starts typescript-language-server/pyright via
npx on demand and appends real compiler/type-checker diagnostics to every
successful edit/write in a supported file - no model action required.
Also adds an install_lsp tool for pre-warming before a large task.

Scoped to TS/JS + Python for v1 (both npx-installable with no extra
toolchain); the registry is a plain extension-to-spec map so adding a
language later is one entry, not a redesign.

Two platform-specific bugs found and fixed via live testing before this
worked at all: Windows' npx.cmd needs spawn(..., {shell: true}) (EINVAL
otherwise), and Node's pathToFileURL produces a different URI format than
what LSP servers actually publish diagnostics against (fixed with the
vscode-uri library instead of a hand-approximation). Also fixed
typescript-language-server failing to find `typescript` in a from-scratch
project with no node_modules yet, via an npx-resolved fallback tsserver
path that only applies when the target repo doesn't already have its own.
…ets on new turns

Fourth and fifth of six queued Code reliability fixes, sharing one root
cause: ctxUsed read the positionally-last assistant message, which is a
fresh empty placeholder the instant any new turn starts (no real ctxUsed
until the turn completes). That explained both the ring sitting frozen
during a whole run and snapping to 0% the moment a follow-up was sent.

Fixed by skipping to the last assistant message that actually has a real
ctxUsed, and adding a rough client-side estimate (accumulated live text
length / 4) on top of that base while a turn is streaming, so the ring
moves during generation instead of staying frozen.
Sixth and last of six queued Code reliability fixes. A completed Code
turn always rendered as reasoning -> all tool calls grouped -> final
text, regardless of how they actually interleaved live, because the
persisted Message shape (content + toolCalls) had no interleave-position
marker once saved - a real data-model gap, not a rendering bug.

Adds a nullable messages.timeline column (migration v31) storing an
ordered {type:'text'} | {type:'tool',id} array, built in
code-run-manager.ts's pump() from the same SSE events that already drive
the live view's own correct ordering. CodeTranscript.tsx reuses the live
view's existing chunkTimeline grouping algorithm for persisted messages
too, via chunkPersistedTimeline: a run of tool calls between two text
chunks collapses into one group, but a tool call after a text chunk
starts a new group instead of merging backward.

Old rows without a timeline fall back to the pre-fix grouped rendering -
no backfill, no breakage.
… Code

All the tools Chat gets from Customize configuration (external MCP tool
providers, plus the built-in sandboxed run_code) are now available to
Code sessions too, closing a gap where only web_search/fetch_url had
been extended so far.

MCP tools are arbitrary external providers with no relationship to a
session's repoRoot containment - unlike web_search/fetch_url, so they're
treated as mutating for Code's own mode-gate: unconfined in auto,
ask-mode-approved through the same waitForToolApproval UI edit/write/bash
already use, and not registered at all in plan mode. run_code (sandboxed,
no fs/network/process access) is unconfined in every mode, same treatment
as web_search/fetch_url, since it's genuinely inert.

MCP tool definitions are fetched fresh once per turn since servers can be
connected/disconnected via Customize while a session stays open.
…ental toggle

Preparing for wider distribution. Adds a persisted daemon.experimental
{code, cloudDeploy} setting (default both false), a new Settings ->
Experimental category (positioned above System, in the existing category
rail) listing Code and Cloud Launch/RunPod as checkboxes with Memory's
existing rich section relocated there too.

Code gets real backend enforcement (a 403 on /api/v1/code/* when
disabled, not just a hidden button) via a new requireExperimentalCode
middleware, plus a frontend route guard that redirects away from
/workspace/code when disabled - the sidebar pill hiding is a UX nicety,
the route guard is the actual gate against a stale bookmark or typed URL.

Found three pre-existing, inconsistent flag mechanisms (an unused
env-var system, Memory's own toggle, a frontend build-time constant)
before adding this fourth, unified one - kept autoMemoryEnabled as its
own field rather than migrating it, since it already worked.
Correction to the experimental-features work: the first version moved
MemorySection's whole settings UI (its facts list) into the new
Experimental tab. Memory's settings belong where they've always been -
the Experimental checkbox should only be a master unlock (visibility and
whether extraction actually runs), not a relocation.

Adds a genuinely separate experimental.memory flag (autoMemoryEnabled
stays the finer per-user opt-in within that unlock, same relationship
experimental.code has to Code's own internal settings). MemorySection
now renders back in General, conditional on the new flag, and the
auto-memory extraction trigger checks both flags so turning Memory off
in Experimental actually stops it acting, not just hides its UI.

Migration: a config that already had autoMemoryEnabled=true (a real
prior opt-in before this gate existed) migrates memory=true too, so
upgrading doesn't silently take away a feature already in use.
- Drop stale "(preview — mock data)" caption on ContextUsageRing now
  that it's fed real context stats.
- Classify a Stop hit while a Code turn is queued behind a foreground
  chat generation as an interrupt, not a failure — the shared gate
  rejects with a plain Error('gate_acquire_aborted'/'gate_acquire_timeout'),
  whose .name isn't 'AbortError'.
- Add a 5s timeout to the git-branch route's execFileSync so a wedged
  git invocation can't block the daemon's event loop indefinitely.
npm 11 (used locally) wrote a lock file missing an explicit nested
zod@4.4.3 entry under @earendil-works/pi-ai/node_modules/zod, dedup'd
against the sibling @mistralai/mistralai copy. npm 10 (bundled with
CI's Node 22) enforces that entry explicitly and fails npm ci with
"Missing: zod@4.4.3 from lock file". Regenerated with npm@10 install
so the lock is valid for both.
…request

Code's before_provider_request hook let pi's declared model.maxTokens (a
fixed 8192 ceiling set once at model-load time) flow through verbatim as
max_tokens/max_completion_tokens on every turn, regardless of how many
tokens that turn's prompt already used. Once a tool-schema-heavy Code
system prompt (~4900 tokens) plus that stale ceiling pushed past the
loaded model's actual context window, llama.cpp stopped generation after
~1 token (reproduced live: reasoning:"The", content:"", aborted:false).
chat-routes.ts already avoids this via clampMaxTokens + deleting the
field when uncapped — mirror that here so generation is bounded only by
the model's real remaining context.
…sions too

Same latent bug as 3c1ed85, flagged by that fix's own Opus review as two
non-blocking sibling paths sharing the identical maxTokens: 8192
declaration with no strip (or an incomplete one):

- Compaction (runs precisely when history is near the context limit, so
  prompt_tokens is largest here of any Code path — the most exposed spot
  for this failure) had no before_provider_request hook at all, so no
  resourceLoader/extensionFactories wiring existed to strip it from.
  Added a minimal one.
- The skill sub-session's hook already existed but only handled the
  gate acquire/release, returning the payload untouched.
…x/experimental

Drop the Code-internal bugs (max_tokens truncation, context-usage %,
tool-call ordering) from the Discord draft — Code is brand new this
release, so nobody experienced those as regressions; they're not
user-facing "fixes." Keep the two real fixes to already-shipped
functionality (macOS engine parity, multi-GPU split) under Bugfix, and
split Code/Memory/Cloud Launch out into their own Experimental section
matching how Settings frames them.
@mohitsoni48
mohitsoni48 merged commit 1ee931d into main Jul 14, 2026
1 check passed
@mohitsoni48
mohitsoni48 deleted the release/v1.8.0 branch July 14, 2026 08:38
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