Commit Graph
20 Commits
Author SHA1 Message Date
webtecnica 4b791bd820 fix(stream): require a current live assistant row to seed the replay floor
The replay-authority predicate accepted any non-empty assistant message,
but a new turn copies the full transcript into INFLIGHT.messages. In an
established conversation that content is the previous turn's reply, so
`canSeed` came back true with both stale cursor halves preserved and the
server kept skipping the range that rebuilds the body.

Message-based evidence now counts only from a current `_live` assistant
positioned after the latest user boundary; a historical assistant row
fails closed to floor 0 and an empty event id.

Refs #7640 (blocker on #7651)
2026-09-23 09:29:00 -03:00
webtecnicaandwebtecnica 68008e824b fix: classify Signal gateway sessions as messaging (#7572)
Classify Signal gateway sessions as messaging so they group, label, and list like other gateway platforms.

Co-authored-by: webtecnica <webtecnica@gmail.com>
2026-09-15 16:16:57 -07:00
webtecnicaandnesquena-hermes 65c185478c fix(ui): suppress false loopback-only warning when public URL is configured (#6573)
* fix(ui): suppress false loopback-only warning when public URL is configured

When a user configures status.browser_url (e.g., via a reverse proxy
at https://dashboard.example.com), the Dashboard link correctly opens
that public URL, but _applyDashboardStatus() still displayed the
'loopback-only' warning because it only checked the browser's hostname.

Fix: suppress the warning when status.browser_url is present — the
user has explicitly configured a public URL, so the loopback bind
is intentional and the warning is a false positive.

Closes #6459

* fix(ui): base loopback warning on resolved dashboard target hostname (#6573)

* fix(ui): complete loopback classifier for dashboard warning (#6573)

---------

Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-08-20 21:32:08 -07:00
webtecnica 20123b1268 fix(skills): decode JSON-array string skills.disabled in panel read and toggle write (#7120) (#7134) 2026-08-18 19:40:04 -07:00
37c59baf68 fix(ui): prevent OOM on tab focus for long sessions (#7006)
* fix(ui): prevent OOM on tab focus for long sessions

Coordinate visibility-recovery and session-updated refresh paths so they
never start concurrent full-transcript loads, window the O(N) pre-render
scans to the virtual render window, and bound the render-window expansion.

Closes #6999

* fix(ui): full turn context + collision-free cache hash (#7006 review rework)

Addresses the maintainer's CHANGES_REQUESTED on PR #7006:

- Turn-context maps (_assistantTurnFinalVisibleContentMap /
  _assistantTurnVisibleContentMap) receive the FULL visWithIdx again.
  The virtualized render window (head+tail around a gap) cut through
  assistant runs and merged distinct turns across an omitted user
  boundary, which could duplicate the final answer in Worklog/Thinking
  or feed later-turn prose into the reasoning echo-strip. Turn context
  is input, not output: it must always be complete.

- _addBoundedHash now streams the FULL string form through the FNV-1a
  loop (no length+head+tail clip, no slice copies), and object fields
  (attachments, tc.args, compression-anchor keys) are walked key-by-key
  so no integral JSON.stringify() is allocated. Same-length middle-only
  edits of tool args, attachment metadata, tool snippets, or
  compression-anchor keys no longer collide in _sessionHtmlCache.

Regression tests:
- tests/test_issue6999_turn_context_virtual_gap.py: multi-assistant turn
  with a virtualized gap proves full-context echo-strip suppresses the
  exact final-answer echo, and documents the gapped head+tail failure.
- tests/test_issue2613_render_cache_signature.py: Node harness executing
  the real _messageRenderCacheSignature() proves same-length middle-only
  mutations change the signature for every structured field while the
  old clip scheme collides on the same data.

Closes #6999

* fix(ui): coalesce session-updated under refresh guard; insertion-order cache hash

Re-gate round 2 for #6999:
- session-updated frames arriving while the external-refresh guard is held
  are latched per SID (max announced count) and drained by the refresh
  owner's finally with ONE guarded follow-up, instead of being dropped
  (production does not guarantee a second event).
- _hashObjectInto walks keys in insertion order (matching Object.entries
  render projection) with an explicit per-key index discriminator, so
  opposite-insertion-order argument objects no longer share a cache
  signature; recursion depth is now threaded (children increment, not
  restart).

* test(jump-scroll): stub _loadingSessionId + _drainSessionUpdatedPendingCount in extraction harness

#7006 adds a reference to the pre-existing module var _loadingSessionId and a new
_drainSessionUpdatedPendingCount() helper inside refreshActiveSessionIfExternallyUpdated.
The jump-to-answer scroll-settle tests extract that function into a node harness, which
didn't declare them -> ReferenceError (19 parametrized failures). Add the missing stubs
to the harness preambles (no-op drain, since these tests exercise scroll ownership, not
the coalesce path -- real drain behavior is covered by test_issue6999_session_updated_coalesce.py).
Test-harness-only; production static/ untouched.

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-08-14 19:42:15 -07:00
webtecnicaandnesquena-hermes 3d18a8ca5a i18n: translate workspace-sort keys in 14 non-English locales (#6902) (#6910)
Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-08-13 22:39:25 -07:00
6261b626a7 fix(streaming): scope streaming message rendering per-session to prevent cross-session render leak (#6449) (#6502)
When the user switches from a streaming session (A) to another session (B),
A's streaming text could render in B's window because:

1. The token handler guards against rendering when the session is not active,
   but the rAF/setTimeout window between _scheduleRender() and _doRender()
   can outlive a session switch.
2. _doRender() writes to assistantBody without checking whether the stream's
   session is still the active frontend pane.

Fix: add _isActiveSession() guards to:
- _scheduleRender() — drop scheduled renders when the session changed
- _doRender() — bail before any DOM write if the session switched
- _flushPendingSegmentRender() — defense-in-depth for any future callers

These guard at the same level the token/interim_assistant event handlers
already do, closing the rAF gap that could leak streamed text into a
different session's view.

Co-authored-by: webtecnica <contato@webtecnica.com.br>
Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-08-13 22:02:33 -07:00
webtecnicaandnesquena-hermes 7a5e6bc27a fix(providers): reduce pool-exhaustion TTL for HTTP 402 from 1h to 2min (#6626)
* fix(providers): reduce pool-exhaustion TTL for HTTP 402 from 1h to 2min

Mirrors the agent-side fix: when a provider returns HTTP 402 (Payment
Required), the credential pool marks the key as exhausted with a 1-hour
TTL — even though recharging credits restores the key immediately.

Both copies of _entry_exhausted_ttl_seconds() now handle HTTP 402 with
a 2-minute TTL, matching the new EXHAUSTED_TTL_402_SECONDS constant in
agent/credential_pool.py.

* fix(providers): tie HTTP 402 TTL to installed runtime contract (#6626)

* test(providers): behavior-test 402 TTL 119/120s boundary on both helper copies (#6626)

Add executed boundary tests for the HTTP 402 pool-exhaustion TTL:

- Host helper: with the runtime TTL monkeypatched to 120, an exhausted
  entry is still unavailable at 119s and becomes available at 120s, for
  both the integer 402 and the persisted string "402" error codes.
- Embedded _ACCOUNT_USAGE_SUBPROCESS_CODE copy: execute the subprocess
  code under the existing quota-subprocess harness and assert the same
  TTL and 119/120s eligibility boundary against the embedded helper,
  instead of counting source-string occurrences.
- Keep the legacy 3600s fallback and last_error_reset_at precedence
  assertions.

* test(providers): make 402 TTL parity tests self-contained for standalone CI

The parity tests imported agent.credential_pool directly, but the WebUI
test suite runs standalone (Hermes Agent runtime is not installed in CI),
causing ModuleNotFoundError on every shard since 08-02. Simulate the
runtime contract with a fake agent.credential_pool module (same pattern
already used by the embedded-subprocess helper test) so both the 120s
cooldown and the legacy 1h fallback paths are covered.

---------

Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-08-13 20:15:30 -07:00
fa087c53d1 fix(cron): mark running cron sessions as active in sidebar (#6734)
* fix(cron): mark running cron sessions as active in sidebar

* fix(cron): include cron_running in effective-streaming predicate + test updates (#6734)

* fix(cron): longest-prefix-first cron job matching in session list cache (#6728)

* fix(cron): require exact run-timestamp suffix in cron_running prefix match

Gate re-review (Codex) found the longest-prefix-first match could misattribute:
cron_job_prefixes is built ONLY from RUNNING jobs, so when the true longer owner
(backup_full) is NOT running, a running shorter job (backup) swallowed a completed
cron_backup_full_YYYYMMDD_HHMMSS session (its prefix cron_backup_ still matched),
deferring that row's completion/unread transition. Now the text after a matched
cron_{jid}_ prefix must be EXACTLY a run timestamp (\d{8}_\d{6}), so a shorter job
id can't claim a longer job id's session regardless of which jobs are running.
Regression test added (fails without the guard).

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>

---------

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-08-09 02:40:05 -07:00
webtecnicaandwebtecnica 064e95a38a fix(streaming): reject obsolete cancel finalizer after successor completes (#6636)
Prevents a delayed cancelled streaming worker from writing back stale output over a completed successor's result, and clears the Gateway writeback-owner entry on teardown/pre-start-cancel to avoid a slow SESSION_WRITEBACK_OWNERS leak.

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-08-09 01:10:49 +00:00
webtecnicaandnesquena-hermes b284371bf2 fix(notifications): new toast on same-session turns (#6673) (#6681)
Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-08-08 22:00:18 +00:00
webtecnica 02b5f885c8 fix(share): harden _embed_share_media with path allow-list + mime validation (#6174)
Embed local MEDIA: image refs as inline base64 at snapshot-build time,
gated by a workspace/attachment-root allow-list, 512KiB cap, image-MIME
allow-list, and magic-byte content validation. Everything else (traversal,
symlink-escape, file://, non-image, SVG, oversize) becomes a static
placeholder. Closes P0 #6174 (public shares no longer emit authenticated
/api/media requests for local paths).

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-23 01:21:59 +00:00
webtecnica 700bd976c1 fix(media): narrow MIME_MAP to only .ts/.tsx text/plain, add regression tests
- Add only .ts→text/plain and .tsx→text/plain to MIME_MAP (exclude .js,
  .jsx, .css and other unsafe mappings that would make workspace files
  executable as same-origin scripts)
- Add test_ts_artifact_served_as_text_plain_with_attachment — asserts
  Content-Type: text/plain, Content-Disposition: attachment, and
  X-Content-Type-Options: nosniff for .ts files via /api/media
- Add test_tsx_artifact_served_as_text_plain_with_attachment — same for .tsx
- Add test_file_raw_js_not_served_as_inline_executable — proves that a
  workspace .js file falls through to application/octet-stream instead of
  text/javascript via /api/file/raw

Closes #6372
2026-07-21 20:02:57 -03:00
webtecnica f35081683a fix tests for #6385 scroll snapshot: update #4970 assertion + add behavioral coverage
- Fix test_stream_done_runs_scroll_preserving_collapse_pass_after_disarm:
  assert _doneLiveScrollSnapshot is captured before arm, and the
  argument-bearing _renderMessagesWithScrollSnapshot({_prescrollSnapshot:_doneLiveScrollSnapshot})
  call is used after disarm (the no-arg literal no longer exists).
- Add test_prescroll_snapshot_bypasses_capture_no_option_fallback_still_captures:
  Node.js behavioral harness that supplies a sentinel _prescrollSnapshot,
  stubs _captureMessageScrollSnapshot with a counter, and proves:
    1) sentinel reaches _restoreMessageScrollSnapshotSameFrame without a capture
    2) no-option / empty-{} calls still capture normally (callers at
       static/ui.js:9565, 14416, 14424).
2026-07-21 19:55:00 -03:00
webtecnica 25d39da00e fix(stream): preserve reader viewport anchor through live-to-settlement collapse
Issue #6385: when a streaming turn settles, the two-render sequence
(keep-open expanded worklog → collapsed worklog) could displace the
reader's viewport because the second render captured its scroll snapshot
from the intermediate expanded state, not from the original live DOM.

Root cause
----------
The STREAM_DONE handler in messages.js:

1. Arms keep-settled-worklog-open token → renderMessages({preserveScroll:true})
   → worklog rendered EXPANDED (height-stable swap preventing shrink jump)

2. Disarms token → _renderMessagesWithScrollSnapshot()
   → This function called _captureMessageScrollSnapshot() which captured
     the scroll anchor from the expanded-worklog DOM (step 1 output),
     then called renderMessages with the worklog COLLAPSED (keep-open gone),
     then tried to restore from the expanded-state snapshot.

   The snapshot's semantic anchor (row key, session idx, top offset) was
   captured from a DOM where the worklog was expanded. After the collapse
   render the worklog is no longer at that position — anchor keys don't
   match, the semantic restore fails, and the viewport jumps to a
   unrelated scrollTop.

Fix
---
- Capture the scroll snapshot from the LIVE DOM (before any settlement
  renders) and pass it as  to the second render.
- Modify _renderMessagesWithScrollSnapshot() to accept a pre-captured
  snapshot via options._prescrollSnapshot, using it instead of capturing
  a fresh one from the current DOM state.

This ensures the collapse render anchors to the content the reader was
actually viewing, not to a stale intermediate expanded-worklog position.

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-21 02:12:06 -03:00
webtecnica c103ab4d32 Add #6247: doc-ref the proposal and merged implementation
Refs #6247 — the public conversation lifecycle regression gate proposal.
Refs #6251 — the merged first implementation slice (normal Live-to-Final row).
2026-07-19 10:31:12 -03:00
webtecnicaandwebtecnica 0ef23c9c5e fix(models): prevent bare-id picker revert when provider hint is empty (#6195) (#6199)
Prevent bare-id model picker revert when the provider hint is empty: an ambiguous bare id that collides across provider groups no longer snaps to the default group on re-render. Adds a revert-sensitive regression test and fixes three cross-file test-isolation leaks found while gating. Thanks @webtecnica.

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-18 16:55:43 -07:00
webtecnica 9ac68feecb fix: stream event timestamps layout on narrow foldable screens (#6099) 2026-07-15 18:31:51 -03:00
webtecnica 2143563109 fix: ensure approval/clarify popups have min-width on narrow screens (#6094) 2026-07-15 18:30:54 -03:00
webtecnica 40d5e17006 fix: allow manual force-check even when auto-update is disabled (#6082) 2026-07-15 12:18:04 -03:00