Skip to content

v1.3.0

Latest

Choose a tag to compare

@github-actions github-actions released this 28 Aug 21:40
· 4142 commits to main since this release

@veyyon/agent-core

Breaking Changes

  • AgentOptions.cursorRulesResolver is removed: an agent no longer supplies a second, per-api rule channel beside its system prompt.

Added

  • A ChatGPT OAuth (Codex) session compacts server-side via the Responses compaction endpoint, preserving encrypted reasoning state.

Changed

  • Compaction imports ProviderHttpError from its owning module rather than the @veyyon/ai/error barrel, cutting 14 modules off the engine's load graph with no change in behavior.
  • Streaming message_update snapshots share tool-call arguments by reference instead of deep-cloning them on every delta, cutting a large structured tool call's per-delta snapshot cost from ~0.5 s to ~8 ms, while terminal messages and the authoritative tool call a toolcall_end carries keep the sanitizing deep clone.
  • Superseded and useless tool results are now pruned as a batch whose combined size pays for the prompt-cache rewrite it forces, instead of only when a single result sits within 8,000 tokens of the end of the conversation.
  • The tokenizer takes estimateTokensFromText from @veyyon/utils/tokens rather than the package barrel, cutting the modules a token estimate loads from 92 to 10.
  • Compaction's directory-list documentation now uses canonical search files terminology instead of the retired find tool name. No runtime behavior changed.
  • A tool that blocks on only some of its operations declares interruptibility per call, so an interrupt arriving beside a non-blocking or malformed call no longer replaces that call's own result with a skipped placeholder.
  • A tool result that ran and failed no longer supersedes an earlier successful read of the same path, which replaced that file's content with a supersede notice and left the conversation only the error text.
  • A tool call whose id already carries a real result in the transcript is never executed a second time, whichever channel answered it; a never-ran placeholder still counts as unanswered and is retried.
  • An interrupted cursor-agent turn keeps a tool call whose arguments the start frame already delivered, instead of deleting it and telling the model its arguments never finished.

Fixed

  • Converted message wrappers preserve reference identity across turns when inputs are unchanged, avoiding unnecessary allocations and memo invalidations.
  • Fixed tool-result supersede pruning to parse multi-target read calls into target sets with per-target URL scheme exemption, retiring an earlier read result when all of its targets are covered by newer reads while preserving results with partial coverage.
  • Side requests derive a stable conversation ID per oneshot kind, preventing compaction, handoff, and branch summaries from overwriting live Cursor and Devin conversation state.
  • Aborting while paused rejects the pause wait and prevents the agent loop from starting another provider turn or paused tool.
  • A branch-summary reserve at or above the model's context window now falls back to the proportional 15% reserve instead of leaving a non-positive budget, which the entry preparation read as "no limit" and which sent the whole branch.

@veyyon/ai

Breaking Changes

  • StreamOptions.cursorRules and the exported CursorRuleInput type are removed, and buildCursorRules takes only the system prompt: the Cursor provider builds exactly one request-context rule, the assembled prompt.

Added

  • ToolCallLoopGuard detects consecutive redundant reads of unchanged files whose requested line ranges are already fully present in recent context, steering runaway exploration loops while preserving prompt cache prefixes.
  • Added Command Code API-key login through the Studio Provider page, with validation against its Provider API, and Nous Research Portal OAuth device login with rotating refresh tokens and short-lived inference JWTs.
  • explain(error) in @veyyon/ai/error/flags returns the classification id together with the names of the rules that produced it, and every classification rule states a name.
  • Added nous-research-api-key, a second way into Nous Research that takes a key pasted from the Portal instead of running the device flow, validated against the inference API and stored as the same nous-research credential.

Changed

  • The assistant-text extractor's one-owner check names the consolidated evals package path instead of the retired metaharness path. No behavior change.
  • ToolCallLoopGuard waits for a third consecutive subsumed read before steering, up from the second, so two narrowing reads of one file are no longer treated as a loop; model.toolCallLoopGuard.readSubsumptionThreshold still sets it.
  • Formatted tool-call loop guard whitespace; behavior is unchanged.
  • The Anthropic provider reads its endpoint, credential placement, rejected betas and retry policy from the catalog's wire-capability table instead of comparing provider ids at seventeen call sites.
  • ToolCall.arguments is a Readonly<Record<string, unknown>>, so a producer replaces the object instead of writing into one a streaming snapshot already shares.
  • A streaming request no longer pins a parsed clone of its wire payload for the life of the stream: every provider's diagnostic dump retains only the exact sent bytes and materializes a body when a 400/413 dump is built.
  • The OpenAI-family, pi-native and Codex request builders serialize the request body once instead of deep-cloning the request graph, which took attempt preparation on a 32MiB context from 82ms to 9ms.
  • A message that names a dead socket reads the same everywhere: namesDeadSocket in @veyyon/ai/error/flags is the one list of errnos and phrases, and ENETUNREACH, EHOSTUNREACH and EAI_AGAIN now count as transient transport failures like the rest of them.
  • Formatted source files for Biome compliance.
  • withAuth imports the two error classes it throws from their owning modules instead of the @veyyon/ai/error barrel, so a consumer of the auth-retry wrapper no longer loads the provider-error registry and every error domain behind it; behavior is unchanged.

