Skip to content

fix(frontend): repair tsc build break on main (pr_link type + stray Chat onRename)#6

Closed
liorp wants to merge 85 commits into
ofekron:mainfrom
liorp:fix/frontend-build-pr-link-onrename
Closed

fix(frontend): repair tsc build break on main (pr_link type + stray Chat onRename)#6
liorp wants to merge 85 commits into
ofekron:mainfrom
liorp:fix/frontend-build-pr-link-onrename

Conversation

@liorp

@liorp liorp commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Problem

main does not build. npm run build (tsc -b, run by ./run.sh) fails:

src/components/MessageBubble.tsx(1074,10): error TS2678: Type '"pr_link"' is not comparable to type 'WSEventType'.
src/App.tsx(5871,15): error TS2322: Type '{ ...; machines: NodeSnapshot[]; }' is not assignable to type 'IntrinsicAttributes & Props'.

Because tsc fails, vite build never runs, so there's no frontend/dist, and the backend then crashes on startup:

RuntimeError: frontend dist directory not found at .../frontend/dist

Fixes

  1. pr_link missing from WSEventTypeMessageBubble.tsx has a case "pr_link" rendering PrLinkEvent, but "pr_link" was never added to the WSEventType union in types.ts. Added it.
  2. Stray onRename on <Chat>App.tsx passed onRename={renameSession} to <Chat>, but Chat's Props declares no onRename and the component never reads it (rename is handled by <SessionList>, which keeps its onRename). The excess prop broke assignment. Removed the stray prop — no behavior change.

Verification

  • npm run build passes.
  • ./run.sh reaches Backend is healthy. and /healthz → 200.

🤖 Generated with Claude Code

ofekron and others added 30 commits June 24, 2026 18:59
Derived from internal main @ d3b0d6b524329be6d6dd84e623811b1974d1bf54.
Generated by .githooks/sync-public-release.sh — do not edit by hand.
…y real-data probe

Restart survival:
- Persist job state to <ba_home>/native_import_job.json, checkpointed after
  every item + on completion (atomic temp+replace writes).
- resume_if_interrupted() on backend startup: a persisted "running" job can
  only mean a crash mid-import, so re-run for its provider scope — the
  idempotency registry skips already-imported sessions, finishing the
  remainder duplicate-free. Hooked into _recover_in_flight_task.
- get_status() falls back to the persisted state when no in-memory job
  exists (right after restart, before resume fires).

Bug fixes (all surfaced by an adversarial probe importing REAL ~/.claude,
~/.codex, ~/.gemini sessions — the synthetic suite missed them):
- Leading metadata (ai-title / queue-operation / file-history-snapshot)
  arrived before the first user prompt and forked an empty leading
  assistant bubble. _segment_turns now buffers leading events into the
  first real turn.
- Re-emitted/duplicate user prompts (agy) forked consecutive prompt-only
  turns with empty assistants. Consecutive boundaries now collapse.
- gemini events carried no uuid, and apply_event only appends uuid-bearing
  events to the render tree — so gemini imported NOTHING (silent drop).
  gemini events now get deterministic uuid5 ids.
- Turns that collapse to pure metadata leave an empty assistant; those
  pairs are dropped, and if every turn does, the session is deleted and
  import raises (no orphan empty bubbles).
- Per-key import lock closes the TOCTOU window where two concurrent
  imports of one native session could create duplicate sessions.

Tests: replaced the circular segmentation differential (compared the code
to its own mirror — proved nothing) with independent structural invariants
over 2800 sequences; added restart-survival + status-fallback tests;
asserted gemini assistant msgs actually carry rendered events. Plus a
manual real-data probe script. 11533 assertions; probe clean across
claude/agy/gemini/codex.

Co-Authored-By: Claude <noreply@anthropic.com>
Source filter (web / cli / import) in advanced search:
- Imported sessions are now tagged source="import" (a first-class value;
  was "cli", indistinguishable from real CLI sessions). session_store,
  session_manager fork path, and the frontend Session.source union accept it.
