release: v1.8.0#58
Merged
Merged
Conversation
…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.
mohitsoni48
force-pushed
the
release/v1.8.0
branch
from
July 14, 2026 06:01
7bedd06 to
96eadd6
Compare
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release branch for v1.8.0 (minor bump from 1.7.7).
Summary of work since v1.7.7
Prepared per docs/RELEASE.md. Release-prep commit (version bump, changelog, README sync, persona KB) will follow on this branch after review.