- Added imageFormatForPath to map explicit save extensions to Puppeteer image formats.
- Derived screenshot capture type from explicit save paths and used it for both element and page screenshots.
- Updated MIME and extension handling so saved full-resolution image bytes and names match the chosen format.
- Removed `resume` from task params and schema, requiring agent and assignment inputs.
- Dropped resume continuation paths in task execution and call rendering, always spawning a new agent.
- Removed the `irc.enabled` setting and computed IRC availability by task-depth rules.
- Updated task follow-up guidance to use IRC messaging/history links instead of `task(resume:)`.
- Switched render outputs from byte arrays to base64 strings across native and TypeScript surfaces.
- Updated `RenderedFrame` and FFI declarations so `data` is a base64 string and `chars` are exposed.
- Added `SnapcompactSerializeOptions` with truncation, head/tail ratio and tool/arg/call limits.
- Tracked dim-marker state in rendering, reopening spans across chunk boundaries and stripping markers.
- Tracked whether strict tool schemas were actually applied and used that flag when deciding strict-to-nonstrict retries.
- Recorded strict-tool failures in session state so later requests skip strict mode retries and avoid extra error rounds.
- Hardened strict schema normalization by flattening nested pure anyOf unions and expanded schema error matching for additional invalid-schema rejections.
- Added a helper to strip model-facing `<task-result>` wrappers from job result text.
- Updated the job tool renderer to preview the unwrapped `<output>/<preview>` body instead of the envelope markup.
- Documented the settled-task preview fix in the coding-agent changelog.
- The `snapcompact-summary.md` prompt was removed from `packages/agent` to further centralize `snapcompact`-related concerns within the dedicated `@oh-my-pi/snapcompact` package.
- Introduced `isSnapcompactShape` for runtime validation of `SnapcompactShape` overrides, enhancing data integrity when loading configuration from dynamic sources.
- Updated README and native module comments to reflect the standalone `snapcompact` package structure.
Only flatten optional anyOf schemas when the union wrapper has no sibling constraints that would exclude null.
Added a strict-schema regression for constrained anyOf properties so null remains an outer branch.
Flattened optional union tool schemas during strict enforcement so OpenRouter DeepSeek V4 no longer receives nested anyOf branches without a type.
Added schema-level and OpenRouter DeepSeek payload regressions for optional string-or-array tool parameters.
Fixes#2270
- Added a shared `formatAnchoredContext` helper and switched mismatch formatting to use it.
- Extended unresolved block-edit errors to append nearby numbered context with `*`-marked anchor lines.
- Updated unit and integration tests and docs to reflect the new unresolved-block preview behavior.
- Added a new @oh-my-pi/snapcompact package and redirected compaction call sites to it.
- Added provider-aware snapcompact shape resolution for model-specific mixed-frame behavior.
- Added optional image detail support by extending ImageContent and passing hints through OpenAI providers.
- Added native snapcompact render options, including 5x8/8x8 font loading and palette/geometry controls.
The status-bar auto-thinking pending indicator used `?`-bearing symbols
in all three presets (`▣?` Unicode, nf-md-help_box Nerd Font, `[?]`
ASCII), which is visually indistinguishable from a terminal missing-
glyph fallback. Users assumed the icon was broken and toggled presets
to no effect.
Replaced each preset with an unambiguous loading indicator:
- Unicode: `⟳` (Clockwise Open Circle Arrow)
- Nerd Font: `\uf1ce` (fa-circle-o-notch)
- ASCII: `[~]`
Fixes#2267
The transform pass dropped or demoted every prior anthropic-messages
assistant turn the moment the conversation crossed a model id —
custom anthropic-messages providers configured via `models.yaml`
(DeepSeek, Z.AI, etc.) and session-level model swaps both lost their
reasoning chains on continuation, silently degrading agent behavior
for compatible reasoning endpoints.
Two coupled changes in `transform-messages.ts`:
* All prior `anthropic-messages → anthropic-messages` turns now keep
their `thinking`/`redactedThinking` blocks as native blocks, not
just the latest surviving assistant. The legacy
`mustPreserveLatestAnthropicThinking` flag is replaced by
`isAnthropicReplay` + `isLatestSurvivingAssistant`; the latest
byte-for-byte abandoned-tool-use exemption is preserved.
* Cross-model signatures are only stripped when an official Anthropic
endpoint is involved on either end of the replay. 3p ↔ 3p replays
(Z.AI, DeepSeek, custom anthropic-messages providers) pass the
signature through unchanged so the reasoning chain stays signed for
the next turn — compatible reasoning endpoints treat signatures as
opaque continuation hints, not cryptographically-verified material.
Source-side official detection uses the canonical catalog provider
id `"anthropic"` (assistant messages carry no `baseUrl`); target-
side detection reads the baked `compat.officialEndpoint` flag. Any
`redacted_thinking` sibling without a native landing spot is still
dropped to avoid lone redacted blocks beside text-demoted reasoning.
Fixes#2257Fixes#2265
Reflects the task tool's resume path (single-agent badge or resume=<id>) and the new irc op/receipt shapes (no awaitReply, op falls back to result.details.op) in the static html exporter.
The task tool now takes a single { agent, assignment, description, ... } and always runs the subagent in the background — the batch tasks[] array and shared context parameter are gone. Fan-out is parallel task calls; shared background flows through a '/Users/can/.omp/agent/sessions/-Projects-.tree-pi-commit/2026-06-10T15-36-32-782Z_019eb22d-970e-7000-8964-72c98becf3e8/local' file referenced in each assignment.\n\nIntroduces a persistent subagent lifecycle: finished subagents stay live as idle, the lifecycle manager parks them to disk after task.agentIdleTtlMs (default 7 minutes; 0 keeps them live until exit), and they revive automatically when prompted from the Agent Hub, messaged on IRC, or resumed via task. New task(resume: "<id>") revives an idle or parked subagent and runs a follow-up assignment in its existing session.\n\nAdds soft request budgets (explore/quick_task 40, others 90, configurable via task.softRequestBudget, 0 disables): crossing the budget injects a one-time wrap-up steer into the child; crossing 1.5× aborts the run gracefully. Cancelled/aborted subagent salvage replaces the old (no output) with the child's last activity snippet plus request/token stats; SingleResult tracks a per-child requests counter (assistant message_end events) used to sort agent lists in runtime-ascending order in both the live progress view (finished agents above pending/running) and the finalized result view, so rows no longer reshuffle on finalize. Adds a task gallery fixture variant for the resume path (renderer key separated from fixture key).\n\nAll task tests are reshaped around the single-call contract; tests for the discarded shared-context flow are removed, and new task-guards/task-resume/task-schema tests pin the new contract surface.
Tracks successful file reads per resolved base path (selector stripped) for the session; once a path has been whole-file-read three times, every subsequent read for that path appends a one-line nudge suggesting narrower line-range re-reads or the context echoed in edit results. Non-file sources (URLs, internal resources, directories, archives, SQLite, images) are never counted.
Adds enforceInlineByteCap() in streaming-output and applies it to bash and browser tool results: oversized outputs are elided head/tail with an artifact:// footer pointing at the full capture, closing paths that previously let 100KB+ inline results past the minimizer. Defense at the tool-result boundary (no-op for already-bounded output).
A job poll whose watched jobs are all still running and nothing was cancelled is pure 'still waiting' noise once a newer block exists. ToolExecutionComponent now detects isWaitingPollDetails on the result, leaves the block displaceable, and keeps its rows out of scrollback. EventController tracks the most recent displaceable poll and resolves it (sealing the block) on every event that proves another tool now owns the live region: a new tool call/result, a streaming arg start, an end-of-turn signal, or another job result that itself isn't displaceable. UiHelpers' transcript rebuild applies the same rule. Docs and the new job-poll-displacement test pin the behavior.
Adds compaction.strategy: "snapcompact" to the schema and the AgentSession routing: when chosen, both manual /compact (without custom instructions) and auto compaction call snapcompactCompact() to archive history as PNG frames instead of an LLM summary. Falls back to context-full with a visible warning notice when the current model is text-only or when /compact gets custom instructions. CustomTool and shared-event payloads carry the new action through. \n\nAlso wires the per-turn supersede pass: #pruneSupersededReads() runs every turn before threshold gating (cache-aware: only fires when the post-candidate suffix is small or the prompt cache is cold), prunes older read results superseded by a newer read of the same file, rewrites the session, and accounts the saved tokens in the next compaction decision. Gated by compaction.supersedeReads (default on).\n\nsession/messages.ts now delegates the core role conversion to agent-core's convertMessageToLlm so snapcompact image blocks flow through the LLM-context conversion path.
Registers a HistoryProtocolHandler with the internal URL router: history:// lists every registered agent (id, status, kind, last activity) and history://<agentId> renders a concise markdown transcript (tool calls collapsed to one line each, thinking elided). Live refs render from the in-memory message array; parked refs load read-only from the JSONL session file via loadSessionMessagesReadOnly (no writer, no lock). System prompt + read tool prompt + docs/tools/read.md learn the new scheme.
The session-observer overlay is gone. The Agent Hub (ctrl+s, alt+a, or double-tap left arrow on an empty editor) presents one overlay with two views: a live registry table (status, unread irc count, current task, last activity; j/k to navigate, r to revive, x to abort/release) and per-agent chat (transcript + input line) — submitting revives a parked agent and steers it via the normal prompt path. Main is the ambient chat and stays out of the table.\n\nrenderInitialMessages no longer takes a prebuilt context: every redraw now reaches for AgentSession.buildTranscriptSessionContext() (full-history transcript with each compaction emitted inline at the point it fired, snapcompact frames re-attached on rebuild). UiHelpers drops the deferred-compaction render and the IRC autoreply branch that the new mailbox bus deprecated. CompactionSummaryMessage renders as a slim divider (── 📷 compacted · ctrl+o ──), expanding to the summary + snapcompact frame count. session-manager.buildSessionContext gains a { transcript: true } mode; an exported buildSessionContextFromFile() reads a session file without taking the writer lock so the hub chat view can tail any agent (parked or live). Theme picks up icon.camera + tool.irc symbol entries.
Shared background now flows through a '/Users/can/.omp/agent/sessions/-Projects-.tree-pi-commit/2026-06-10T15-36-32-782Z_019eb22d-970e-7000-8964-72c98becf3e8/local' file referenced in each prompt instead of a context string forwarded into the subagent's system prompt. The JS and Python preludes drop the context kwarg from agent(), the subagent system prompt drops the {{#if context}} block and the conversation-context file pointer, and runEvalAgent no longer writes a per-call conversation context file. AgentSession sheds the now-unused formatCompactContext() helper that supplied the file's body, and ToolSession.getCompactContext is removed alongside it.
Replaces the blocking auto-reply IRC turn with a process-global IrcBus and a four-op tool (send/wait/inbox/list). send is fire-and-forget with per-recipient delivery receipts (injected/woken/revived/failed); replies become real turns by the recipient, observed via wait (or the send await:true sugar). Bounded per-agent mailboxes (cap 100) drop oldest on overflow; AgentSession.deliverIrcMessage folds an in-flight delivery in as a non-interrupting aside at the next step boundary, or starts a real wake turn when idle. AgentRegistry/lifecycle handle the idle→woken / parked→revived transitions, so messaging a non-running peer brings it back. Adds a dedicated TUI renderer (directional headers, delivery-outcome coloring, quoted bodies, per-recipient receipt trees, status-badged peer lists with unread counts). irc.timeoutMs is now the default timeout for wait / send await:true. AgentSession sheds the agentRegistry config field, the dedupeIrcReply → dedupeEphemeralReply rename (now used by /btw and /omfg), and the background-channel exchange queue / forwardIrcRelayToMain plumbing the auto-reply model needed.
Introduces AgentLifecycleManager: when the task executor adopts a finished subagent the manager arms a TTL timer on idle, parks the agent on expiry (disposes the live session, keeps the AgentRef + sessionFile), and revives it on demand via an injected reviver. Only this manager flips parked ↔ idle. AgentRegistry now annotates session: AgentRef["session"] as null exactly when parked/aborted, and sdk.ts wires the lifecycle dispose into main-session teardown, derives agentKind once, and gates ref unregistration on parking so a parked agent stays addressable (history://, revive).
Removes isBackgroundJobSupportEnabled and JobTool.createIf; the tool is now registered unconditionally via `new JobTool(s)`. `async.enabled` now gates async bash commands only — the task tool runs asynchronously regardless. Deletes the async/support module and its barrel re-export.
Interrupting mid-tool execution (e.g. Enter with a pending steer) drained the steering queue into the dying run — it landed in history without a response and the post-abort resume saw an empty queue, so the agent stopped instead of continuing. Steering/follow-up/aside queue polls in runLoopBody and the post-tool-call check in executeToolCalls are now skipped once the run's abort signal fires, leaving the queue intact for Agent.continue().
Adds snapcompactCompact() in compaction/snapcompact.ts: instead of an LLM-generated summary, discarded history is printed onto dense 2576px PNG frames with the public-domain X.org 5x8 pixel font and re-attached to the compaction summary message as image blocks. Fully local — no model call; ~7x cheaper than raw text at near-parity recall. CompactionSummaryMessage now charges per attached frame in estimateTokens(), frames persist under preserveData.snapcompact with an 8-frame budget that evicts middle-out (session-head frame pinned so head and tail both survive). Rasterization and PNG encoding run in native code via renderSnapcompactPng().
Adds pruneSupersededToolResults() and the opt-in PruneConfig.supersedeKey hook: when a tool call shares a key with a newer one (e.g. a re-read of the same file), the older result is pruned even inside the protectTokens window and replaced with a [Superseded by a newer read of this file] placeholder. Adds readToolSupersedeKey() and the shared splitReadSelector() implementing the read-tool path/selector grammar (including the .. range alias and L-prefix forms) so selector-free reads supersede range reads of the same file and URL-scheme paths are exempt. Strips selectors before tracking in <read-files> compaction lists, so reads dedupe to the base path and match write/edit paths when splitting read-only vs modified lists (selector-polluted lists from earlier compactions self-heal on the next pass). Gated by the new compaction.supersedeReads setting (default on).
Splits the array-form defaultConvertToLlm into a single-message convertMessageToLlm that embedders can delegate every core role to instead of duplicating the conversion. Adds the optional images field on CompactionSummaryMessage so the converter attaches snapcompact frames after the summary text (snapcompact strategy lands next). Renames every convertToLlm call site in compaction.ts and branch-summarization.ts to the canonical defaultConvertToLlm.\n\nNote: the bundled CHANGELOG entries also cover the supersede-reads, snapcompact, and steering-queue fixes that follow in this batch (the entries land in directly adjacent lines and cannot be split by diff).
- Added pre-TUI startup input capture that queued non-empty Enter submissions.
- Replayed queued startup submissions after initialization and injected startup editor text into InteractiveMode.
- Handled startup Ctrl+C to clear typed text and Ctrl+D to exit, with detach restoring terminal state.
- Extended TUI Editor `onSubmit` to accept `void | Promise<void>` for async handlers.
- Added standalone Bun script reading session JSONL with real per-request usage.
- Scanned main-vs-subagent split, residency, repeated reads, and edit churn.
- Classified costliest sessions via pi-ai for hygiene, spawn, and waste verdicts.
- Added stats:audit script, prompt, and scanFile unit tests.
- Fixed npm CLI bundling by invoking `generate-client-bundle.ts --generate` before build and `--reset` after.
- Added `PI_BUNDLED` handling in `packages/stats/src/server.ts` and switched bundled distributions to embedded assets.
- Decoded embedded archives via `decodeEmbeddedClientArchive` and treated legacy placeholder content as missing.
- Handled missing dashboard source/build directories as ENOENT during mtime scans to avoid startup crashes.
- Extended `--smoke-test` to start the stats server and validate dashboard HTML contains expected asset markers.