@veyyon/agent-core
Breaking Changes
AgentOptions.cursorRulesResolveris 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
ProviderHttpErrorfrom its owning module rather than the@veyyon/ai/errorbarrel, cutting 14 modules off the engine's load graph with no change in behavior. - Streaming
message_updatesnapshots 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 atoolcall_endcarries 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
estimateTokensFromTextfrom@veyyon/utils/tokensrather than the package barrel, cutting the modules a token estimate loads from 92 to 10. - Compaction's directory-list documentation now uses canonical
searchfilesterminology instead of the retiredfindtool 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-agentturn 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
readcalls 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.cursorRulesand the exportedCursorRuleInputtype are removed, andbuildCursorRulestakes only the system prompt: the Cursor provider builds exactly one request-context rule, the assembled prompt.
Added
ToolCallLoopGuarddetects 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/flagsreturns 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 samenous-researchcredential.
Changed
- The assistant-text extractor's one-owner check names the consolidated evals package path instead of the retired metaharness path. No behavior change.
ToolCallLoopGuardwaits 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.readSubsumptionThresholdstill 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.argumentsis aReadonly<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:
namesDeadSocketin@veyyon/ai/error/flagsis the one list of errnos and phrases, andENETUNREACH,EHOSTUNREACHandEAI_AGAINnow count as transient transport failures like the rest of them. - Formatted source files for Biome compliance.
withAuthimports the two error classes it throws from their owning modules instead of the@veyyon/ai/errorbarrel, 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
sourcefield 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-backand 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-agentmodel 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-agentrequest 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-agentbatch keeps its own arguments: updates route by the frame'scall_idinstead 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-agentturn 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_unavailablerefusal 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 Gatewayor504 Gateway Timeoutis read as the upstream failure it is and costs a twenty-second retry, matching500; 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,529and500as the status codes they are rather than as digits inside a longer number, so an exhausted balance reporting5030 credits remainingrotates 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
finishReasonsettles 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,SPIIand theirIMAGE_forms), asfinish_reason: sensitive, or as a Codex event carryingcode=cyber_policyis classified as a content verdict and vetoes a retry, where onlyMALFORMED_FUNCTION_CALLhad 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
toolResultunder 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.statusandextractHttpStatusFromErrorare one reader, so a provider message spellederror(503),status_code: 429or429 Too Many Requestsyields 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-nativepayload 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 quoted401does not rotate the operator's credential. - OpenAI Codex request diagnostics redact every credential header rather than
authorizationalone, so a Codex request carryingx-api-key,proxy-authorizationor 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 intologs/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
ToolCallLoopGuarddeciding 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
ToolCallLoopGuardto 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
Authorizationheader 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
storeCredentialsAsnow 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
searchtool instead of the retiredfindidentity, 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
normalizeOllamaBaseUrlandtoOllamaNativeBaseUrl, 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
publishesOwnModelLimitsprovider flag, which stops generation from backfilling a context window or output cap from another host's same-family model. ProviderWireCapabilities.anthropicMessagesdeclares how a provider serves the Anthropic Messages API — its endpoint, credential placement, rejected request features and retryable model errors — anddeclaredProviders()anddeclaredCapabilityNames()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
supportsServerCompactioncapability 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
searchtool taking ordered requiredtype("files" | "text" | "structure") andinput, replacing the separateglob,grep, andast_greptool IDs. Retired per-engine andsearch.enabledvalues are discarded while text-context values and persisted tool inventories migrate.
Added
searchtakespathsfor 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 searchingbuildSystemPromptunderpackages/coding-agent/srccosts 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.
/advisorreports advisor status, opens theWATCHDOG.ymlroster 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.newKeepsBackgrounddecides what/newdoes 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.cheapModelandprewalk.strongModelconfigure the cheap model prewalk switches into at the first edit and the strong model it starts on./prewalkaccepts an optional model argument to arm a per-session target model override.edit.afterEditselects what one turn that changed files owes before it finishes:verify(the default) runs one check when none followed the last edit,reviewreads back every code file changed since the last user message and judges correctness, maintainability and cross-file contracts,offneither; the legacyedit.critiqueCodeMutationsboolean migrates torevieworverifyon load.- Configurable
launch.cleanupWaitMssetting (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.tsreports 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 bySESSION_MB.- Added
model.toolCallLoopGuard.readSubsumptionThreshold(default3) to steer models that re-read unchanged code lines back-to-back before consuming full context. VEYYON_DEBUG_STARTUP=1writes 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.readtakesdepthandlimitarguments 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.displayRootsnames the workspace roots the working directory is shown relative to, with~accepted for the home directory, replacing the two hard-coded conventions (~/Projectsand/work); the first matching entry wins and a non-absolute entry is dropped and named in the log once.readaccepts a semicolon-delimited list of internal resources (skill://demo/one.md;skill://demo/two.md), the same list formsearchtakes, 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, anddefs(), 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 listoutput 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: offlands on the click frame. /omfgforges 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./omfgsaves forged rules to the active profile's rules directory only; the project target is gone, because project.veyyon/ruleswas never discovered across sessions.- Settings → Stream Interrupts (TTSR) groups the profile's own rules under a leading
User createdsection instead ofFrom native, ahead of foreign-tool and built-in sections. - An opt-in
eval.pyWorkspaceexperiment 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
searchresult 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 cappedsearch filesreturned five paths and said nothing about the cap, nosearchof 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
skipand names the match count instead of advisinglimit, 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
$$$BODYwas 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.
skippages files and reaches nothing past either cap, so each notice names narrowing the pattern. - A
searchcall rejected for a cross-type field names the fields that type accepts. In a recorded trial a model sentlimitto a text search, readSearch 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
readwhose line bound came from the default rather than from the caller is held to thetools.artifactSpillThresholdbudget every other tool result is held to, instead of a compiled 512-bytes-a-line allowance: readingpackages/coding-agent/CHANGELOG.mdcost 19,768 tokens and now costs 10,586, and:50cost 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.artifactSpillThresholdinstead 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
@pathmention in a prompt is bounded bytools.artifactSpillThresholdinstead 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
readtakes thetools.artifactSpillThresholdbudget: the unsliced listing followed a compiled 50KB constant the setting could not reach, and a listing sliced by:N-Mcarried 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
readtakes thetools.artifactSpillThresholdbudget instead of a compiled 50KB constant the setting could not reach. - A structural summary from
readstops atread.defaultLimitlines or thetools.artifactSpillThresholdbudget, whichever binds first, clips lines totools.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.tscost 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 fromreadtake thetools.artifactSpillThresholdbudget. 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
readof a directory that names nodepthlists the top level instead of recursing two levels: every entry up to 100, each subdirectory annotated with its direct-child count, and a footer namingdepth: 2for 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, withpackages/coding-agent/srcat 8,163 against 1,273. A directory wider than 100 entries states how many it held back and namesdepth: 1for the flat listing of all of them; a caller that namesdepthorlimitis unaffected. - A
search_tool_bm25call activates the tools it matched well instead of every ranked match up to its limit of 8, which undertools.discoveryMode: allis 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" activatedtodoplusset_cwd,taskandweb_searchfor 2,239 tokens a request wheretodoalone 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 inalso_matchedand a query naming it activates it. tools.artifactSpillThresholdbounds what a spilled tool result costs a request, not only when an artifact is written. The window kept inline wastools.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 theartifact://id. This covers every tool result exceptread, which is bounded by the lines its caller asked for.- The
debugtool 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.contextAfterdefaults 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/evalspaths instead of the retired@veyyon/metaharness,@veyyon/typescript-edit-benchmarkand@veyyon/deepswe-benchpackages. No behavior change. - The
/providersaccount card filters its provider sidebar:ctrl+senters search, typing narrows the list by fuzzy match on provider name and id, the arrows move within the matches, andescleaves search before it closes the card (#922 by @Crqptx). - A parked subagent is pruned rather than closed:
subagent.autoClose.enabled,.parkedMsand.waitingMsare nowsubagent.prune.enabled,.afterMsand.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.newKeepsBackgroundstates 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
verifyvalue ofedit.afterEditand 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.
/agentsis the subagent dashboard: the surface is named that in its title, its/agentsdescription, its keybinding descriptions,/hotkeysand the handbook, in place of "Agent Control Center".- The subagent dashboard no longer opens across every conversation the process is running:
/process-managerand theascope 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 --helprenders 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-lspstill 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.
ModelRegistrypersists its static bundled, cached-standard and cached-discovery layers to a content-verifiedresolved-models.jsonthat 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
bestEffortandoptionalResultthrough@veyyon/utils/discarded-faultrather 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,
launchscored zero for "tail the output of a server I launched" andevalscored zero for "evaluate javascript and keep the variables", while short-description tools such asgoalandset_cwdranked in six of ten realistic queries;launchnow 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:
SQLitewas indexed only assqandlite,TypeScriptonly astypeandscript, andIPythononly asiandpython, so queries spelling any of them scored zero against the tool that documented it. - A
searchlimit 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 stringlogger.warn. - The
searchtool states thatcaseis on by default and that passingfalsematches 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
--modelpointing 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 tosearchnaming thetypefield the tool accepts, instead of thepurposefield 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 --helpdescribes thegrepdev 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_exitrecord 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
/collabor/joinruns instead of during every interactive startup, and a settings domain reads the relay default from@veyyon/wirerather 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.enabledoff no longer evaluates them. - The stats dashboard's aggregator, SQLite layer and embedded client load when
/statsfirst 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-agentmodel 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
toStringorconstructor, 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 anypersonality/default.mdoverride 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/personalitiesfile, which arrives with a cloned repository and outranks the user's own, is injected into every request. plugin install <name>@<marketplace> --dry-runno 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 withEISDIRwhen 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
§handletext; 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+Vdisappeared. - 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 statusnames 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_DEVICEandVEYYON_TINY_DTYPEreference states theproviders.tinyModelDeviceandproviders.tinyModelDtypedefaults as thedefaultsentinel each setting actually holds, and names what that sentinel selects, instead of listingCPUandq4as the stored values. - A collapsed
sshresult 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
writetool accepts a file holding an indented numeric mapping key, so a docker-compose80: 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:42andarchive.zip:dir/file.ts:5-9are 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,debugandset_cwdshow~/project/srcinstead 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
/omfgpanel 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
launchstatus 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
/newleaves 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
/resumepicker 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/newor 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,falseornullvalues 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
AbortErroron 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+gpasses 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
/guidedgoal 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
/guidedgoal 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 sendwithawaitends as soon as its recipient is terminated or leaves the roster, instead of blocking for the full timeout, or forever attimeoutMs: 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_editdiff preview, and anevaldisplay()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 aPI_*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
pathargument carrying no usable value can no longer suppress thepathsit also inspects. - An auto-compaction failure names its actual cause when the rejection is not an
Error, instead of reporting the literal textcompaction 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 viaPRAGMA user_versionand 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 sessiondeclares thesessionsalias so the plural command routes to session analysis.- Error-formatting call sites use
errorMessageso thrown non-Error values and empty-message errors surface readable text, and a reported cause no longer repeats a redundantError:prefix ahead of its message. AgentSessionlogs compaction tail elision artifact persistence failures explicitly instead of swallowing them.- Quitting no longer hangs when a background session never settles.
/newtyped 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./resumeonto 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 @roleandveyyon dry-balance --model @roleresolve 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
.giton a host with nogiton PATH; the lookup fails to themainfallback 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_cwdrender 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 stopandlaunch listreachable, 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 relativecwd, 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
ircsend reports its own validation error instead of being reported as an interrupted wait when a peer message arrives in the same batch. - A
joblist 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.
/newand/resumerestart 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-runnow 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 a0.0.0-dryrunplaceholder (#911 reported by @Crqptx).veyyon plugin uninstallnow removes a plugin installed from a local path, which was permanently unremovable because uninstall read onlyplugins/package.jsondependencies while linking registers the plugin in the runtime config andnode_modules; the linked directory itself is left untouched.veyyon plugin doctorno 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.
/agentskeeps 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 relativeast_editpath 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
launchtool block no longer renders as a bare title with no rows: the header drops the placeholder ellipsis whileopis still streaming, and every operation falls back to the result text when the structured detail it renders from is absent. - The
compaction.remotesetting description documents that server-side compaction applies to supported OpenAI, Azure OpenAI, and ChatGPT Codex Responses models. branchSummary.reserveTokensreaches the branch summarizer, which previously used the built-in 16384 reserve whatever the setting said.- The
sshtool 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. debugreaches the Python debugger on a host that installspython3and no unsuffixedpython, because an adapter may now declare alternate command spellings, while a command written indap.jsonis still used exactly as written.- A subagent that calls
yieldwith 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.
launchrecovers 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.bashrunscd - && …again, instead of reading the leadingcdas a directory literally named-and rejecting the call with a path the operator never typed.- A
bashworking-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. searchreports why an archive could not be opened or read when the failure is not anError, instead of the wordundefined.- 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 stampedmodels.dband 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; andauthoritativeFreshProviderswas serialized as aSet, 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.tsdefends 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
pathowned by other modes while retaining the historical/-means-workspace alias, structure search rejects unsupportedssh://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-equivalentfindcommands to a read-only file search. - The plan-mode extension example now keeps canonical
searchavailable both while planning and after restoring normal tools. It previously advertised retiredgrep/findidentities, requested a nonexistentfindtool, and dropped search when plan mode ended. - Memory summarization now retains canonical
searchresults from session rollouts. It previously allowlisted the retiredgreptool name but discarded every result emitted by unified search before Stage 1 summarization.
Removed
- The
/providersaccount card no longer writesaccounts.loadBalancing: itsbkey 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
/cwdand the status line state the session's directory. - The
tools.unifiedRuntimeexperiment and theruntimetool it gated. The unified tool never shipped enabled, duplicated theevalandlaunchprompts at 2,368 tokens, and the experiment was abandoned in favour of keepingevalandlaunchas separate tools.
@veyyon/evals
Added
@veyyon/evalsis the single package holding every evaluation in this repository, replacing@veyyon/deepswe-bench,@veyyon/metaharnessand@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-runand--list. Each suite produces its own run record, a--tasksentry is scoped to one suite by a<suite>=prefix, and--dataset-diris refused when the run names more than one suite.engine/run-plan.tsdecides 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.tsdrives 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: nullwith 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 tagv3.0.0(2b0442c3c583b710ca8da14c8e601b99f2f1f244, 74 tasks), Harbor task-config parsing, provenance, and the committedsmoke.txtandpilot.txttask 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 bythe autoscan loader. agents/harbor/veyyon_agent.pyruns the veyyon harness inside a Harbor container.- Folded the TypeScript-edit mutation, verification, and benchmark suite into
suites/typescript-edit/. - Added in-process
AgentSessionexecution client atbackends/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 toagents/harbor/veyyon_local.py. - Moved the SQLite run store, experiment grouping layer, and REST/SSE manager server into
store/andapi/. - 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.dbin the setup step. Preflight accepts the auth DB as an alternative to--omp-api-keyor$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-fixturesgenerates TypeScript-edit fixtures fromdatasets/typescript-edit/typescript-sourceinstead 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, andbunfig.tomlpathIgnorePatternskeeps test discovery out of the gitignored data trees (runs/,datasets/repo-cache/,datasets/deep-swe/corpus/,.cache/). engine/package-paths.tsis the single owner of the package's directory layout, replacing the DeepSWE-scopedpaths.tsand the manager's second copy.- The search benches write their scratch corpora to the repository's
.internal/directory instead of creating a straypackages/.internal/. - Record and config parsing calls
isRecordanderrorMessagefrom@veyyon/utilsinstead 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.jsonincludes the package root and excludesdashboard/instead of the removedsrc/tree..gitignoreandscripts/local-endpoint-bridge.shno longer referencesrc/.
Fixed
- DeepSWE dry-run preflight reports missing or stale binary artifacts with their build command instead of triggering a product build.
--dry-runrefuses 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.compileinsuites/typescript-edit/argot-bench.tsandgenerate.tsreceives the prompt text (.text) instead of thePromptEntryobject, 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_X64orVEYYON_BENCH_BINARY_ARM64is set, so a pinned-binary run does not fail on a compose overlay the binary mode never uses. - The Harbor compose overlay targets the
mainservice that harbor's build template defines, not a non-existenttaskservice, sodocker compose buildno longer fails with "service has neither an image nor a build context". - The Harbor backend passes
--agentinstead 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:
buildModelsYmluses the gateway URL with/v1appended as the provider base URL,openai-responsesas the API, andno-authas the API key when no key is resolved. - The omp harness parses
vey models refresh --jsonoutput as a single{"models":[...]}JSON object instead of NDJSON lines, somodels.ymlis staged for providers that return the object format. - The omp harness stages the host's
bunbinary 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_modulesinto 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 includesextra_hosts: host.docker.internal:host-gatewayfor omp runs. - The Harbor compose overlay supports
cfg.extraVolumesfor harness-specific bind mounts in addition to source-tree mounts. - The Harbor backend strips
VEYYON_AUTH_BROKER_URLandVEYYON_AUTH_BROKER_TOKENfrom 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 athost.docker.internal:4000. - The omp harness uses the gateway bearer token as the
apiKeyinmodels.ymlwhen routing through the gateway, so omp sends an authorized request instead ofno-authand getting 401. - The Harbor backend passes
gatewayTokenthrough to the harness staging options, so a harness with acontainerProgram(omp) receives the gateway token alongside the gateway URL.
@veyyon/hashline
Fixed
- A numeric-keyed body whose values are
true,falseornullkeeps itsN: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/utilsbarrel 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,nooroffnow leaves embeddings on everywhere instead of disabling them on the API path.- Every
MNEMOPI_*value is read byconfig.tsalone; the local-model, extraction and embedding modules ask it instead of parsing the variable again. getDiagnosticsis nowextractionDiagnosticsincore/extraction/diagnosticsandrecallDiagnosticsincore/recall-diagnostics, so the two registries are no longer reached by one name.core/embeddings.tsimportsProviderHttpErrorfrom@veyyon/ai/error/classesinstead of the error barrel, cutting twelve modules off the import graph of every module that can remember something; behavior is unchanged.config.tsandcore/extraction/client.tstaketrimTrailingSlashesandwithScopedTimeoutSignalfrom@veyyon/utils/urland@veyyon/utils/scoped-timeoutinstead 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 viaPRAGMA user_versionand dynamically backfills missing columns on legacy databases.
@veyyon/natives
Fixed
- A load failure carries
code: "VEYYON_NATIVE_ADDON_UNAVAILABLE", exported asNATIVE_ADDON_UNAVAILABLE_CODEwithisNativeAddonUnavailablefrom@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
cargorun under--message-format=jsonthat fails before rustc, such as a build script exiting non-zero, reports the text cargo printed, so theCaused by:chain and the script's own stderr reach the operator instead of a bare failure verdict. - A
cargo nextestrun whose profile setsfailure-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.execPathis 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
ThemeTogglefrom@veyyon/tool-render.
@veyyon/swarm-extension
Changed
- Swarm's documented agent tool inventory now names the canonical
searchandevaltools instead of retired workspace-search names. No runtime behavior changed.
@veyyon/tool-render
Added
- Added
ThemeTogglecomponent to shared React renderers for cycling system, light, and dark theme preferences.
Changed
- Unified
searchrenderer 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.onBeforeComposeruns 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.Imageaccepts anonDisplayedcallback and reports the cause each time an image starts or stops falling back to a placeholder.MOTION.reflowstates the curve for a row that reflows its content sideways: 320ms, symmetric, whereexpandis 180ms and front-loaded.TUI.composedFrameLinesexposes 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;setTextstill 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/harborinstead of the retired metaharness package path. No behavior change. imageFallbacktakes the file name, media type, pixel size and cause of an undrawn image and returns a row naming all four;ImageFallbackReasonstates 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-blockedwarning reportsphasewith thephaseMsthat 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 asunknownwith the observed label carried astopPhase.
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.setSelectedIndexholds 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:exportedDeclarationsInanddeclarersOfNamereport 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. definePromptRowsanddefinePromptRegistryre-readVEYYON_EVAL_PROMPTSwhen 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-variablesdocumentation examples name thesearchtool, which is the workspace-search tool that now exists, instead of the retiredgreptool. No behavior change. bestEffortandoptionalResultare imported from@veyyon/utils/discarded-fault. The barrel does not re-export them, so a consumer reaching them through@veyyon/utilsnames the module instead.relativePathWithinRootreturns the candidate's own spelling instead of the case-folded copy the containment check used, so on Windows a path underC:\Users\dev\Projectsno 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.bestEffortandoptionalResultare imported from@veyyon/utils/discarded-fault, which the barrel does not re-export, so a consumer reaching them names the module instead.winstonandwinston-daily-rotate-fileare 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:
takeLoopPhaseProfilebanks elapsed time per phase and reports the costliest one with its cost, replacingtakeRecentLoopPhase, which returned the most recent label and blamedui.renderfor a stall the render pass never took part in. - No user-visible change: doc comments name the subagent dashboard, the surface
/agentsopens, instead of the Agent Control Center. - Prompt-variable documentation now uses canonical
toolRefs.searchexamples instead of the retiredtoolRefs.grepname. No runtime behavior changed.
Fixed
- An empty
~/.veyyon/agentbeside a migratedprofiles/defaultno 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 globalAGENTS.md,vault.jsonandvault.keystay where every profile reads them. extractHttpStatusFromErrorreads the status line a message opens with whatever wording follows it, so401 Your session has expired,403 You have run out of creditsand503 {"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.extractHttpStatusFromErrorreads a status field anywhere in an error's cause chain before falling back to prose anywhere in it, and matches thestatus_code: 503and429 Too Many Requestsspellings it previously missed.extractHttpStatusFromErrorreads a reason phrase only when it is the phrase that belongs to the code beside it, soProcessed 200 Total Recordsandgave up after 401 Failed Attemptsno longer report a status, the second of which reached credential rotation.extractRetryHintreadsx-ratelimit-resetthrough the same owner asx-ratelimit-reset-ms, so the common delta formx-ratelimit-reset: 60waits the 60 seconds the server asked for instead of computing a negative delay from it and falling back to the caller's default backoff.onProcessExithonors itsAbortSignalfor every process shape: waiting on aSubprocessends when the caller cancels instead of running to the child's exit, and a cancelled wait on a pid returnsfalserather 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).
splitTrailingPartialEscapelets 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:bestEffortandoptionalResultstate 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-inproc.kill()(SIGTERM) when the nativeProcessclass 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.