Run next-turn preparation only when the agent loop continues, allowing threshold compaction before the next provider request. Restore interactive progress when the same run resumes.
Closes#6879
Important to notice:
this changes when prepareNextTurn & prepareNextTurnWithContext are invoked:
the now run only when a next turn actually happens, and after shouldStopAfterTurn.
Reuse the stable renderer, viewport layout, theme detection, and terminal appearance updates. Keep successful operation status derived from replicated lane state.
Replace raw Harness and Chat service exposure with AgentController and replicated lane state. Start directly in new or resumed sessions and persist model selection.
Make facets non-generic and resolve host-local dependencies through service bindings. Rename local assembly to createFacetHost and consolidate TUI host mechanics behind one service.
A presentation draws tool calls and colours; it executes nothing and validates
nothing. Both cost it typebox anyway, which is 17 MB of module graph: 682 ESM
modules whose narrowest entry re-exports ~300 of them, so there is no
cherry-picking and both doors had to close before any process stopped paying.
ToolExecutionComponent no longer looks up createAllToolDefinitions by tool name;
callers pass what to render, and its parameter type now says so. Each built-in
tool's presentation moves to core/tools/renderers/<tool>.ts, leaving the schema
and execute path behind; bash and powershell share createShellRenderers(prompt).
Tool definitions keep their exact public shape and spread the renderers in.
withBuiltInRenderers() preserves the old fallback, where an extension override
without renderers still draws like the built-in - that behaviour used to live
inside the component, and two tests caught its absence.
theme.ts keeps palettes; the typebox schema and its error formatting move to
theme-json.ts, installed through setThemeJsonValidator. Built-in themes were
never validated, so only processes that load user-authored themes need it. pi
installs it during startup, before the first theme loads, and the invalid-theme
message is unchanged.
Measured with mini, running built output: the presentation drops from 87 MB and
99 files to 72 MB and 88 files, with typebox absent from its graph, while still
rendering tool output through the real built-in renderers.
Also: packages/server's vitest aliases were prefix matches, so a subpath import
resolved onto index.ts/utils/uuid and failed five suites; they are exact now
with explicit subpath rules. And mini-test.sh spawns mini the way pi-test.sh
spawns pi, with --fresh/--stop for the detached session server.
A barrel forces Node to evaluate its whole graph: importing reduceLaneSnapshot
(1 file, 6 KB) through pi-agent-core's index evaluated 106 files, and mini's
server loaded 112 files to enumerate JSONL headers. Types are free, so this only
ever affected value imports.
pi-ai gains ./utils/* alongside the existing ./providers/* and ./api/*
wildcards. pi-agent-core gains ./harness/context, ./harness/env/nodejs,
./harness/runtime/reducer and ./harness/session, and ./session/testing becomes
./harness/session/testing so every export mirrors its source path. The barrel
and ./node keep their batteries-included semantics.
The session layer takes uuidv7 from @earendil-works/pi-ai/utils/uuid rather than
the pi-ai barrel, which is what let ./harness/session drop from 42 files to 19.
Two imports there were written `import { type Usage } from` - not a type-only
statement, so it emitted a runtime import of the barrel.
scripts/check-entry-graphs.mjs walks each declared entry's value-import graph and
enforces per-entry budgets and forbidden paths, so a stray `export *` cannot
silently undo this. It caught the two `import { type ... }` regressions above.
Measured with mini: server 112 files and 98 MB to 28 files and 69 MB, presentation
204 files and 125 MB to 99 files and 110 MB. The worker still loads the provider
graph, correctly.
Each presentation opens its own harness watch, and the server broadcast every
worker event to every attached presentation, so with N presentations each event
crossed N^2 times and each client discarded the copies that were not its own.
The event frame now carries an optional destination. A presentation mints an id
and passes it to both sessions.attach and lane.watch, so the server learns it at
attach and the worker learns it at watch. The worker addresses that
subscription's events; the server routes them to exactly one presentation
without parsing a payload. Shared events, such as models state, still broadcast.
The client keeps its subscription id check: after a rebase the superseded
subscription is still open until unwatch lands, and its events carry the same
destination.
An experimental client for the new AgentHarness, built to find out what an
RPC-shaped presentation actually needs from it.
Three hosts: presentations (TUI) connect to a server over a unix socket, the
server spawns one worker process per session over stdio pipes and routes
between them. A peer provides named services and forwards names it does not
provide to its next hop, so a TUI reaches server-provided and worker-provided
services through one connection, and a worker reaches server services the same
way.
The worker owns every live object: storage, harness, lane, model runtime. A
presentation holds a replicated LaneSnapshot and nothing else. Each presentation
opens its own lane.watch() in the worker and applies events with the harness's
reduceLaneSnapshot, so no client re-implements snapshot/stream alignment.
The TUI reuses interactive-mode components and holds no live objects.
Replace ad hoc service assembly with atomic local and remote facet generations. Gate service proxy access by lifecycle and move the experimental Session worker and TUI onto facets.
Extension messages sent with triggerTurn: false while the agent was
running were pushed into agent state and the session tree immediately,
landing between an assistant tool call and its result. Providers that
validate message order reject the replayed history. Queue them during
the run and append at turn_end, mirroring recordBashResult.
closes#8537