Fixed

  • An auth-broker snapshot containing an API key or OAuth credential stored by an interactive login validates again; the source field on either credential type made every client reject the whole credential pool.
  • A long name whose tail cycles is no longer read as a runaway sampler: a folder, path segment, hex digest or identifier that repeats a short group past the 180-character threshold ended the turn with Thinking loop detected: repeated "…" N× back-to-back and re-sampled a prompt that produced the same name for the same reason. A whitespace-free run that continues a longer token is data, on both the streamed detector and the completed-text scanner; a run that begins at a token boundary still trips.
  • A cursor-agent model receives the operator's instructions again: the server rebuilds the prompt head with its own system prompt and applies none of the request-context rules, so the assembled prompt now rides on the active user turn inside an <operator-instructions> block.
  • A cursor-agent request uploads the operator's instructions once instead of three times: the request-context rule payload and the prompt-head blobs, both discarded by that server, no longer carry a copy, and a request that would send any count other than one fails before it is written.
  • Each tool call in a cursor-agent batch keeps its own arguments: updates route by the frame's call_id instead of a single "current call" pointer, which let a completing call overwrite the arguments of the one opened after it and left the first call with {}.
  • A cursor-agent turn that ends with tool calls still streaming closes every open call rather than only the one the pointer last named, so a second call of a batch is no longer dropped as unfinished, and each closed call keeps its own parsed arguments and carries no streaming marker.
  • A 503 auth_unavailable refusal is classified as an authentication failure rather than a bare server status, so compaction falls back to an authenticated model instead of failing the whole compaction (#986).
  • A llama.cpp tool-call JSON parse failure explains itself and names the fix on every route to a local server, not only when the provider id is ollama, so an LM Studio or llama-cpp user sees why the turn stopped instead of a bare HTTP 500 whose retry was already being suppressed.
  • A llama.cpp tool-call JSON parse failure stops the retry ladder whether it arrives thrown from a request or recorded on an assistant message; the two classifier entry points share one post-walk latch instead of each deciding, so the same 500 no longer burned every attempt on one route while surfacing immediately on the other.
  • A bare 502 Bad Gateway or 504 Gateway Timeout is read as the upstream failure it is and costs a twenty-second retry, matching 500; both previously matched no rule, came back as an unreadable body, and suppressed the failing model for five minutes over a gateway blip.
  • A rate-limit message reads 503, 529 and 500 as the status codes they are rather than as digits inside a longer number, so an exhausted balance reporting 5030 credits remaining rotates the credential instead of retrying the same account after a 45-second capacity backoff.
  • A Gemini or Cloud Code Assist body that carried a whole turn and then ended without a finishReason settles on what arrived rather than failing as a truncated response, matching the four dialects that already read the shared end-of-stream judgement; a body carrying nothing usable is still refused.
  • A stream that ended without a terminal finish reason is classified as the truncation it is whatever the provider called it, so an OpenAI completions turn that stopped early is retried like the identically-worded Cloud Code Assist one instead of ending the turn; an empty response body is the same fault and is classified with it.
  • A turn that ended on an error finish reason is retried whichever provider reported it: Amazon Bedrock said "Generation failed with stop reason: error" and both Google paths "Generation failed with finish reason: error", neither of which the turn domain's pattern matched, so the identical failure retried on OpenAI and ended the turn on the other three.
  • A refusal spelled as a finish reason (PROHIBITED_CONTENT, SAFETY, RECITATION, BLOCKLIST, SPII and their IMAGE_ forms), as finish_reason: sensitive, or as a Codex event carrying code=cyber_policy is classified as a content verdict and vetoes a retry, where only MALFORMED_FUNCTION_CALL had a rule.
  • An abnormal WebSocket closure is transport vocabulary, so a Codex stream that died with code 1006 is retried rather than reported.
  • A 5xx is no longer read as an authentication failure because its body names an authentication service, so Anthropic's 503 overloaded_error: Authentication service is temporarily unavailable. Retry the request. is retried instead of walling the turn and pointing credential recovery at an account with nothing wrong with it.
  • A Cursor MCP tool call the exec channel already dispatched is marked resolved on the assistant message, so the agent loop no longer runs the same call a second time after the turn closes and appends a duplicate toolResult under an id that already had one.
  • A compact route that answers 404 is recorded as absent for that model for the rest of the process, so server-side compaction is asked once instead of once per compaction, and the error names the model rather than repeating "Server-side compaction failed".
  • AIError.status and extractHttpStatusFromError are one reader, so a provider message spelled error(503), status_code: 429 or 429 Too Many Requests yields the same status to the auth ladder and the retry ladder instead of one of the two seeing nothing; a status field anywhere in the cause chain now outranks prose anywhere in it.
  • A Nous Portal call that a gateway answers with an HTML 502, 503 or 504 reports the gateway status instead of "returned invalid JSON".
  • A pi-native payload hook rejection names the reason it gave rather than only the seam it came from, and an error may declare its text describes a local decision so a quoted 401 does not rotate the operator's credential.
  • OpenAI Codex request diagnostics redact every credential header rather than authorization alone, so a Codex request carrying x-api-key, proxy-authorization or a provider-specific key spelling no longer writes it in plaintext to the debug log.
  • A first-event stall is retried once on every provider that does not run its own stall ladder, so a single silent connect on OpenAI completions, OpenAI Responses, Azure Responses or Ollama no longer ends the turn unretried.
  • A persisted 400/413 request dump redacts x-goog-api-key, so a rejected Google Generative AI or Vertex request no longer writes the operator's plaintext API key into logs/http-400-requests/.
  • A failed Amazon Bedrock turn reports its elapsed duration again, instead of carrying time-to-first-token with no total while a successful turn reported both.
  • Normalized cumulative tool-call argument delta snapshots for OpenAI Codex streams while preserving true incremental deltas on standard OpenAI Responses streams via declared per-provider wire shapes.
  • The Cursor HTTP/2 client session handles error and close events directly so connection drops, DNS resolution failures and socket resets reject the turn with a classified error instead of raising an unhandled exception.
  • A read of several ranges, such as :5-16,960-973, is judged already-read only when every one of its ranges was read before, instead of keeping the first range, discarding the rest and steering the model away from lines nobody had read.
  • Streamed tool-call argument deltas in OpenAI Responses streams append incrementally rather than truncating on coincidental prefix matches.
  • Fixed ToolCallLoopGuard deciding read subsumption from rendered result text and summary phrases, preventing follow-up range reads of summarized files from being falsely blocked.
  • Fixed read tool target parsing in ToolCallLoopGuard to correctly handle URI schemes, Windows drive prefixes, compound raw-range selectors, and open-ended ranges without falsely subsuming distinct reads.
  • Fixed OpenAI server-side compaction requests omitting the Authorization header when constructing headers from request setup.
  • Supported server-side compaction on the ChatGPT Codex backend with OAuth credential and turn identity headers.
  • API option mapping preserves side-request conversation IDs, preventing Cursor and Devin requests from falling back to the live session ID.
  • Cursor turns fail immediately when an asynchronous exec-server handler fails; malformed grep line or count values and oversized Connect frames fail before protobuf or buffer exhaustion; and success waits for queued handlers and gRPC trailers so quota and availability statuses are preserved.
  • A rejected API key reports the provider's own sentence from its JSON error envelope, so Command Code's plan-limit refusal reads as "Your Go plan doesn't include API access. Upgrade to Provider or higher at https://commandcode.ai/billing to use these endpoints." instead of the raw body.
  • An API-key login for a provider that declares storeCredentialsAs now stores the credential under that provider id, as an OAuth login already did, instead of filing it under the login mechanism's id where nothing reads it.
  • Anthropic strict-tool planning now recognizes the unified search tool instead of the retired find identity, so canonical workspace search receives strict schema enforcement without reviving a legacy tool name.

argot

Changed

  • The dictionary generator and its constants name the packages/evals/datasets/dicts/ corpus path instead of the retired deepswe-bench path. No behavior change.

@veyyon/catalog

Added

  • Export normalizeOllamaBaseUrl and toOllamaNativeBaseUrl, the single definition of how an Ollama base URL is spelled for each of its two APIs.
  • Added the Command Code provider catalog, with its documented coding flagships as the offline seed and credentialed discovery for the wider Provider API list.
  • Added the Nous Research provider catalog, whose credentialed discovery keeps tool-capable chat models and excludes embedding, media-generation and non-tool rows.
  • Added the publishesOwnModelLimits provider flag, which stops generation from backfilling a context window or output cap from another host's same-family model.
  • ProviderWireCapabilities.anthropicMessages declares how a provider serves the Anthropic Messages API — its endpoint, credential placement, rejected request features and retryable model errors — and declaredProviders() and declaredCapabilityNames() derive the declaring sets from the table.
  • Bundled model resolution persists a content-verified enriched snapshot, and a registry cache stamp moves on every row-content write, and on a row crossing the freshness window it is read under, without treating SQLite sidecar churn or a provider re-verifying models it already had as a change.
  • Added supportsServerCompaction capability data for ChatGPT Codex backend models on the Responses API.

Fixed

  • LM Studio discovery reports the context window the running server accepts (loaded_context_length) rather than the model's compiled ceiling (max_context_length), so a model loaded below its ceiling no longer plans a session for context the server refuses.

Removed

  • Removed derive-tmp.ts, a scratch probe swept into the package by accident; nothing imported it and no entry point exposed it.

@veyyon/coding-agent

Breaking Changes

  • The model-facing workspace search surface is now one mandatory search tool taking ordered required type ("files" | "text" | "structure") and input, replacing the separate glob, grep, and ast_grep tool IDs. Retired per-engine and search.enabled values are discarded while text-context values and persisted tool inventories migrate.

Added

  • search takes paths for a text search, returning the matching files with per-file counts instead of match lines; the shell route it replaces (rg -l) is intercepted, and searching buildSystemPrompt under packages/coding-agent/src costs 3,492 tokens as match lines against 215 as a file list.
  • Esc pressed twice within half a second over a composer holding text discards the draft; undo brings it back, and a single Esc still leaves the draft alone.
  • /advisor reports advisor status, opens the WATCHDOG.yml roster editor and applies a save to the running session, starts or stops the advisor for the session, and copies the advisor's own transcript; the subsystem shipped complete but no command, key or menu row reached it.
  • session.newKeepsBackground decides what /new does to a turn still streaming: off (the default) stops it and closes its provider stream before the new session starts, on keeps the old conversation running and says which one.
  • The status line carries a background chip counting conversations this process is still running that no screen is showing, present in every preset and silent at zero.
  • The terminal renderer composer zone gains a formal defect oracle and automated invariant sweep suite covering prompt counts, output bleed, row mixing, footer alignment, mouse click routing, caret positioning, overflow, pad transparency, hairline integrity, and virtual scroll stability.
  • prewalk.cheapModel and prewalk.strongModel configure the cheap model prewalk switches into at the first edit and the strong model it starts on.
  • /prewalk accepts an optional model argument to arm a per-session target model override.
  • edit.afterEdit selects what one turn that changed files owes before it finishes: verify (the default) runs one check when none followed the last edit, review reads back every code file changed since the last user message and judges correctness, maintainability and cross-file contracts, off neither; the legacy edit.critiqueCodeMutations boolean migrates to review or verify on load.
  • Configurable launch.cleanupWaitMs setting (default 15 minutes) purges exited launch daemon records from memory and disk after a retention TTL.
  • Exporting a session to HTML streams the snapshot into the output file instead of assembling the whole document in memory, taking an 80MiB transcript from 1007MiB of peak resident memory to 532MiB with byte-identical output.
  • A session snapshot that contains a reference cycle fails the HTML export with an error instead of writing until the disk fills.
  • bench/session-memory.bench.ts reports heap after a forced GC, current RSS and high-water RSS at each of three phases (module baseline, SessionManager.open, buildSessionContext) over a synthetic transcript sized by SESSION_MB.
  • Added model.toolCallLoopGuard.readSubsumptionThreshold (default 3) to steer models that re-read unchanged code lines back-to-back before consuming full context.
  • VEYYON_DEBUG_STARTUP=1 writes one line per phase of a prompt submission (compaction check, plan arm, context build, memory context), so a slow submit names the phase that spent the time.
  • read takes depth and limit arguments for directory listings, and a read of the session working directory root with neither now returns a concise top-level listing with per-subdirectory entry counts instead of the recursive tree.
  • A tool result that carries an image now states whether the picture reached the screen, so a model reading a file describes what it shows instead of reporting that it displayed it.
  • A picture the block gives up on after the fact, because the session's image budget demoted it or a Kitty session could not convert it, is stated to the model as undrawn instead of being reported as displayed.
  • statusLine.segmentOptions.path.displayRoots names the workspace roots the working directory is shown relative to, with ~ accepted for the home directory, replacing the two hard-coded conventions (~/Projects and /work); the first matching entry wins and a non-absolute entry is dropped and named in the log once.
  • read accepts a semicolon-delimited list of internal resources (skill://demo/one.md;skill://demo/two.md), the same list form search takes, and returns one section per entry.
  • Eval kernels gain kv, a bounded JSON store under the session's artifacts directory that survives kernel resets and is shared between JavaScript and Python without cross-session filename collisions or lost concurrent updates, and defs(), which lists the names user code has defined in the kernel.
  • Every supervised process termination records which component ended it and why, with distinct attribution for each path (operator stop, signal, restart, broker shutdown, idle reaper, OS signal, broker recovery, launch failure, external signal, and natural exit); launch list output shows the lifetime owning condition and retained completion records with exit codes, reasons, and output tails, queryable after the name is reused and across broker restarts.
  • A click on the working directory, git branch or pull-request text in the composer status line widens the location to the row and retracts the model chip to pay for it, animated over the shared expand curve, and a second click reverses it; display.transitions: off lands on the click frame.
  • /omfg forges rules that carry the extended TTSR frontmatter (astCondition, interruptMode, pathScope, repeatMode, repeatGap, repeatCompactions, warmupMatches), confirms ast-grep conditions against the conversation's tool history through the same gate chain a live stream applies, and fails loudly on a malformed optional field instead of dropping it.
  • /omfg saves forged rules to the active profile's rules directory only; the project target is gone, because project .veyyon/rules was never discovered across sessions.
  • Settings → Stream Interrupts (TTSR) groups the profile's own rules under a leading User created section instead of From native, ahead of foreign-tool and built-in sections.
  • An opt-in eval.pyWorkspace experiment teaches the agent to keep large tool results and repeated repository operations inside the persistent Python kernel, reducing intermediate transcript output without changing kernel execution.

Changed

  • A search result carries its limit and truncation notice again. The unified tool nested each type's result one level deeper than the output layer reads, so a capped search files returned five paths and said nothing about the cap, no search of any type ever named a raised limit or a page selector, and an already-spilled result was spilled and truncated a second time.
  • A capped structure search pages with skip and names the match count instead of advising limit, which the tool accepts only for a file search: following the old advice cost a rejected call, an error result and a retry.
  • A structure-search metavariable binding is elided past 60 bytes, because a binding is a source range inside the match the result has already printed line by line. A multi-node capture arrives joined onto one line, so $$$BODY was a second copy of the whole body, and a capture spanning lines entered the result with no line number for a caller to anchor on. Over five patterns of this repository the bindings cost 8,414 tokens on top of 9,052 tokens of match text, and the same five searches now cost 12,769 tokens against 19,826. A value inside the bound is kept, since it names which fragment bound to which variable for about ten tokens.
  • A text search states its per-file match cap and its internal ceiling. A search of one file returned the first 200 matches and said nothing, so a file with 4,000 matches read as a file with 200; a multi-file search capped each file at 20 with the same silence; and a search that stopped at the 2,000-match native ceiling reported a file count that was a floor as though it were a total. skip pages files and reaches nothing past either cap, so each notice names narrowing the pattern.
  • A search call rejected for a cross-type field names the fields that type accepts. In a recorded trial a model sent limit to a text search, read Search type "text" does not accept: limit, and spent a second request rediscovering the field by removing it; the rejection already costs a full round trip, so it now states the set that would have worked.
  • A read whose line bound came from the default rather than from the caller is held to the tools.artifactSpillThreshold budget every other tool result is held to, instead of a compiled 512-bytes-a-line allowance: reading packages/coding-agent/CHANGELOG.md cost 19,768 tokens and now costs 10,586, and :50 cost 23,643 and now costs 10,357. A read that names a line count still returns those lines.
  • Reading a notebook, a converted document, an archive entry, a URL body or an internal resource is bounded by tools.artifactSpillThreshold instead of a compiled 50KB constant the setting could not reach, so lowering the setting now lowers those results as it already lowered bash, eval and ssh output.
  • A @path mention in a prompt is bounded by tools.artifactSpillThreshold instead of a compiled 50KB constant, and a capped mention names the budget in effect rather than that constant; the file body, an over-long first line and a mentioned directory's listing all take the same budget.
  • A multi-range read whose first line alone exceeds the output budget names that line and its size, instead of a backwards [Lines 1-0 …] window whose continuation selector pointed back at the line that just failed.
  • A directory listing from read takes the tools.artifactSpillThreshold budget: the unsliced listing followed a compiled 50KB constant the setting could not reach, and a listing sliced by :N-M carried no byte bound at all. A capped slice names how many listing lines remain and the selector that continues them.
  • An archive directory listing from read takes the tools.artifactSpillThreshold budget instead of a compiled 50KB constant the setting could not reach.
  • A structural summary from read stops at read.defaultLimit lines or the tools.artifactSpillThreshold budget, whichever binds first, clips lines to tools.outputMaxColumns, and names the bound that stopped it and the line that continues it. A summary is a projection over the whole file and carried no bound at all, so a selector-free read of a declaration-dense file returned the whole projection: packages/catalog/src/discovery/cursor-gen/agent_pb.ts cost 326.5KB, about 82,000 tokens in one result, and now costs 3,138 tokens. Across 4,039 summaries in this repository the median is 57 lines; the 54 files over 300 lines cost 311,211 tokens between them and now cost 174,450.
  • A PDF image-member list and an agent://<id>/<field> extraction from read take the tools.artifactSpillThreshold budget. Both returned the whole collection whichever size the document or the artifact happened to be: a scanned PDF listed every extracted image, and an extraction takes no line selector, so nothing could page it. A capped list names how many members it dropped, and a capped extraction is cut by bytes and names its full size and the URL that pages it.
  • A read of a directory that names no depth lists the top level instead of recursing two levels: every entry up to 100, each subdirectory annotated with its direct-child count, and a footer naming depth: 2 for the recursive listing. Only the session working directory root answered this way before, and the second level is a default nobody asked for: over seven directories of this repository the selector-free read cost 27,568 tokens and now costs 7,131, with packages/coding-agent/src at 8,163 against 1,273. A directory wider than 100 entries states how many it held back and names depth: 1 for the flat listing of all of them; a caller that names depth or limit is unaffected.
  • A search_tool_bm25 call activates the tools it matched well instead of every ranked match up to its limit of 8, which under tools.discoveryMode: all is the whole hidden set. An activated tool's schema is re-sent on every later request of the session, so one loose query cost more than the mode saved: "keep track of what is left to do" activated todo plus set_cwd, task and web_search for 2,239 tokens a request where todo alone costs 1,048, and across eight such queries the tail was 3,319 of 7,349 tokens. A tool now activates when it scores at least half the best match, so a near-tie still activates and the same queries cost 4,030; a weaker match is returned in also_matched and a query naming it activates it.
  • tools.artifactSpillThreshold bounds what a spilled tool result costs a request, not only when an artifact is written. The window kept inline was tools.artifactHeadBytes + tools.artifactTailBytes, so a 404KB result delivered 39.8KB at a threshold of 8KB and the same 39.8KB at 200KB; it now delivers 7.8KB and 39.8KB. The head and tail settings shape the window inside the budget and keep their ratio, and the elided bytes stay recoverable through the artifact:// id. This covers every tool result except read, which is bounded by the lines its caller asked for.
  • The debug tool loads only where at least one configured DAP adapter command resolves, so a host with no debugger installed no longer pays about 1,000 tokens of debug schema on every request for a tool whose every call would fail on the missing adapter.
  • search.contextAfter defaults to 1 line instead of 3. A tool result is sent again on every later request of the session, so each line of a search result is billed once per remaining request; over eight searches of this repository the wider window cost 16,836 tokens against 11,483.
  • The eval prompt-override registry, the system-prompt eval hooks, the argot cache and the reroot hint name @veyyon/evals paths instead of the retired @veyyon/metaharness, @veyyon/typescript-edit-benchmark and @veyyon/deepswe-bench packages. No behavior change.
  • The /providers account card filters its provider sidebar: ctrl+s enters search, typing narrows the list by fuzzy match on provider name and id, the arrows move within the matches, and esc leaves search before it closes the card (#922 by @Crqptx).
  • A parked subagent is pruned rather than closed: subagent.autoClose.enabled, .parkedMs and .waitingMs are now subagent.prune.enabled, .afterMs and .waitingAfterMs, existing config files migrate on load, and the settings tab states park and prune as two stages in their own groups.
  • A parked subagent keeps its roster row for an hour, and two hours when it stopped waiting on a peer, instead of five and thirty minutes.
  • A subagent restored from a previous run is aged from its own transcript rather than from the moment this session found it, so restored agents no longer sit at "just now" forever and are pruned on the same budget as the rest.
  • session.newKeepsBackground states that a change needs a restart, because switching it does not affect the running session (#928).
  • A turn that changed files takes at most one continuation before it finishes; the verification pass that always ran unconditionally is now the verify value of edit.afterEdit and no longer stacks a second forced continuation under the review pass.
  • The Julia, Python and Ruby eval kernels share one execution loop instead of three copies of it; no change to how a kernel behaves.
  • Reading a file or fetching a URL no longer loads the document converters, and a web search no longer loads the browser fingerprint generator, because the constants those paths wanted are separated from the libraries that sat behind them, taking about 40ms off session startup.
  • The launch card is painted and flushed before the agent runtime graph is loaded, taking an interactive launch from a blank terminal for 760ms to a typable composer at 111ms.
  • The agent runtime is loaded in stages that hand the event loop back between them, so a character typed while the launch card is up is drawn in 6ms instead of 198ms; the load itself takes the same time.
  • /agents is the subagent dashboard: the surface is named that in its title, its /agents description, its keybinding descriptions, /hotkeys and the handbook, in place of "Agent Control Center".
  • The subagent dashboard no longer opens across every conversation the process is running: /process-manager and the a scope toggle are removed, and the card is scoped to the conversation on screen.
  • A locally built binary minifies identifiers the way the released one does, cutting it from 310.7MB to 303.4MB and its launch card from 143ms to 131ms; function names are still kept, so stack traces are unchanged.
  • veyyon --help renders its command list from registry summaries verified against command statics and loads only the hidden default command for its flag table, reducing a measured warm Windows invocation from 1.2 seconds to 0.13 seconds.
  • The CPU model is read once per process instead of on every system prompt build, removing about 30ms from the window before the composer accepts input.
  • No user-visible change: the once-per-process CPU model cache gained a reset the test suite calls, so a suite that fakes the platform reads its own answer instead of the one an earlier suite in the same process cached.
  • Row band painting compiles its escape pattern once for the process rather than once per painted row.
  • The default launch command imports the session runtime and ACP terminal authentication only when it runs, so loading its flag table no longer imports the runtime graph.
  • The vibe screens, the image-inspection call and an LSP hover code block draw no border of their own inside a tool block, so a block keeps one left edge; a tree connector remains only where a row belongs to the row above it, in the eval value tree, the search line gutter, the job tree and the LSP reference tree.
  • Classified runner output (cargo, bun, Go, ctest, dotnet, clippy, golangci-lint, Gradle lint, pytest, and tsc/eslint-family) opens with a result-contract header, [clean] <command> or [errors] / [errors N] <command>, carrying the verdict above a body of retained diagnostics.
  • Files → LSP is one enterable row whose nested page independently controls language servers, the agent tool, diagnostics after write or edit, format after write, lazy startup, and diagnostics deduplication; --no-lsp still disables the full stack.
  • Startup paints the resting composer, with its real hairline, ghost prompt and exact row count, in the first frame from one static component shared with the mounted zone, instead of reserving eight blank rows until mode initialization finished and letting the prompt arrive seconds into a cold launch.
  • Multi-target structure searches now execute concurrently while preserving globally ordered paging, totals, parse errors, cancellation, and target-order failures.
  • A picture a terminal will not draw now leaves a row naming the file, the media type, the pixel size and the cause, in place of [Image: image/png], including when a Kitty session cannot convert it to PNG.
  • Session creation overlaps two serial file reads with neighboring startup work: rules discovery starts as soon as the session context loads rather than after the skills await, and the vault key read starts before the secrets, env and vault entries load.
  • ModelRegistry persists its static bundled, cached-standard and cached-discovery layers to a content-verified resolved-models.json that a warm launch restores instead of rebuilding, invalidated by catalog, cache-row and custom-model content changes or a cached row crossing its 24 h freshness window, but not by SQLite sidecar churn or a provider re-verifying models it already had.
  • A failed MCP tool call decides on a reconnect from the shared socket vocabulary plus this layer's own stale-session rules, so an unreachable or unresolvable host reconnects the server the way a refused connection already did, while a live server answering 500 or holding a request past its deadline stays a failed call.
  • The debug log records which classification rules decided a failed turn's retry, next to the classified kind, so a retry nobody expected is diagnosed from the log instead of by re-reading the provider's sentence.
  • The browser tab worker and supervisor state why each teardown step and each optional probe discards its failure; behavior is unchanged.
  • The browser tab worker and supervisor reach bestEffort and optionalResult through @veyyon/utils/discarded-fault rather than the package barrel; behavior is unchanged.
  • Daemon completion parsing and eval-store serialization errors use shared type guards; behavior is unchanged.
  • Broad multi-file text searches now keep only deterministic representative matches inline and save the complete formatted result behind an artifact:// reference. The preview budget follows the turn-aware output curve from an 8 KiB search ceiling (~2 KiB early at turn 0), emitting up to two representative matches per file while preserving counts and warnings; explicit single-file and line-range searches retain their full output, and only visible representative lines are recorded as seen for anchored edits.

Fixed

  • Tool discovery ranks a tool on its full description instead of its one-line summary alone, which left 96 to 99 percent of each tool's own text out of the retrieval corpus. Over the 17 built-ins, launch scored zero for "tail the output of a server I launched" and eval scored zero for "evaluate javascript and keep the variables", while short-description tools such as goal and set_cwd ranked in six of ten realistic queries; launch now ranks first in eight of those ten.
  • Tool discovery indexes a compound word both whole and in parts, so a lowercase query reaches the word it names: SQLite was indexed only as sq and lite, TypeScript only as type and script, and IPython only as i and python, so queries spelling any of them scored zero against the tool that documented it.
  • A search limit notice is true of the result it prints beside. A text search over 84 files reported "at least one file had more than 20 matches" for a page whose largest file held 12, reported "stopped at its internal ceiling of 2000 matches" for a result of 63, and marked its complete file total "84+": the per-file flag was computed over every matching file rather than the 20-file page, and one flag meant both an exhausted 2000-match fetch budget, which leaves files unopened, and a single file's clipped match list, which does not.
  • A structure search excludes matches in prose grammars (markdown, reStructuredText, AsciiDoc, LaTeX, plain text, logs, CSV, TSV) and states how many it withheld, instead of reporting documentation paragraphs as code matches: over this repository logger.warn($$$ARGS) returned three changelog paragraphs averaging 2,000 characters, none of which contains the string logger.warn.
  • The search tool states that case is on by default and that passing false matches case-insensitively, so a case-insensitive text search no longer depends on guessing the default.
  • A directory listing reports a native addon that could not load instead of answering "(empty directory)". A container whose glibc was older than the shipped addon required got an empty listing for a full checkout on the first read ., both in the tool result and in the workspace tree the system prompt carries.
  • A directory listing reports the errno for a directory the process cannot open instead of answering "(empty directory)", which a model reads as a directory worth no further look; only a directory that is not there still answers empty.
  • An explicit --model pointing at a dynamically-discovered model (a providers /v1/models` entry or models.dev overlay absent from the bundled catalog) no longer fails with "not found among N models" when the background discovery refresh has not completed before model resolution. The deferred pattern path does a synchronous cache-aware discovery pass when none of the patterns resolve against the static catalog, mirroring the fallback already present for default-role models.
  • Unified search now preserves purpose-specific field semantics through the Antigravity tool-schema adapter, replaces primitive search tools in explicit tool lists, keeps plan/subagent/bash guidance aligned with the active tool, and redirects intercepted shell searches to search.
  • A bash-interceptor rule that still names a retired search primitive (grep, glob, find, ast_grep) now redirects to search naming the type field the tool accepts, instead of the purpose field it rejects.
  • File and structure search results are head-truncated at their own byte budget with the full output saved to an artifact, instead of relying on the shared spill layer's middle-elision. File search uses a 4 KB head window (paths are dense and mtime-sorted, so the most recently modified files are on top), and structure search uses the same 8 KB budget as text search. Both recover the full output through an artifact:// footer.
  • veyyon --help describes the grep dev command as the standalone native text-search probe, which is what the command itself says, instead of naming the retired standalone grep tool.
  • Bundled edit and write guidance now uses TTSR's deferred reminder path instead of inheriting the global interrupt policy and aborting the active model response. Every bundled tool-scoped rule must now declare its interrupt policy explicitly.
  • A streaming answer lands in the empty space below the conversation instead of pushing every row already on screen up one row per token, so the screen no longer shakes while a model talks into a viewport that is not yet full.
  • A settings search box reduced to nothing but spaces leaves search and shows the settings list again, instead of holding an apparently empty box over zero matches until esc.
  • A permission prompt for a long command keeps its answer rows on screen: the card sheds lines from the command, saying how many it dropped, instead of clipping the option list off the bottom.
  • A session file that another window wrote its session_exit record into no longer reports that window as a live second writer, so a session whose duplicate window was closed by SIGHUP stops telling the operator to close a session that has already closed.
  • veyyon --resume <id> finds the session under any profile, so the id printed on exit resolves after relaunching under a different one instead of reporting the session as not found.
  • The collab host, guest client and relay socket load when /collab or /join runs instead of during every interactive startup, and a settings domain reads the relay default from @veyyon/wire rather than through the collab protocol module.
  • Argot's dictionary generator, corpus walker and project vocabulary load when a project dictionary is first read instead of during every startup, so a session with argot.enabled off no longer evaluates them.
  • The stats dashboard's aggregator, SQLite layer and embedded client load when /stats first runs instead of during every interactive startup, so a session that never opens the dashboard stops parsing them.
  • The Python, Ruby and Julia eval backends call their kernel and executor modules on every cell instead of holding a copy of each taken when the backend module loaded, so an availability check or executor replaced after startup is the one that runs.
  • A cursor-agent model receives the operator's global, profile and project instruction files again: the assembled prompt was blanked for that api in favour of a channel the server ignores, so every layer reached the model on no channel at all; one prompt is now built for every api and carried to Cursor on the active user turn.
  • A personality named after a property JavaScript objects inherit, such as toString or constructor, is reported as unknown and falls back to the default like any other unrecognized name; the built-in catalog was indexed without an own-property check, so those names resolved to a function, the system prompt build failed silently, and the default was substituted with no warning and any personality/default.md override ignored.
  • A personality spec can no longer spell a prompt tag such as <critical> and have it render as prompt structure; only <personality> was neutralized before, and a project's .veyyon/personalities file, which arrives with a cloned repository and outranks the user's own, is injected into every request.
  • plugin install <name>@<marketplace> --dry-run no longer performs the install: the marketplace branch never read the flag, so it fetched the plugin, wrote the cache and both registries, and reported a completed install; it now resolves the version from the catalog and writes nothing.
  • plugin link <path> no longer fails with EISDIR when the plugin is already installed as a real directory from npm or a marketplace, which is the state a local checkout replaces.
  • The Agent Control Center's read-only transcript viewer expands argot shorthand instead of showing the model's raw §handle text; it parses a subagent's or advisor's persisted transcript directly, and the persisted form keeps the handles.
  • Characters typed in the same terminal read as a paste are no longer discarded; the editor read the pasted payload and the bytes following it but dropped the ones preceding it, so the last thing typed before Cmd+V disappeared.
  • Backspace works at the launch card: the startup gate refused any chunk carrying a control byte, so a character typed by mistake before the composer mounted could not be taken back and the typo was what the session started with.
  • An unattended goal keeps driving after a turn whose post-turn maintenance outlasts the continuation delay, instead of sitting active and idle until someone types.
  • Text typed at the launch card appears there as it is typed, instead of staying invisible until session startup finishes and the composer mounts.
  • Text typed before the launch card paints reaches the composer instead of being destroyed, because the startup tty flush now runs only for the relaunch backlog it was written for.
  • The advisor roster editor reports a failed save instead of clearing the buffer and claiming the write reached disk.
  • The advisor roster editor states why the model list is unavailable when the model registry fails, instead of opening a picker holding nothing.
  • /advisor status names the next move on a text client too — the setting to change or the model role to assign — instead of reporting the state and stopping.
  • Interrupting a turn stops every configured advisor's review as well, instead of leaving one model call per advisor streaming and billing against a turn that was just stopped.
  • A prompt typed at the launch card while the session is still starting is carried into the composer instead of being discarded, and the terminal's own probe replies are still excluded so no escape sequence reaches the draft.
  • A bash working directory on a different Windows drive from the project renders as the absolute path it is, instead of being reported as a path inside the project.
  • Web search keeps trying the next provider when one returns only follow-up suggestions or intermediate search queries, instead of counting that metadata as a result and handing the model a list of questions; a SearXNG "did you mean" on a misspelled query no longer ends the search.
  • Public Web search reports which engines did not answer within the deadline instead of reporting an empty web, so a slow or bot-walled engine no longer surfaces as "returned no renderable search content".
  • The VEYYON_TINY_DEVICE and VEYYON_TINY_DTYPE reference states the providers.tinyModelDevice and providers.tinyModelDtype defaults as the default sentinel each setting actually holds, and names what that sentinel selects, instead of listing CPU and q4 as the stored values.
  • A collapsed ssh result measures its preview in rendered rows at the frame's inner width and shows the newest three lines with a count of what was hidden, instead of slicing the first five newlines and overflowing the frame whenever a remote line wrapped.
  • A terminal resize, theme switch or session switch no longer seals a backgrounded subagent's tool card mid-flight, so the progress it reports afterwards still reaches the card instead of being dropped for the rest of the turn.
  • The composer defect oracle counts only the prompt glyph the frame states, so a transcript row opening with $, > or ! is no longer reported as a second composer prompt.
  • An exported or shared session escapes quotes in every value it places in an HTML attribute, so a link target, link title or image mime type carrying a " renders as text instead of closing the attribute and adding an event handler that runs on the share origin.
  • An exported or shared session escapes the model names in its header, so a model name carrying markup renders as text.
  • The write tool accepts a file holding an indented numeric mapping key, so a docker-compose 80: http, a Kubernetes container port and a dict literal keyed by port number are written instead of being refused as pasted search output.
  • A read, write, search or image target that reaches outside the working directory through a symlink asks for approval even when it carries a selector suffix, so link.env:1-10, db.sqlite:users:42 and archive.zip:dir/file.ts:5-9 are no longer auto-approved where the bare path would have prompted.
  • A server-side compaction failure states the reason once instead of wrapping it in its own prefix, so a host without the compact route reports "Server-side compaction is not available for openai-codex/… (404 Not Found); falling back to local compaction." rather than nesting the message inside itself.
  • A tool status line shortens the paths it was given, so search, ast_edit, debug and set_cwd show ~/project/src instead of printing the home directory into the transcript, and a long path list is truncated rather than pushing the row past the terminal width.
  • The /omfg panel names the rule it saved under ~, truncates it to one row, and shortens the paths embedded in a failure message, instead of printing the home directory in its subheader, footer and error text.
  • The launch status header truncates the command it is starting instead of drawing a row wider than the terminal, and a failed launch collapses to a few lines with a count of the rest until it is expanded, instead of printing every line the process wrote.
  • A conversation /new leaves running in the background keeps its own row in the agent registry instead of being overwritten by the session that replaced it, so it stays listed and its finished turn no longer marks the foreground conversation idle.
  • The /resume picker floats its card in the middle of the screen instead of drawing it against the top edge over a half-blank terminal, most visible right after /new or a profile switch, when the folder holds few enough sessions to make the card short.
  • Clearing memory waits the full deletion retry window before reporting the database files removed, instead of half of it, so a Windows SQLite lock that outlives close() no longer leaves files behind under a success message.
  • Ollama discovery keeps a configured base URL's path, so an endpoint mounted at a subpath behind a reverse proxy is found instead of reporting no models.
  • The write tool and the hashline parser agree on what a numeric-keyed mapping looks like, so a body of true, false or null values is accepted by both instead of one each way.
  • Cancelling an LSP request while a project is still loading returns on every language server, instead of returning on most and throwing AbortError on rust-analyzer alone.
  • Reading a directory the process cannot open reports the permission error instead of rendering it as (empty directory).
  • A commit-analysis or changelog reply that a model writes as text is matched against the shape the caller needs, so a refusal, an error object or a reasoning object no longer crashes the parse or fills a changelog with one bullet per character.
  • Launch daemon teardown and browser process cleanup no longer throw on a host where the native addon cannot load; a daemon falls back to ending its PTY and a browser scan reports no candidates, instead of the failure ending the session (#917).
  • ctrl+g passes a GUI editor the flag that makes it block, so an edit made in VS Code, Cursor, Zed or Sublime is read back into the composer instead of the editor forking and the composer keeping the text it already held.
  • A /guided goal turn survives a model that wraps its JSON in prose, a code fence or a brace run that is not JSON, instead of ending the interview with a parse error.
  • A /guided goal turn shows a spinner while it runs, so the screen between an answer and the next question no longer looks inert.
  • A streaming answer no longer composes a frame one row taller than the viewport on every chunk, which moved the window down to fit and back up on the next frame and shook the screen for as long as the answer kept arriving.
  • Mounting a chat block no longer routes home-anchor slack for rows the content has already taken, which composed a frame taller than the viewport and moved the window on that frame and back on the next.
  • An irc send with await ends as soon as its recipient is terminated or leaves the roster, instead of blocking for the full timeout, or forever at timeoutMs: 0, on a reply that can no longer arrive; a recipient that is merely idle or parked is still woken by the delivery and given the full timeout to answer.
  • A truncated advisor preview, retry reason, and background-task label measure their budget in display columns rather than UTF-16 code units, so a wide or multi-code-unit character is no longer cut in half or counted as one column.
  • The tool approval card, the ast_edit diff preview, and an eval display() value cut long text at a code point rather than a UTF-16 code unit, so an emoji or rare CJK character sitting at the limit is no longer split into an invalid lone surrogate.
  • The Stream First Event Timeout and Stream Idle Timeout settings name the VEYYON_* environment variables that override them, instead of a PI_* prefix that no longer exists.
  • A Kagi search refused for exhausted credits reports that, instead of a generic request failure, on any status code other than 401, 402 and 403.
  • A rendered tool error shortens embedded home-directory paths, so an error card no longer prints the home directory and no longer spends its width budget on the prefix instead of the reason.
  • A job label truncated for display keeps a regional-indicator flag whole instead of emitting half of one, and an empty width budget yields an empty label rather than an ellipsis a column too wide.
  • Brave and Jina web search honor an API key held in the credential store, instead of reporting themselves unconfigured unless the key was also exported as an environment variable.
  • A JSON-RPC header field whose name merely ends in Content-Length, or a server log line that mentions it, no longer sets the frame length and parks the language-server connection on a byte count the stream never reaches.
  • A grep over an archive member removes its extracted scratch directory when extraction fails partway, instead of leaving it behind for the life of the host.
  • Waiting for a language server's project load removes its abort listener, instead of leaving one on the turn's signal for every feature request made during that turn.
  • A path list naming an entry the process is not permitted to stat resolves that entry as present, instead of failing the whole tool call with a permission error.
  • The working-directory boundary selects its targets by value, so a path argument carrying no usable value can no longer suppress the paths it also inspects.
  • An auto-compaction failure names its actual cause when the rejection is not an Error, instead of reporting the literal text compaction failed.
  • A liveness probe of the lspmux server cancels its timeout once the process answers, instead of leaving a timer pending for the rest of the window on every probe.
  • SQLite path detection restarts its scan at the beginning of the string, so a scan that ended abnormally cannot make the next one miss candidates.
  • The write tool rejects content carrying hashline patch markers, unified diff hunks, or read-output display prefixes with an error naming the detection and stating corrective action instead of silently stripping prefixes before writing.
  • Converted LLM message wrappers preserve reference identity across turns so the provider context canonicalizer re-renders only newly appended messages.
  • Memory pipeline SQLite storage (storage.sqlite) manages schema migrations via PRAGMA user_version and dynamically backfills missing columns on legacy databases.
  • The session tree selector formats custom tool arguments using width-aware truncation without double-stringifying string arguments or splitting surrogate pairs.
  • Renderer failure notices sanitize thrown errors, subjects, and fallback descriptions by shortening embedded home directory paths, replacing tabs, and truncating long payloads.
  • The extension inspector origin path truncates Windows backslash paths on directory boundaries and uses standard preview limits.
  • veyyon session declares the sessions alias so the plural command routes to session analysis.
  • Error-formatting call sites use errorMessage so thrown non-Error values and empty-message errors surface readable text, and a reported cause no longer repeats a redundant Error: prefix ahead of its message.
  • AgentSession logs compaction tail elision artifact persistence failures explicitly instead of swallowing them.
  • Quitting no longer hangs when a background session never settles.
  • /new typed while the agent is answering starts the new session without interrupting the answer: the running turn finishes in the background and is flushed to its own transcript, while the composer attaches to a fresh session immediately.
  • /resume onto a session that is still answering re-attaches the running session instead of replaying its transcript as finished text, so its answer keeps streaming into the view and the session being left takes its place in the background.
  • veyyon bench --model @role and veyyon dry-balance --model @role resolve a configured model role instead of failing to find a model named after the alias.
  • The model name segment on the composer status line is preserved against wide working directories and git branch names by ranking it above location shortening in footline degradation.
  • A clipped working directory on the composer status line now carries one ellipsis at the front instead of one at each end, so the directory the session is in stays visible and the visible text reads as a suffix of the real path.
  • The composer status line clips the working directory and the git branch from their own fronts together instead of dropping the branch, never clips a branch short enough to read whole, keeps the path's icon in front of the clip mark, and gives up the context gauge before letting either part fall under its floor.
  • A clip mark on the composer status line is painted in the colour of the text it kept instead of the colour in force before the segment, and a clipped directory or branch opens on a name boundary within four cells of the cut rather than on an orphaned separator.
  • The composer status line no longer paints an empty location zone beside a wide gap: the cells a shed right-group part frees are given back to the working directory and branch, including the cells freed by the shed that ended the fitting, and a token estimate or context gauge is given up before the zone falls under the width at which a name reads.
  • The composer status line keeps the running-subagent count after every other part has gone, instead of giving it up to widen the working directory; at the widths where the count is the whole row the location zone is empty rather than the count being absent.
  • The composer status line's state chips stay on the right edge of the row when no working directory or branch shares it, instead of rendering against the left margin.
  • The composer status line leaves the location zone empty rather than painting a directory fragment with its icon cut off, at the widths where the zone cannot hold an icon, a clip mark and a letter.
  • A click on the composer status line's working directory or branch now shows that name in full, spending the model chip and then the rest of the row's readouts for the room, and only leaves it clipped when the name is longer than the whole row; the click previously paid with the model chip alone, and lost even that room to a context gauge the collapsed row had shed.
  • The composer status line's expansion travels on a 320ms symmetric curve instead of a 180ms front-loaded one, and the room it frees now tracks the widening text frame by frame, so the row no longer steps backward at the start of a click before opening.
  • A click on the composer status line shows the half that was clicked in full rather than widening both, and a readout the click narrows past the width a name reads at is given up whole instead of resting as a fragment.
  • A clipped working directory on the composer status line opens on a directory boundary on Windows, where a path outside the home directory keeps its \ separator; the clip previously had no boundary to find in such a path and always opened mid-name.
  • The composer status line's path budget is counted in terminal cells rather than UTF-16 code units, so a working directory holding wide or astral characters is clamped to the width it paints and is never cut between the halves of one character.
  • The composer footline's click targets — the context gauge, the secrets chip, the goal readout and the path expansion — answer a click in a session whose transcript has not yet overflowed the viewport, instead of staying inert until it does.
  • A stripped working directory keeps the case it has on disk on Windows, instead of being lowercased by the case-insensitive comparison that decided it was under the root.
  • The status line's default-branch lookup no longer raises an unhandled rejection in a directory holding a .git on a host with no git on PATH; the lookup fails to the main fallback instead.
  • The composer status line no longer prints a control character or an escape sequence a name carries: a working directory, git branch, worktree label, multi-repo suffix or provider model name holding a tab, carriage return, bell, newline or escape is sanitized before it reaches the row, where it previously opened a hole in the width arithmetic, overwrote the row's own start, rang the terminal on every repaint, or handed the terminal a sequence of its own.
  • Outbound wire path canonicalization only relativizes paths matching the active session working directory instead of accumulating prior working directory roots, preventing distinct absolute paths in command output from collapsing to the same relative representation.
  • A working-directory change in a live session no longer re-renders earlier messages already sent to the provider, so only messages appended after a set_cwd render against the new directory.
  • Session CPU limits fail closed on unsupported or failed budget groups, lift rate control on removal, refuse a process-creating command before the process exists while leaving launch stop and launch list reachable, escalate over-budget termination from SIGTERM to SIGKILL, and track descendant processes on macOS.
  • Saturated session CPU limits now refuse spawns for MCP servers, extensions, hooks, and custom tools before the process is created.
  • Windows session CPU limits disable Job Object rate control on non-positive or non-finite core counts rather than throttling the process to the minimum rate.
  • A bash command carrying a leading cd, or a relative cwd, is now judged for approval against the directory it will actually run in.
  • Patch failure error rendering shortens absolute paths to avoid displaying home-directory paths and bounds large unmatched hunks with an omitted line count.
  • Filesystem cwd boundary checks expand comma- and whitespace-delimited path arguments matching execution, preventing multi-target reads or searches from bypassing working-directory approval prompts in non-yolo modes.
  • The read tool renderer sanitizes resolved directory paths with shortenPath to avoid displaying unshortened home-directory paths.
  • Stopping a daemon during restart backoff cancels the timer and attributes operator stop without recording a duplicate completion entry, and broker recovery terminates daemons left in restarting state without dead recovery branches.
  • Multi-target structure searches across overlapping paths deduplicate matches so totals, file counts, and paged results are not duplicated or truncated.
  • Acknowledging a completed background job before lifting its watch no longer delivers a duplicate completion notification when retention is zero.
  • A generic tool card with an undrawable image result no longer accumulates duplicate image placeholder rows on rebuild.
  • With Language Servers off, which is the default, the write and edit tools no longer start a language server to inject diagnostics, format the file, or notify the workspace that a file changed, including on the ACP client-bridge write path.
  • A malformed irc send reports its own validation error instead of being reported as an interrupted wait when a peer message arrives in the same batch.
  • A job list snapshot or cancel-only call keeps its own result when an interrupt lands beside it.
  • The composer sits on the viewport bottom on the frame it mounts instead of appearing mid-screen for a moment after the launch card is adopted.
  • The Agent Control Center reports the model an agent is running now instead of the one recorded when it registered.
  • /new and /resume restart the driving session's roster clock, so the Agent Control Center no longer ages the main agent from the conversation that ended.
  • The main agent's roster age advances with its turns instead of freezing at process start.
  • Web search no longer reports "Public Web returned no renderable search content" when one engine serves a bot wall: Startpage's proof-of-work interstitial served at HTTP 200 is now refused as a challenge, and an engine answering with zero results no longer ends the aggregate's wait for a slower engine that has results.
  • Adding an account with a key the provider rejects now leaves the error on screen, instead of remounting the account manager over it so the attempt looked like it silently did nothing; cancelling still returns to the account card.
  • veyyon plugin install --dry-run now resolves the target and fails when it cannot be installed, instead of exiting 0 with "Would install" for an unpublished npm name or a missing git repository, and reports the name and version the target resolves to rather than a 0.0.0-dryrun placeholder (#911 reported by @Crqptx).
  • veyyon plugin uninstall now removes a plugin installed from a local path, which was permanently unremovable because uninstall read only plugins/package.json dependencies while linking registers the plugin in the runtime config and node_modules; the linked directory itself is left untouched.
  • veyyon plugin doctor no longer reports "no plugins installed" in a profile whose plugins were all linked, and names how many linked plugins it found.
  • veyyon plugin config <plugin> now names the missing subcommand instead of reporting "Plugin name required" for a plugin name that was supplied.
  • A completed background job now fills its still-pending originating tool call instead of starting an unrelated recap turn after an interruption, including when zero retention is configured or foreground completion races background delivery.
  • Automatic compaction parks the run, or drains already-queued input once, when every summarizer candidate refuses, instead of reporting that nothing happened and looping.
  • A successful local compaction rescue retries without restoring the failed overflow or truncated assistant turn, and idle compaction stays silent.
  • Agent transcript headers and roster rows share terminal, approval-blocked, and peer-waiting status precedence, so interrupted agents settle red and untyped agents render without a dangling separator.
  • /agents keeps a parked subagent focused, preserves its reconstructed assistant messages, tool calls, and results, reattaches a revived session, and rejects stale revival, removal, scope, and rapid-focus races instead of switching to the wrong transcript.
  • IRC broadcasts no longer wake completed idle peers; direct messages still wake the addressed peer.
  • ImageMagick pixel caches used by proof capture and HD demo scripts stay inside an owned scoped directory that the parent removes after child failure without deleting concurrent or inherited unrelated directories.
  • An indented row inside a tool block keeps its indent when it wraps at a narrow width, instead of continuing at the block's left edge.
  • The finalization reminder counts the files a multi-file edit actually wrote: a call that reports overall failure after writing some of its files is now unverified evidence, and a file a per-file entry skipped is no longer named as affected.
  • A mutated path is XML-escaped before it reaches the hidden finalization reminder, so a file name spelling </system-reminder> cannot end the reminder envelope early, and a relative ast_edit path is resolved against the call's working directory before duplicate paths are collapsed.
  • A colour or title escape sequence a command writes in two pieces no longer leaves part of itself in tool output: the sink holds a sequence its chunk ended inside until the piece that finishes it arrives, and drops one the stream never completes.
  • A launch tool block no longer renders as a bare title with no rows: the header drops the placeholder ellipsis while op is still streaming, and every operation falls back to the result text when the structured detail it renders from is absent.
  • The compaction.remote setting description documents that server-side compaction applies to supported OpenAI, Azure OpenAI, and ChatGPT Codex Responses models.
  • branchSummary.reserveTokens reaches the branch summarizer, which previously used the built-in 16384 reserve whatever the setting said.
  • The ssh tool works again on a profile whose directory nests more than a few levels deep, because its connection multiplexing socket is named from a 16-character digest instead of a 64-character hash that exceeded the 108-byte Unix socket limit, and a path that still cannot fit drops multiplexing with one warning rather than failing the connection.
  • debug reaches the Python debugger on a host that installs python3 and no unsuffixed python, because an adapter may now declare alternate command spellings, while a command written in dap.json is still used exactly as written.
  • A subagent that calls yield with unusable data now fails the run instead of returning success with the system warning as its result, matching how a subagent that never yields at all is already reported.
  • A subagent result that cannot be serialized now fails the run and reports the serialization error, instead of returning success with an unparseable error envelope as its payload.
  • launch recovers from a broker connection that fails while the broker is still binding its socket, instead of caching the rejection for the life of the process and reporting that first error on every later call.
  • bash runs cd - && … again, instead of reading the leading cd as a directory literally named - and rejecting the call with a path the operator never typed.
  • A bash working-directory error shortens the path it reports instead of printing the absolute one, which put the home directory into the tool result and the transcript.
  • search reports why an archive could not be opened or read when the failure is not an Error, instead of the word undefined.
  • A detached daemon that exited while no broker was supervising it is recorded as its own exit, not as a non-detached daemon terminated by the replacement broker.
  • Background conversations abandoned at shutdown before their transcript finished flushing are named in the log, instead of leaving a short file as the only trace.
  • A streaming answer no longer slides the whole conversation up one row per streamed row: the anchor slack now sits below the content and above the composer, so a streamed row lands in the empty space and the composer keeps the viewport bottom.
  • The registry's static-stage snapshot (resolved-models.json) now actually hits on a warm launch. Two defects kept it missing every time: the fingerprint stamped models.db and its SQLite sidecars by mtime, and SQLite moves those files on every connection — including the writer's own — so the launch after every write rebuilt and rewrote the 12 MB file; and authoritativeFreshProviders was serialized as a Set, which JSON turns into {}, so the reader's array guard rejected every restore regardless. The fingerprint now reads a row-content stamp from the cache table, the field serializes as an array, and a relocated registry writes its snapshots beside its own database instead of beside the default profile's. Warm registry init drops from about 183 ms to about 49 ms; test/the-static-model-stage-snapshot-hits-unless-cache-content-changes.test.ts defends each case.
  • Unified search approval preflight now covers every multi-target syntax execution accepts; search also excludes byte-truncated context from editable seen lines, unions ranged and unrestricted text scopes, orders equal-mtime file results by path, suppresses pattern errors from unrelated structure languages, and distinguishes exhausted structure pages from a search with no matches.
  • Unified search now keeps warning-heavy text results within the inline byte budget, preserves semicolon path lists longer than one filename component, excludes matches hidden by generic truncation from editable seen lines, classifies every SSH path-list encoding at the execution approval tier, matches native Unicode tie ordering, and reports exhausted text pages with their totals.
  • Unified search no longer broadens or misroutes malformed calls: files-mode rejects a path owned by other modes while retaining the historical /-means-workspace alias, structure search rejects unsupported ssh:// scopes without an approval prompt for work it cannot execute, immutable internal or fetched sources never receive editable hashline anchors, and the Bash interceptor no longer redirects mutating or otherwise non-equivalent find commands to a read-only file search.
  • The plan-mode extension example now keeps canonical search available both while planning and after restoring normal tools. It previously advertised retired grep/find identities, requested a nonexistent find tool, and dropped search when plan mode ended.
  • Memory summarization now retains canonical search results from session rollouts. It previously allowlisted the retired grep tool name but discarded every result emitted by unified search before Stage 1 summarization.

Removed

  • The /providers account card no longer writes accounts.loadBalancing: its b key and footer chip are gone, Settings → Providers → Accounts is the one writer, and the card reports the stored value.
  • Dropped the Ecosia web search engine; it answered a search with a Cloudflare challenge rather than results, and the Public Web aggregate now fans out to Startpage, Google, DuckDuckGo and Mojeek.
  • A launch from your home directory no longer prints the three-line notice about relocating to a scratch directory; the relocation is unchanged, and /cwd and the status line state the session's directory.
  • The tools.unifiedRuntime experiment and the runtime tool it gated. The unified tool never shipped enabled, duplicated the eval and launch prompts at 2,368 tokens, and the experiment was abandoned in favour of keeping eval and launch as separate tools.

@veyyon/evals

Added

  • @veyyon/evals is the single package holding every evaluation in this repository, replacing @veyyon/deepswe-bench, @veyyon/metaharness and @veyyon/typescript-edit-benchmark.
  • evals --suite <name,name> runs any number of suites in one invocation across five axes (suite × harness × config × prompt variant × model), with --tasks, --repeats, --jobs, --dry-run and --list. Each suite produces its own run record, a --tasks entry is scoped to one suite by a <suite>= prefix, and --dataset-dir is refused when the run names more than one suite.
  • engine/run-plan.ts decides every trial cell before anything executes, task-major with variants innermost, and refuses an empty selection, an unknown task id or a non-integer repeat count.
  • engine/execute-run.ts drives a plan through one execution backend with a bounded worker pool, records results in plan order rather than completion order, and runs cleanup for a cell whose trial threw.
  • A trial that throws records reward: null with the error text, so a broken container is no longer indistinguishable from an agent that scored zero.
  • Terminal-Bench 3.0 is an eval suite: suites/terminal-bench/ with the dataset pinned at tag v3.0.0 (2b0442c3c583b710ca8da14c8e601b99f2f1f244, 74 tasks), Harbor task-config parsing, provenance, and the committed smoke.txt and pilot.txt task lists.
  • engine/ holds the shared contracts (EvalSuite, HarnessAdapter, ExecutionBackend), the three registries, the variant matrix with deterministic variant naming and collision detection, and the suite-tagged run record model.
  • Pier, Harbor and in-process are registered execution backends: pierBackend, harborBackend, inProcessBackend, each with a preflight verdict naming what is missing.
  • The harness adapters (veyyon, omp, factory, hermes) are shared across suites at harnesses/, registered by the autoscan loader.
  • agents/harbor/veyyon_agent.py runs the veyyon harness inside a Harbor container.
  • Folded the TypeScript-edit mutation, verification, and benchmark suite into suites/typescript-edit/.
  • Added in-process AgentSession execution client at backends/in-process/client.ts.
  • Moved TypeScript-edit benchmark fixtures and datasets to datasets/typescript-edit/.
  • Moved the Harbor execution backend to backends/harbor/ and local Harbor agent to agents/harbor/veyyon_local.py.
  • Moved the SQLite run store, experiment grouping layer, and REST/SSE manager server into store/ and api/.
  • Moved the React live evaluation dashboard into dashboard/.
  • Moved benchmark and trace reporting tools into tools/.
  • Harness adapters declare their supported execution backends in their backend map, refusing planning for unbound backend pairs and supporting multi-harness trial matrix generation.
  • The in-process backend loads a config overlay and a prompt-variant overlay per trial, applying settings to the agent session and the prompt text through VEYYON_EVAL_PROMPTS, and refuses a missing file, an unknown setting key or a prompt id no registry holds before any trial starts.
  • The omp harness stages an OAuth credential store (auth-agent.db) into the container when no API key is resolved, copying it to ~/.omp/agent/agent.db in the setup step. Preflight accepts the auth DB as an alternative to --omp-api-key or $PROVIDER_API_KEY, probing it can serve the run's model.

Changed

  • Parameterized the Harbor backend default dataset and upgraded the run store schema to version 2 with explicit suite and backend identities, so rows from two suites cannot be aggregated into one pass rate.
  • The run store is assets/evals.sqlite, and the manager server, dashboard and report renderers are named for the evals package rather than the retired metaharness.
  • The DeepSWE runner keeps its suite-specific flags at suites/deep-swe/main.ts; its harness registry, Pier execution and reporting are now the shared ones.
  • bench:gen-fixtures generates TypeScript-edit fixtures from datasets/typescript-edit/typescript-source instead of a path under /tmp.
  • The React dashboard is its own TypeScript project (dashboard/tsconfig.json), the only DOM-typed project in the package, so the rest of the package typechecks against the harness's own DOM shims.
  • Every test lives under test/, mirroring the package tree, and bunfig.toml pathIgnorePatterns keeps test discovery out of the gitignored data trees (runs/, datasets/repo-cache/, datasets/deep-swe/corpus/, .cache/).
  • engine/package-paths.ts is the single owner of the package's directory layout, replacing the DeepSWE-scoped paths.ts and the manager's second copy.
  • The search benches write their scratch corpora to the repository's .internal/ directory instead of creating a stray packages/.internal/.
  • Record and config parsing calls isRecord and errorMessage from @veyyon/utils instead of eight local copies.
  • Zero barrel files (export * from) remain in the package. Every importer reaches the source module directly, so adding a member requires writing exactly one file with no index or barrel edit.
  • tsconfig.json includes the package root and excludes dashboard/ instead of the removed src/ tree. .gitignore and scripts/local-endpoint-bridge.sh no longer reference src/.

Fixed

  • DeepSWE dry-run preflight reports missing or stale binary artifacts with their build command instead of triggering a product build.
  • --dry-run refuses an overlay the real run would refuse: the backend's preflight now receives the plan's variants, so a missing overlay file, an unknown setting key or a prompt id no registry holds is reported before any quota is spent instead of hours into the run.
  • Handlebars.compile in suites/typescript-edit/argot-bench.ts and generate.ts receives the prompt text (.text) instead of the PromptEntry object, fixing an import-time crash.
  • The entry-point flag-refusal sweep scans the package root instead of the removed src/ directory, and the one-flag-grammar test no longer references the retired deep-swe runner entry point.
  • The Harbor backend skips source-tree mount preparation when VEYYON_BENCH_BINARY_X64 or VEYYON_BENCH_BINARY_ARM64 is set, so a pinned-binary run does not fail on a compose overlay the binary mode never uses.
  • The Harbor compose overlay targets the main service that harbor's build template defines, not a non-existent task service, so docker compose build no longer fails with "service has neither an image nor a build context".
  • The Harbor backend passes --agent instead of the deprecated --agent-import-path, so harbor 0.22.0 no longer rejects the invocation.
  • The omp harness routes OAuth providers through the auth gateway: buildModelsYml uses the gateway URL with /v1 appended as the provider base URL, openai-responses as the API, and no-auth as the API key when no key is resolved.
  • The omp harness parses vey models refresh --json output as a single {"models":[...]} JSON object instead of NDJSON lines, so models.yml is staged for providers that return the object format.
  • The omp harness stages the host's bun binary alongside the omp binary and invokes omp through it, so a task container with an older Bun runtime does not crash the omp bundle.
  • The omp harness mounts the host's ~/node_modules into the container at /opt/omp-assets/node_modules, so the omp binary can resolve its external dependencies (@oh-my-pi/pi-natives, turndown, etc.).
  • The omp harbor binding declares authGateway: true, so the compose overlay includes extra_hosts: host.docker.internal:host-gateway for omp runs.
  • The Harbor compose overlay supports cfg.extraVolumes for harness-specific bind mounts in addition to source-tree mounts.
  • The Harbor backend strips VEYYON_AUTH_BROKER_URL and VEYYON_AUTH_BROKER_TOKEN from the subprocess environment and the forward-env denylist, so the host's loopback broker address does not leak into containers that can only reach the gateway at host.docker.internal:4000.
  • The omp harness uses the gateway bearer token as the apiKey in models.yml when routing through the gateway, so omp sends an authorized request instead of no-auth and getting 401.
  • The Harbor backend passes gatewayToken through to the harness staging options, so a harness with a containerProgram (omp) receives the gateway token alongside the gateway URL.

@veyyon/hashline

Fixed

  • A numeric-keyed body whose values are true, false or null keeps its N: keys instead of being stripped as read-tool output.
  • A wide line clipped in the unseen-line reveal is cut at a code point rather than a UTF-16 code unit, so an emoji or rare CJK character sitting at the column limit is no longer split into an invalid lone surrogate.

@veyyon/mnemopi

Changed

  • Replaced @veyyon/utils barrel imports across mnemopi with direct subpath imports, reducing the memory engine load reach from 161 to 110 modules and consolidate reach from 151 to 99 modules.
  • MNEMOPI_NO_EMBEDDINGS=0, false, no or off now leaves embeddings on everywhere instead of disabling them on the API path.
  • Every MNEMOPI_* value is read by config.ts alone; the local-model, extraction and embedding modules ask it instead of parsing the variable again.
  • getDiagnostics is now extractionDiagnostics in core/extraction/diagnostics and recallDiagnostics in core/recall-diagnostics, so the two registries are no longer reached by one name.
  • core/embeddings.ts imports ProviderHttpError from @veyyon/ai/error/classes instead of the error barrel, cutting twelve modules off the import graph of every module that can remember something; behavior is unchanged.
  • config.ts and core/extraction/client.ts take trimTrailingSlashes and withScopedTimeoutSignal from @veyyon/utils/url and @veyyon/utils/scoped-timeout instead of the package entry point, cutting the extraction client's import graph from 127 modules to 66; behavior is unchanged.

Fixed

  • Mnemopi cost log SQLite database (cost_log.db) manages schema migrations via PRAGMA user_version and dynamically backfills missing columns on legacy databases.

@veyyon/natives

Fixed

  • A load failure carries code: "VEYYON_NATIVE_ADDON_UNAVAILABLE", exported as NATIVE_ADDON_UNAVAILABLE_CODE with isNativeAddonUnavailable from @veyyon/natives/loader-state, so a caller that catches a native call can tell an unavailable addon from a scan that found nothing.
  • The tracked-process renice test states which direction it can move a nice value on the host running it, so a host whose cargo wrapper already starts the test binary at nice 19 lowers where it can, raises where the host permits it, and reports a skip naming the reason where it can do neither, instead of failing an assertion about headroom that host never had.
  • A cargo run under --message-format=json that fails before rustc, such as a build script exiting non-zero, reports the text cargo printed, so the Caused by: chain and the script's own stderr reach the operator instead of a bare failure verdict.
  • A cargo nextest run whose profile sets failure-output = "final" keeps its panic bodies, so the assertion diff, message, file and line survive the summary instead of being dropped and leaving a failure count with no evidence.
  • A native addon load that fails is reported once instead of once per native call; the failed pipeline is memoized, so a run that reached a hundred native calls no longer prints a hundred copies of the candidate report (#917).
  • Fixed stock Windows AVX2 detection by trying PowerShell 7 before an isolated modern-addon trial; only explicit shell answers or illegal-instruction exits become verdicts, while missing, incompatible, timed-out, and unexpectedly crashing addons remain unknown.
  • Persisted AVX2 verdicts are schema-versioned and keyed by platform, architecture, and CPU model, so copied or stale caches cannot select a native variant for different hardware.
  • The AVX2 trial load answers from the addon loader's first import and exits, so a compiled host, whose process.execPath is the product binary rather than a JavaScript runtime, reports a verdict instead of booting the whole CLI and spawning a trial child of its own at every level.
  • A wrapped line now continues under the indent its first row opened at, so an indented row no longer reads as a new top-level row at a narrow width.
  • An indented row inside a tool block keeps its indent when it wraps at a narrow width, instead of continuing at the block's left edge.
  • Native text context rows now report truncation explicitly, and mixed-language structure searches suppress pattern-compilation diagnostics from unrelated languages when another candidate language accepts the pattern.
  • The native addon loader memoizes a load failure. A failed loadNative() re-throws the cached error on subsequent calls instead of re-running the full candidate scan and printing every GLIBC/version warning again on each native access, which produced ~1000 lines of warning spam in containers with older glibc (#917).
  • A tab-indented row hangs its wrapped continuations under the tab, instead of counting only spaces as indent and continuing at column zero.
  • The Darwin process list is sized from the count the kernel reports rather than a fixed 4096 entries, so a host past that many live processes no longer loses whole branches of a tracked process tree out of its CPU budget.

@veyyon/stats

Changed

  • Migrated dashboard theme toggle to shared ThemeToggle from @veyyon/tool-render.

@veyyon/swarm-extension

Changed

  • Swarm's documented agent tool inventory now names the canonical search and eval tools instead of retired workspace-search names. No runtime behavior changed.

@veyyon/tool-render

Added

  • Added ThemeToggle component to shared React renderers for cycling system, light, and dark theme preferences.

Changed

  • Unified search renderer handles canonical { type, input } schemas across files, text, and structure search with nested { type, result } metrics and malformed-input guards. Retired search tool aliases and registry entries for glob, grep, find, and ast_grep are removed.

@veyyon/tui

Added

  • TUI.onBeforeCompose runs at the top of every frame, before any root child renders, so a layout whose height is a function of its siblings' heights is sized against the children about to render rather than the previous frame's.
  • Image accepts an onDisplayed callback and reports the cause each time an image starts or stops falling back to a placeholder.
  • MOTION.reflow states the curve for a row that reflows its content sideways: 320ms, symmetric, where expand is 180ms and front-loaded.
  • TUI.composedFrameLines exposes the rows of the frame just composed, so a check can tell a row the layout composed blank from a row the renderer composed with content and failed to paint.
  • Editor.discardDraft() clears the composer and records an undo state first, so the discarded draft comes back with undo; setText still drops the undo history, because it loads text from elsewhere rather than editing what was typed.

Changed

  • The ANSI owner check scans packages/evals/src/backends/harbor instead of the retired metaharness package path. No behavior change.
  • imageFallback takes the file name, media type, pixel size and cause of an undrawn image and returns a row naming all four; ImageFallbackReason states the cause.
  • The fuzzy-match benchmark fixture now names the canonical text-search source path instead of the retired grep-tool path. No benchmark behavior changed.
  • Settings rows can open nested panels, used by Files → LSP to keep its dependent switches behind one parent row.
  • The ui.loop-blocked warning reports phase with the phaseMs that earns it, and names the phase only when it held at least half the block; a phase that ran for a sliver of it is reported as unknown with the observed label carried as topPhase.

Fixed

  • A tab bar holding no tabs keeps its active index at 0, instead of reporting tab -1 and handing an undefined tab to the change callback.
  • SelectList.setSelectedIndex holds its low bound on an empty list rather than storing an inverted -1; no behavior changes today, since no caller can distinguish the two.
  • Derive scroll-isolation pinned footer hit-test boundaries from the rendered window top and clamp child frame-local mouse coordinates within valid segment bounds.
  • Nested optional-argument LaTeX constructs parse in linear time without character-by-character concatenation allocations.
  • Exclude pinned footer rows from the scroll-isolation snapshot and scroll space so the composer does not duplicate inside scrolled-back history.
  • Extract LaTeX argument text by slicing the source rather than appending one character at a time, so a deeply nested optional-argument chain degrades linearly instead of quadratically.

@veyyon/utils

Added

  • run() accepts verified command summaries for root help and falls back to loading the full registry when any summary is absent.
  • source-declarations.ts: exportedDeclarationsIn and declarersOfName report which modules declare a name, so a one-owner gate no longer matches the declaration's own bytes; a reflowed signature, a signature quoted in a comment and a second module declaring the same name are now all answered correctly.

Changed

  • The prompt registry and the eval prompt-override loader read benchmark prompts from packages/evals/src/suites/typescript-edit/adapter/prompts/, the path the consolidated evals package holds them at. No behavior change.
  • definePromptRows and definePromptRegistry re-read VEYYON_EVAL_PROMPTS when it changes instead of applying it once at import, so a prompt variant set per arm reaches the model in a process that runs several arms. The evals harness's in-process backend builds a session without spawning, so every arm after the first was served the first arm's prompt text while the run reported a variant. A read while the variable is unchanged costs one string comparison and allocates nothing.
  • The prompt-variables documentation examples name the search tool, which is the workspace-search tool that now exists, instead of the retired grep tool. No behavior change.
  • bestEffort and optionalResult are imported from @veyyon/utils/discarded-fault. The barrel does not re-export them, so a consumer reaching them through @veyyon/utils names the module instead.
  • relativePathWithinRoot returns the candidate's own spelling instead of the case-folded copy the containment check used, so on Windows a path under C:\Users\dev\Projects no longer comes back lowercased, and a root configured in a different case than the directory on disk resolves to the tail rather than to a .. walk.
  • bestEffort and optionalResult are imported from @veyyon/utils/discarded-fault, which the barrel does not re-export, so a consumer reaching them names the module instead.
  • winston and winston-daily-rotate-file are resolved on the first log write instead of at module load, taking 4.7ms off every process that imports the logger without logging, which is every entry point.
  • A blocked event loop names the phase that spent the time rather than the phase that happened to be open: takeLoopPhaseProfile banks elapsed time per phase and reports the costliest one with its cost, replacing takeRecentLoopPhase, which returned the most recent label and blamed ui.render for a stall the render pass never took part in.
  • No user-visible change: doc comments name the subagent dashboard, the surface /agents opens, instead of the Agent Control Center.
  • Prompt-variable documentation now uses canonical toolRefs.search examples instead of the retired toolRefs.grep name. No runtime behavior changed.

Fixed

  • An empty ~/.veyyon/agent beside a migrated profiles/default no longer refuses to start; a directory holding nothing is not a second candidate profile, so it is removed and startup continues, while one holding data still fails closed.
  • The legacy-layout migration leaves cross-profile state at the config root instead of sweeping it into profiles/default: shared-auth/, the global AGENTS.md, vault.json and vault.key stay where every profile reads them.
  • extractHttpStatusFromError reads the status line a message opens with whatever wording follows it, so 401 Your session has expired, 403 You have run out of credits and 503 {"type":"error",...} report their codes again; pinning a reason phrase to its own code had also stopped a status line naming its own reason from reporting anything, and 401 is what credential rotation reads.
  • extractHttpStatusFromError reads a status field anywhere in an error's cause chain before falling back to prose anywhere in it, and matches the status_code: 503 and 429 Too Many Requests spellings it previously missed.
  • extractHttpStatusFromError reads a reason phrase only when it is the phrase that belongs to the code beside it, so Processed 200 Total Records and gave up after 401 Failed Attempts no longer report a status, the second of which reached credential rotation.
  • extractRetryHint reads x-ratelimit-reset through the same owner as x-ratelimit-reset-ms, so the common delta form x-ratelimit-reset: 60 waits the 60 seconds the server asked for instead of computing a negative delay from it and falling back to the caller's default backoff.
  • onProcessExit honors its AbortSignal for every process shape: waiting on a Subprocess ends when the caller cancels instead of running to the child's exit, and a cancelled wait on a pid returns false rather than throwing.
  • Ending a child process no longer throws on a host where the native addon cannot load, such as a container whose glibc predates the modern build; the direct child is terminated through the runtime and process liveness falls back to signal 0, so the tree walk is the only capability lost (#917).
  • splitTrailingPartialEscape lets a streaming reader hold back an escape sequence a chunk ended inside, so a sequence divided across two reads is stripped whole instead of losing its head and leaking its tail as text.
  • discarded-fault.ts: bestEffort and optionalResult state which contract discarded a promise's failure, one for a step nobody waits on and one for a probe whose failure is the answer, each taking a mandatory reason.
  • ChildProcess.kill() falls back to Bun's built-in proc.kill() (SIGTERM) when the native Process class cannot load, instead of throwing an uncaught exception that crashes the host. This fixes a crash in containers with older glibc where the native addon fails to load (#917).

What changed

852 commits since v1.2.0.

Breaking Changes

  • feat(agents)!: drop the process-wide scope from the Agent Control Center

Features

  • feat(edit): one after-edit pass per turn, selected by edit.afterEdit
  • feat(tui): esc twice discards the composer draft
  • feat(evals): omp harness stages OAuth auth DB instead of requiring API key
  • feat(search): return matching paths instead of match lines
  • feat(evals): a settled run states what it measured
  • feat(evals): forward the endpoint bridge to another host
  • feat(evals): bound the agent phase, and deliver every parsed option
  • feat(evals): reach a locally served model from a trial container
  • feat(advisor): make /advisor a slash command and wire its roster editor
  • feat(evals): one declarative container program per harness
  • feat(coding-agent): filter the providers card, and stop it writing the balancing setting
  • feat(evals): the search bench extends along corpus, case suite and arm
  • feat(coding-agent): add search/filter to /providers account manager
  • feat(coding-agent): reach a conversation the screen is not showing
  • feat(coding-agent): count the conversations running off screen, and let /new stop them
  • feat(evals): make a search bench case declare the answer the corpus has
  • feat(evals): bind harnesses to backends and make every axis reach the agent
  • feat(renderer): add composer zone defect oracle, sweep, and replay corpus (Refs #920)
  • feat(ai): let a Nous key in without the browser round trip
  • feat(deepswe-bench): stream omp agent output via --mode json
  • feat(status-line): name the roots the footline shortens a path against
  • feat(bench): unify --arms to accept config arms and system adapters in any combination
  • feat(shell): classified runner output opens with a [clean] or [errors] verdict
  • feat(tools): make unified search the default
  • feat(tools): unify workspace search
  • feat(omfg): save forged rules to the profile and badge them User created
  • feat(statusline): clip the footline path from one end and widen it on a click
  • feat(omfg): forge rules carrying the extended TTSR frontmatter
  • feat(launch): add configurable cleanup wait setting for exited daemons
  • feat(coding-agent): every daemon death names its killer and stays queryable
  • feat(ai): detect consecutive redundant reads of unchanged files in tool-call loop guard
  • feat(coding-agent): bound read directory listings with depth/limit and a concise root default
  • feat(coding-agent): a session store that outlives the eval kernel, and defs()
  • feat(proof): composite window chrome after capture and assert pipeline contracts
  • feat(utils): state which contract discarded a promise's failure
  • feat(utils): read a one-owner claim from the declarations, not the bytes
  • feat(ai): name the classification rule that decided a failure
  • feat(coding-agent): read a semicolon-delimited list of internal resources
  • feat(proof): 60fps hero camera that follows /secret then pans
  • feat(proof): direct the hero take — zoom the secret, short prompt, 6× middle
  • feat(settings): Files → LSP is one nested panel
  • feat(session): prompt code review reminder on multi-file mutations
  • feat(catalog): Command Code replaces the misread Cohere half of #884
  • feat(catalog): Cohere and Nous Research join the built-in providers
  • feat(tools): frame search by purpose
  • feat(prompt): route structural search explicitly
  • feat(tools): teach unified search routing
  • feat(tools): add unified search ablation
  • feat(eval): add Python workspace ablation
  • feat(tools): add unified runtime ablation

Fixes

  • fix(read): refuse a directory that cannot be scanned instead of calling it empty
  • fix(catalog): restore the opencode-go rows the merge rolled back
  • fix: reconcile the ratchets and paths the merge left pointing at nothing
  • fix(workspace-tree): give both new directory builders the addon-failure contract
  • fix: resolve merge conflicts with origin/main
  • fix(edit): fall back to the default after-edit pass on an out-of-schema value
  • fix: update baselines and tests for runtime removal and evals restructure
  • fix(session): resolve a resume id under any profile
  • fix(tui): a long command no longer pushes the approval options off the card
  • fix(web-search): drop the ecosia engine
  • fix(tui): attribute a blocked loop to the phase that spent the time
  • fix(session): an exit record is not a live second writer
  • fix(evals): route prompt-render test through registry, fix anti-vacuity count
  • fix(evals): update stale harbor tests for --agent flag and remove unified-runtime arm
  • fix(utils): an empty legacy agent dir is not a second profile
  • fix(settings): a search box holding only spaces is a cleared box
  • fix(ai): a long name whose tail cycles is not a runaway sampler
  • fix(evals): read DOCKER_GATEWAY_TOKEN from env as fallback
  • fix(evals): strip broker env leak, pass gateway token to omp
  • fix(search): file and structure search own their byte budget
  • fix(evals): omp harness gateway routing, harbor --agent flag, bun staging
  • fix(tui): stream a new row into the empty space instead of sliding the screen
  • fix(startup): load collab and argot runtime on first use, not at startup
  • fix(tui): keep the cursor still on a frame that paints nothing
  • fix(stats): load the dashboard graph on first /stats, not at startup
  • fix(render-oracle): judge a rebuilt tape against the committed record
  • fix(coding-agent): an eval backend reaches its kernel module on every call
  • fix(render-oracle): judge the composer against the placement the renderer produced
  • fix: compose overlay targets the 'main' service harbor builds, not 'task'
  • fix(cursor): route tool-call updates by call_id and never rerun an answered call
  • fix: auth-broker snapshot validates OAuth source field; harbor skips source mount for pinned binary
  • fix(scripts): point the focused-view demo at the render-oracle terminal
  • fix(tui): pull a keystroke's frame out from behind stream backpressure
  • fix(cursor): upload the operator's instructions once, not three times
  • fix(tui): write nothing for a frame the screen already shows
  • fix(evals): remove last stale src/ references in config and scripts
  • fix(cursor): deliver operator instructions on the active user turn
  • fix(evals): replace stale src/ paths after directory restructure
  • fix(evals): repair Handlebars compile calls and entry-point discovery after restructure
  • fix(tui): release a bracketed paste whose end marker never arrives
  • fix(render-oracle): decide blank space against the frame, not the screen
  • fix(tui): one owner for the live-tail window anchor
  • fix(prompt): deliver project instruction files to cursor-agent models
  • fix(evals): verify repo root and remote before trusting dataset SHA
  • fix(coding-agent): biome formatting after path update
  • fix(coding-agent): update stale evals import paths after restructure
  • fix(tools): a search cap notice is true of the result it prints
  • fix(tools): discovery ranks a tool on all of its own text
  • fix(personality): an inherited property name resolves like any other unknown name, and a spec cannot spell prompt structure
  • fix(tools): a rejected search field names the set that works
  • fix(tools): speak every search cap and own the notice prefix
  • fix(test): type the search type enum options as strings
  • fix(tools): restore the search notice and bound a structure binding
  • fix(discovery): activate the tools a search matched well, not its tail
  • fix(read): list a directory at its top level when the caller named no depth
  • fix(read): stop a structural summary at the line default, not only at the byte budget
  • fix(read): price a PDF member list and an agent extraction like every other read window
  • fix(read): price a structural summary like every other read window
  • fix(ai): a bare gateway status is a twenty-second retry, not a five-minute park
  • fix(ai): a deterministic parse failure stops the retry ladder on both classifier routes
  • fix(tools): make the spill threshold bound what a result costs, not only when an artifact is written
  • fix(argot): the transcript viewer expands handles instead of showing them raw
  • fix(launch): price a log read like every other tool result
  • fix(plugins): a marketplace dry run installs nothing, and link replaces what is there
  • fix(read): price an archive listing and a glob path list like every other tool result
  • fix(modes): input the terminal delivered is not dropped
  • fix(read): price a directory listing like every other tool result
  • fix(tools): load debug only where an adapter command resolves
  • fix(test): three gates read state a bucket-mate left behind
  • fix(mentions): price a @path mention like a tool result
  • fix(read): bound every read window by the configured budget
  • fix(read): price a default window like every other tool result
  • fix(search): stop reporting documentation prose as a code match
  • fix(search): stop billing three trailing lines per text match
  • fix: never report an unloadable native addon as an empty workspace
  • fix(evals): read the build flag the veyyon adapter declares
  • fix(evals): record the build a program-delivered arm staged
  • fix(evals): keep the grade a bounded agent phase earned
  • fix(evals): put a pier run's output where the run named
  • fix(startup): clear the relaunch marker when no launch card paints
  • fix(startup): keep what was typed before the launch card painted
  • fix(goals): keep driving a goal whose post-turn work outlasts the continuation delay
  • fix(catalog): read the context window LM Studio actually accepts
  • fix(tui): show text typed at the launch card while it is being typed
  • fix(session): stop the old conversation by default on /new
  • fix(evals): probe the auth gateway only for arms that route through it
  • fix(coding-agent): carry a prompt typed at the launch card into the composer
  • fix(ai): read a named auth refusal as a credential verdict, not a bare 503
  • fix: a sweep that reads what git tracks survives a deleted path
  • fix(evals): a refused fixtures archive states its kind instead of being re-read
  • fix(coding-agent): say a search found nothing in the reader's words
  • fix(coding-agent): tell a search deadline apart from an empty web
  • fix(evals): a re-aggregation returns its verdict instead of setting the exit code
  • fix(coding-agent): stop treating search metadata as a search result
  • fix(evals): migrate an older run record, refuse a newer one, and carry the recorded arm
  • fix(evals): an attachment set is a matrix axis, not a cell index
  • fix(evals): a prompt attempt ends at its deadline even when the client cannot be aborted
  • fix(evals): a harbor run that stops making progress ends instead of rendering forever
  • fix(evals): the deep-swe executor no longer reads a trial's pipes with its deadline cancelled
  • fix(evals): one bounded request, and the last two callers use it
  • fix(evals): a dashboard request the manager never answers ends instead of hanging
  • fix(evals): bound every external command a run spawns
  • fix(coding-agent): decide project containment with the owner, not a local copy
  • fix(coding-agent): measure a collapsed ssh preview in rows, not newlines
  • fix(evals): a trial leaves no listener on the run it was cancelled with
  • fix(ai): explain a llama.cpp parse failure on every route to a local server
  • fix(evals): a cancelled trial stops waiting on the pipes its tree held open
  • fix(ai): read a rate-limit status code as a number, not a substring
  • fix(evals): bound the output read that follows a killed trial
  • fix(ai): decide a marker-less Google EOF with the owner every dialect uses
  • fix(evals): one bounded terminator ends a trial's process tree
  • fix(coding-agent): keep a running subagent's card live across a transcript rebuild
  • fix(utils): read x-ratelimit-reset through the header's owner
  • fix(ai): word a provider finish-reason error in one place
  • fix(evals): prove a pid before escalating a cancel to SIGKILL
  • fix(evals): bound a trial's teardown so a hung dispose cannot wedge a worker
  • fix(evals): retry a trial that threw before its row is lost
  • fix(evals): the progress bar and every polled pane show what was read
  • fix(evals): one reader parses a harbor trial result
  • fix(evals): one owner classifies a trial status
  • fix(utils): a status line the message opens with reports its code
  • fix(shell): the renice test states the headroom the host gives it
  • fix(error): a provider failure the field produced reaches a decision
  • fix(cursor): a tool the exec channel already ran is not run again
  • fix(oracle): a transcript row is not a second composer prompt
  • fix(minimizer): a cargo failure before rustc keeps its evidence
  • fix(minimizer): a deferred nextest panic body is not dropped
  • fix(export): a value in an HTML attribute cannot close it
  • fix(write): an indented numeric mapping key is not a pasted search prefix
  • fix(tools): a selector suffix no longer hides a symlink escape
  • fix(compaction): a compact route that answers 404 is not asked again
  • fix(evals): a dashboard action states what the manager did
  • fix(evals): a broken Terminal-Bench checkout refuses instead of shrinking the dataset
  • fix(evals): an orphaned run gets its status from the timestamp its directory carries
  • fix(evals): the disclosure bench refuses a measurement nothing backs
  • fix(evals): a harbor trial nobody priced reports absent spend, not $0
  • fix(evals): the dashboard says when a table it is showing went stale
  • fix(evals): a terminal-bench corpus hash and task id state what the run read
  • fix(evals): an entry point that cannot act on a flag refuses the invocation
  • fix(evals): a count the deep-swe runner cannot act on refuses the run
  • fix(evals): the edit adapter refuses a wrong invocation before it unpacks anything
  • fix(evals): a harbor flag the runner cannot act on refuses the launch
  • fix(evals): a search bench run that measured nothing refuses instead of passing
  • fix(evals): a bench results block is keyed safely and filed in its own section
  • fix(evals): a benchmark snapshot reports what it read, not an empty run
  • fix(evals): one rule names a trial's directory, and .. never climbs out of the run
  • fix(evals): a resumed run keeps the log of the attempt it is resuming
  • fix(evals): a job name names one run, and a cancel reports what it killed
  • fix(evals): a dashboard subscriber that stops reading is dropped, not buffered
  • fix(evals): a trial that cannot be recorded stops the run
  • fix(evals): one owner decides a trial's deadline and its output bound
  • fix(evals): a run id names one plan, and a run refusal is one line
  • fix(evals): refuse a variant axis nobody applies
  • fix(natives): annotate the Darwin child map so it still type-checks
  • fix(evals): a run id and a job name are one directory name
  • fix(evals): refuse a directory or task-list file the run could not use
  • fix(evals): refuse a flag no entry point declares, and register the harnesses the deep-swe runner resolves
  • fix: hang a tab indent when wrapping, size the Darwin pid list from the kernel
  • fix: pin an HTTP reason phrase to its own code, bound the launch renderer
  • fix(evals): check every mutating request body against its declared fields
  • fix: shorten and bound every path a tool status line or the /omfg panel prints
  • fix(evals): check every launch field before a run starts
  • fix: seven defects found auditing the range since v1.2.0
  • fix(evals): the dashboard bundles for a browser, and an empty store says so
  • fix(evals): a resume of a run with no journal refuses instead of starting it
  • fix(evals): --list states every registry an invocation selects from
  • fix(evals): a preflight refusal states each credential once
  • fix(evals): a dictionary row states the revision it was measured against, and two harbor suites register their own harnesses
  • fix(evals): a generation attempt carries no invented provenance
  • fix(evals): a trial journal states the record shape it holds
  • fix(evals): one deep-swe source root, and an ungraded trial leaves the denominator
  • fix(evals): the deep-swe runner throws instead of ending the process
  • fix(evals): the typescript-edit corpus regenerates to the same cases
  • fix(evals): suite arithmetic reports the denominator it actually measured
  • fix(evals): a broken run refuses instead of reporting a result
  • fix(evals): unmeasured spend and unrecorded identity leave the store as absent
  • fix(evals): an unscored trial is never counted as a zero
  • fix(coding-agent): remove any casts in search test fixtures, use typed builders
  • fix(coding-agent): seed provider inventory in search tests and sync root changelog
  • fix(coding-agent): address review feedback for providers search
  • fix(coding-agent): give every live conversation its own registry row
  • fix(evals): read the withheld-cost note's rates from the card that owns them
  • fix(coding-agent): float the resume card instead of pinning it to the top edge
  • fix(evals): run the model the run named, on every backend, or refuse
  • fix(evals): stage the assets a pier trial's agent requires, and stop scoring a setup failure zero
  • fix(evals): report what a trial spent instead of reporting nothing
  • fix(evals): give a harbor trial the mounts, gateway and load order it needs
  • fix(ai): serve a broker snapshot that holds a logged-in API key
  • fix(coding-agent): clear memory on the shared deletion retry window
  • fix(evals): make a long parallel evaluation run survivable and its scores honest
  • fix(coding-agent): keep a configured Ollama base URL's path
  • fix(hashline): one definition of what a numeric-keyed mapping row looks like
  • fix(ai): call the errorMessage owner in the pi-native payload hook error
  • fix(coding-agent): answer an LSP abort the same way on every server

Release notes were shortened from 161,150 characters to fit GitHub's 125,000-character body limit. Read the complete package changelogs and full commit range.