67 Commits
Author SHA1 Message Date
webtecnica c784ef7a12 fix(ui): preserve endpoint-style custom slugs host:port in model parsers (#6884) 2026-09-24 08:50:30 -03:00
webtecnica 0cbf16a51c fix(perf): treat only non-consecutive repeat cycle keys as oscillation (#6717) 2026-09-24 08:44:04 -03:00
webtecnica 98b002d96b fix(sessions): preserve state.db archived status for sessions without sidecars (#7548) 2026-09-24 01:06:03 -03:00
webtecnica 6caa0c1963 fix(lint): clean up unused import and variable in test_issue7600 2026-09-23 11:24:48 -03:00
webtecnica 618325501e Merge upstream/master into fix/oob-marker-writeback 2026-09-23 11:19:34 -03:00
webtecnica 6d1753bb6c fix(streaming): extract inner steer text from typed steer rows in place (#7600) 2026-09-23 11:19:25 -03:00
webtecnica 301ed54ab7 fix(context-length): restore base_url for two guarded configs (#7743)
The #7535 ownership guard skipped the global model.base_url backfill for
two shapes that master handled:

- A `model` section with `base_url` but no `provider` declares no owner,
  so `_providers_match_for_context("", <session provider>)` was false and
  the endpoint was dropped, silently falling back to 256,000 instead of
  querying the configured /v1/models (the profile-setup path writes this
  shape).
- `opencode_go` and `opencode-go` are two spellings of the same built-in
  provider, but `_canonical_context_provider` does not fold underscores,
  so the owner session lost its URL.

Allow the backfill when no owner is declared, and compare both sides
through api.config._canonicalise_provider_id, which folds `_` to `-` while
keeping distinct `custom:*` slugs distinct. The #7535 case (a custom:ark
owner vs an opencode-go session) still resolves to an empty base_url.
2026-09-23 09:26:24 -03:00
webtecnica a44e329997 Merge origin/master into fix/7535-provider-base-url-ownership 2026-09-23 09:20:41 -03:00
webtecnica 03013606fe Merge remote-tracking branch 'origin/master' into fix/7640-transparent-replay-body 2026-09-23 09:20:31 -03:00
webtecnica 67135b3b1e test(models): pin live-rebuild budget in model-cache metadata tests
The three get_available_models() tests in tests/test_model_cache_metadata.py
depend on the rebuild finishing inside _LIVE_REBUILD_BUDGET_SECONDS (4.0s).
On a loaded host the rebuild exceeds it and api/config.py::get_available_models
serves the over-budget stale payload, failing the rebuilt-metadata assertions.

Pin the budget to 0.0 (synchronous rebuild), the same pattern already used by
tests/test_issue2513_custom_provider_remote_models.py and
tests/test_copilot_provider_model_settings_not_allowlist.py.

Closes #7735
2026-09-22 22:58:20 -03:00
webtecnica a8180bc51f fix(context-length): guard provider ownership on global model.base_url backfill (#7535)
A session on a built-in registry provider (e.g. opencode-go, endpoint in
PROVIDER_REGISTRY, empty base_url by design) had its empty base_url slot
backfilled with the global model.base_url owned by a *different* provider
(custom:ark). The context-length resolver then probed the wrong endpoint's
/models catalog and fell back to the generic 128K catalog entry for a
1M-context model, while the on-disk session and SSE stream carried 1,000,000.

Only fill the global model.base_url slot when the session provider IS the
configured model.provider owner, mirroring the ownership predicate used for
model_cfg's API key in _context_length_config_api_key_for_provider.
2026-09-22 22:54:12 -03:00
webtecnica 26702fd19f Remove flaky source-text oracle test (issue #7726)
test_delete_cli_session_still_opens_writable_connection asserted on
inspect.getsource(models.delete_cli_session) text, so it failed only in
single-process full-suite runs when an earlier test leaves a
monkeypatched/decorated wrapper bound on api.models.

The writable-connection contract is already covered end to end by
tests/test_issue1494_state_db_fd_leak.py::test_delete_cli_session_closes_connection,
which performs a real delete and asserts the rows are gone. Remove the
source oracle without a textual replacement; keep a short comment
pointing at the behavioral coverage.
2026-09-22 22:49:23 -03:00
webtecnica 813b293c82 fix(commands): remove duplicate top-level const breaking messages.js load
static/commands.js and static/messages.js both declared
const _BACKEND_EXECUTED_AGENT_COMMANDS at file scope. static/index.html
loads them consecutively as classic defer scripts in the same global
lexical realm, so the second declaration threw
SyntaxError: Identifier '_BACKEND_EXECUTED_AGENT_COMMANDS' has already
been declared, aborting messages.js entirely.

The constant was unused in both files (a duplicate authority mirror, not
a genuinely shared source), so both declarations are removed. The
existing three-way parity test is preserved, and a new regression
evaluates commands.js followed by messages.js in one shared
classic-script vm realm, asserting both files evaluate cleanly and the
dispatch consts of both files are bound.
2026-09-20 11:59:09 +00:00
webtecnica ff06f6d032 fix(commands): exact cross-authority parity for announced slash commands 2026-09-20 11:59:09 +00:00
webtecnica a85c1d1f62 fix(commands): exact cross-authority parity for announced slash commands (#6951) 2026-09-20 11:59:09 +00:00
webtecnica d03891a307 fix(commands): autocomplete only announces webui-dispatchable slash commands (#6951) 2026-09-20 11:59:09 +00:00
webtecnica 06fc0575d5 fix(async): close pre-ACTIVE_RUNS stream window, never strand wakeup admission (#6989) 2026-09-20 10:36:36 +00:00
webtecnica b7f536b006 fix(async): atomic per-origin wakeup admission prevents delivery budget exhaustion (#6959) 2026-09-20 10:36:36 +00:00
webtecnica da4e4ef316 fix(picker): catalog is authority for @custom labels; keep plain-lane colon models whole
The #7240 fix peeled the leading @custom: segment on first-colon split, but a
plain-lane custom id whose model carries a tag/variant colon
(@custom:ollamacloud/qwen3.5:397b) was still misread as slug=ollamacloud/qwen3.5,
rendering only "397b". Two changes:

- _dynamicModelLabels (the backend-populated catalog) is now consulted BEFORE
  the string peel: the backend knows the true provider/model split and ships an
  exact m.label per routing id, so catalogued ids — including plain-lane colon
  models and collision cases — render verbatim.
- The legacy string fallback now treats a slash-bearing first segment as the
  model itself (a provider slug is a config key or host:port authority and
  never contains '/'), mirroring the '/' means routable rule api/config.py
  applies when building ids.

Tests: add plain-lane @custom:ollamacloud/qwen3.5:397b controls, a
post-catalog dynamic-label control, and a collision case proving labels stay
distinct by full routing id. Driver now accepts an optional pre-hydrated
catalog map.
2026-09-20 06:33:58 +00:00
webtecnica e71c3e1f97 fix(picker): stop truncating model names with colons 2026-09-20 06:33:58 +00:00
webtecnica d9440e8654 fix(stream): rebuild the assistant body when a replay cursor outlives its live state
A transparent-mode SSE drop followed by a run-journal re-attach could settle a
turn with its footer (duration / model / TTFT / Done) painted over a blank
message body. The cached INFLIGHT entry kept `lastRunJournalSeq` (206 in the
report) while its live assistant projection was gone; re-attach passed that
cursor to the server as `after_seq`, so every journal event that rebuilds the
message body was skipped and only the tail came back. A manual reload - a
zero-floor replay - restored the message, which is what proves the server-side
journal was complete the whole time.

- `_inflightCanSeedJournalReplay()`: only recoverable live output (assistant
  content/reasoning, `lastAssistantText`/`lastReasoningText`, live DOM, tools,
  burst anchors, a populated anchor scene) authorises a nonzero replay floor.
- `_runJournalReplayFloorForInflight()` / `_runJournalReplayEventIdForInflight()`
  gate BOTH halves of the cursor. The server reads `after_event_id` before
  `after_seq`, so zeroing the seq alone would still resume at the stale event id
  and skip the same range.
- `_normalizeInflightReplayCursorForReattach()` re-validates the selected
  recovery object immediately before reattach (both `loadSession` reattach
  paths), so a cursor that outlived its projection cannot reach the wire.

`_inflightHasVisibleLiveState()` keeps its keep-vs-discard role: a bare user row
has to stay visible across a mid-turn reload until pending/live reconciliation
runs, so replay authority lives in the floor predicate rather than overloading
that contract.

Fixes #7640
2026-09-19 15:27:17 -03:00
webtecnica 65e968c447 fix(commands): drop slash-skill caches on profile switch
The slash-skill caches (_slashSkillCache, _slashSkillCachePromise,
_skillCommandCache) outlived a profile switch, so the outgoing profile's
/api/skills payload kept hiding skills that the incoming profile enables
(#7509).

- call invalidateSlashSkillCaches() from both switch handlers
  (static/panels.js switchToProfile, static/sessions.js
  _switchProfileForSessionLoad), after the /api/profile/switch request
- bump a generation counter in invalidateSlashSkillCaches() and capture it
  in both loaders: a reply issued for the outgoing profile can no longer
  commit once the switch has landed
- tests: in-flight race and cache-refresh regression on both picker
  surfaces, plus a check that both switch paths call the invalidation
2026-09-18 02:23:23 +00:00
webtecnica 9cb0235f5e fix(commands): keep disabled skills out of the slash-command picker
`skills.disabled` entries are already excluded from the backend skill-command
map, but the composer still offered them as selectable slash commands and as
`/use` sub-args: both picker surfaces read `/api/skills` and ignored the
`disabled` flag the endpoint reports per skill.

Add one `_isSkillDisabled()` gate in static/commands.js and apply it at the two
places that turn `/api/skills` rows into completions:

* `_buildSkillCommandEntry()` returns null for a disabled skill, which keeps
  `_skillCommandCache` (and every consumer of it, including the dropdown) clean;
* `_loadSlashSkillSubArgs()` skips disabled skills when building `/use`
  sub-args.

Both caches still rebuild from `/api/skills`, so a skill toggled off in the
skills panel disappears from the picker on the next load.

Adds tests/test_issue7509_disabled_skills_slash_picker.py, which runs the real
static/commands.js in a vm context with a mocked `api()` and asserts on
`getSlashAutocompleteMatches()` for both surfaces, over 0 / 1 / many skills,
enabled / disabled / flag-absent entries and a disable-then-refresh cycle.

Fixes #7509
2026-09-18 02:23:23 +00:00
webtecnica 9a6a9acffa fix(streaming): gate the OOB scrub to the carrier tool row (#7600)
`_strip_oob_markers_from_messages()` matched on marker presence alone and passed
the whole row to `_strip_oob_blocks()`, so a user row that legitimately quotes a
complete `[OUT-OF-BAND USER MESSAGE]…[/OUT-OF-BAND USER MESSAGE]` example lost
that text on the next settlement — and the shortened pre-match row broke
active-turn identity matching, which duplicated the turn.

The transport only ever appends a consumed `/steer` wrapper to the turn's last
tool result, so the scrub now considers `role == "tool"` rows only and rebuilds
just their `content` (shallow copy): user/assistant rows, stable ids, reasoning
metadata, and turn bookkeeping are never touched.

Measured on the focused file, helper restored to the pre-fix body as sabotage:
- pre-fix body: 1 failed, 7 passed — the new regression prints the deleted
  example plus 3 user rows instead of 2 (the duplicated turn)
- with the fix: 8 passed
Neighbors: test_context_history_sanitization.py 20 passed; writeback/settle
family (12 files) 120 passed, 5 skipped; media slice (7 files) 197 passed,
18 subtests.

Docs: docs/rfcs/webui-pending-intent-controls.md now records that the transport
wrapper is dropped from the settled display and the persisted context state, is
scoped to the carrier row, and that a transcript stored before this behavior
heals on its next settlement.
2026-09-16 18:32:45 -03:00
webtecnica 5c610866c8 fix(streaming): scrub OOB steer wrappers from the merged display transcript (#7600)
The display merge carries earlier turns' rows across verbatim, so a wrapper
persisted by a pre-fix settle would stay rendered for the rest of the session.
Scrub session.messages after the merge — the merge's identity matching still saw
the rows unchanged — so the persisted display copy is marker-free even when the
row was not first written by this turn. Covered by
test_settle_heals_a_transcript_that_already_stored_the_wrapper.
2026-09-16 16:14:28 -03:00
webtecnica 4a3c55fdb0 fix(streaming): strip consumed OOB steer wrapper from the settled transcript (#7600)
A mid-turn /steer is delivered to the agent as an [OUT-OF-BAND USER MESSAGE ...]
block appended to the turn's last tool result. The model-facing gateway history
always dropped that wrapper, but the writeback that produces session.messages
(rendered verbatim) and session.context_messages kept it, so the frontend showed
the raw control wrapper as a chat bubble.

Strip it in _prepare_marker_clean_writeback, the chokepoint both writebacks are
built from, reusing _strip_oob_blocks. Rows without a marker are returned
untouched so stable ids / reasoning metadata keep riding the same objects and the
scrubbed rows still compare equal to the rows persisted by earlier turns.
2026-09-16 16:11:25 -03:00
webtecnica 80610c7b6c docs(architecture): document the /api/models cache invalidation contract
The /api/models cache identity shipped in #7556 (release #7558) changed from a
stat identity to content fingerprints with volatile-key deny-lists, and the #7556
review flagged that this runtime contract had no project-level documentation.
Record the three source-fingerprint axes and their rationale, the one-directional
deny-list invariant, the stat-identity fallbacks (including the deep-tree
RecursionError path), the independent schema/version stamps, and the change
protocol; index the document from docs/CONTRACTS.md. Docs only.
2026-09-13 15:02:03 -03:00
webtecnica aeab2fb99f test(profiles): derive skill-count expectations from the agent walk output (#7305)
The positive control asserted on_disk > walked, i.e. it froze the agent
package's index-walk exclusion list. In an environment whose
agent.skill_utils yields every SKILL.md (no pruning) both sets are equal
and the assertion failed, which is what broke CI on the previous head.

Expectations are now derived from the paths the walk actually yielded, so
the test keeps asserting the property that belongs to this code - the
compatible total stays strictly below the walked file count via platform
filtering and frontmatter-name deduplication - without freezing another
package's exclusion policy.
2026-09-11 18:27:46 -03:00
webtecnica 86c2252dc8 fix(profiles): report skill stats as unknown when agent source is unmounted (#7305)
Review follow-up on the #7305 regression fix.

- Drop the local shadow of ``agent.skill_utils`` (an ``os.walk`` +
  frontmatter parser + always-True platform filter). A local re-implementation
  cannot preserve the index walk's directory exclusions, frontmatter-name
  identity or platform filtering, so it reported inaccurate counts: a SKILL.md
  declared for another platform was counted as enabled, and support /
  dependency / VCS / inactive org-mirror files were counted as skills.
- The Agent-absent branch now reports the stats as unknown — a stable
  ``(0, 0)``. GET /api/profiles still cannot 500, and the picker never shows a
  count it cannot verify (the UI omits the skills line when the total is 0).
- Tests now pin that contract: force ``ImportError`` for ``agent.skill_utils``
  (and ``hermes_cli``) and assert the unknown-stats result, the fallback row
  from ``_default_profile_dict()``, and the isolated-mode ``/api/profiles``
  path end to end. The fixture reproduces the shapes a partial local scan gets
  wrong — a directory name differing from the declared ``name``, two
  directories declaring the same name, a declared name disabled for the webui
  platform, a platform-incompatible skill, and the paths the index walk prunes
  (support dirs of a skill package, dependency/VCS/metadata dirs, an inactive
  org mirror) — and a positive control runs the same tree through the real
  Agent-backed path so the fixture cannot degrade into a tree with nothing to
  count.
2026-09-11 18:17:38 -03:00
webtecnica d2e470b218 test(profiles): fix ruff F401/F841 in #7305 regression 2026-09-11 17:34:07 -03:00
webtecnica cd51670419 fix(profiles): guard agent.skill_utils import in skills-stats fallback (#7305) 2026-09-11 17:34:07 -03:00
webtecnica 617881feab test: drive the auxiliary loader behaviorally instead of grepping panels.js (#7521) 2026-09-10 23:18:44 -03:00
webtecnica dc1d41db04 fix: keep endpoint-error provider groups in auxiliary model pickers (#7521) 2026-09-10 23:11:12 -03:00
webtecnica e34980d969 test(7514): pass strict=True to zip() in the namespace projection test
Fixes the B905 finding flagged by the forward ruff gate on #7518.
2026-09-10 22:39:18 -03:00
webtecnica 4f109aebbe fix(settings): preserve configured aux provider when absent from catalog (#7486) 2026-09-10 22:37:09 -03:00
webtecnica 4359ed4fee fix(onboarding): use OpenRouter namespace z-ai/ for Z.AI models (#7514) 2026-09-10 22:30:44 -03:00
webtecnica ca653eca1d test: tolerate conversation_id kwarg in title mock 2026-09-08 22:15:46 -03:00
webtecnica 61f3cf7ed9 fix(title): publish conversation context for aux title calls
Title generation via the aux client ran without the Agent's ambient
conversation context, so relay targets that key on it (OpenCode Go)
rejected the completion call with HTTP 400.

Publish the webui session id as the Agent conversation context around
the aux completion (restoring it in a finally block) and pass it from
all three title entry paths: background update, background refresh and
the on-demand regenerate helper.

Refs #7470
2026-09-08 22:06:32 -03:00
webtecnica 2c0b4902d0 fix(renderer): emit nested lists iteratively to avoid stack overflow (#6700) 2026-09-08 01:16:01 -03:00
webtecnica 660d54af63 test(renderer): mirror nests same-marker child lists inside parent li (#6700)
greptile P2: the Python mirror's handle_ul/handle_ol closed the parent
<li> before opening the child <ul>/<ol> for same-marker nested lists
('- parent\n  - child'), so structural assertions passed without
validating renderMd()'s parent-child hierarchy. Child lists now render
inside the parent <li>, byte-matching renderMd() for the two-level case;
adds a mirror regression test asserting the exact nested structure.
2026-09-08 01:16:00 -03:00
webtecnica b810e5466f fix(renderer): preserve ordered flag on list items (#6700)
openItem() received ordered but dropped it from the item constructor, so
it.ordered was always undefined and every item looked unordered at the
task-list check. An ordered item whose text begins with [x] or [ ] (e.g.
'1. [x] shipped') was silently converted to a task icon and lost its
literal prefix. Task-list rendering now applies only to unordered items.
2026-09-08 01:16:00 -03:00
webtecnica 13dd97d60d fix(renderer): preserve nested mixed ul/ol list hierarchy (#6700) 2026-09-08 01:16:00 -03:00
webtecnica ec4bac9fc9 fix: model picker search matches OpenRouter display names (#7228) 2026-08-31 15:29:28 -03:00
webtecnica 7db2695a5e fix: non-greedy parse for @custom prefix + restore selected row
- Fix _providerFromModelValue and _getOptionProviderId to correctly parse
  @custom:<slug>:<model> format (non-greedy, stops at first colon after @custom:)
- This prevents truncation of colon-bearing model IDs like model-a:free
- The restore path (_isSelectedModelRow) now correctly matches the row
  because the provider is parsed correctly
- Related: #6884, #6895, #6221 re-gate
2026-08-30 21:37:05 -03:00
webtecnica 2e12075400 fix(session): apply branch-marker guard to transcript-stitcher lineage query (#7021 r2)
The open/import transcript stitcher (get_state_db_session_messages with
stitch_continuations=True) runs its own lineage SELECT that omitted
session_source/model_config, so the shared _is_continuation_session guard
added for the listing path never fired there: an explicit branch/delegate
child (model_config._branched_from/_delegate_from) starting within the 2s
tolerance window got its rows stitched into the parent transcript on open.

Select both identity columns when the schema has them (NULL AS fallback for
older state.db schemas), so the same guard applies in the stitch path.

Tests (tests/test_session_lineage_metadata_api.py):
- branched/delegate children inside the tolerance stay out of the parent
  transcript on open/import (fail before this fix, pass after);
- old-schema rows without the identity columns still stitch genuine
  compression continuations (no crash, no behavior change).
2026-08-30 21:37:05 -03:00
webtecnica cef2521f2a fix(ui): restore "Selected" row for bare custom model ids (#6884)
After the send path emits the bare model id (e.g. "model-a:free"), the
restore/refresh path compared the qualified catalog row value
(@custom:hetmer.net:model-a:free) against the bare state model, so no
row rendered as active/"Selected". Normalize both sides through
_modelPickerOptionIdentity (the same identity used for picker dedup)
and keep the provider equality gate, so exactly one row is restored.

Adds a round-trip regression test (send -> persist -> restore) with a
colon-bearing custom model id asserting exactly one "Selected" row with
the model intact, and updates the #5748 source-assertions to the
normalized comparison.
2026-08-30 21:37:05 -03:00
webtecnica 6f0028cfa1 fix(session): exclude model_config branches before tolerance; drop title fallback
Re-gate #7021: two silent sidebar regressions on the #6931 continuation
tolerance.

1. MUST-FIX 1 — branch marker in model_config. The session_source=='fork'
   guard only covers WebUI-created forks (/api/session/branch). Explicit
   Agent branches/delegates carry no session_source: they are marked in the
   model_config JSON column (_branched_from / _delegate_from). The caller
   queries did not select model_config at all, so an Agent branch starting
   inside the 2s tolerance window of its parent's compression passed the
   widened check and vanished from the sidebar. Fix: select model_config in
   the classifier's caller queries (get_cli_sessions, lineage report, lineage
   metadata) and reject a child whose _branched_from / _delegate_from points
   at the parent being classified, BEFORE the tolerance is applied. The
   marker must reference this parent: compression continuations inherit the
   rotated agent's model_config verbatim (mirrors hermes-agent
   _NON_CONTINUATION_CHILD_FILTER_SQL).

2. MUST-FIX 2 — unbounded title fallback removed. Titles are user-controlled
   and non-unique, so parent_title == child_title beyond the tolerance window
   collapsed independent same-title branches arbitrarily far apart. The
   fallback is gone entirely; only the bounded 2s early-side tolerance
   (CONTINUATION_STARTED_AT_TOLERANCE_SECONDS) remains, after the reliable
   non-continuation guards.

Tests: same-title independent child outside tolerance stays visible (was
collapsed); _branched_from/_delegate_from children inside the tolerance stay
visible (reviewer's adversarial probe); unit coverage for marker parsing
(JSON string, dict, unparsable) and marker-points-at-other-session
preservation. All 4 new tests are RED on the previous head and GREEN here.

Closes #6931
2026-08-30 21:37:04 -03:00
webtecnica c82c6a542a fix(ui): use option metadata provider for @custom prefix strip (#6884)
The #6884 prefix strip built its prefix from explicitProvider, which
re-parses the value at its LAST colon. For a colon-bearing model id
rendered by the normal catalog path (e.g. @custom:backup:model-a:free,
no data-model), the strip truncated the model to just "free". Build the
strip prefix from the option metadata's authoritative provider
(routedProvider, e.g. custom:backup), falling back to the parsed
provider only when no option metadata exists. The #1771 gate is kept:
non-custom namespaces like @safe:gpt-4o-mini are still preserved.

Adds regression coverage for the colon-bearing model case beside the
existing #6884 normal-catalog test.
2026-08-30 21:37:04 -03:00
webtecnica 52201ac9f7 fix(session): recognize compression continuation across sub-second timestamp overlap
_is_continuation_session() required float(child.started_at) >= float(parent.ended_at).
A compression/cli_close handoff can persist the continuation row a few ms BEFORE
the parent's ended_at lands (write-order race), so the strict comparison returned
False despite every other piece of evidence (direct parent_session_id link, same
source, end_reason=compression/cli_close, non-fork). The backend then labeled the
continuation as relationship_type=child_session, causing duplicate same-title
sidebar rows and spurious child-session entries (#6931).

Allow a small tolerance (2s) for handoff-consistent timing; beyond it, an exact
title match on the parent/child rows still confirms the same handoff. Fork,
cross-source, and non-compression/cli_close guards are preserved, and materially
overlapping children remain separate child sessions.

Adds API-level regression tests (sub-second overlap forms one lineage; material
overlap stays a child session; same-title evidence beyond tolerance) plus focused
unit coverage of the classifier.

Closes #6931
2026-08-30 21:37:04 -03:00
webtecnica 1e53cc4f1b fix(ui): restrict @provider prefix strip to custom providers only (#6884)
The strip introduced for #6884 must only apply to @custom:<slug>:<model>
values rendered by the catalog (which never set data-model). A generic
@<provider>: strip also rewrote non-custom qualified ids like
@safe:gpt-4o-mini, breaking the #1771 contract that fallback session
models keep their provider namespace. Gate the strip on the parsed
provider being 'custom' or 'custom:<slug>'.
2026-08-30 21:37:04 -03:00
webtecnica 377087da06 fix(ui): strip @custom:<slug>: prefix when sending non-default custom-provider models 2026-08-30 21:37:04 -03:00
webtecnica 05b6d5b472 fix(perf): absolute burst cap + per-render provenance (#6717) 2026-08-30 21:29:25 -03:00
webtecnica b1ea6141b3 fix(perf): keep burst-reset before early returns within renderMessages guard window (#6654)
The cycle-aware burst tracking re-gate reset the measurement burst at the
top of renderMessages(), but the added block pushed the session-load
loading-placeholder guard past the 1400-char window asserted by
test_renderMessages_preserves_loading_placeholder_for_session_switch, and
the standalone renderMessages harness in test_anchor_fallback_ownership.py
lacked the new _resetMessageVirtualMeasurementBurst dependency. Compact the
reset (identical behavior: every unmarked external render starts a fresh
burst before any early return or rAF coalescing) and add the helper stub to
the harness. CI test shard 2 is green again.
2026-08-30 21:29:24 -03:00
webtecnica e5742d5dda fix(perf): cycle-aware measurement burst tracking (#6654)
Replace the flat 2-render burst cap with cycle-aware tracking: unseen
monotonic window keys proceed (genuine convergence A->B->C->settled is
never capped), while repeated oscillating keys (A->B->A->B) terminate the
burst so the rAF/measure loop stays bounded. Thread an internal-measurement
provenance marker through both rAF layers into renderMessages() and reset
burst state on every unmarked (externally initiated) render trigger, so a
pending internal callback can never starve a legitimate new cycle.
2026-08-30 21:29:24 -03:00
webtecnica f88cc78286 fix(perf): per-burst retry budget lifecycle, reset only on external render (#6717) 2026-08-30 21:29:24 -03:00
webtecnica 4df32ea332 fix(perf): don't reset virtual measurement retry budget on window-key oscillation (#6654)
_scheduleMessageVirtualMeasurementRefresh() reset _messageVirtualMeasurementRetryCount
every time _messageVirtualMeasurementCycleKey changed. When WebKit alternates
between two measured window-metric sets (A -> B -> A -> B) after
getBoundingClientRect(), each key transition renewed the two-render budget and
the requestAnimationFrame/geometry-measurement loop never terminated
(WebContent ~30% CPU for minutes after session recovery).

The MESSAGE_VIRTUAL_MEASUREMENT_MAX_RERENDERS budget now caps the whole
convergence burst globally; the cycle-key branch only records the new key.
The budget still resets at the correct lifecycle boundaries:
_markMessageVirtualMeasurementsSettled() and _clearMessageVirtualHeightCache().

Adds a deterministic node-based regression test driving the real ui.js
function with alternating keys ['A','B','A','B'] (before: 4 scheduled
re-renders; after: 2), plus a stable-key guard and a structural test.
2026-08-30 21:29:24 -03:00
webtecnica b36fbdd633 fix(regeneration): refuse bounded tail on in-tail duplicate keys + real WAL interleaving test (#6826 r5) 2026-08-26 00:24:07 +00:00
webtecnica 7abd4773ca fix(regeneration): snapshot reuses canonical projection+ordering; WAL data_version guard (#6826 r4) 2026-08-26 00:24:07 +00:00
webtecnica b33aff9d09 fix(regeneration): occurrence-count guard + single-snapshot (no TOCTOU) for bounded tail read (#6826 r3 round-3) 2026-08-26 00:24:07 +00:00
webtecnica 40ff74b6fc fix(regeneration): bounded tail read only when skipped state.db prefix is provably identical; compression-anchor coverage (#6826 r3) 2026-08-26 00:24:07 +00:00
webtecnica 3672e0038b fix(regeneration): bounded sidecar-anchored tail read instead of full transcript refetch (#6826) 2026-08-26 00:24:07 +00:00
webtecnica 8c9f505fab fix(ui): avoid full transcript fetch on regenerate (#6826) 2026-08-26 00:24:07 +00:00
webtecnica 91ceb60996 fix(sessions): dedup title on collision instead of leaving blank (#6964) 2026-08-12 18:35:09 -03:00
webtecnica a5b1898e20 fix(api): read memory flags from nested memory config section (#6498) 2026-08-05 22:23:55 -03:00
webtecnica ed09ee6930 fix(api): request-owned config snapshot for memory gates (#6498)
- _handle_memory_read: pass the request-owned get_config_snapshot() into
  _external_notes_sources_enabled() so no ambient/process-global config is
  re-read later in the handler (review requirement).
- tests: add cross-profile forced-reload regressions for both directions
  (A disabled/B enabled and inverse), covering memory_enabled and
  user_profile_enabled for read (no content/path/mtime) and write (403 +
  byte-identical file).
- tests: adapt _external_notes_sources_enabled mock arity in
  test_3866_project_context_memory_section.py to the new snapshot param.
2026-08-04 11:46:32 -03:00
webtecnica 6dd1fed8d3 fix(ui): guard all bare _autoScrollFollow reads (#6614) 2026-08-01 17:52:30 -03:00
webtecnica f4f9157f8d fix: guard _autoScrollFollow access against ReferenceError before settings init
Browser race in static/ui.js: _autoScrollFollow can be undefined when
scroll/scrollpin listeners fire before boot.js initializes settings.
Bare global reference (ReferenceError) occurs on the golden path.

Fix: wrap both consumer sites with a typeof guard that defaults to
true (matching the boot.js fallback) when the variable hasn't been set.

Closes #6606
2026-08-01 17:45:43 -03:00