- Backend GET /api/sessions accepts a `sources` filter (web/cli/import)
  threaded through the full filter chain.
- Frontend SessionList advanced search gains a Source filter mirroring the
  existing provider/mode filters; useSession sends it as `sources`.

Import limit + safe in-backend trigger:
- start_import(provider_ids, limit): caps NEW imports (already-imported
  still skipped). POST /api/native-import accepts {limit}.
- New /api/internal/native-import (+ /status) gated by X-Internal-Token so
  the import can run INSIDE the live backend. This is the only safe path
  when a backend is up: a separate process writing session.json races the
  backend's in-memory cache (it re-persists and clobbers the render tree,
  leaving empty assistant bubbles — found the hard way).
- scripts/import_real_native.py now drives the internal route when a
  backend is reachable, falling back to standalone with a warning.

Co-Authored-By: Claude <noreply@anthropic.com>
First-comers get a native-session import step in the wizard (reuses the
self-contained NativeImportSetting) so they can pull in their existing
Claude/Codex/Gemini CLI history right after setup. Gracefully shows
nothing-to-import when no native sessions exist yet.

Co-Authored-By: Claude <noreply@anthropic.com>
session_manager._persist_root is an async debounced write; a short-lived
standalone import process can exit before the session.json stubs land, so
the backend wouldn't list those sessions until a later reload. Drain
flush_pending_persists() once at the end of import_real_native.py's
standalone path. (The render tree itself is durable in events.jsonl, which
the backend reconciles on load regardless — verified the rows are stamped
with the assistant message id.)

Co-Authored-By: Claude <noreply@anthropic.com>
- Recover the working directory for claude imports (read the first `cwd`
  field from the session jsonl) instead of leaving cwd empty. codex/agy/
  gemini already carried cwd.
- Project filter on the import: enumerate_native_sessions / start_import
  accept project_paths; only sessions whose cwd is under a loaded project
  are returned, and junk cwds (system temp, the BA state home) are excluded.
  None disables both (legacy import-everything). Threaded through both
  native-import POST routes.
- scripts/import_real_native.py defaults to the loaded BA projects
  (project_store.list_projects, resync) and shows each session's cwd;
  --projects overrides, --all disables the filter.

Tests: cwd recovery from jsonl, junk detection, project-filter keep/drop.

Co-Authored-By: Claude <noreply@anthropic.com>
Both wire-event types are dispatched globally via
SessionWSBroadcaster._dispatch but were missing from
Coordinator.GLOBAL_EVENT_ALLOWLIST, so broadcast_global raised
ValueError on every fire. The throw landed in a fire-and-forget
task (logged as 'Task exception was never retrieved'), so the WS
frame was silently dropped — extension attention-marker dots on
session tabs and per-message auto-retry pills never propagated to
other tabs.

Add both types and close the coverage gap: the allowlist test
trusted the _dispatch dynamic-caller audit note but never verified
the broadcaster's on_change 'type' literals. Now it scans them.

Co-Authored-By: Claude <noreply@anthropic.com>
…nd alert

Generalize the bcsize font sentinel into a bcstyle sentinel that also carries
a transparent background highlight; render it in MessageBubble. Carry an
optional marker.sound flag end-to-end and play a synthesized attention sound
on session_marker_changed.

Co-Authored-By: Claude <noreply@anthropic.com>
…tive

Backend's session-list endpoint filters out sessions with no
content_scores match, so brand-new sessions never appeared in the
sidebar while a search query was active. Insert new sessions at the
top unconditionally — refetching wouldn't help since the content
index doesn't know about the new session yet.
f785a83d replaced the provisioned search-worker turn with a shallow
local grep (title + first_prompt only), so Ask queries that matched
only transcript content returned 0 results. Revert run_search_sessions_session
to dispatch SEARCH_SPEC via provisioning.run; delete the dead
_run_local_search_sync helper and update the test that locked the
regression to assert the worker is called.

