Skip to content

Integrate T3Code roadmap slices#98

Merged
Jay1 merged 39 commits into
mainfrom
feature/t3code-roadmap-integration
Jul 2, 2026
Merged

Integrate T3Code roadmap slices#98
Jay1 merged 39 commits into
mainfrom
feature/t3code-roadmap-integration

Conversation

@Jay1

@Jay1 Jay1 commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • add the JCode design contract and OpenCode resume hardening
  • add chat wrapping controls, provider maintenance opt-out, composer stabilization, timeline affordances, and provider usage status
  • add managed worktree cleanup, read-only VCS status, Copilot provider ADR/spike, and backend discovery seams

Verification

  • Final roadmap verification wave completed before committing: F1 plan compliance, F2 code quality, F3 manual/data QA, and F4 scope fidelity all confirmed
  • Working tree was clean after 20 atomic commits

Notes

  • Browser QA for affected slices used data/source-surface evidence where Playwright Chromium was unavailable on ubuntu26.04-x64
  • Aikido/security MCP scans were unavailable where attempted during roadmap evidence capture
  • No DPCode adaptation is included; this PR is T3Code-roadmap only

Summary by CodeRabbit

  • New Features
    • Added managed worktree cleanup safety/status (including blocked explanations) and safer delete behavior.
    • Improved chat markdown with code block and table wrapping controls; new provider usage chip and a VCS command-center status panel.
    • Added timeline minimap navigation and richer expandable activity details (commands + file changes).
    • Introduced a new Copilot support direction for model-source integration (including offline “auth missing” messaging).
  • Bug Fixes
    • Improved session resume handling, scroll-follow behavior, and worktree cleanup safety.
  • Documentation
    • Added cockpit visual system design guidance and an ADR for the Copilot entry path approach.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds WSL-aware backend resolution and discovery, managed worktree cleanup classification, provider update-check settings, a Copilot spike with ADR, OpenCode resume hardening, provider usage UI, chat markdown wrapping, and timeline activity details/minimap updates.

Changes

Backend WSL Path Resolution and Registry

Layer / File(s) Summary
Backend contracts and path helpers
packages/contracts/src/backend*.ts, packages/contracts/src/index.ts, apps/server/src/backend/backendPathResolver.ts
Adds backend schemas and WSL/Windows path translation helpers used by backend resolution.
Backend discovery and project resolution
apps/server/src/backend/backendRegistry*.ts
Implements host/WSL backend discovery and project backend selection, with tests.

Managed Worktree Cleanup

Layer / File(s) Summary
Managed worktree cleanup contract
packages/contracts/src/server*.ts
Extends managed worktree data with cleanup fields.
GitCore worktree safety and listing
apps/server/src/git/Layers/GitCore*.ts, apps/server/src/git/Services/GitCore.ts
Parses worktree porcelain output and computes cleanup status for managed worktrees.
wsRpc worktree listing endpoint
apps/server/src/wsRpc.ts
Aggregates managed worktrees across projects and hardens legacy websocket auth.
Web cleanup classification and settings UI
apps/web/src/worktreeCleanup*.ts, apps/web/src/routes/_chat.settings.tsx
Classifies worktrees for removal and updates the settings delete flow and settings rows.

Provider Update Checks and Copilot Spike

Layer / File(s) Summary
Provider version advisory update-check toggle
apps/server/src/provider/providerMaintenance*.ts, apps/server/src/provider/Layers/ProviderHealth.ts
Skips latest-version checks when provider update checks are disabled.
Copilot provider spike module and ADR
apps/server/src/provider/copilotProviderSpike*.ts, docs/adr/*
Adds Copilot model-source helpers, an offline auth-missing snapshot, and ADR documentation.

Chat Markdown Wrap and Provider Update Check Settings

Layer / File(s) Summary
Server settings schema
packages/contracts/src/settings*.ts
Adds chatMarkdownWordWrap and enableProviderUpdateChecks to server settings and patches.
App settings mapping
apps/web/src/appSettings*.ts
Maps the new settings through app settings and migration logic.
Chat markdown wrap UI
apps/web/src/components/ChatMarkdown*.tsx, apps/web/src/index.css
Adds code/table wrapping controls and styling driven by chat markdown wrap settings.
Settings route toggle rows
apps/web/src/routes/_chat.settings.tsx
Adds runtime behavior rows for the new settings.

Provider Usage UI and Runtime Controls

Layer / File(s) Summary
Provider usage chip resolution logic
apps/web/src/components/ProviderUsageStatusChip*.ts(x)
Derives a status chip from rate limits or usage lines.
Usage chip and runtime controls components
apps/web/src/components/RuntimeUsageControls.tsx
Renders the usage chip, runtime toggle, and optional context window meter.
BranchToolbar refactor and environment picker
apps/web/src/components/BranchToolbar*.tsx, apps/web/src/components/BranchToolbarEnvironmentPicker.tsx
Extracts the environment picker and reuses the shared runtime controls export.
VCS command center status panel
apps/web/src/components/VcsCommandCenterStatusPanel*.ts(x), apps/web/src/components/GitActionsControl.tsx
Adds a read-only VCS status panel to the git actions menu.
ChatView provider usage wiring
apps/web/src/components/ChatView*.tsx, apps/web/src/components/chat/ChatTranscriptPane.browser.tsx
Threads provider usage data into ChatView and updates transcript test harnesses.

Composer Prompt Traits Extraction

Layer / File(s) Summary
Prompt traits derivation and provider registry wiring
apps/web/src/components/chat/composerTraits.ts, apps/web/src/components/chat/composerProviderRegistry*.tsx, apps/web/src/components/ChatView.tsx
Replaces raw prompt checks with derived prompt traits for composer state selection, with tests.

Chat Timeline Activity Details and Minimap

Layer / File(s) Summary
Session log command/patch extraction
apps/web/src/session-logic*.ts
Extracts command output and patch data into work-log entries.
Activity details rendering
apps/web/src/components/chat/MessagesTimelineActivityDetails*.ts(x), apps/web/src/components/chat/MessagesTimeline.browser.tsx
Renders command output and file-change details for expandable rows.
Timeline expandable rows wiring
apps/web/src/components/chat/MessagesTimeline*.tsx
Wires expansion state, workspace root, and activity details into timeline rows.
Timeline minimap component
apps/web/src/components/chat/MessagesTimelineMinimap*.tsx
Adds a minimap for jumping to user turns and proposed plans.
Chat scroll live-edge helper and tail-follow state
apps/web/src/chat-scroll*.ts, apps/web/src/components/ChatView.tsx
Adds live-edge resolution and tail-follow state for transcript scrolling.

Cockpit Design System Documentation

Layer / File(s) Summary
DESIGN.md cockpit visual system document
DESIGN.md
Documents the cockpit visual system, tokens, accessibility, and motion rules.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

  • Jay1/jcode#14: Modifies the same chat markdown table wrapping and CSS surface extended here.
  • Jay1/jcode#66: Both PRs modify OpenCode session resumption logic in OpenCodeAdapter.ts.
  • Jay1/jcode#68: Both PRs touch ChatView.tsx chat header/transcript wiring surfaces.

Suggested labels: vouch:trusted, size:XXL

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description has a summary and notes, but it is missing the required Why, Validation checklist, UI Changes, and Reviewer Notes sections. Add the missing Why, Validation, UI Changes, and Reviewer Notes sections, including test evidence and screenshots or "Not applicable" where appropriate.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the PR, but it is generic and doesn’t clearly convey the main change. Use a concise title that names the primary change, such as adding roadmap slices for design, backend, and UI updates.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Jul 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/src/routes/_chat.settings.tsx (1)

540-543: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate thread-linking logic diverges from classifyManagedWorktreeCleanupChoices.

normalizeManagedWorktreePath and the linkedThreadsFromSnapshot filter here re-implement the same worktree/thread path-matching logic already defined in worktreeCleanup.ts's normalizeWorktreePath and classifyManagedWorktreeCleanupChoices. Since this copy runs against a freshly-fetched snapshot rather than the memoized managedWorktreeChoices, the two implementations can silently diverge (e.g., a future change to the matching rule in one file won't be reflected in the other), producing inconsistent confirmation-dialog messaging vs. actual cleanup-safety classification.

♻️ Suggested consolidation
-function normalizeManagedWorktreePath(value: string | null | undefined): string | null {
-  const trimmed = value?.trim();
-  return trimmed && trimmed.length > 0 ? trimmed : null;
-}
+// Reuse the shared helper from worktreeCleanup.ts instead of duplicating it here.

Then export normalizeWorktreePath (and ideally a small linkThreadsToWorktreePath helper) from worktreeCleanup.ts and call it from both the memoized classification and the fresh-snapshot lookup in deleteManagedWorktree.

Also applies to: 1306-1312

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/routes/_chat.settings.tsx` around lines 540 - 543, The
thread-linking and worktree path matching logic in
`deleteManagedWorktree`/`linkedThreadsFromSnapshot` is duplicated from
`worktreeCleanup.ts`, which risks the two paths diverging over time. Move the
canonical matching into `worktreeCleanup.ts` by exporting
`normalizeWorktreePath` and, ideally, a shared `linkThreadsToWorktreePath`
helper, then have both `classifyManagedWorktreeCleanupChoices` and the
fresh-snapshot lookup in `deleteManagedWorktree` call that shared logic instead
of `normalizeManagedWorktreePath` and the local filter.
apps/web/src/components/BranchToolbar.tsx (1)

34-50: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Provider usage resolution diverges from ChatView, causing inconsistent chip state for draft threads.

activeProvider here only reads serverThread, so for any thread that hasn't started yet (serverThread is undefined), it resolves to null regardless of the composer's selected/default provider. usageSummary is then computed from that null provider, and both BranchToolbarEnvironmentPicker's rate-limit panel and the RuntimeUsageControls rendered here will show "no provider" data.

Meanwhile, ChatView.tsx computes its own activeProviderUsageSummary with a full fallback chain (session.provider ?? modelSelection.provider ?? selectedProvider) and spreads it into branchToolbarProps — but BranchToolbarProps doesn't declare provider/providerRateLimits/providerUsageLines/providerUsageIsLoading/providerUsageLearnMoreHref, so those computed values are silently dropped when passed to <BranchToolbar {...branchToolbarProps} />. Worse, ChatView renders the same RuntimeUsageControls widget directly (with its own, more complete provider) whenever isGitRepo is false, so the exact same UI element shows different provider/usage data depending on whether the project is a git repo.

🔧 Suggested fix: accept the already-computed provider/usage props instead of recomputing locally
 interface BranchToolbarProps {
   threadId: ThreadId;
   className?: string;
   onEnvModeChange: (mode: EnvMode) => void;
   envLocked: boolean;
   runtimeMode?: RuntimeMode;
   onRuntimeModeChange?: (mode: RuntimeMode) => void;
+  activeProvider?: ProviderKind | null;
   onHandoffToWorktree?: () => void;
   onHandoffToLocal?: () => void;
   handoffBusy?: boolean;
   onCheckoutPullRequestRequest?: (reference: string) => void;
   onComposerFocusRequest?: () => void;
   contextWindow?: ContextWindowSnapshot | null;
   cumulativeCostUsd?: number | null;
   activeContextWindowLabel?: string | null;
   pendingContextWindowLabel?: string | null;
 }
-  const activeProvider =
-    serverThread?.session?.provider ?? serverThread?.modelSelection.provider ?? null;
+  const activeProvider =
+    activeProviderProp ??
+    serverThread?.session?.provider ??
+    serverThread?.modelSelection.provider ??
+    null;

Then thread activeProvider (renamed prop) from ChatView's already-computed value instead of relying solely on serverThread.

Also applies to: 83-84, 194-198, 244-259

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/BranchToolbar.tsx` around lines 34 - 50,
BranchToolbar is recomputing provider usage from serverThread and ignores the
already-computed provider data coming from ChatView, which causes draft threads
to show inconsistent “no provider” state. Update BranchToolbarProps and
BranchToolbar to accept and use the provider/usage props that ChatView already
prepares (activeProvider, providerRateLimits, providerUsageLines,
providerUsageIsLoading, providerUsageLearnMoreHref) instead of deriving usage
locally, and thread the renamed activeProvider through the
BranchToolbar/RuntimeUsageControls/BranchToolbarEnvironmentPicker path so both
git and non-git flows render the same provider data.
apps/web/src/session-logic.ts (1)

49-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the redundant DetailedWorkLogEntry alias

WorkLogEntry already includes output, stdout, stderr, exitCode, durationMs, and patch, so this local intersection can be simplified to WorkLogEntry unless the readonly modifiers are intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/session-logic.ts` around lines 49 - 72, Simplify the local
work-log type by removing the redundant DetailedWorkLogEntry intersection and
using WorkLogEntry directly in session-logic.ts. The relevant symbols are
WorkLogEntry and the alias currently defined around it; if any readonly intent
was meant for subagent-related fields, preserve that explicitly instead of
duplicating the same output/stdout/stderr/exitCode/durationMs/patch properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/git/Layers/GitCore.ts`:
- Around line 458-460: The `listManagedWorktrees` parsing in `GitCore` only
matches an exact `prunable` line, so it misses `git worktree list --porcelain`
entries that include a reason suffix. Update the `prunable` handling in the
worktree entry parser to recognize lines that start with `prunable` and still
set `currentPrunable`, preserving the reason text for any existing parse flow.
Keep the fix localized around the `listManagedWorktrees` logic and the
`prunable` branch in `GitCore`.

In `@apps/server/src/provider/Layers/OpenCodeAdapter.test.ts`:
- Around line 1484-1531: The OpenCodeAdapter resume test only checks session
reuse, so it can still pass even if the resumed event changes or session.update
drops permission data. Strengthen the existing test in OpenCodeAdapter.test by
asserting the resumed turn emits the expected resumed event and that the
sessionUpdate payload includes the runtime-mode permissions for the resumed
session. Use the existing session and turn assertions as anchors, and add
focused expectations around the mocked runtime event and sessionUpdate call
details.

In `@apps/server/src/provider/Layers/ProviderHealth.ts`:
- Around line 2044-2055: `enrichStatuses` is refetching settings instead of
using the snapshot already loaded by `loadProviderStatuses`, which can cause
inconsistent enrichment behavior. Update `enrichStatuses` to accept the upstream
`settings` (or at least `enableProviderUpdateChecks`) as an argument and pass it
through from the `loadProviderStatuses` call site. Keep the existing
`Effect.forEach`/`enrichProviderStatusWithVersionAdvisory` flow unchanged, but
remove the internal `serverSettings.getSettings` read so the same settings
snapshot is reused end to end.

In `@apps/web/src/components/chat/MessagesTimeline.tsx`:
- Around line 1895-1896: The expand/collapse state in SimpleWorkEntryRow is
shared across every changed-file button, so multi-file entries toggle together
and hide the per-file diff-open behavior. Update the per-file rendering in
MessagesTimeline to scope expansion by changed file path (or move the patch
toggle out of the file buttons), and make the
FileChangeActivityDetails/ActivityEntryDetails flow target the clicked file
instead of one shared expanded boolean. Preserve the existing
onOpenTurnDiff(turnId, changedFilePath) behavior for entries with patch data so
a file click can still open that specific diff when applicable.

In `@apps/web/src/components/chat/MessagesTimelineActivityDetails.tsx`:
- Around line 40-46: The command-activity check is duplicated in
isCommandActivity and can drift from the existing isCommandEntry logic in
session-logic.ts. Move or export the shared predicate from toDerivedWorkLogEntry
in session-logic.ts, then import and reuse it in MessagesTimelineActivityDetails
so both places rely on the same command-classification rule.
- Around line 175-184: CommandOutputBlock truncates long command output without
telling the user, so add a visible truncation note when visibleLines is shorter
than lines. Update CommandOutputBlock in MessagesTimelineActivityDetails to
render a small “showing last 40 lines” style indicator alongside the
ActivityDetailBlock content whenever COMMAND_OUTPUT_TAIL_LINES is applied, using
the existing props.title, getRenderableCommandOutputLines, and visibleLines
logic.
- Around line 1-204: Extract the pure predicate/formatting helpers from
MessagesTimelineActivityDetails into a separate
MessagesTimelineActivityDetails.logic.ts module and add matching tests in a
.logic.test.ts file. Move functions like hasExpandableActivityDetails,
hasCommandActivityDetails, isCommandActivity, hasFileChangeActivityDetails,
isFileChangeActivity, formatWorkspaceRelativePath,
getRenderableCommandOutputLines, and related output-detection helpers so the
React component only renders JSX and imports the logic. Keep the component names
ActivityEntryDetails, CommandActivityDetails, and FileChangeActivityDetails as
the UI entry points, and cover the extracted logic with unit tests.

In `@apps/web/src/components/chat/MessagesTimelineMinimap.tsx`:
- Around line 65-76: Memoize the timeline item derivation in TimelineMinimap so
it does not rescan rows on every parent re-render. Update
MessagesTimelineMinimap.tsx by using useMemo around
deriveTimelineMinimapItems(rows), and consider wrapping TimelineMinimap with
memo if it still re-renders with unchanged props. Keep the existing
TIMELINE_MINIMAP_MIN_ITEMS guard and onJump props behavior unchanged.

In `@apps/web/src/components/ChatView.structure.test.ts`:
- Around line 6-11: The structural test for runtimeUsageControlsProps is too
dependent on the exact ChatView.tsx text layout and adjacent branchToolbarProps
ordering, so it can fail on harmless reformatting. Update
ChatView.structure.test.ts to avoid the brittle regex in
extractRuntimeUsageControlsPropsSource() and instead verify the wiring through a
render-based assertion or AST-based inspection using a stable symbol from
ChatView.tsx such as runtimeUsageControlsProps (and, if needed, the ChatView
component) so the test remains valid across formatting changes.

In `@apps/web/src/index.css`:
- Around line 823-828: Remove the deprecated word-break: break-word declaration
from the wrapped chat markdown code block styles in index.css, since
overflow-wrap: anywhere already covers the wrapping behavior. Update both
affected rule blocks that target .chat-markdown
.chat-markdown-codeblock--wrapped pre and .chat-markdown
.chat-markdown-codeblock--wrapped pre code so only the non-deprecated
white-space and overflow-wrap declarations remain.
- Around line 1143-1156: The wrap toggle button in the chat table scroll
container is positioned in a way that lets it move out of view during horizontal
scrolling. Update the `.chat-markdown .chat-markdown-table-wrap-button` behavior
so it stays visible inside `.chat-markdown-table-scroll` when the table
overflows horizontally, using the existing table scroll/wrap styles as the
anchor for the fix. Keep the hover/focus visibility logic for
`.chat-markdown-table-scroll:hover .chat-markdown-table-wrap-button` and
`.chat-markdown-table-scroll:focus-within .chat-markdown-table-wrap-button`
intact while changing the positioning approach so the button remains accessible
in the unwrapped state.

In `@apps/web/src/session-logic.ts`:
- Around line 737-739: The command classification logic is duplicated between
session-logic extraction and MessagesTimelineActivityDetails.tsx rendering,
which risks future drift. Extract the predicate from session-logic.ts into a
shared helper such as isCommandWorkLogEntry and use it both in the command-entry
handling path around extractCommandResult/extractChangedFiles/extractToolPatch
and in the existing isCommandActivity logic. Keep the helper colocated with the
current classification symbols so both modules import the same source of truth.

In `@docs/adr/README.md`:
- Line 28: Update the metadata table in the README to keep it current after
adding the new ADR row. Specifically, adjust the “Last reviewed” value in the
document’s metadata table so it reflects this change, and make sure the entry in
the ADR index that includes 0009 is accompanied by the updated review date. Use
the README’s metadata table and ADR list entry as the reference points when
making the change.

In `@packages/contracts/src/backend.ts`:
- Around line 30-37: The Backend schema currently allows invalid combinations
because Backend.kind and Backend.connection.kind are modeled as separate
independent fields. Update Backend in backend.ts so kind is derived from
connection (or refactor the schema into a single discriminated union) and make
sure the Backend and BackendConnection definitions enforce only valid local/wsl
combinations. Keep the fix centered on the Backend and BackendConnection symbols
so the type no longer represents domain-invalid states.

---

Outside diff comments:
In `@apps/web/src/components/BranchToolbar.tsx`:
- Around line 34-50: BranchToolbar is recomputing provider usage from
serverThread and ignores the already-computed provider data coming from
ChatView, which causes draft threads to show inconsistent “no provider” state.
Update BranchToolbarProps and BranchToolbar to accept and use the provider/usage
props that ChatView already prepares (activeProvider, providerRateLimits,
providerUsageLines, providerUsageIsLoading, providerUsageLearnMoreHref) instead
of deriving usage locally, and thread the renamed activeProvider through the
BranchToolbar/RuntimeUsageControls/BranchToolbarEnvironmentPicker path so both
git and non-git flows render the same provider data.

In `@apps/web/src/routes/_chat.settings.tsx`:
- Around line 540-543: The thread-linking and worktree path matching logic in
`deleteManagedWorktree`/`linkedThreadsFromSnapshot` is duplicated from
`worktreeCleanup.ts`, which risks the two paths diverging over time. Move the
canonical matching into `worktreeCleanup.ts` by exporting
`normalizeWorktreePath` and, ideally, a shared `linkThreadsToWorktreePath`
helper, then have both `classifyManagedWorktreeCleanupChoices` and the
fresh-snapshot lookup in `deleteManagedWorktree` call that shared logic instead
of `normalizeManagedWorktreePath` and the local filter.

In `@apps/web/src/session-logic.ts`:
- Around line 49-72: Simplify the local work-log type by removing the redundant
DetailedWorkLogEntry intersection and using WorkLogEntry directly in
session-logic.ts. The relevant symbols are WorkLogEntry and the alias currently
defined around it; if any readonly intent was meant for subagent-related fields,
preserve that explicitly instead of duplicating the same
output/stdout/stderr/exitCode/durationMs/patch properties.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 004b6917-a9b6-4219-b98b-97bd28619f3c

📥 Commits

Reviewing files that changed from the base of the PR and between b7930a3 and 0d61a2d.

📒 Files selected for processing (59)
  • DESIGN.md
  • apps/server/src/backend/backendPathResolver.ts
  • apps/server/src/backend/backendRegistry.test.ts
  • apps/server/src/backend/backendRegistry.ts
  • apps/server/src/git/Layers/GitCore.test.ts
  • apps/server/src/git/Layers/GitCore.ts
  • apps/server/src/git/Services/GitCore.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.ts
  • apps/server/src/provider/Layers/ProviderHealth.ts
  • apps/server/src/provider/copilotProviderSpike.test.ts
  • apps/server/src/provider/copilotProviderSpike.ts
  • apps/server/src/provider/providerMaintenance.test.ts
  • apps/server/src/provider/providerMaintenance.ts
  • apps/server/src/serverSettings.test.ts
  • apps/server/src/wsRpc.ts
  • apps/web/src/appSettings.test.ts
  • apps/web/src/appSettings.ts
  • apps/web/src/chat-scroll.test.ts
  • apps/web/src/chat-scroll.ts
  • apps/web/src/components/BranchToolbar.structure.test.ts
  • apps/web/src/components/BranchToolbar.tsx
  • apps/web/src/components/BranchToolbarEnvironmentPicker.tsx
  • apps/web/src/components/ChatMarkdown.test.tsx
  • apps/web/src/components/ChatMarkdown.tsx
  • apps/web/src/components/ChatView.structure.test.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/GitActionsControl.tsx
  • apps/web/src/components/ProviderUsageStatusChip.logic.test.ts
  • apps/web/src/components/ProviderUsageStatusChip.logic.ts
  • apps/web/src/components/ProviderUsageStatusChip.tsx
  • apps/web/src/components/RuntimeUsageControls.tsx
  • apps/web/src/components/VcsCommandCenterStatusPanel.logic.ts
  • apps/web/src/components/VcsCommandCenterStatusPanel.test.tsx
  • apps/web/src/components/VcsCommandCenterStatusPanel.tsx
  • apps/web/src/components/chat/MessagesTimeline.browser.tsx
  • apps/web/src/components/chat/MessagesTimeline.test.tsx
  • apps/web/src/components/chat/MessagesTimeline.tsx
  • apps/web/src/components/chat/MessagesTimelineActivityDetails.tsx
  • apps/web/src/components/chat/MessagesTimelineMinimap.test.tsx
  • apps/web/src/components/chat/MessagesTimelineMinimap.tsx
  • apps/web/src/components/chat/composerProviderRegistry.test.tsx
  • apps/web/src/components/chat/composerProviderRegistry.tsx
  • apps/web/src/components/chat/composerTraits.ts
  • apps/web/src/index.css
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/session-logic.test.ts
  • apps/web/src/session-logic.ts
  • apps/web/src/worktreeCleanup.test.ts
  • apps/web/src/worktreeCleanup.ts
  • docs/adr/0009-copilot-provider-entry-path.md
  • docs/adr/README.md
  • packages/contracts/src/backend.test.ts
  • packages/contracts/src/backend.ts
  • packages/contracts/src/index.ts
  • packages/contracts/src/server.test.ts
  • packages/contracts/src/server.ts
  • packages/contracts/src/settings.test.ts
  • packages/contracts/src/settings.ts

Comment thread apps/server/src/git/Layers/GitCore.ts Outdated
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
Comment thread apps/server/src/provider/Layers/ProviderHealth.ts
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx
Comment thread apps/web/src/components/chat/MessagesTimelineActivityDetails.tsx Outdated
Comment thread apps/web/src/index.css
Comment thread apps/web/src/index.css
Comment thread apps/web/src/session-logic.ts
Comment thread docs/adr/README.md Outdated
Comment thread packages/contracts/src/backend.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/server/src/provider/Layers/ProviderHealth.ts (1)

2044-2055: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Thread the same settings snapshot into capability resolution too.

This still mixes snapshots: loadProviderStatuses probes providers with the outer settings, but getProviderMaintenanceCapabilities(status.provider) re-reads settings during enrichment. A concurrent settings edit can therefore return provider statuses from one snapshot and version-advisory/update capability data from another in the same refresh. Pass the already-loaded settings through this path as well.

Proposed fix
-    const getProviderMaintenanceCapabilities = Effect.fn("getProviderMaintenanceCapabilities")(
-      function* (provider: ProviderKind) {
-        const settings = yield* serverSettings.getSettings;
+    const getProviderMaintenanceCapabilities = Effect.fn("getProviderMaintenanceCapabilities")(
+      function* (provider: ProviderKind, settings?: ServerSettings) {
+        const resolvedSettings = settings ?? (yield* serverSettings.getSettings);
         if (provider === "cursor") {
           return makeProviderMaintenanceCapabilities({
             provider,
             packageName: null,
-            updateExecutable: getProviderBinaryPath(provider, settings) || "agent",
+            updateExecutable: getProviderBinaryPath(provider, resolvedSettings) || "agent",
             updateArgs: ["update"],
             updateLockKey: "cursor-agent",
           });
         }
-        if (provider === "opencode" && isExternalOpenCodeRuntimeActive(settings)) {
+        if (provider === "opencode" && isExternalOpenCodeRuntimeActive(resolvedSettings)) {
           return makeProviderMaintenanceCapabilities({
             provider,
             packageName: null,
@@
-        const binaryPath = getProviderBinaryPath(provider, settings) || null;
+        const binaryPath = getProviderBinaryPath(provider, resolvedSettings) || null;
         return yield* resolveProviderMaintenanceCapabilitiesEffect(definition, {
           binaryPath,
           env: process.env,
           platform: process.platform,
         }).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem));
       },
     );
@@
-          getProviderMaintenanceCapabilities(status.provider).pipe(
+          getProviderMaintenanceCapabilities(status.provider, settings).pipe(

Also applies to: 2079-2109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/provider/Layers/ProviderHealth.ts` around lines 2044 - 2055,
The enrichment path in enrichProviderStatuses is re-reading settings during
capability lookup, which can mix snapshots from different refreshes. Update
getProviderMaintenanceCapabilities usage in enrichProviderStatuses (and the
related flow covered by
loadProviderStatuses/enrichProviderStatusWithVersionAdvisory) to accept the
already-loaded settings snapshot, so provider status enrichment and capability
resolution both use the same ServerSettings instance. Ensure the settings object
passed into enrichProviderStatuses is threaded through the capability resolution
call instead of fetching settings again.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/session-logic.ts`:
- Around line 81-85: The command work-log detection in isCommandWorkLogEntry is
using nullish coalescing, which skips a valid raw command when command is an
empty string. Update the boolean check to fall back with logical OR on
workEntry.command and workEntry.rawCommand so empty command values still count
as command entries. This should preserve the derived entry handling in
toDerivedWorkLogEntry so command rows keep output, stdout, stderr, exitCode, and
durationMs.

---

Duplicate comments:
In `@apps/server/src/provider/Layers/ProviderHealth.ts`:
- Around line 2044-2055: The enrichment path in enrichProviderStatuses is
re-reading settings during capability lookup, which can mix snapshots from
different refreshes. Update getProviderMaintenanceCapabilities usage in
enrichProviderStatuses (and the related flow covered by
loadProviderStatuses/enrichProviderStatusWithVersionAdvisory) to accept the
already-loaded settings snapshot, so provider status enrichment and capability
resolution both use the same ServerSettings instance. Ensure the settings object
passed into enrichProviderStatuses is threaded through the capability resolution
call instead of fetching settings again.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dc619534-6ef7-4016-b9d8-739c7f8026fd

📥 Commits

Reviewing files that changed from the base of the PR and between 0d61a2d and 57eec4a.

📒 Files selected for processing (18)
  • apps/server/src/backend/backendPathResolver.ts
  • apps/server/src/backend/backendRegistry.test.ts
  • apps/server/src/git/Layers/GitCore.test.ts
  • apps/server/src/git/Layers/GitCore.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
  • apps/server/src/provider/Layers/ProviderHealth.ts
  • apps/web/src/components/ChatView.structure.test.ts
  • apps/web/src/components/chat/MessagesTimeline.test.tsx
  • apps/web/src/components/chat/MessagesTimeline.tsx
  • apps/web/src/components/chat/MessagesTimelineActivityDetails.logic.test.ts
  • apps/web/src/components/chat/MessagesTimelineActivityDetails.logic.ts
  • apps/web/src/components/chat/MessagesTimelineActivityDetails.tsx
  • apps/web/src/components/chat/MessagesTimelineMinimap.tsx
  • apps/web/src/index.css
  • apps/web/src/session-logic.ts
  • docs/adr/README.md
  • packages/contracts/src/backend.test.ts
  • packages/contracts/src/backend.ts

Comment thread apps/web/src/session-logic.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/server/src/provider/Layers/OpenCodeAdapter.ts (1)

3928-3934: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle session.update not-found as a stale cursor too.

Line 3929 can still race: the probed session may be deleted after session.get succeeds but before permissions are updated, causing startSession to fail instead of creating a fresh session. Treat only update-time 404/not-found as stale and fall through to session.create; keep other update failures surfaced. Add a focused regression for this path. As per coding guidelines, provider adapters must preserve provider error/state paths and add targeted regression tests rather than suppressing errors broadly.

Suggested direction
                 if (reusableSessionId) {
-                  yield* runOpenCodeSdk("session.update", () =>
-                    client.session.update({
-                      sessionID: reusableSessionId,
-                      permission: buildOpenCodePermissionRules(input.runtimeMode),
-                    }),
-                  );
-                  return {
-                    sessionScope,
-                    server,
-                    client,
-                    openCodeSessionId: reusableSessionId,
-                    created: false,
-                  };
+                  const updateExit = yield* Effect.exit(
+                    runOpenCodeSdk("session.update", () =>
+                      client.session.update({
+                        sessionID: reusableSessionId,
+                        permission: buildOpenCodePermissionRules(input.runtimeMode),
+                      }),
+                    ),
+                  );
+                  if (Exit.isSuccess(updateExit)) {
+                    return {
+                      sessionScope,
+                      server,
+                      client,
+                      openCodeSessionId: reusableSessionId,
+                      created: false,
+                    };
+                  }
+                  if (!isOpenCodeSessionNotFound(Cause.squash(updateExit.cause))) {
+                    return yield* Effect.failCause(updateExit.cause);
+                  }
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/provider/Layers/OpenCodeAdapter.ts` around lines 3928 - 3934,
In startSession inside OpenCodeAdapter, handle a 404/not-found returned by
client.session.update as a stale reusable session the same way session.get is
handled, so the code falls through to session.create instead of failing. Keep
non-not-found update errors surfaced by limiting the fallback to the update-time
not-found case, using the existing runOpenCodeSdk("session.update", ...) and
reusableSessionId path. Add a focused regression test for the race where
session.get succeeds but session.update returns not-found, verifying a new
session is created.

Source: Coding guidelines

apps/web/src/components/ChatView.tsx (1)

854-854: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Use the derived all-threads selector here

store.threads can lag behind hot-path thread updates (syncServerThreadDetailHotPath intentionally leaves the array untouched), so useProviderUsageSummary may show stale rate-limit data. Switch this to the same createAllThreadsSelector() pattern used elsewhere in the app.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/ChatView.tsx` at line 854, The thread list in
ChatView is reading the raw store.threads array, which can be stale after
hot-path updates and lead to outdated usage summaries. Update the selector used
in ChatView to the derived all-threads selector by wiring in
createAllThreadsSelector(), matching the existing pattern used elsewhere and
ensuring useProviderUsageSummary sees the latest thread data.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/wsRpc.ts`:
- Around line 921-923: The project-level worktree aggregation is swallowing
failures from git.listManagedWorktrees via Effect.orElseSucceed, so add logging
before falling back to noManagedWorktrees. Update the managed-worktree
collection flow in wsRpc.ts to use the existing logger/process logger around the
listManagedWorktrees call, emit the project/workspaceRoot and error details when
it fails, and then return noManagedWorktrees so the endpoint still completes.
- Around line 1741-1744: The owner-guard handling in providerBootstrapRuntime
and providerRepairRuntime still uses the older ownerOnly wrapper instead of the
refactored withCurrentSession(requireOwnerWsRpcAccess, ...) pattern used by the
rest of the WS dispatch table. Update these two handlers to match the new
session-based guard approach, using the same style as the nearby provider
methods and preserving the existing rpcEffect error messages. This keeps the
auth-sensitive logic consistent and removes the parallel owner-check
implementation from wsRpc.
- Around line 161-176: The legacy websocket owner bypass in
resolveLocalLegacyWsAuthSession is using a timing-sensitive equality check and
should not be allowed for non-loopback access. Replace the direct authToken vs
legacyToken comparison with a constant-time secret comparison, and ensure this
shortcut only succeeds for loopback requests in the /ws flow. Also verify the
legacy path still goes through the normal session lifecycle so
sessions.markConnected and sessions.markDisconnected are not skipped.

In `@apps/web/src/components/chat/MessagesTimeline.browser.tsx`:
- Around line 23-50: The browser test is bypassing the production integration
path by using ActivityDetailsHarness instead of the real row component that owns
the expand/collapse wiring. Update the test to render SimpleWorkEntryRow from
MessagesTimeline.tsx, or a thin wrapper around it, so it exercises the actual
threading of workEntry and workspaceRoot and verifies the expand button behavior
in the real component tree.

---

Outside diff comments:
In `@apps/server/src/provider/Layers/OpenCodeAdapter.ts`:
- Around line 3928-3934: In startSession inside OpenCodeAdapter, handle a
404/not-found returned by client.session.update as a stale reusable session the
same way session.get is handled, so the code falls through to session.create
instead of failing. Keep non-not-found update errors surfaced by limiting the
fallback to the update-time not-found case, using the existing
runOpenCodeSdk("session.update", ...) and reusableSessionId path. Add a focused
regression test for the race where session.get succeeds but session.update
returns not-found, verifying a new session is created.

In `@apps/web/src/components/ChatView.tsx`:
- Line 854: The thread list in ChatView is reading the raw store.threads array,
which can be stale after hot-path updates and lead to outdated usage summaries.
Update the selector used in ChatView to the derived all-threads selector by
wiring in createAllThreadsSelector(), matching the existing pattern used
elsewhere and ensuring useProviderUsageSummary sees the latest thread data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b745d144-a491-4355-93b5-ae6909a3239c

📥 Commits

Reviewing files that changed from the base of the PR and between 2078d4c and 925223c.

📒 Files selected for processing (12)
  • DESIGN.md
  • apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.ts
  • apps/server/src/wsRpc.ts
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/chat/ChatTranscriptPane.browser.tsx
  • apps/web/src/components/chat/MessagesTimeline.browser.tsx
  • docs/adr/0011-copilot-provider-entry-path.md
  • docs/adr/README.md
  • packages/contracts/src/index.ts
  • packages/contracts/src/settings.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/server/src/provider/Layers/OpenCodeAdapter.ts (1)

3928-3934: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle session.update not-found as a stale cursor too.

Line 3929 can still race: the probed session may be deleted after session.get succeeds but before permissions are updated, causing startSession to fail instead of creating a fresh session. Treat only update-time 404/not-found as stale and fall through to session.create; keep other update failures surfaced. Add a focused regression for this path. As per coding guidelines, provider adapters must preserve provider error/state paths and add targeted regression tests rather than suppressing errors broadly.

Suggested direction
                 if (reusableSessionId) {
-                  yield* runOpenCodeSdk("session.update", () =>
-                    client.session.update({
-                      sessionID: reusableSessionId,
-                      permission: buildOpenCodePermissionRules(input.runtimeMode),
-                    }),
-                  );
-                  return {
-                    sessionScope,
-                    server,
-                    client,
-                    openCodeSessionId: reusableSessionId,
-                    created: false,
-                  };
+                  const updateExit = yield* Effect.exit(
+                    runOpenCodeSdk("session.update", () =>
+                      client.session.update({
+                        sessionID: reusableSessionId,
+                        permission: buildOpenCodePermissionRules(input.runtimeMode),
+                      }),
+                    ),
+                  );
+                  if (Exit.isSuccess(updateExit)) {
+                    return {
+                      sessionScope,
+                      server,
+                      client,
+                      openCodeSessionId: reusableSessionId,
+                      created: false,
+                    };
+                  }
+                  if (!isOpenCodeSessionNotFound(Cause.squash(updateExit.cause))) {
+                    return yield* Effect.failCause(updateExit.cause);
+                  }
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/provider/Layers/OpenCodeAdapter.ts` around lines 3928 - 3934,
In startSession inside OpenCodeAdapter, handle a 404/not-found returned by
client.session.update as a stale reusable session the same way session.get is
handled, so the code falls through to session.create instead of failing. Keep
non-not-found update errors surfaced by limiting the fallback to the update-time
not-found case, using the existing runOpenCodeSdk("session.update", ...) and
reusableSessionId path. Add a focused regression test for the race where
session.get succeeds but session.update returns not-found, verifying a new
session is created.

Source: Coding guidelines

apps/web/src/components/ChatView.tsx (1)

854-854: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Use the derived all-threads selector here

store.threads can lag behind hot-path thread updates (syncServerThreadDetailHotPath intentionally leaves the array untouched), so useProviderUsageSummary may show stale rate-limit data. Switch this to the same createAllThreadsSelector() pattern used elsewhere in the app.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/ChatView.tsx` at line 854, The thread list in
ChatView is reading the raw store.threads array, which can be stale after
hot-path updates and lead to outdated usage summaries. Update the selector used
in ChatView to the derived all-threads selector by wiring in
createAllThreadsSelector(), matching the existing pattern used elsewhere and
ensuring useProviderUsageSummary sees the latest thread data.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/wsRpc.ts`:
- Around line 921-923: The project-level worktree aggregation is swallowing
failures from git.listManagedWorktrees via Effect.orElseSucceed, so add logging
before falling back to noManagedWorktrees. Update the managed-worktree
collection flow in wsRpc.ts to use the existing logger/process logger around the
listManagedWorktrees call, emit the project/workspaceRoot and error details when
it fails, and then return noManagedWorktrees so the endpoint still completes.
- Around line 1741-1744: The owner-guard handling in providerBootstrapRuntime
and providerRepairRuntime still uses the older ownerOnly wrapper instead of the
refactored withCurrentSession(requireOwnerWsRpcAccess, ...) pattern used by the
rest of the WS dispatch table. Update these two handlers to match the new
session-based guard approach, using the same style as the nearby provider
methods and preserving the existing rpcEffect error messages. This keeps the
auth-sensitive logic consistent and removes the parallel owner-check
implementation from wsRpc.
- Around line 161-176: The legacy websocket owner bypass in
resolveLocalLegacyWsAuthSession is using a timing-sensitive equality check and
should not be allowed for non-loopback access. Replace the direct authToken vs
legacyToken comparison with a constant-time secret comparison, and ensure this
shortcut only succeeds for loopback requests in the /ws flow. Also verify the
legacy path still goes through the normal session lifecycle so
sessions.markConnected and sessions.markDisconnected are not skipped.

In `@apps/web/src/components/chat/MessagesTimeline.browser.tsx`:
- Around line 23-50: The browser test is bypassing the production integration
path by using ActivityDetailsHarness instead of the real row component that owns
the expand/collapse wiring. Update the test to render SimpleWorkEntryRow from
MessagesTimeline.tsx, or a thin wrapper around it, so it exercises the actual
threading of workEntry and workspaceRoot and verifies the expand button behavior
in the real component tree.

---

Outside diff comments:
In `@apps/server/src/provider/Layers/OpenCodeAdapter.ts`:
- Around line 3928-3934: In startSession inside OpenCodeAdapter, handle a
404/not-found returned by client.session.update as a stale reusable session the
same way session.get is handled, so the code falls through to session.create
instead of failing. Keep non-not-found update errors surfaced by limiting the
fallback to the update-time not-found case, using the existing
runOpenCodeSdk("session.update", ...) and reusableSessionId path. Add a focused
regression test for the race where session.get succeeds but session.update
returns not-found, verifying a new session is created.

In `@apps/web/src/components/ChatView.tsx`:
- Line 854: The thread list in ChatView is reading the raw store.threads array,
which can be stale after hot-path updates and lead to outdated usage summaries.
Update the selector used in ChatView to the derived all-threads selector by
wiring in createAllThreadsSelector(), matching the existing pattern used
elsewhere and ensuring useProviderUsageSummary sees the latest thread data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b745d144-a491-4355-93b5-ae6909a3239c

📥 Commits

Reviewing files that changed from the base of the PR and between 2078d4c and 925223c.

📒 Files selected for processing (12)
  • DESIGN.md
  • apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.ts
  • apps/server/src/wsRpc.ts
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/chat/ChatTranscriptPane.browser.tsx
  • apps/web/src/components/chat/MessagesTimeline.browser.tsx
  • docs/adr/0011-copilot-provider-entry-path.md
  • docs/adr/README.md
  • packages/contracts/src/index.ts
  • packages/contracts/src/settings.ts
🛑 Comments failed to post (4)
apps/server/src/wsRpc.ts (3)

161-176: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Should websocket bearer token comparisons use crypto.timingSafeEqual in Node.js?

💡 Result:

Yes, you should use crypto.timingSafeEqual in Node.js when comparing sensitive information like bearer tokens [1][2][3]. Using standard equality operators (=== or!==) for secrets is vulnerable to timing side-channel attacks [1][2][4]. Because these operators "short-circuit"—they return false as soon as the first mismatched character is found—the time taken to perform the comparison correlates with the number of correct characters in the input [1][2][4]. By measuring response latency over many requests, an attacker can incrementally guess the token character by character [1][5][4]. To implement this correctly: 1. Convert both the received token and the expected (server-side) secret to Buffers or TypedArrays, as crypto.timingSafeEqual only accepts these types [6][7][2]. 2. Use crypto.timingSafeEqual directly [8]. It is designed to perform a constant-time comparison [1][2]. Note on Implementation: Some older implementation patterns suggested adding an explicit length check (e.g., if (a.length !== b.length) return false;) before calling crypto.timingSafeEqual to prevent errors [7][2][3]. However, this can inadvertently leak information about the length of the secret [8]. It is more secure to ensure the comparison is handled in constant time regardless of length [8]. According to common usage patterns in Node.js, simply passing the buffers to crypto.timingSafeEqual is the preferred approach, provided the inputs are correctly normalized to Buffers [8]. Ensure that both inputs are either the same length or padded correctly if you need to strictly maintain length obfuscation, though crypto.timingSafeEqual itself is the standard tool for mitigating the timing oracle vulnerability [2][8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and locate websocket auth/session plumbing.
ast-grep outline apps/server/src/wsRpc.ts --view expanded || true
printf '\n---\n'
rg -n "resolveLocalLegacyWsAuthSession|authenticateWebSocketUpgrade|markConnected|markDisconnected|token query|legacyToken|authToken|wsRpc" apps/server/src -S

Repository: Jay1/jcode

Length of output: 8236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant region of wsRpc.ts with line numbers.
wc -l apps/server/src/wsRpc.ts
printf '\n---\n'
sed -n '1,260p' apps/server/src/wsRpc.ts | cat -n

Repository: Jay1/jcode

Length of output: 10943


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where the websocket upgrade is exposed and how the server is bound.
rg -n "wsRpc|WebSocket|upgrade|listen\\(|127\\.0\\.0\\.1|localhost|0\\.0\\.0\\.0|::" apps/server/src -S

Repository: Jay1/jcode

Length of output: 32429


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the websocket upgrade handler and nearby helpers.
sed -n '1800,1855p' apps/server/src/wsRpc.ts | cat -n

printf '\n---\n'

# Inspect the startup-access helpers that gate loopback/wildcard hosts and remote addresses.
sed -n '1,140p' apps/server/src/startupAccess.ts | cat -n

printf '\n---\n'

# Inspect the server binding / host-selection logic.
sed -n '210,260p' apps/server/src/main.ts | cat -n

Repository: Jay1/jcode

Length of output: 8006


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "legacy websocket|local legacy|legacyToken|resolveLocalLegacyWsAuthSession|bearer-session-token|websocket connections|wsToken|token=" apps/server/src apps/server -S

Repository: Jay1/jcode

Length of output: 16432


Guard the legacy WS owner bypass. This plain === comparison on a bearer secret is timing-sensitive, and the /ws route is reachable whenever the server binds off-loopback. Use a constant-time compare here, and keep the legacy shortcut constrained to loopback-only access; it also skips sessions.markConnected/sessions.markDisconnected.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/wsRpc.ts` around lines 161 - 176, The legacy websocket owner
bypass in resolveLocalLegacyWsAuthSession is using a timing-sensitive equality
check and should not be allowed for non-loopback access. Replace the direct
authToken vs legacyToken comparison with a constant-time secret comparison, and
ensure this shortcut only succeeds for loopback requests in the /ws flow. Also
verify the legacy path still goes through the normal session lifecycle so
sessions.markConnected and sessions.markDisconnected are not skipped.

921-923: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Log project-level listManagedWorktrees failures instead of silently dropping them.

Effect.orElseSucceed(() => noManagedWorktrees) swallows any failure from git.listManagedWorktrees(project.workspaceRoot) with no logging. Since this is the endpoint backing the worktree cleanup-safety UI, a project whose listing fails (e.g. transient git error, permission issue, deleted directory) will simply vanish from the aggregated result with no trace in logs and no signal to the operator — making it hard to diagnose why a worktree that should appear for cleanup review is missing.

🪵 Suggested fix: log the failure before falling back
                 const worktreeGroups = yield* Effect.forEach(
                   snapshot.projects,
                   (project) =>
                     git
                       .listManagedWorktrees(project.workspaceRoot)
-                      .pipe(Effect.orElseSucceed(() => noManagedWorktrees)),
+                      .pipe(
+                        Effect.tapError((cause) =>
+                          Effect.logWarning("Failed to list managed worktrees for project", {
+                            workspaceRoot: project.workspaceRoot,
+                            cause,
+                          }),
+                        ),
+                        Effect.orElseSucceed(() => noManagedWorktrees),
+                      ),
                   { concurrency: 4 },
                 );

Also applies to: 1333-1351

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/wsRpc.ts` around lines 921 - 923, The project-level worktree
aggregation is swallowing failures from git.listManagedWorktrees via
Effect.orElseSucceed, so add logging before falling back to noManagedWorktrees.
Update the managed-worktree collection flow in wsRpc.ts to use the existing
logger/process logger around the listManagedWorktrees call, emit the
project/workspaceRoot and error details when it fails, and then return
noManagedWorktrees so the endpoint still completes.

1741-1744: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Inconsistent owner-guard pattern left over from the refactor.

providerBootstrapRuntime/providerRepairRuntime still use the older ownerOnly/requireOwnerSession helper (defined pre-existing at Lines 898-920), while every other handler in this diff was migrated to withCurrentSession(requireOwnerWsRpcAccess, ...) (e.g. Line 1319, Line 1734). Having two parallel "require owner" implementations in the same auth-sensitive dispatch table is a duplication/consistency risk — a future change to one (e.g. adding audit logging or a new role check) could easily be missed in the other.

♻️ Suggested consolidation
-        [WS_METHODS.providerBootstrapRuntime]: (input) =>
-          rpcEffect(ownerOnly(bootstrapOpenCodeRuntime(input)), "Failed to bootstrap runtime"),
-        [WS_METHODS.providerRepairRuntime]: (input) =>
-          rpcEffect(ownerOnly(repairOpenCodeRuntime(input)), "Failed to repair runtime"),
+        [WS_METHODS.providerBootstrapRuntime]: (input) =>
+          withCurrentSession(
+            requireOwnerWsRpcAccess,
+            rpcEffect(bootstrapOpenCodeRuntime(input), "Failed to bootstrap runtime"),
+          ),
+        [WS_METHODS.providerRepairRuntime]: (input) =>
+          withCurrentSession(
+            requireOwnerWsRpcAccess,
+            rpcEffect(repairOpenCodeRuntime(input), "Failed to repair runtime"),
+          ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        [WS_METHODS.providerBootstrapRuntime]: (input) =>
          withCurrentSession(
            requireOwnerWsRpcAccess,
            rpcEffect(bootstrapOpenCodeRuntime(input), "Failed to bootstrap runtime"),
          ),
        [WS_METHODS.providerRepairRuntime]: (input) =>
          withCurrentSession(
            requireOwnerWsRpcAccess,
            rpcEffect(repairOpenCodeRuntime(input), "Failed to repair runtime"),
          ),
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/wsRpc.ts` around lines 1741 - 1744, The owner-guard handling
in providerBootstrapRuntime and providerRepairRuntime still uses the older
ownerOnly wrapper instead of the refactored
withCurrentSession(requireOwnerWsRpcAccess, ...) pattern used by the rest of the
WS dispatch table. Update these two handlers to match the new session-based
guard approach, using the same style as the nearby provider methods and
preserving the existing rpcEffect error messages. This keeps the auth-sensitive
logic consistent and removes the parallel owner-check implementation from wsRpc.
apps/web/src/components/chat/MessagesTimeline.browser.tsx (1)

23-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test harness bypasses the real integration point.

ActivityDetailsHarness reimplements expand/collapse state and directly wires hasExpandableActivityDetails/ActivityEntryDetails itself, instead of exercising the actual production component (SimpleWorkEntryRow in MessagesTimeline.tsx) that owns this wiring per the stack outline. This test now only verifies the logic functions compose correctly in isolation, not that the real timeline row correctly threads workEntry/workspaceRoot and renders the expand button — regressions in the actual wiring could slip through undetected.

Consider rendering the real row component (or a thin wrapper around it) so this browser test continues to guard the production integration path.
[recommended_refactor]
[test_coverage]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/chat/MessagesTimeline.browser.tsx` around lines 23 -
50, The browser test is bypassing the production integration path by using
ActivityDetailsHarness instead of the real row component that owns the
expand/collapse wiring. Update the test to render SimpleWorkEntryRow from
MessagesTimeline.tsx, or a thin wrapper around it, so it exercises the actual
threading of workEntry and workspaceRoot and verifies the expand button behavior
in the real component tree.

@Jay1 Jay1 merged commit d5708af into main Jul 2, 2026
8 checks passed
@Jay1 Jay1 deleted the feature/t3code-roadmap-integration branch July 2, 2026 03:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant