Commit Graph

306 Commits

Author SHA1 Message Date
Peter Steinberger
d0948c33b5 fix: prevent cron and Workboard lifecycle races (#114744) 2026-07-27 17:55:17 -04:00
Peter Steinberger
428b5cc7df perf(board): stop reading widget html on board.get and calm the ticket poll (#114748) 2026-07-27 17:53:08 -04:00
Peter Steinberger
e57549c8b9 fix(ui): prevent stale and read-only Workboard dashboard actions (#114686)
* fix(ui): keep Workboard dashboard card actions connection-safe

* fix(ui): retain type-safe read-only widget enforcement
2026-07-27 14:56:08 -04:00
Peter Steinberger
e1e62f2a6e fix: prevent cron and Workboard lifecycle races (#114674)
* fix: harden cron and workboard lifecycle races

* fix: include missing app-server test in CI shard
2026-07-27 14:52:06 -04:00
Peter Steinberger
f0a963a2cb fix(ui): isolate case-sensitive channel sessions (#114638) 2026-07-27 12:55:36 -04:00
Peter Steinberger
ee9bc9a502 fix: harden cron and Workboard scheduling under load (#114564)
* fix: harden cron and Workboard scheduling under load

* test: refresh cron pagination prompt snapshots

* test: verify Workboard stress and generated cron snapshots

* fix(workboard): reclaim expired cross-board worker capacity safely

* fix(workboard): keep reclaim interval private to lifecycle owner

* fix(ci): pin and verify reliable OpenGrep security scanner

* test: guard checksum-verified OpenGrep installation
2026-07-27 09:29:42 -04:00
Peter Steinberger
8e848f457e fix: harden cron delivery and Workboard dispatch (#114499) 2026-07-27 07:03:41 -04:00
Peter Steinberger
c56689416e fix(ui): scope global route resolution by agent 2026-07-27 05:43:19 -04:00
Peter Steinberger
51f228f6fe fix(ui): preserve agent-owned session navigation
Add agent ownership to observer digests, scope global observer delivery and reconciliation, and centralize application session selection across navigation and reconnects.

Co-authored-by: xbrxr03 <abrarhabib03@gmail.com>
2026-07-27 05:43:19 -04:00
Peter Steinberger
2e9621018a fix(ui): keep derived session titles after sidebar mutations (#114474) 2026-07-27 05:36:56 -04:00
Peter Steinberger
a2ff6cb52b fix(ui): consistently load and cache user avatars (#114444)
* fix(ui): consistently load and cache user avatars

* fix(ui): retain avatar images across profile updates
2026-07-27 05:00:58 -04:00
Peter Steinberger
af552d4bc6 feat(ui): resolve session URLs through the gateway with best-effort slug matching (#114422)
* feat(ui): resolve session URLs through the gateway with best-effort slug matching

Session URLs now resolve a stored board face and a display-name slug through
the gateway instead of guessing from the browser's cached session page.

- An uncached open marks only the in-app navigation, never the shareable href,
  so the chat loader can re-derive the face and replace the URL.
- A cached row settles the lookup locally, keeping navigation off the wire.
- A one-segment reference resolves short id, then literal key, then display-name
  slug; ties reuse the existing disambiguation view.

* refactor(ui): keep slug ties on short disambiguation prefixes

Slug candidates reused the full uuid, which produced 32-character links in the
disambiguation view. uniqueShortIdPrefix already falls back to the full id when
the bounded search was truncated, so reuse it and drop the flag.

* fix(ui): navigate worktree owner links in-app so the face marker survives

The Worktrees owner link was a bare anchor consuming only the shareable href,
so preferenceDerivedFace was a no-op there and an uncached owner always opened
on chat. Match the other session surfaces: keep the clean href and navigate
with the options on plain clicks.

* fix(ui): search the first uuid block so long session references resolve

The gateway matches sessions.list search as a plain substring and stored keys
hold a hyphenated uuid, so any hyphen-stripped reference past the first block
matched nothing and 404'd. This hit disambiguation links, which offer prefixes
longer than eight characters. Send the first block and keep applying the full
prefix per row.

A resolved slug now canonicalizes to that same short reference instead of a
full uuid, so /chat/main/deploy-monitor settles on -6db92d48.

* docs(ui): record why the face marker stays out of shareable hrefs
2026-07-27 04:31:33 -04:00
Peter Lee
0406e66369 fix(ui): keep stable chat rows in insertion order and only sort live tool/stream items by timestamp (#113266)
* fix(ui): keep stable chat rows in insertion order and only sort live tool/stream items by timestamp

* fix(ui): keep live rows within current turn

* fix(ui): keep current work above queued turns

* fix(ui): keep streamed replies above queued turns

* fix(ui): preserve reconnecting run order

* fix(ui): preserve causal terminal ordering

* fix(ui): bound replay rows to owning turns

* fix(ui): keep question summaries in owning turns

* fix(ui): scope question run ownership to session

* fix(ui): restore reconnecting chat run identity

* fix(ui): remove unused chatItemTimestamp import in chat-thread-build.ts

* fix(ui): correlate question summaries with agent runs

Co-authored-by: Peter Lee <li.xialong@xydigit.com>

* chore(i18n): refresh native source baseline

Co-authored-by: Peter Lee <li.xialong@xydigit.com>

* fix(ui): remove unused chatItemTimestamp export and split tool-stream test file

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-27 02:08:22 -04:00
Peter Steinberger
256988e068 refactor(ui): consolidate workboard regression fixtures (#114348)
* refactor(ui): consolidate workboard regression fixtures

* test(ui): preserve nullable workboard refresh fixture
2026-07-27 01:48:05 -04:00
Peter Steinberger
e0eed257c8 fix(ui): persist Control UI settings across reconnects via gateway-owned LWW prefs writes (#114286)
* fix(ui): sync Control UI prefs via hash-free LWW config.patch

* test(ui): prove prefs reconnect replay end to end

* fix(gateway): surface hash-free prefs commit races instead of replaying stale intent

* fix(ui): never re-reconcile a retained config snapshot over acked prefs

* fix(ui): merge persisted pending prefs across tabs instead of clobbering

* test(ui): pass current runtime config through sidebar prefs reconcile fixture
2026-07-27 01:17:03 -04:00
Peter Steinberger
8b66fc103d feat(ui): durable session board face and dashboards index (#114262)
* feat(ui): durable session board face and dashboards index

Board face lived only in client-side boardSessionViews, capped at 50 entries,
so the preference never followed the user to another device, evicted as
sessions accumulated, and could not be seen as a set.

Persist it as SessionEntry.boardFace, which rides the existing entry_json blob
and so needs no SQLite schema change or version bump. Expose it on the session
list row and add it to the sessions.patch write-scope allowlist alongside label,
pinned, and archived: setting your own view preference is user-level chat
organization, not policy. Unknown patch fields still fail closed to
operator.admin.

Generic navigation now reads the stored face, so the sidebar and session list
open a thread on the face you left it on. boardSessionViews keeps only
activeTabId and reopenDockByTab, which are genuinely per-device.

Add /dashboards listing threads whose preferred face is dashboard. Filtering
runs server-side in filterSessionEntries before pagination, because the client
holds only a capped page and a client-side filter would silently omit
dashboards.

* test(protocol): assert the pre-rename face param is rejected

The gateway-protocol validator test still passed the pre-rename 'face' key,
which the closed schema rejects. Use boardFace, and pin the old name as a
negative case so it cannot silently return.

* chore(protocol): regenerate Swift bindings and docs map for boardFace

Adding boardFace to the sessions schema changes two committed generated
artifacts: the Swift gateway models (pnpm protocol:gen:swift) and the docs map
(pnpm docs:map:gen), which now lists the dashboards index section.
2026-07-27 00:35:34 -04:00
ClawSweeper
ca11ee6db1 fix(ui): new worktree sessions stay in Coding during creation (#113834)
* fix(ui): preserve new coding session selection

* test(ui): cover prepared sidebar sessions

* fix(ui): retire prepared work sessions on delete

* test(ui): refresh prepared sidebar mock for the configured main key

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 22:18:07 -04:00
Jesse Merhi
d7627d6f4c refactor(prompt): use plain inbound context labels and drop system-tag sanitizer (#112000)
* refactor(prompt): plain inbound context labels with a provenance marker

Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.

Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.

Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.

Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.

* fix(ci): resolve gate failures for plain inbound context labels

- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
  so the Kysely connection-boundary guardrail holds; unexport the now-internal
  transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
  plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
  the 700-line oxlint cap; suppressions are disallowed, so follow the existing
  sibling record-module pattern. Public exports and PluginCompatCode literals
  unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
  only). The bracket de-fang and System: rewrite it expected were removed with
  sanitizeInboundSystemTags; forged system lines are neutralized at the
  system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.

* fix(prompt): harden inbound context label migration and drop in-band sanitizer

Review follow-ups on the plain-label + provenance-marker change:

- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
  look-alike `System:`/`[System]` markers corrupted legitimate user text and is
  not a real injection boundary; role separation plus external-content wrapping
  is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
  now requires a complete known legacy sentinel line, a legacy label followed by
  a fenced JSON body, or the complete legacy external-content header. The prior
  predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
  deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
  alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
  header's line break, migrated assistant rows skip newline normalization, and
  without it the marked-header replace wins and the body strips to empty. Added
  a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
  prose-block strip path.

Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
2026-07-27 11:27:53 +10:00
Patrick Erichsen
c66ca2fbb2 feat: show ClawHub skill icons in Control UI (#114152) 2026-07-26 14:44:36 -05:00
Peter Steinberger
cca5b14785 feat(ui): path-based session and dashboard URLs (#113883)
* feat(ui): path-based session and dashboard URLs

* docs(ui): document control UI URLs

* fix(ui): finalize session path routing

* feat(ui): anchor session URLs on stable keys

* docs(ui): clarify stable session URL identity

* fix(ui): resolve session prefixes with full prefix and pagination

Query sessions.list with the full supplied prefix instead of an eight-char
truncation, and paginate up to a bounded page count so longer disambiguation
links resolve instead of being reported ambiguous. Zero strict-prefix matches
now fall through to literal-key resolution rather than rendering an empty
ambiguity view.

Also document the ~dot/~dotdot segment escape: peer ids reach session keys
trimmed and lowercased only (src/routing/session-key.ts), so a literal '.' or
'..' segment is reachable and browsers would normalize it away.

* fix(ui): synchronize committed session routes

* test(ui): split native shell host coverage

* fix(ui): thread configured mainKey through session URL builders

Reserved-set disambiguation needs the operator-configured mainKey at runtime, so
thread it from agentsList through every session path builder and the ClickClack
control URL. Unambiguous non-hex single-segment rests now resolve literally while
short-id-shaped rests still fail closed, which restores ClickClack channel
compatibility detection and control-link reconciliation.

Also bound prefix-resolution retries, preserve catalog thread identity, and keep
draft state on ambiguous candidate links.

* fix(ui): repair session URL CI integration

* perf(ui): lazy-load session route resolution

* perf(ui): isolate session prefix resolution

* perf(ui): defer session path parsing

* perf(ui): defer session navigation startup

* fix(ui): preserve first-run and literal session navigation

* fix(ui): satisfy session routing type and export gates

* fix(clickclack): preserve unscoped control link agent

* style(ui): satisfy chat page line limit

* refactor(ui): move chat page helpers to owning modules

* fix(ui): preserve destination session route identity

* fix(ui): preserve agent identity in session routes

* fix(ui): escape dots in literal session path segments

encodeURIComponent leaves periods intact, so a literal key segment like
channel:release.js produced /chat/main/channel/release.js. In-app navigation is
intercepted by the SPA, but a refresh, an external link, or a ClickClack link
would be served as a static asset request and never reach the app.

pathForWorkboardBoard already escapes dots for this reason; mirror it in both the
session URL contract and the ClickClack encoder, route the agent id through the
same segment encoder, and pin the case in both shared vector tables.

* fix(ui): stop bootstrap after teardown race

* refactor(ui): centralize session navigation targets

* fix(ui): consume bootstrap teardown abort

* fix(ui): canonicalize configured main session routes

* fix(ui): preserve distinct session references

* fix(ui): redirect released session query links

* fix(ui): make bootstrap teardown abort-safe
2026-07-26 12:46:04 -04:00
Peter Steinberger
08a2f51e4e fix(ui): show the correct main-session title for every agent (#114120) 2026-07-26 10:59:14 -04:00
Peter Steinberger
201e02f226 fix(ui): preserve concurrent background task progress (#114085)
* fix(ui): preserve concurrent background task progress

* fix(ui): retain concurrent task events across stale snapshots

* fix(ui): preserve chronological task snapshot progress

* fix(ui): type buffered task event replay
2026-07-26 08:03:18 -04:00
Peter Steinberger
cd5a5ecb4c fix(ui,gateway): make catalog sidebar sections drag-reorderable (#114074)
* fix(ui,gateway): make catalog sidebar sections drag-reorderable

* test(ui): split sidebar section-reordering cases under max-lines

* perf(ui): keep catalog section rendering out of startup bundle
2026-07-26 05:31:33 -04:00
Peter Steinberger
2de58d408a Control UI: gateway-owned sidebar section order (#113930)
* feat(ui): gateway-owned sidebar section order with hover-only drag grips

* fix(ui): repair sidebar section order CI gates

* fix(protocol): keep sectionOrder optional for older gateways

Native Swift clients ship separately from gateways, so new clients must decode older gateway responses that omit sectionOrder.

* fix(ui): reconcile gateway section ordering with main

* fix(state): allow lazy tables in v5 maintenance

* refactor(ui): retire prefs session section order in favor of gateway-owned order

Supersedes #113948 by deleting the unreleased ui.prefs.sessionSectionOrder key and its browser plumbing. Gateway SQLite sidebar_sections remains the single canonical store.
2026-07-26 01:15:19 -04:00
Peter Steinberger
36404eaa3b fix(ui): dashboard widgets leave a gap below content and hover chrome blocks widget buttons (#114012)
* fix(ui): hug auto widget cards to content and compact board chrome into a top-left pill

* test(gateway): retry temp-dir cleanup in chat abort persistence suite to absorb ENOTEMPTY race

* fix(ui): strip board pill to move and menu on narrow widgets so the action corner stays widget-owned
2026-07-26 01:14:40 -04:00
Peter Steinberger
852a686a1b feat(chat): restore prompt image attachments on rewind/fork (#113945)
* feat(chat): restore prompt image attachments on rewind/fork

* chore(i18n): refresh native source inventory anchors

* fix(gateway): restore media-store images on rewind/fork

* fix(gateway): bound and dedupe rewind media reads

* fix(sessions): bound inline editor attachments in message cut

* fix(ui): align restored attachment cap with gateway 5 MiB limit

* fix(gateway): dedupe rewind media refs by resolved id

* fix(sessions): lint-safe __openclaw access in message cut
2026-07-25 20:32:16 -07:00
Peter Steinberger
71cb266c1f fix(ui): keep Coding sessions visible after reconnect (#113981)
* fix(ui): preserve scoped session hydration

* chore: leave changelog to release automation

* build(ui): refresh startup budget baseline
2026-07-25 19:56:50 -07:00
Peter Steinberger
f90cef67c8 feat(ui): drag custom sidebar groups between built-in session zones (#113948)
* feat(ui): drag custom sidebar groups between built-in session zones

* test(ui): remove stale split ratio fixture

* perf(ui): raise startup budget baseline for session-section ordering pref
2026-07-25 19:11:25 -07:00
Peter Steinberger
a328e900fd fix(ui): keep sidebar selection on archived sessions and replace the composer with an archived notice (#113882)
* fix(ui): keep sidebar selection on archived sessions and replace composer with archived notice

* fix(ui): narrow lineage-row lookup and restructure fallback projection scan

* fix(ui): publish routed lineage row so cold-loaded archived sessions replace the composer

* chore(ui): drop unused mergeChildSessionRows export
2026-07-25 19:07:25 -07:00
Peter Steinberger
360fe2317c feat(ui): person grouping and people filter in the sidebar catalog view menu (#113943) 2026-07-25 18:17:12 -07:00
Peter Steinberger
fb26903d6c feat: add flexible multi-sidebar chat layout (#113712)
* feat(ui): add persisted sidebar layout model

* feat(ui): render flexible chat sidebar columns

* test(ui): align board sidebar fixture type

* fix(ui): preserve sidebar panels across responsive layout

* fix(ui): satisfy sidebar CI ownership and performance

* test(ui): derive discussion helpers from panel config

* test(ui): mount session rail through its registry

* fix(ui): preserve sidebar state across projections

* style(ui): format sidebar state fixes

* fix(ui): satisfy sidebar lint constraints

* refactor(ui): break sidebar layout import cycle

* fix(ui): stabilize sidebar panel rendering

* fix(ui): keep the narrow sidebar grid off for an empty layout

The two-row narrow grid reserved a panel row even with no sidebar panel open, halving the primary surface height on every default mobile chat pane.

* fix(ui): lazy-load chat sidebar region

* style(ui): format rebased chat state page

* chore(ui): raise sidebar startup baseline

* fix(ui): preserve sidebar move and resize state

* chore(ui): align sidebar startup baseline

* chore(ui): refresh sidebar startup baseline

* test(ui): register discussion element in isolated test

* fix(ui): persist the dragged panel as the collapsed active panel

Drag moves activated the panel in its destination column but left the separate persisted collapsed-mode selection stale, so the narrow layout foregrounded the wrong panel after a move and the stale choice survived reload.

* fix(ui): preserve resolved canvas URL in detail panel
2026-07-25 17:45:29 -07:00
Peter Steinberger
52c11daed6 fix(control-ui): renew canvas capability so agent widgets keep rendering (#113702)
* fix(control-ui): renew canvas plugin surface capability so widgets keep loading

* fix(control-ui): scope canvas capability renewal to connections

* test(control-ui): cover rotated capability on a newly mounted widget

* fix(control-ui): bind canvas URLs to mounted frames

* fix(control-ui): keep reported widget heights across capability rotation

* fix(control-ui): keep canvas lease type private

* test(gateway): prove Canvas capability rotation over HTTP

* perf(control-ui): lazy-load canvas surface renewal

* chore(control-ui): raise startup budget baseline for canvas renewal lease

* fix(control-ui): satisfy promise-executor lint in gateway store test
2026-07-25 16:14:20 -07:00
Peter Steinberger
81d9d1a1f4 feat: announce critical session health beyond the rail and teach companion delegation (#113819)
* feat(agents): teach Control UI companion usage

* feat(gateway): announce critical observer health

* test(ui): cover critical observer notice flow

* fix(ui): wire critical notice strings to real i18n keys

* fix(ui): complete session row literal in notice test

* perf(ui): lazy-load critical observer notice off the startup chunk

* test(ui): capture post-navigation e2e artifact
2026-07-25 14:46:12 -07:00
Peter Steinberger
eb555bd033 fix(ui): respect agent-owned model fallbacks (#113812)
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
2026-07-25 12:45:30 -07:00
Peter Steinberger
6277c35d9a fix(ui): restore one-of skill binary setup and installation (#113767)
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
2026-07-25 11:01:25 -07:00
Peter Steinberger
1a2252e1a7 fix(ui): keep cron history matched to the latest request (#113765)
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
2026-07-25 10:30:32 -07:00
Peter Steinberger
a95486cefc feat: session rail with read-only session companion in Control UI chat (#113698)
* feat(protocol): add session companion schemas

* feat(gateway): add session companion service

* feat(gateway): expose session companion rpc

* fix(gateway): harden companion runtime limits

* test(gateway): fix companion type assertions

* test(gateway): align companion test target

* feat(ui): replace observer HUD and side chat with session rail

* docs: session rail companion for control ui

* test(ui): align session rail client mock

* fix(ui): drop stale chat search import

* fix: satisfy session companion lint contracts

* refactor(gateway): isolate companion state contract

* fix(ui): keep session rail reachable while idle

* fix(agents): clamp derived openai prompt cache keys at boundary

* fix(ai): clamp chatgpt responses session_id affinity header

* fix(ci): align session companion branch gates

* fix(ui): require run id for sessionless terminal chat events

* fix(agents): scope internal run events to transcript

* chore: revert changelog edit (release generation owns changelog)
2026-07-25 08:14:32 -07:00
Peter Steinberger
3c5415805c refactor(ui): mechanical dedup batch (#113649)
* refactor(ui): share sidebar menu contracts

* refactor(ui): share segmented controls

* refactor(ui): deduplicate slash menu keys

* refactor(ui): share persisted set storage

* refactor(ui): centralize fnv hashing

* refactor(ui): reuse workboard helpers

* refactor(ui): remove stale terminal style

* fix(ui): align sidebar menu callback type
2026-07-25 05:37:57 -07:00
Peter Steinberger
fde3cdf27b perf: multi-select archiving no longer stalls a second per row (#113623)
* perf(ui): refresh the session list once per batch action

Batch sidebar actions patched each row and then forced a full sessions.list
replacement per row, so archiving a nine-row multi-select paid nine list
rebuilds on top of nine patches. Measured against the gateway handlers on a
60-session store, sessions.list costs ~2.9s versus ~176ms for sessions.patch.

Batch rows now pass deferListRefresh and each batch helper issues one refresh
per owning agent after its last row; pushed sessions.changed events keep rows
reconciled while the batch runs. Single-row actions are unchanged.

* test(ui): prove batch archive costs one list refresh in the browser

Adds a Control UI e2e case that cmd-clicks three sidebar rows, archives the
selection from the batch menu, and asserts the Gateway saw three sessions.patch
calls in row order plus exactly one sessions.list. It holds past the batch so a
late per-row refresh would still be caught.

Before the batch-refresh change this flow issued one full sessions.list per
archived row.
2026-07-25 05:06:07 -07:00
Peter Steinberger
421e287c45 fix(ui): show Swarm progress in Chat (#113619)
* fix(ui): show Swarm progress in chat

* style(ui): format rebased Swarm integration

* fix(ui): bound inline Swarm progress height
2026-07-25 04:36:50 -07:00
Peter Steinberger
3018e8b78a refactor: deduplicate micro helpers onto canonical homes (#113534)
* refactor: deduplicate micro helpers

* fix(ai): keep hash prefix helper internal
2026-07-25 00:41:01 -07:00
Peter Steinberger
7eb9fa3389 refactor(media): read persisted media facts first across all consumers (#113496)
* refactor(media): read persisted media facts first across all consumers

PR 2 of the media legacy retirement program. Gateway display projection,
Control UI (history filtering, attachment rendering, initial-turn
handoff), embedded and plugin-harness hydration, replay/prune, late-media
reconstruction, hooks, media understanding, and trajectory capture now
read nested __openclaw.media facts first. Legacy top-level Media* fields
survive only in the narrow persisted-message compatibility reader (rows
predating the PR-1 dual-write) and the public SDK projection.

Non-goals pinned by tests: rendered batches, reply-payload dedupe,
durable session/outbound queues, and lowercase ReplyPayload.mediaUrl(s)
are untouched contracts.

Consumer matrix covers {legacy-only, facts-only, both-equal,
both-conflict, sparse, type-only, media-only} inputs across every
consumer family; prompt/cache goldens byte-identical; 131-file legacy
consumer sweep run individually (two failures reproduce unchanged on
clean main); 1,047 broad media tests and the full UI lane (5,553) green.

* refactor(media): drop caller-less staging predicate and localize empty-user-row classification

The consumer flip left hasStagedMediaProjection without production
callers (facts-only staging uses hasStagedMediaFacts); delete it and
retest the staged merge matrix against the facts predicate. Unexport
LEGACY_MEDIA_CONTEXT_KEYS (internal only; the derived type stays public).
Move isEmptyUserTextOnlyMessage beside the other message-shape
classifiers in ui/src/lib/chat/message-extract.ts where chat-history
imports it cross-module.

* chore(ui): drop unused hasTranscriptMediaFacts import after predicate move

* chore(ui): localize hasTranscriptMediaFacts after its last external consumer moved in

* style: format staged-media matrix assertion
2026-07-24 23:02:53 -07:00
Peter Steinberger
013b24dd6d feat(sessions): archive attribution (archivedBy) (#113116)
* feat(sessions): attribute archive transitions

* docs: add archive attribution report

* fix(sessions): restore alias layout after archive audit failure

* docs: update archive attribution report

* fix(sessions): isolate archive audit rollback

* docs: record final archive rollback design

* fix(sessions): make archive audit note best-effort

* docs: record best-effort archive audit policy

* chore: refresh native i18n baseline
2026-07-23 11:23:30 -07:00
Jason (Json)
2f5e7cfc5d fix(ui): prevent duplicate image prompt and flicker (#113113)
* fix(ui): stabilize initial image prompt handoff

* chore(protocol): refresh sessions create model

* chore(i18n): refresh native source inventory
2026-07-23 12:11:52 -06:00
Peter Steinberger
09672312c4 feat(gateway): incognito sessions for the web Control UI (#113006)
* feat(gateway): add web-only incognito sessions held in process memory

* feat(ui): add incognito toggle and badges to the web new-session flow

* fix(sessions): classify incognito by key shape, fail closed on stale keys, and gate memory writes

* fix(codex): start harness threads ephemeral for incognito sessions

* fix(sessions): reshape internal-effects incognito keys and add doctor repair for reserved key collisions

* refactor(plugin-sdk): export canonical incognito key classifier and guard the sentinel path

* fix(state): classify incognito DB handles from the recorded open-time set

* fix(gateway): isolate incognito sessions from durable lineage and allocation on read-only misses

* docs(sessions): pin the reserved incognito namespace ownership decision

* feat(gateway): admin-scope incognito visibility and incognito-blind cross-session surfaces

* fix(ci): repair kysely guardrails, dead export, docs map, protocol bindings, and ACP reset rotation

* fix(gateway): remove non-admin observability side channels for incognito sessions

* fix(gateway): enforce admin-scope incognito access and cover all parent-reference creation paths
2026-07-23 09:04:36 -07:00
Peter Steinberger
29d5dcfac6 feat(ui): restructure chat transcript for multi-user sessions (#112938)
* feat(ui): restructure chat transcript for multi-user sessions

Viewer-relative alignment: attributed messages from other participants
(senderId != viewer) render left-aligned as peers with their avatar,
name, and identity tint; only the viewer's own messages stay right-aligned.
System-role transcript entries (e.g. local command output) now render as
centered notice rows instead of a pseudo-participant with a question-mark
avatar. In threads with 2+ attributed senders, assistant replies carry a
'Replying to <name>' attribution chip derived from the preceding attributed
user turn; unattributed turns clear the attribution rather than mislabeling.
Also drops redundant role lowercasing on already-normalized roles.

* docs(web): describe multi-user chat transcript layout

* docs(web): refresh chat transcript docs map
2026-07-23 07:46:34 -07:00
Peter Steinberger
1e3192c0b4 refactor(ui): model gateway connection state as one closed phase union (#112909)
* refactor(ui): model gateway connection state as one closed phase union

ApplicationGatewaySnapshot carried connected + reconnecting as parallel
booleans whose illegal combination (both true) was representable, and
every consumer re-derived the same flag logic. The snapshot now carries
phase: "stopped" | "connecting" | "connected" | "reconnecting" |
"offline"; offlineStable stays as the store-owned 2s debounced
presentation boolean, and lastError/lastErrorCode are unchanged.

Mapping preserves shipped behavior exactly: never-connected terminal
closes return to "stopped" (login gate), established-connection drops
with retry are "reconnecting" (shell stays mounted), without retry
"offline". Component props stay boolean, derived once at the app-host
boundary; no compat aliases remain on the snapshot.

Closes #112741.

* fix(ui): migrate rebased-in test fixtures to the gateway phase union

* fix(ui): migrate post-rebase gateway snapshot readers to the phase union

* fix(ui): migrate document-title test harness snapshot to the phase union
2026-07-23 07:00:26 -07:00
Peter Steinberger
ad505a7b55 fix(swarm): keep collector results reliable through races and restarts (#112989)
* fix(swarm): harden collector lifecycle and dashboards

* fix(swarm): initialize collector completion state

* test(swarm): satisfy cross-environment type checks

* test(codex): allow direct request handler calls

* style(ui): avoid Swarm widget shadowing

* test(swarm): keep internal helpers private

* refactor(ui): own Swarm roster helpers in runtime
2026-07-23 06:26:31 -07:00
Peter Steinberger
8a3e2cff73 fix(mcp): harden concurrent writes and diagnostics (#113026) 2026-07-23 09:04:30 -04:00
Peter Steinberger
89cfea034a refactor(ui): consolidate workboard controller tests (#112897)
* test(ui): consolidate workboard fixtures

* test(ui): classify workboard fixtures as test-only
2026-07-23 01:25:18 -04:00