Co-Authored-By: Claude <noreply@anthropic.com>
Distinguish human-started sessions from ones created by Better Agent's own
tooling. Backend derives `initiated_by` ("user"/"tool") from existing fields
(no migration): tool = imported (source=import), extension/virtual, internal
kinds (delegate_fork/sub_session/adv_sync_fork/supervisor_worker), or
caller_agent_session_id; everything else is user. Exposed on the session
list payload + a new `initiated_by` filter on GET /api/sessions. Frontend
gains an "Initiated by" toggle in advanced search.

Co-Authored-By: Claude <noreply@anthropic.com>
The non-user bucket is "agent initiated" (covers imports, delegate /
sub-session forks, adv-sync review, supervisor, extensions) — clearer
than "tool". Value/label renamed across backend derivation and frontend
filter; classification unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
… lag

Every keystroke in a modal textarea invalidated the page-wide blur layer,
forcing GPU re-rasterization of everything behind it. The supervisor and
new-session modals have no per-keystroke JS work yet still lagged — the
backdrop-filter was the only shared cost.
Full rename across ids, fields, files, CSS classes, translations.
Persisted session_store entries with old keys will be ignored on load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regression test for the namespace-package shadow bug where five private
extensions declared `module: "mcp.server"` and the launcher loaded the
SDK package instead of the extension's own `mcp/server.py`, exposing
zero tools. Asserts each affected manifest now uses
`python: "mcp/server.py"` and the server file exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Identify native sessions Better Agent itself spawned — its own user
sessions plus internal agent sessions (delegate forks, sub-sessions,
adv-sync review runs, workers, supervisor) — via their run-dir
session_id and agent_session_id references on current session trees.
These already live in BA (or did), so importing their native transcripts
would duplicate agent/internal sessions instead of recovering a real
external conversation. enumerate_native_sessions now always excludes
them (on this machine: 30/11657 claude sessions skipped). 0 of the
already-imported 210 are affected — no cleanup needed.

Co-Authored-By: Claude <noreply@anthropic.com>
The user/agent distinction only matters at import time (only import user
sessions), which the BA-managed skip in native_import already handles.
A persisted derived `initiated_by` field + advanced-search filter added
nothing, so remove it entirely: backend _initiated_by helper, the filter
param/chain, the payload field, and the frontend type/filter UI/plumbing.

Co-Authored-By: Claude <noreply@anthropic.com>
Boundary fix: supervisor-specific state belonged in the supervisor
extension, not core's config_store. Drops `last_supervisor_prompt`
from `config_store` schema, removes the write from
`/api/internal/supervisor/toggle`, and removes `last_prompt` from
`/api/internal/supervisor/default-prompt`. The extension now owns
persistence via SDK extension-scoped storage in its own routes.

Deletes the orphan unit test for the removed core helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A live/streaming optimistic assistant placeholder (id `live-*`) carries
no `seq`. The load-older cursor reduce used `m.seq ?? 0`, which folded
the cursor to 0 whenever such a ghost was in the message list, silently
disabling "Load older messages" for the whole thread. Compute the oldest
cursor over numeric seqs only via a shared `oldestNumericSeq` helper;
applied to Chat and both ForkSplitView load-older sites.

Co-Authored-By: Claude <noreply@anthropic.com>
Add message_count==0 as the first order field in both backend
_session_list_sort_key and frontend sortSessionsForList so a brand-new
empty session always appears first, above pinned/recency. Also repair
two sibling sort tests that broke on the now-required sort_by arg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the advanced filter panel inside the .session-list-items scroll
container as its first child so it scrolls together with the session
rows instead of staying pinned above them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CopilotProvider (subclasses GeminiProvider — reuses RunState, tailer
bootstrap, completion watcher, disk recovery) drives `copilot -p ... \
--allow-all-tools` via runner_copilot.py. The runner tails Copilot's own
session-state event log — current dir layout
<config_dir>/session-state/<sid>/events.jsonl, legacy <sid>.jsonl — and
normalizes its type-tagged events (user.message, assistant.message,
tool.execution_start/complete) to Claude-shaped agent_message events.

Subscription-only (GitHub OAuth via gh), native-mode only: no fork, no
team/manager, no steering, no reasoning effort. Registered in the
provider registry, model refresh + cold-start dispatch, and the setup
installer map (brew install copilot-cli). Frontend gains a Copilot setup
template + i18n (en/he). Verified end-to-end against the real CLI; 10
focused unit tests in backend/scripts/test_provider_copilot.py.

Co-Authored-By: Claude <noreply@anthropic.com>
ui_hooks() iterated list_extensions() with the default include_hidden=False,
so the marketplace — added to PUBLIC_EXTENSION_LIST_HIDDEN_IDS for the public
release — also lost its Settings page hook, removing the Marketplace UI
entirely. The hidden-list is meant to hide it from the manage-list only.
Pass include_hidden=True so the page still surfaces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion DB

agy/antigravity runs rendered the assistant response as one giant stdout
block instead of ordered, separated text turns + tool calls. The parent
conversation DB carries the real ordered turn structure (step types 14/15/
127/132/101), but only the subagent fan-out was being decoded.

Add _ParentMainState (per-step, stateful) that walks parent steps in idx
order and emits user prompts, assistant text turns (with protobuf bot-
suffix artifacts stripped), and non-subagent tool_use events. Dedup the
type-15 vs type-127/132 duplicate tool pair by payload signature so each
logical tool call emits once. invoke_subagent payloads stay routed to the
worker-panel path (single representation, no double-render).

_agy_worker_events now interleaves main-thread + worker-panel events in
true step order, so the streaming watcher and the post-exit flush produce
the same ordered list — parent text/tools stream live during the run.
_run skips the single-stdout fallback when ordered parent events were
emitted; falls back only when extraction yielded nothing or the run failed.

Refactored _extract_parent_subagent_events into _ParentSubagentWalker
(per-step, stateful) so the unified walker can merge by step idx without
duplicating the worker-panel logic.

Co-Authored-By: Claude <noreply@anthropic.com>
…ASKS__DONE blue dot

Add a second tag rule so the agent can signal completion with
<ALL_TASKS__DONE>, surfacing a blue attention dot that clears on view.
Rename the extension (id/dir/name/instructions) to reflect both markers.
Old id migrates via _OBSOLETE_EXTENSION_IDS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The marketplace is missing from Settings because the installed package is the
public-repo MCP-only build (surfaces: ['runtime_mcp'], no frontend), not the
full-UI private package. Its real UI is a frontend_modules "settings" slot
rendered from the extensions list, not a ui_hooks() page/quick_button, so the
prior include_hidden=True change in ui_hooks() fixed nothing. Reverting it and
its test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…prompt

agy model steps carry assistant prose AND a tool call in the same step (a
Claude-style text+tool_use turn); the previous parent extractor returned
early on the tool and dropped the accompanying prose. Emit text first then
the tool so narration before acting is no longer lost.

Strip the protobuf bot-identifier wherever it appears in the string (the
blob often continues past the uuid, so the old end-anchored regex missed
it and _meaningful_text's bot- guard rejected the whole turn).

Stop re-emitting the type-14 user prompt as a main-thread event — it is
already the session's user message; emitting it duplicated the user bubble.
Track prompt texts only to suppress the type-23 echo.

Co-Authored-By: Claude <noreply@anthropic.com>
…bble

Two Ask UI bugs surfaced after restoring the LLM search worker:

1. No running indication while the ~40s worker fork is in flight. The
   Ask session never enters `_run_state`, so the normal running_changed
   recompute path can't flag it. Emit `session_running_changed` True at
   the start of `_ask_search` and False in a finally, guarded by
   latest-wins (`_inflight is current_task`) so a cancelled prior search
   doesn't snuff the newer one's badge.

2. The worker fork's full transcript (inherited provision "ready" priming
   + every grep tool_use) was grafted onto the Ask assistant message's
   `events`, leaking "ready" and noise into the bubble. The Ask message
   now keeps only its answer text (content, derived from the worker
   reply) + the picker; `events` is empty. The worker transcript remains
   in the worker panel/provenance.

Co-Authored-By: Claude <noreply@anthropic.com>
All 26 extension test files now pass as standalone scripts (was 5 failing
files, plus ~15 additional failures hidden behind the first failure in the
large test_extension_store.py runner). Fixes are test-side corrections for
stale assumptions; production behavior is unchanged and correct.

- consent_gate: use the V2 first-party source type (better_agent_bundled)
  instead of the never-valid literal "builtin".
- iframe_token_auth: assert the real security invariant — HTTP never
  authenticates via query-param tokens. Extension frontend assets are
  intentionally public static assets (browser dynamic-import constraint),
  so a ?token= is ignored there; protected APIs must reject query tokens.
- provider_config_sync_extension, extension_store (harness/ask/marketplace/
  supervisor/purge tests): trigger the explicit reconcile path
  (list_extensions_with_reconciliation / _load_with_changes) before reading.
  Pure reads (_load/get_extension) no longer seed/bootstrap by design.
- extension_store: repurpose the dissolved-requirements rehydrate test to a
  non-managed synthetic id (requirements now ships from better-agent-private,
  so the managed-id skip correctly short-circuits it); fix the runtime-MCP
  token assertion to verify per-extension minting (not the global input
  passthrough); add mcp.server to the smoke fixture's python_modules; resolve
  the venv bin path for the macOS /var -> /private/var symlink; gate the two
  LLM-readiness tests on clearing providers (tasks now resolve via
  inheritance from the default provider, so clearing assignments no longer
  makes a task unready); reset the project-structure tombstone so reconcile
  re-seeds the fixture.

Co-Authored-By: Claude <noreply@anthropic.com>
ofekron and others added 28 commits June 25, 2026 15:06
The selected session is shown in the pinned anchor above the toolbar, so
drop it from the list pool; its sub-sessions reparent to root.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The offline backlog's create_session payload omitted folder_id from its
Pick<Session>, but App.tsx passes queued.folder_id to createSession —
breaking the production tsc -b build. Add folder_id so an offline-created
session preserves its folder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate the prompt

rewind_and_retry returned the failed turn's prompt but left the failed
user+assistant pair in history; the frontend then appended a fresh user
message and re-sent, persisting two consecutive identical user prompts.

The endpoint now rewinds the session to before the failed user message
(removing the pair and broadcasting rewind_complete) before returning the
prompt. When the failed user message has a provider anchor the CLI is
rewound too; otherwise rewind_files runs with provider_rewind=False to
truncate the render tree only. Drop the stale frontend comment that
claimed the backend already deleted the pair.

Replaces test_retry_preserves_history (which locked in the buggy
"preserve" behavior) with test_retry_discards_failed_turn covering both
the anchored-rewind and truncation-only paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline span with a background wrapping block-level markdown collapsed its
background box, so the bcstyle font-scale/highlight painted nothing. Render
the styled segment as a .bc-font-scaled block with padding and rounded
corners so the highlight and scale render reliably.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r cwd as a project

Provisioned machine-completion workers (e.g. the ofek-dev.requirements
extension worker, cwd = its own install dir) and TestApe-isolated runs
leaked hash-named projects into the user's project list. Gate all three
auto-register paths (session create, REST create route, seed-from-sessions)
through a single session_store.should_auto_register_project predicate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Printing the minted password to stdout leaked a full-access credential to
terminal scrollback, tmux/screen buffers, and any CI/launchd/systemd log
capturing run.sh output. First-run now sets credentials without ever
echoing a secret:

- BA_PASSWORD set    -> use it silently (headless / scripted)
- interactive TTY    -> prompt the operator to choose one
- no TTY and no env  -> mint a random password, do NOT print it; onboard
                        via the login-screen QR or ./run.sh --reset-auth

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…duplicate sends can't double-render

A prompt re-dispatched with the SAME client_id (frontend offline-backlog
replay after a brief WS reconnect) raced the send_message handler's
lock-free dedup: the three read-only checks (user_messages / queued_prompts
/ active_prompt_for_client_id) all missed, the duplicate persisted and
broadcast a phantom user_message_queued (kind=queued_behind, new lifecycle)
bubble, and only then did submit_prompt dedup+remove its queued row —
leaving an orphan duplicate bubble in the UI (one persisted message, two
shown).

Close the TOCTOU: the handler now atomically claims (session, client_id)
via try_claim_prompt_client_id BEFORE persisting/emitting anything. A
concurrent duplicate is detected and echoed (kind=send, in-flight
lifecycle) instead of emitting a phantom; submit_prompt honors the
handler's pre-claim via _client_id_claimed and the claim is released on
submit failure. Also release the claim on the cancel-after-dequeue path
(orchestrator) so a cancelled queued prompt can't leak its claim.

Test: test_prompt_client_id_claim_race covers duplicate detection,
claim-aware admission, and cancel claim-release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p concurrent steer

Adversarial review of the claim-admission fix found two gaps:
- Leak window: the atomic client_id claim was released only on submit
  failure, but the interrupt/alter cancel_turn and add_queued_prompt
  between claim and submit can also raise. On those failures the claim
  leaked and, since the offline backlog re-dispatches the same client_id,
  the re-dispatch would be deduped and the prompt silently lost. Now every
  failure between claim and submit calls _release_claim_on_failure (fail
  closed toward not losing user intent).
- Steer bypass: send_mode=steer injects into the live turn and never
  reached the claim, so a concurrent same-client_id steer could
  double-inject. Added a localized atomic claim gate on the steer path
  (claim before inject, drop duplicate, release in finally).

Also make _client_id_claimed conditional on a client_id actually being
claimed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rd console.error

POST /api/logs/frontend was a stub returning {dropped:true}; implement it to
validate+clip the payload and write via the frontend logger. Override
console.error in the frontend logger so React ErrorBoundary crashes and manual
console.error calls reach logs/frontend.log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface the Claude CLI pr-link agent_message as an auto-dismissing
(10s) toast in the chat panel. pr-link has no uuid so it never lands
on the render tree; the live WS frame is diverted in useWebSocket
(before the turn reducer) to an onPrLink callback that App renders as
a floating bottom-right toast. Fires only on live push, not replay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ECISION emphasis + dot

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…time

Worker/sub-session panels were placed by a backend-stamped insert_at
captured synchronously at MCP-tool-fire time, before the triggering
tool_use event was tail-appended to the message — so a
create_sub_session -> ask sub-session group rendered BEFORE
create_sub_session instead of after the ask.

Derive each panel's anchor at render time from the actual tool_use
entry position (tagEvents + render_stub.timeline_events), matching
delegation tool_use blocks to panels in firing order by name->kind;
fall back to stored insert_at when no match (Codex native panels,
queued/cross-message). create_worker is excluded (approval-gated;
its panel appears later via a separate delegation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…first

- Portal the pinned selected-session anchor into a slot above the
  Sessions/Workers tabs; it hides automatically on the Workers tab
  (SessionList unmounts there).
- Auto-select the remembered (else first) session when the route lands
  on the empty Ask home and the project has sessions; explicit Ask
  navigation opts out via an intentional-ask flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression test proving the shipping tagEvents places the
sub_session_created marker after create_sub_session and ask panels
after their ask tool calls, using real render-tree data captured from
a create_sub_session->ask session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ded color

- message-group-children padding-inline-start 16->14 so its border-to-child
  pitch (2+14=16) matches sub-agent-block's 16px grid (was 18, snapping the
  rail off-grid at the first delegation level). Mobile override aligned too.
- auto-action-group + timeline-block left stripe 3px->2px so every nesting
  rail shares one width.
- add a deeper sub-agent selector so L2+ dims past L1's 35%, keeping the
  rail readable past two levels.
Ask view overrode .ask-group margin-bottom to 24px while regular turns
use .message-group 16px, making ask turns spaced wider. Align to 16px.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The manifest quick button bare-navigated to the Ask singleton path; the
auto-select-first effect immediately bounced it to a real session, so Ask
never opened. Detect the ask path in the navigate-action dispatcher and
call openAsk (sets the intentional flag + ensures the singleton). Matches
both encoded and raw spellings of the path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agy stores several noisy variants of each assistant prose string in a step
blob: a trailing "2(bot-<uuid>:)" identifier and a leading snake_case tool
name + field-type marker bytes (e.g. "grep_searchB^I am searching...",
"^I am...", "II am...", "UI am...", ">I will..."). The previous strip only
handled the trailing bot suffix and a doubled first char, so the rendered
text carried "grep_searchB^", "^", "II", ">" prefixes and trailing ":".

Strip a short leading marker prefix (<=20 chars) up to the first sentence
boundary (I / I' / Capitalized-word+space) and tolerate ':', ')', or a
truncated uuid on the trailing bot identifier. Verified against a real
conversation DB: all parent prose now renders clean.

Co-Authored-By: Claude <noreply@anthropic.com>
…d extension

Publishes the FACT that a session is being dragged (session_drag_start/end on
the eventBus, carrying session id + name) and exports SESSION_DRAG_MIME, so the
private agent-board extension can reveal a themed drop overlay. Session rows are
now draggable outside folder view. App reflects the drag into a new
session-drag-overlay extension slot.

Adds a narrow /api/internal/agent-board/run-prompt endpoint gated to the
agent-board builtin token identity, delivering a lane's prompt action to the
dropped session via session_bridge._run — the privileged cross-session op stays
in core. Registers ofek-dev.agent-board in the private extension tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng survives

The NEEDS_USER_DECISION tag rule wrapped inner text in literal **...**.
When the agent's prose already contained markdown (**bold**, `code`), the
injected ** collided with it (****..**) and CommonMark mis-parsed the
emphasis, dropping the inner styling inside the highlight box.

Bold now rides the same frontend bcstyle sentinel (b=1 -> font-weight) used
for font-scale/highlight, leaving the agent's markdown untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… guard

Adversarial-review fixes:
- Restore the provider-change-during-active-run 409 guard in
  update_session_selectors that a concurrent working-tree change had dropped
  (it was swept into the prior agent-board commit by staging the whole file).
- run-prompt: validate the target session exists (no arbitrary/virtual ids),
  cap prompt length at the endpoint, and hold a strong ref to the delivery
  task so it can't be GC'd mid-await.
- Route delivery through new public session_bridge.run_for_extension instead of
  the private _run.
- Tests cover unknown-session 404 and over-long-prompt 400.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arity

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run-prompt now rejects a busy target session synchronously with 409 instead of
scheduling a delivery that run_for_extension would refuse and discard; the
background _deliver also logs any residual error-dict from the race. Test
covers the busy-target 409.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Crash recovery's _replay_and_apply picked the native replay reader by
provider kind, but only special-cased "gemini"/"codex". agy and copilot
(both GeminiProvider subclasses that write Gemini-shaped
session_events.jsonl) fell through to the Claude parser, so recovered
turns rendered empty/partial after a backend restart.

Add a single _GEMINI_FAMILY_KINDS set used at both dispatch sites, and
give agy/copilot bumped ingestion versions so runs reconciled under the
broken path re-digest on next startup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hat onRename)

`npm run build` (tsc -b) fails on two errors, so run.sh produces no
frontend/dist and the backend crashes at mount_frontend:

1. MessageBubble.tsx switches on a "pr_link" WS event (rendering
   PrLinkEvent), but "pr_link" was never added to the WSEventType union
   → TS2678. Add it to the union.
2. App.tsx passes onRename={renameSession} to <Chat>, but Chat's Props
   has no onRename (the prop is handled by <SessionList>, which keeps
   its onRename). The excess prop fails assignment → TS2322. Drop the
   stray prop; Chat never read it, so behavior is unchanged.

Verified: npm run build passes and ./run.sh reaches "Backend is healthy."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants