发布

  • feat(x): robot-driven code mode, unified chat, tabless navigation, voice everywhere (#855)

    frostbyte_neo 发布于 2026-08-13 09:06:23 +00:00

    • feat(x): todo view keyboard accessibility + new-chat affordances

    Pre-existing work-in-progress from the working tree, committed separately
    from the code/nav/voice overhaul that follows: focus-visible states for
    hover-only controls, Enter/Space to edit items, and optional onNewChat /
    onFocusComposer hooks on TodoView.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • feat(x): robot-driven code mode, unified chat, tabless navigation, voice everywhere

    One holistic overhaul of four coupled problems:

    Code mode — direct drive removed; everything goes through Rowboat, and the
    Rowboat path actually works now:

    • A code session IS a chat session on the turns runtime (same id). The old
      rowboat mode was broken at the foundation: sessions lived on the legacy
      run runtime while the chat pane had moved to the new one, so binding
      loaded nothing and sends failed.
    • code_agent_run resolves the session's agent/cwd/policy/engine-model
      server-side from the turn's sessionId (new ToolExecutionContext.sessionId
      threading), and keys the ACP conversation per session so follow-up turns
      resume the coding agent instead of cold-starting.
    • Deleted: codeSession:sendMessage IPC, CodeSessionService.sendMessage,
      suppressReplay (replay muting is now unconditional), CodeChat,
      use-code-chat, ResizableRightPane, the "Who drives" picker and the
      header mode Switch. CodeSession.mode is gone from the schema.
    • Status tracker rewritten on the turn event spine; pre-overhaul session
      metas get chat-session files backfilled at boot; launch-code-task drives
      via sessions.sendMessage with a settle watcher.

    Unified chat — the Code view's right pane is the same assistant chat as
    everywhere (full composer: attachments, mentions, model picker,
    permissions, voice). Code sessions appear in chat recents like any
    conversation, carrying the repo pin.

    Navigation — the editor-style tab strip and all _rowboat* sentinel tabs
    are gone. Every section is plain view state funneled through one
    closeAllSections() reset, which kills the stuck-section bug family by
    construction (Code was the one section bulk clears couldn't close — the
    "sometimes clickable, sometimes not" reports). Single chat binding via
    bindChatToRun(); one markdown editor keyed by path; expandedFrom is a
    ViewState snapshot so closing full-screen chat restores ANY section; the
    dock toggle works from every section; body pointer-events guard clears
    Radix modal leaks on navigation.

    Voice — the Home composer gains mic + call; transcripts route to the
    composer that owns the mic (a spoken to-do lands on the list, not in a
    chat); ownership token released on every cancel path; dock recording UI
    gated on ownership; the code chat's rogue second microphone died with it.

    Verified: typecheck + lint clean; 607 core / 107 shared / 313 renderer
    tests green; live Playwright drive of the dev app (11/11 checks: boot,
    section round-trips, session creation on the new runtime, dock binding +
    pin, dock toggle in Code, mic/call on Home and the code dock).

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • feat(x): one hover mode — unified surface, no pop-out button, companion chat decoupled

    The companion ("hover mode") had grown two different surfaces and a hard
    coupling to the app window's chat. This unifies all of it:

    One surface for every entry point

    • The composer call button now starts the SAME companion voice session
      (Skipper card: mascot + pins + text panel) that ⌥⇧Space summons — it
      used to open the mascot-less pill via the 'share' preset, which was the
      reported confusion. Screen share moved into the call menu and stays one
      tap away on the Skipper's share pin (sticky).
    • Surface choice is now derived from devices only (camera on → pill, the
      self-view surface; otherwise → Skipper card). The old tuckOrigin
      write-once flag — which made the card unreachable from composer-started
      calls and raced device acquisition on a 5s time-box — is gone.
    • startHoverCall() is the one flow behind the ⌥⇧Space relay, the tuck
      handle, and the call button; re-asserts the companion surface when
      already in a call (fixes the inert re-tuck), and the pinned path
      recreates a destroyed companion window instead of wedging.
    • ⌥⇧Space is never a silent no-op: if the app never answers the tuck
      relay, main falls back to the text card after 1.5s.

    Pop-out button removed

    • The dock header's pop-out duplicated hover mode — button, quickAsk:popOut
      IPC, and the dead 'send-text' popout action are gone.

    Companion chat decoupled from the app chat

    • The companion holds its OWN session binding (hoverRunId + a second
      SessionChatStore). Bar submits and call utterances create/continue that
      session via a lean sender (same per-turn config: voice flags, frames,
      search/code, fast-thinking default) — never the app's visible chat.
    • The TTS pipeline, call response panel, activity line, and quick-ask
      answer mirror all read the hover session's store, so app navigation
      mid-call never retargets or silences the conversation.
    • The bar's chip/switcher/new-chat rebind only the companion; switching
      chats in the app no longer moves the hover conversation, and vice versa.
      Calls started from a composer seed the companion from that chat, then
      the bindings are independent. "Open in Rowboat" is the one deliberate
      bridge (binds the app chat to the companion's conversation).
    • The bar's model/effort picks are companion-scoped instead of silently
      overwriting the app composer's selection.

    Verified: typecheck + lint clean, 313 renderer tests green, live Playwright
    drive (7/7): call button → Skipper card, tuck relay → same card, companion
    submit creates its own session with the app window untouched.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • feat(x): call button means "float THIS chat" — seed, retarget, defer-bind, card ↗

    Completes the hover/companion binding model:

    • The composer's call button always floats the chat it lives on. No call →
      starts the hover session bound to that chat (seeded from the tab binding,
      which is authoritative — the old runId mirror missed fresh chats and
      minted an orphan hover conversation). Call live on ANOTHER chat → the
      button stays a call button ("bring this chat into the call") and clicking
      re-points the live call: same devices, same Skipper, only the conversation
      switches (stale reply audio cancelled). Call live on THIS chat → End call.
      The composer now gets a per-chat callOnThisChat signal instead of the
      global inCall flag deciding the button.

    • Fresh-chat defer-bind: starting a call on an unbound chat arms a capture
      of that chat's identity; when the first utterance/bar message creates the
      hover session, the originating chat binds to it too (identity-checked so
      a user who switched away is never hijacked). One conversation, both
      surfaces — verified live: the companion's message renders in the app chat.

    • ↗ "Open in Rowboat" now shows on the Skipper card too (it was gated off
      the primary hover surface), and lands the conversation in the SIDE PANE
      instead of the maximized full view.

    • Deleting the companion's chat clears the hover binding.

    Verified: typecheck + lint clean, 313 renderer tests green, live Playwright
    drive 9/9: seed-no-new-session, chip shows seeded chat, mid-call retarget
    affordance + End-call flip, card ↗, fresh-chat defer-bind with the shared
    conversation visible in the app.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • fix(x): review follow-ups — server-side pins, history migration, tracker fixes, binding bugs

    Addresses Ramnique's review of #855, holding the changes to the "coding is
    strictly a tool call" principle:

    Retiring code-specific special-casing

    • "Is this a code session" is no longer a client decision: the session
      layer merges authoritative composition pins (agent + cwd) server-side on
      every sendMessage, via a DI-injected provider the layer knows nothing
      about. Composer, voice, quick-ask, and background senders now all
      assemble the identical code-mode prompt; the renderer chip is cosmetic.
    • Old code sessions keep their history: the runs→turns migration now
      treats code_session runs as the chat sessions they are (the skip and the
      copilot_chat-only session-shape check were obsolete); code-* timeline
      rows drop, the conversation survives, and migrated files archive out.
      The boot backfill is now the fallback for metas with no legacy run and
      runs exactly once (marker file) instead of rescanning every launch.
    • The status tracker still reads the turn spine — session-level status
      cannot see a coding agent's mid-turn approval card (the turn keeps
      RUNNING while code_agent_run blocks on the ask) — but it no longer
      misbehaves: events are processed strictly in arrival order (interleaved
      async handling could apply transitions out of order and pin a spinner),
      and an answered ask-human (async tool_result) now flips needs-you back
      to working, so the badge stops lying and completion notifications fire.

    Concrete bugs

    • Background coding tasks can't hang 90 minutes on an approval no one will
      answer: SendMessageConfig gains humanAvailable (default true); the task
      runner passes false so gated tools auto-deny and ask-human is
      unavailable; a suspended settle fails fast; the watch timer is cleared.
    • Code rail ↔ dock desync: the stale boundCodeSessionRef guard is gone
      (bindChatToRun already dedupes on the live binding) and re-clicking the
      already-selected rail session re-notifies, so "click the session again"
      always re-asserts the binding.
    • Drafts survive A→B→A: rebinding uses a deterministic chat identity (the
      session id) instead of a fresh uuid, so a half-typed message is restored
      when you come back.
    • Ending a call releases the companion's mirrored binding, so the second
      session store unsubscribes instead of double-processing the chat forever.
    • The body pointer-events safety net keys on the whole view state, so
      file→file navigation also clears a leaked modal lock.
    • Stale popout device state is cleared on unpin — the next summon no
      longer flashes the camera pill before morphing to the card.

    Docs: turn-runtime-design.md no longer claims the legacy runtime serves
    code mode; ANALYTICS.md code_session rows updated.

    Verified: typecheck + lint clean; 607 core / 313 renderer tests green
    (migration test now asserts code_session history survives); live drives
    23/23 across code smoke, hover binding, rail re-click, and draft survival.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • fix(x): drop the Video call menu entry; kill the last hover-remnant flash
    • The call menu loses "Video call" — the hover companion and Practice cover
      the real uses; the preset stays valid for the engine (practice shares the
      camera path, and the pill remains the self-view surface when a practice
      call is minimized).

    • The "old hover without the hat" sighting was the summoned text card — the
      only hatless mascot left — flashing when a ⌥⇧Space summon's device
      startup outran the 1.5s "nothing answered" fallback. The app now ACKS the
      tuck relay the moment it starts handling it (quickAsk:tuckAck), which
      cancels the fallback timer; the timer is also cleared on pin and on a
      deliberate card show. Every remaining companion surface is intentional:
      Skipper card / tucked mascot / pill tile all wear the on-duty hat, and
      the hatless summoned card appears only as the explicit no-voice fallback.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • feat(x): tap right ⌘ while the mascot speaks = stop; hold stays one-gesture barge-in

    While the assistant is speaking, a quick tap of right ⌘ used to silence the
    audio and then drop the user into LOCKED hands-free capture — a hot mic
    nobody asked for, whose next sound gets submitted. The gesture now
    disambiguates the intent:

    • Tap while speaking → STOP the mascot: full interrupt (audio, the queued
      reply backlog, and generation — an audio-only mute would go quiet and
      then resume a beat later as new sentences streamed in), mic left closed.
    • Tap again (in silence) → hands-free listening, as always.
    • Hold while speaking → unchanged one-gesture barge-in (silence + talk,
      release sends) — interrupting to speak must never become a two-step.

    Also fixes a decoupling miss this made load-bearing: the interrupt's
    stop-generation half still checked the APP chat's processing state and
    stopped ITS run — speech comes from the companion session, so the
    interrupt now stops that turn (applies to the Skipper/pill stop button and
    the full-screen call's interrupt too).

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com


    Co-authored-by: Claude Fable 5 noreply@anthropic.com

    下载附件