Skip to content

feat: new features with code review fixes#2

Merged
aaditagrawal merged 3 commits intomainfrom
feat/new-features
Mar 9, 2026
Merged

feat: new features with code review fixes#2
aaditagrawal merged 3 commits intomainfrom
feat/new-features

Conversation

@aaditagrawal
Copy link
Owner

@aaditagrawal aaditagrawal commented Mar 9, 2026

Summary

  • Adds Copilot turn tracking, cursor usage reporting, command palette, plan sidebar, Ghostty terminal split view, project thread navigation, and session text generation
  • Adds CI workflows for release and PR vouch, install script, and desktop build improvements
  • Comprehensive bug fixes identified and applied through two rounds of code review

Bug Fixes

Critical

  • ProviderStartOptions schema: restored opencode/kilo provider fields (6 fields each) that were silently stripped during decoding, and restored value re-export from provider.ts
  • CopilotAdapter lifecycle: restored concurrent-turn guard, session cleanup on creation failure, lastError lifecycle (clear only on idle, not abort), teardown event emission, pending resolver .clear(), reconfigure error guard, attachment validation via Effect error channel

Medium

  • Orchestration: added interrupt re-raise in approval/user-input catchCause handlers; threadProviderOptions cleanup on both session stop and thread deletion
  • Type safety: restored satisfies over as cast for copilot usage result in wsServer
  • React correctness: fixed mutable render variable in CommandPalette (Strict Mode), stale drag closure in Sidebar (ref-based), onInterrupt memoization, duplicate keys in PlanSidebar, timeout cleanups in PlanSidebar and OpenInPicker

Low

  • Extracted assistantDeliveryMode (was duplicated 3x)
  • Settings back button uses router navigate instead of window.history.back()
  • Added onDragLeave handler for project drag indicator
  • os.homedir() instead of process.env.HOME for macOS app detection
  • stopSession errors now logged before swallowing
  • Editor .app detection/launch mismatch on macOS (fallback to open -a for all editors)

Test plan

  • bun typecheck passes (7/7 packages, 0 errors)
  • bun lint passes (0 errors, 19 pre-existing warnings)
  • Manual test: verify opencode/kilo provider sessions start correctly
  • Manual test: verify Copilot turn lifecycle (start, interrupt, error recovery)
  • Manual test: verify command palette, plan sidebar, Ghostty split view
  • Manual test: verify editor open from macOS .app detection path

Summary by CodeRabbit

  • New Features

    • Command palette (Mod/Cmd+K) for quick navigation.
    • Ghostty multi-pane terminal split view.
    • Plan sidebar: view, copy, download, and save plans to workspace.
    • Claude Code effort options and UI controls.
    • Dynamic Cursor provider: model discovery and usage reporting.
    • Project drag‑and‑drop reordering.
    • Server keybinding removal endpoint.
  • Bug Fixes

    • Improved desktop context‑menu spelling suggestions.
    • More reliable editor launches on macOS.
    • Better error reporting when interrupting turns.
  • Documentation

    • Added CONTRIBUTING guide and updated README.

- Add Copilot turn tracking, cursor usage, command palette, plan sidebar,
  ghostty terminal split view, project thread navigation, and session
  text generation
- Fix ProviderStartOptions schema: restore opencode/kilo fields and
  value re-export
- Fix CopilotAdapter: restore concurrent-turn guard, session cleanup on
  failure, lastError lifecycle, teardown event emission, pending resolver
  cleanup, reconfigure error guard, attachment validation Effect channel
- Fix orchestration: interrupt re-raise in approval/user-input handlers,
  threadProviderOptions cleanup on stop and delete
- Fix frontend: CommandPalette render mutation, stale drag closure,
  assistantDeliveryMode dedup, timeout cleanups, onInterrupt memoization,
  PlanSidebar duplicate keys, settings navigation, onDragLeave handler
- Fix server: satisfies vs as type safety, stopSession error logging,
  os.homedir() for macOS paths, editor app detection/launch mismatch
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds multi-provider support and session-scoped text generation; wires provider start options through orchestration, runtime, adapters, and contracts; expands provider discovery/usage (Cursor, Copilot, Claude Code); introduces many web UI features (command palette, plan sidebar, Ghostty), build/release scripts, CI vouch workflow, and numerous tests.

Changes

Cohort / File(s) Summary
GitHub + Docs
\.github/workflows/pr-vouch.yml, \.github/workflows/release.yml, \.github/pull_request_template.md, \.github/VOUCHED.td, CONTRIBUTING.md, README.md
Add PR vouch workflow and trust list; update release workflow to use new scripts and manifest merging; add PR template, contributing guide, and README rewrite describing fork and provider changes.
Session Text Generation & Git
apps/server/src/git/Layers/SessionTextGeneration.ts, apps/server/src/git/Layers/SessionTextGeneration.test.ts, apps/server/src/git/Services/SessionTextGeneration.ts, apps/server/src/git/Layers/GitManager.ts, apps/server/src/git/Layers/GitManager.test.ts, apps/server/src/serverLayers.ts
New SessionTextGeneration layer/service with streaming JSON decoding, timeouts, error normalization; integrated into GitManager with provider/model propagation and selection (session vs default generators).
Provider Contracts & Start Options
packages/contracts/src/orchestration.ts, packages/contracts/src/provider.ts, packages/contracts/src/server.ts, packages/contracts/src/model.ts, packages/contracts/src/git.ts, packages/contracts/src/ws.ts, packages/contracts/src/ipc.ts
Introduce ProviderStartOptions schemas (multiple providers) and redacted variants; expose providerOptions in turn-start payloads; extend server/ws/ipc schemas (including serverRemoveKeybinding types).
Provider Adapters & Usage
apps/server/src/provider/Layers/ClaudeCodeAdapter.ts, .../ClaudeCodeAdapter.test.ts, CopilotAdapter.ts, copilotTurnTracking.ts, .../copilotTurnTracking.test.ts, CursorAdapter.ts, .../CursorAdapter.test.ts, CursorUsage.ts, .../CursorUsage.test.ts, CodexAdapter.ts, ProviderHealth.ts, Services/TextGeneration.ts, ProviderService.ts
Claude Code: desktop env sanitization, diagnostics, effort option; Copilot: turn-tracking, API/usage changes; Cursor: model discovery, parsing, usage API; ProviderHealth adds Copilot probe; providerOptions threaded through runtime and persisted payloads.
Orchestration & Runtime Ingestion
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts, apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts, apps/server/src/orchestration/decider.ts, apps/server/src/opencodeServerManager.ts, apps/server/src/orchestration/redactEvent.ts
Per-thread providerOptions caching/propagation; assistant message segmentation for streaming assistant deltas; enhanced provider error/interrupt handling; event redaction for persisted/broadcast boundaries.
Server Utilities & Checkpointing
apps/server/src/checkpointing/Utils.ts, apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts, .../CheckpointDiffQuery.test.ts, apps/server/scripts/cli.ts
Add resolveExistingThreadWorkspaceCwd (existence-checked), update callsites/tests; enable Windows shell mode for certain spawned child processes in CLI scripts.
Web UI & Components
apps/web/src/components/CommandPalette.tsx, PlanSidebar.tsx, GhosttyTerminalSplitView.tsx, ChatView.tsx, ProjectScriptsControl.tsx, Sidebar.tsx, GitActionsControl.tsx, ui/input.tsx, DiffPanel.tsx, ThreadTerminalDrawer.tsx, Icons.tsx, index.css
Add command palette, plan sidebar (copy/download/save), Ghostty split terminal, Claude Code UI options, project scripts deletion UI, project reordering, PR CTA label change, new editor icons, and font/mono styling.
Web State, Hooks & Store
apps/web/src/hooks/useProjectThreadNavigation.ts, composerDraftStore.ts, store.ts, session-logic.ts, store.test.ts
New project-thread navigation hook, persist Claude Code effort in drafts, project order persistence and moveProject, worklog/timeline tool-activity metadata and helpers.
Web Integration & Transport
apps/web/src/wsServer.ts, apps/web/src/wsServer.test.ts, apps/web/src/wsNativeApi.ts, apps/web/src/lib/gitReactQuery.ts, apps/web/src/keybindings.ts, apps/web/src/keybindings.test.ts, apps/web/src/routes/__root.tsx, apps/web/src/routes/_chat.settings.tsx, apps/web/src/proposedPlan.ts, apps/web/vite.config.ts, apps/web/package.json, apps/web/src/appSettings.ts, apps/web/src/wsTransport.ts
Integrate Cursor model/usage into WS handlers; add serverRemoveKeybinding API and client binding; add command-palette keybinding (mod+k) and matcher; extend git mutation payloads with provider/model; add plan export/download utilities and ghostty-web dep; WebSocket reconnect robustness.
Provider Runtime Tests & Ingestion Tests
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts, apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts, apps/server/src/provider/Services/CursorAdapter.test.ts, apps/server/src/provider/Layers/CursorUsage.test.ts, packages/contracts/src/provider.test.ts, apps/server/src/main.test.ts, apps/server/src/wsServer.test.ts
Add/extend tests for streaming segmentation, Claude env sanitization, Cursor parsing/usage, provider payloads, and adjusted test timeouts/assertions.
Build, Release & Scripts
scripts/install.sh, scripts/build-desktop-artifact.ts, scripts/update-release-package-versions.ts, scripts/merge-mac-update-manifests.ts, scripts/merge-mac-update-manifests.test.ts, scripts/release-smoke.ts, scripts/dev-runner.ts, apps/server/scripts/cli.ts, package.json
Add interactive installer; desktop build bundling changes (Copilot, asarUnpack); new scripts for release package version updates, merging mac manifests (plus tests), release-smoke test; enable Windows shell mode for dev tooling; add npm script release:smoke.
Type & Shared Model Updates
packages/shared/src/model.ts, packages/shared/src/model.test.ts, packages/contracts/src/model.ts, packages/contracts/src/keybindings.ts, packages/contracts/src/editor.ts, packages/contracts/src/git.ts, packages/contracts/src/ws.ts, packages/contracts/src/ipc.ts, packages/contracts/src/server.ts
Expose Claude code effort options/types and defaults; extend Cursor model families and normalization utilities (resolveCursorPickerModelSlug); add new editors; extend Git stacked action input with provider/model; add server keybinding removal types; enrich ServerProviderStatus with models and quota snapshots.

Sequence Diagram(s)

sequenceDiagram
  participant GitManager as GitManager
  participant SessionGen as SessionTextGeneration
  participant Provider as ProviderSession
  participant Repo as Repo/Git
  rect rgba(52,152,219,0.5)
  GitManager->>SessionGen: request generateCommitMessage(input, provider?, model?)
  SessionGen->>Provider: open session + stream events (thread-scoped)
  Provider-->>SessionGen: streaming deltas / JSON chunks
  SessionGen->>SessionGen: assemble & validate JSON, sanitize
  SessionGen-->>GitManager: structured commit/pr suggestion
  GitManager->>Repo: create branch/commit/pr with generated content
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I hopped through code, a multi-provider spree—

Cursor, Copilot, Claude, and Git singing to me;
Sessions that stream, a palette that sings,
Plans in a sidebar and Ghostty with wings;
Carrots for CI—hooray! 🥕

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/new-features

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/wsTransport.ts (1)

41-47: ⚠️ Potential issue | 🟠 Major

Avoid building ws://host: when the page is on the default port.

window.location.port is empty on 80/443, so this fallback becomes ws://example.com: / wss://example.com: and new WebSocket() will reject it. Use window.location.host so the port is only included when it exists.

Suggested fix
     this.url =
       url ??
       (bridgeUrl && bridgeUrl.length > 0
         ? bridgeUrl
         : envUrl && envUrl.length > 0
           ? envUrl
-          : `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.hostname}:${window.location.port}`);
+          : `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/wsTransport.ts` around lines 41 - 47, The URL fallback in the
WebSocket transport builds a host:port string using window.location.port which
is empty on default ports and produces an invalid trailing colon; update the
construction of this.url (in wsTransport.ts, the assignment to this.url) to use
window.location.host instead of window.location.port so the host includes the
port only when present (i.e., use `${window.location.protocol === "https:" ?
"wss" : "ws"}://${window.location.host}`) and keep the existing bridgeUrl/envUrl
precedence logic unchanged.
🟡 Minor comments (8)
apps/server/src/provider/Layers/CodexAdapter.ts-1534-1534 (1)

1534-1534: ⚠️ Potential issue | 🟡 Minor

Use nullish checks for numeric rate-limit fields.

Line 1534 and Line 1565 currently treat 0 as “missing”. If the provider ever returns 0 for resetsAt or windowDurationMins, we’ll drop the reset timestamp and fall back to the generic "Session" label instead of formatting the actual value.

🩹 Proposed fix
-  const resetsAt = bucket.resetsAt ? new Date(bucket.resetsAt * 1000).toISOString() : undefined;
+  const resetsAt =
+    bucket.resetsAt != null ? new Date(bucket.resetsAt * 1000).toISOString() : undefined;
...
-  const sessionLabel = limits.primary?.windowDurationMins
-    ? formatCodexSessionWindowLabel(limits.primary.windowDurationMins)
-    : "Session";
+  const sessionWindowDurationMins = limits.primary?.windowDurationMins;
+  const sessionLabel =
+    sessionWindowDurationMins != null
+      ? formatCodexSessionWindowLabel(sessionWindowDurationMins)
+      : "Session";

Also applies to: 1565-1567

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CodexAdapter.ts` at line 1534, The code
currently treats numeric fields as missing via truthy checks (e.g., const
resetsAt = bucket.resetsAt ? ...) which wrongly drops valid zero values; update
both occurrences to use nullish checks instead so 0 is preserved — for example,
guard with bucket.resetsAt != null (or the nullish coalescing approach) before
constructing new Date(...) for resetsAt, and likewise check
bucket.windowDurationMins != null when computing windowDurationMins/label;
adjust the expressions that reference bucket.resetsAt and
bucket.windowDurationMins in CodexAdapter.ts (the resetsAt and
windowDurationMins assignments/labels) to use these nullish checks.
apps/server/src/provider/claude-agent-sdk.d.ts-90-95 (1)

90-95: ⚠️ Potential issue | 🟡 Minor

Remove readonly modifiers from ThinkingConfig to match actual SDK types.

The ThinkingConfig and EffortLevel type definitions are correctly structured, but the declaration file adds readonly modifiers that don't appear in the actual @anthropic-ai/claude-agent-sdk package types. The actual SDK defines:

  • ThinkingConfig: { type: "adaptive" } | { type: "enabled"; budgetTokens?: number } | { type: "disabled" }
  • EffortLevel: "low" | "medium" | "high" | "max"

Remove readonly from the discriminant and budgetTokens properties to accurately reflect the package API and avoid stricter typing than the actual package provides.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/claude-agent-sdk.d.ts` around lines 90 - 95, The
declared types in the file are too strict: remove the readonly modifiers from
the ThinkingConfig union so its variants are { type: "adaptive" } | { type:
"enabled"; budgetTokens?: number } | { type: "disabled" } and ensure EffortLevel
remains "low" | "medium" | "high" | "max" without readonly; update the
ThinkingConfig declaration (and any affected references) to match the SDK's
shapes exactly by deleting readonly on the discriminant and on budgetTokens.
apps/web/src/keybindings.test.ts-79-80 (1)

79-80: ⚠️ Potential issue | 🟡 Minor

Mirror the real commandPalette.toggle guard in this fixture.

apps/server/src/keybindings.ts Line 64 defines the default binding with when: "!terminalFocus", but DEFAULT_BINDINGS makes it unconditional. These assertions will still pass even if the web matcher starts firing while terminal focus is active.

🔧 Suggested test fixture update
 const DEFAULT_BINDINGS = compile([
-  { shortcut: modShortcut("k"), command: "commandPalette.toggle" },
+  {
+    shortcut: modShortcut("k"),
+    command: "commandPalette.toggle",
+    whenAst: whenNot(whenIdentifier("terminalFocus")),
+  },
   { shortcut: modShortcut("j"), command: "terminal.toggle" },
assert.isFalse(
  isCommandPaletteShortcut(event({ key: "k", metaKey: true }), DEFAULT_BINDINGS, {
    platform: "MacIntel",
    context: { terminalFocus: true },
  }),
);

Also applies to: 254-265

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/keybindings.test.ts` around lines 79 - 80, The test fixture
DEFAULT_BINDINGS is missing the real guard used by the app for the
"commandPalette.toggle" binding, so update the compiled bindings (the object
passed into compile / DEFAULT_BINDINGS) to include when: "!terminalFocus" for
the entry with command "commandPalette.toggle"; likewise update any other test
fixtures around the later block (the other DEFAULT_BINDINGS usage at lines
254-265) so that isCommandPaletteShortcut calls use bindings that include the
when: "!terminalFocus" guard (this will ensure events with terminalFocus true do
not match).
apps/web/src/components/CommandPalette.tsx-382-393 (1)

382-393: ⚠️ Potential issue | 🟡 Minor

Show bound shortcuts for script commands too.

packages/contracts/src/keybindings.ts Lines 22-34 already allow script.<id>.run commands, but these items never populate shortcut. That makes user-defined script bindings invisible in the palette even though the action items surface theirs.

packages/contracts/src/model.ts-19-21 (1)

19-21: ⚠️ Potential issue | 🟡 Minor

Copilot reasoning support is only half-wired here.

This schema now accepts modelOptions.copilot.reasoningEffort, but REASONING_EFFORT_OPTIONS_BY_PROVIDER.copilot and DEFAULT_REASONING_EFFORT_BY_PROVIDER.copilot still advertise no reasoning support on Lines 319-339. Any picker/helper built from those tables will stay out of sync with the contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/contracts/src/model.ts` around lines 19 - 21, The schema
CopilotModelOptions now allows modelOptions.copilot.reasoningEffort, but the
lookup tables REASONING_EFFORT_OPTIONS_BY_PROVIDER and
DEFAULT_REASONING_EFFORT_BY_PROVIDER still omit a "copilot" entry; update those
constants to include a "copilot" key whose options are
CODEX_REASONING_EFFORT_OPTIONS (or the equivalent array/enum used elsewhere) and
set an appropriate default value matching DEFAULT_REASONING_EFFORT_BY_PROVIDER's
shape (e.g., the same default used for codex if intended). Touch the symbols
REASONING_EFFORT_OPTIONS_BY_PROVIDER and DEFAULT_REASONING_EFFORT_BY_PROVIDER to
add the copilot mapping so any pickers/helpers stay in sync with
CopilotModelOptions.
packages/contracts/src/git.ts-57-58 (1)

57-58: ⚠️ Potential issue | 🟡 Minor

Use the same non-empty model contract here.

model is now Schema.String, so "" and whitespace-only overrides are accepted even though the rest of the contracts use TrimmedNonEmptyString for model selection. That makes it easy to pass an explicit-but-invalid model into the git generation path.

Suggested fix
   featureBranch: Schema.optional(Schema.Boolean),
   provider: Schema.optional(ProviderKind),
-  model: Schema.optional(Schema.String),
+  model: Schema.optional(TrimmedNonEmptyStringSchema),
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/contracts/src/git.ts` around lines 57 - 58, The model field
currently uses Schema.optional(Schema.String) which allows empty or
whitespace-only values; change the model contract to use the same non-empty
trimmed string type (e.g., Schema.optional(TrimmedNonEmptyString)) so
empty/whitespace model values are rejected; update the import/usage to reference
TrimmedNonEmptyString alongside ProviderKind and ensure any validations relying
on model continue to work with the new type.
apps/web/src/index.css-39-39 (1)

39-39: ⚠️ Potential issue | 🟡 Minor

Use the new mono token at the call site.

--font-mono is introduced on Line 39 but never used. Line 166 still hardcodes the identical font stack in the pre, code, textarea, input selector, creating duplicate sources of truth.

Update Line 166 to use font-family: var(--font-mono); instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/index.css` at line 39, Replace the hard-coded mono font stack in
the pre, code, textarea, input selector with the new CSS variable --font-mono:
find the selector that currently sets the font-family to "Geist Mono", "SF
Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace and
change it to use font-family: var(--font-mono); so the --font-mono token is the
single source of truth for the mono font stack.
apps/web/src/components/ProjectScriptsControl.tsx-169-169 (1)

169-169: ⚠️ Potential issue | 🟡 Minor

Reset the delete confirmation state when the edit dialog resets.

deleteConfirmOpen is controlled independently from dialogOpen, but the main dialog’s close/reset path never clears it. If the editor closes while the confirmation is open, the alert can stay mounted and bleed into the next add/edit session.

♻️ Proposed fix
   const openAddDialog = () => {
+    setDeleteConfirmOpen(false);
     setEditingScriptId(null);
     setName("");
     setCommand("");
@@
   const openEditDialog = (script: ProjectScript) => {
+    setDeleteConfirmOpen(false);
     setEditingScriptId(script.id);
     setName(script.name);
@@
         onOpenChangeComplete={(open) => {
           if (open) return;
+          setDeleteConfirmOpen(false);
           setEditingScriptId(null);
           setName("");

Also applies to: 488-501

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ProjectScriptsControl.tsx` at line 169, The
deleteConfirmOpen state is not reset when the edit/add dialog is closed, so
ensure you setDeleteConfirmOpen(false) whenever the dialog resets/closes: add
the reset call inside the same close/reset handler used for dialogOpen (e.g.,
the function that closes the editor modal) or add a useEffect that watches
dialogOpen and clears deleteConfirmOpen when dialogOpen becomes false; apply the
same change to the other dialog instance that manages deleteConfirmOpen so the
confirmation alert cannot persist across sessions.
🧹 Nitpick comments (19)
apps/server/src/checkpointing/Utils.ts (1)

14-41: Consider extracting shared input type.

Both resolveThreadWorkspaceCwd and resolveExistingThreadWorkspaceCwd use identical input types. Extracting a shared type alias would reduce duplication.

♻️ Optional: Extract shared type
+type ThreadWorkspaceInput = {
+  readonly thread: {
+    readonly projectId: ProjectId;
+    readonly worktreePath: string | null;
+  };
+  readonly projects: ReadonlyArray<{
+    readonly id: ProjectId;
+    readonly workspaceRoot: string;
+  }>;
+};
+
-export function resolveThreadWorkspaceCwd(input: {
-  readonly thread: {
-    readonly projectId: ProjectId;
-    readonly worktreePath: string | null;
-  };
-  readonly projects: ReadonlyArray<{
-    readonly id: ProjectId;
-    readonly workspaceRoot: string;
-  }>;
-}): string | undefined {
+export function resolveThreadWorkspaceCwd(input: ThreadWorkspaceInput): string | undefined {

Then reuse ThreadWorkspaceInput for resolveExistingThreadWorkspaceCwd.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/checkpointing/Utils.ts` around lines 14 - 41, Extract a
shared input type alias (e.g., ThreadWorkspaceInput) that captures the repeated
shape used by resolveThreadWorkspaceCwd and resolveExistingThreadWorkspaceCwd
(thread with projectId and worktreePath, and projects array with id and
workspaceRoot), then replace the concrete inline types in both function
signatures with that alias so both functions reference the same type definition.
apps/web/src/appSettings.ts (1)

98-101: Derive BuiltInAppModelOption from AppModelOption to avoid drift.

This duplicates the slug/name shape already exported by AppModelOption. Using a derived type keeps both declarations aligned if those shared fields ever change.

♻️ Proposed refactor
-export interface BuiltInAppModelOption {
-  slug: string;
-  name: string;
-}
+export type BuiltInAppModelOption = Pick<AppModelOption, "slug" | "name">;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/appSettings.ts` around lines 98 - 101, The BuiltInAppModelOption
type duplicates fields from AppModelOption; change BuiltInAppModelOption to be
an alias or extension of AppModelOption (e.g., "type BuiltInAppModelOption =
AppModelOption" or "interface BuiltInAppModelOption extends AppModelOption {}")
so the shape is derived from AppModelOption and cannot drift; update the export
of BuiltInAppModelOption accordingly and remove the duplicated slug/name
declaration.
apps/web/src/components/Sidebar.tsx (1)

515-523: Consider extracting the provider exclusion logic.

The hardcoded provider !== "claudeCode" check could become harder to maintain as more providers are added. Consider using a configuration object or adding a property to USAGE_PROVIDERS to indicate whether session usage should be shown.

♻️ Suggested refactor
-const USAGE_PROVIDERS: ReadonlyArray<{ provider: ProviderKind; label: string }> = [
+const USAGE_PROVIDERS: ReadonlyArray<{ provider: ProviderKind; label: string; showSessionUsage?: boolean }> = [
   { provider: "copilot", label: "Copilot" },
   { provider: "codex", label: "Codex" },
   { provider: "cursor", label: "Cursor" },
-  { provider: "claudeCode", label: "Claude Code" },
+  { provider: "claudeCode", label: "Claude Code", showSessionUsage: false },
   { provider: "geminiCli", label: "Gemini" },
   { provider: "amp", label: "Amp" },
 ];

Then in the rendering logic:

-    if (
-      provider !== "claudeCode" &&
-      data?.sessionUsage &&
+    const providerConfig = USAGE_PROVIDERS.find((p) => p.provider === provider);
+    if (
+      providerConfig?.showSessionUsage !== false &&
+      data?.sessionUsage &&
       (data.sessionUsage.totalTokens || data.sessionUsage.totalCostUsd)
     ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/Sidebar.tsx` around lines 515 - 523, Extract the
hardcoded provider exclusion into configuration by adding a boolean like
showSessionUsage (or supportsSessionUsage) to the USAGE_PROVIDERS entries, then
replace the inline check (provider !== "claudeCode") with a lookup against
USAGE_PROVIDERS for the current provider; update the render condition that uses
provider, data?.sessionUsage and ProviderSessionUsageBar to check that the
provider's config allows session usage and that data.sessionUsage has tokens or
cost before pushing ProviderSessionUsageBar.
apps/server/src/provider/Layers/CursorAdapter.ts (1)

183-206: Consider handling stderr output in runCursorModelCommand.

The execFile callback ignores stderr. If the command fails with diagnostic output on stderr, it may be useful for debugging. Consider logging or including stderr in the error.

♻️ Optional: Include stderr in error context
       (error, stdout, stderr) => {
         if (error) {
-          reject(error);
+          const enhancedError = new Error(error.message);
+          (enhancedError as any).stderr = stderr;
+          reject(enhancedError);
           return;
         }
         resolve(stdout);
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CursorAdapter.ts` around lines 183 - 206, The
execFile callback in runCursorModelCommand ignores stderr, losing useful
diagnostic output; update the callback signature to (error, stdout, stderr) and,
when error is non-null, augment the rejected Error with stderr content (e.g.,
append stderr to error.message or wrap it in a new Error with both message and
stderr) or include stderr as a property on the rejected Error; ensure any
successful resolve still returns stdout only, and keep changes localized to the
runCursorModelCommand function and its execFile invocation.
apps/web/src/routes/__root.tsx (1)

38-50: Avoid duplicating provider literals in the web app.

This array is now a second source of truth for ProviderKind. The next provider added in contracts will silently skip usage invalidation here until someone updates both places. Prefer a shared runtime export from contracts instead of hard-coding the literals again.

Also applies to: 218-224

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/__root.tsx` around lines 38 - 50, Replace the hard-coded
PROVIDER_KINDS array and the duplicate isProviderKind logic with a single
runtime export from the contracts package: import the shared provider-kind list
(the runtime export of provider literals) and use that list as the source of
truth (typed as ReadonlyArray<ProviderKind>) wherever PROVIDER_KINDS and
isProviderKind are used (references: PROVIDER_KINDS, isProviderKind,
ProviderKind); remove the duplicated literal array and instead call .includes on
the imported list (and update the second occurrence around lines 218-224) so new
providers in contracts are automatically reflected in the web app.
apps/web/src/proposedPlan.ts (1)

57-67: URL revocation timing may be fragile on some browsers.

Using setTimeout(() => URL.revokeObjectURL(url), 0) revokes the blob URL immediately after the current event loop. While this typically works for small synchronous downloads, it could cause issues on slower systems or with larger files where the browser hasn't yet started the download.

A safer approach is to use a longer delay or listen for user interaction completion, though this pattern is commonly used and works in most cases.

♻️ Optional: Use a longer delay for safer revocation
   anchor.click();
-  window.setTimeout(() => {
-    URL.revokeObjectURL(url);
-  }, 0);
+  // Delay revocation to ensure browser has initiated the download
+  window.setTimeout(() => {
+    URL.revokeObjectURL(url);
+  }, 100);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/proposedPlan.ts` around lines 57 - 67, The current
downloadPlanAsTextFile function revokes the blob URL with setTimeout(..., 0),
which can be too soon; update the URL.revokeObjectURL call so revocation occurs
after the browser has started the download (e.g., replace the 0 delay with a
longer delay like 1000ms or revoke inside a click event handler/after an
onload-confirmation) to avoid cancelling downloads for larger files; adjust the
setTimeout delay or move the revoke into the anchor click handler in
downloadPlanAsTextFile so URL.revokeObjectURL(url) runs only after the download
has had time to begin.
apps/server/src/open.ts (1)

255-288: Consider extracting the repeated macOS app fallback pattern.

The pattern if (platform === "darwin" && !isCommandAvailable(editorDef.command)) { ... isMacAppInstalled ... } appears three times with slight variations. This could be simplified with a helper function.

♻️ Optional: Extract macOS fallback helper
+function resolveMacAppFallback(
+  editorId: EditorId,
+  command: string,
+  cwd: string,
+  extraArgs?: ReadonlyArray<string>,
+): EditorLaunch | null {
+  const macApp = MAC_APP_NAMES[editorId];
+  if (macApp && isMacAppInstalled(macApp)) {
+    return {
+      command: "open",
+      args: ["-a", macApp, ...(extraArgs ?? []), cwd],
+    };
+  }
+  return null;
+}

This would reduce the repeated conditional checks, though the current implementation is also readable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/open.ts` around lines 255 - 288, Extract the repeated macOS
fallback into a helper (e.g. resolveMacOpenFallback or getMacOpenCommand) that
accepts editorDef (or editorDef.id and editorDef.command) and the final args to
use when the .app is installed; the helper should check platform === "darwin" &&
!isCommandAvailable(editorDef.command), look up MAC_APP_NAMES[editorDef.id],
call isMacAppInstalled(macApp) and return the {command: "open", args: [...] }
result (or null/undefined if no fallback), then replace the three repeated
blocks in the code paths that handle shouldUseGotoFlag,
WORKING_DIRECTORY_EDITORS and the final fallback with calls to this helper and
fall back to the original return { command: editorDef.command, args: ... } when
the helper returns nothing; keep references to editorDef.id, editorDef.command,
MAC_APP_NAMES, isCommandAvailable and isMacAppInstalled so the search is easy.
apps/server/src/git/Layers/SessionTextGeneration.test.ts (1)

5-8: Test only verifies export existence, not behavior.

This smoke test confirms SessionTextGenerationLive is defined but doesn't exercise any actual functionality. Consider adding tests that verify the Layer can be built and that its service methods behave correctly.

Would you like me to generate more comprehensive tests for the SessionTextGeneration layer, or should I open an issue to track this?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/SessionTextGeneration.test.ts` around lines 5 - 8,
The current test only asserts that SessionTextGenerationLive is defined; expand
it to construct/build the Layer and exercise its behavior by instantiating the
layer (e.g., call the build/compose factory on SessionTextGenerationLive or the
exported constructor) and invoking key service methods (such as the layer's
generate/session methods or public API) with mocked dependencies to assert
expected outputs and side effects; add mocks/stubs for external dependencies and
assertions for success and error paths to ensure SessionTextGenerationLive not
only exports but functions correctly.
apps/web/src/components/GhosttyTerminalSplitView.tsx (2)

456-460: Global mutable counter may cause issues with HMR and SSR.

The module-level nextPaneCounter variable persists across hot module reloads during development, which can lead to non-deterministic pane IDs. Additionally, in SSR scenarios, this counter could be shared across requests.

Consider using a component-scoped ref or a more robust ID generation approach.

🔧 Alternative using crypto.randomUUID
-let nextPaneCounter = 0;
-function createPaneId(): string {
-  nextPaneCounter += 1;
-  return `ghostty-pane-${nextPaneCounter}-${Date.now().toString(36)}`;
-}
+function createPaneId(): string {
+  return `ghostty-pane-${crypto.randomUUID()}`;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/GhosttyTerminalSplitView.tsx` around lines 456 - 460,
The module-level nextPaneCounter and createPaneId produce non-deterministic IDs
across HMR/SSR; move ID generation into component scope by removing the
top-level nextPaneCounter and instead generate pane IDs inside the
GhosttyTerminalSplitView component using a stable per-instance ref (e.g. useRef
counter) or a robust random UUID (crypto.randomUUID() with a safe fallback)
inside a new createPaneId helper in the component; update all usages of
createPaneId to call the component-scoped version so IDs are not shared across
module reloads or requests.

686-690: SplitDivider resize handlers are not implemented.

The SplitDivider component is rendered with no-op handlers. The visual divider appears but dragging it does nothing. If horizontal pane resizing is intended, this needs implementation; otherwise, consider hiding the divider or adding a TODO.

Would you like me to implement horizontal pane resizing logic, or should I open an issue to track this feature?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/GhosttyTerminalSplitView.tsx` around lines 686 - 690,
The SplitDivider is wired with no-op pointer handlers so dragging does nothing;
implement pointer-based drag resizing for the horizontal split inside
GhosttyTerminalSplitView by wiring onPointerDown to record the initial pointer X
and initial left/right pane sizes (e.g., in component state or refs), call
event.currentTarget.setPointerCapture(event.pointerId), then onPointerMove
compute deltaX from the stored startX to update the left/right pane widths
(update state or CSS variables/flex-basis used by the panes), and onPointerUp
release the pointer capture and clear drag state; update the handlers named
onPointerDown/onPointerMove/onPointerUp on the SplitDivider and ensure you use
the same pointerId for capture/release and clamp sizes to min/max values.
apps/server/src/git/Layers/GitManager.test.ts (1)

287-295: Use the shared ProviderKind in this helper.

This re-hardcodes the provider union in test code. Importing ProviderKind from @t3tools/contracts removes another drift point the next time providers are added or renamed.

♻️ Suggested cleanup
+import type { ProviderKind } from "@t3tools/contracts";
+
 function runStackedAction(
   manager: GitManagerShape,
   input: {
     cwd: string;
     action: "commit" | "commit_push" | "commit_push_pr";
     commitMessage?: string;
     featureBranch?: boolean;
-    provider?: "codex" | "copilot" | "claudeCode" | "cursor" | "opencode" | "geminiCli" | "amp" | "kilo";
+    provider?: ProviderKind;
     model?: string;
   },
 ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/GitManager.test.ts` around lines 287 - 295, The
test helper runStackedAction currently hardcodes the provider union type;
replace that union with the shared ProviderKind from `@t3tools/contracts` by
importing ProviderKind and using it for the provider parameter type in
runStackedAction (and any related test helpers/signatures in GitManager.test.ts)
so the test stays in sync when providers change.
apps/server/src/provider/Layers/CursorUsage.ts (4)

45-55: Consider adding a timeout to execFile to prevent indefinite hangs.

The execFileText function doesn't specify a timeout, which could cause the process to hang indefinitely if the security command stalls.

♻️ Proposed fix
 function execFileText(command: string, args: ReadonlyArray<string>): Promise<string> {
   return new Promise((resolve, reject) => {
-    execFile(command, [...args], { env: process.env }, (error, stdout) => {
+    execFile(command, [...args], { env: process.env, timeout: 10_000 }, (error, stdout) => {
       if (error) {
         reject(error);
         return;
       }
       resolve(stdout);
     });
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CursorUsage.ts` around lines 45 - 55, The
execFileText helper (execFileText) can hang because execFile is called without a
timeout; update execFile invocation to include a reasonable timeout option
(e.g., { env: process.env, timeout: <ms> }) and handle the timeout error path
when the callback returns an error (ensure promise rejects with that error and
consider including context). Locate execFileText and add the timeout option to
the execFile options object and ensure existing error handling covers timeout
errors from execFile.

79-88: Consider the security implication of treating tokens without expiration as fresh.

When decodeJwtExpirationMs returns undefined (e.g., malformed JWT or missing exp claim), hasFreshAccessToken returns true. This is a reasonable fallback to avoid blocking users, but could potentially use an expired or invalid token.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CursorUsage.ts` around lines 79 - 88, The
current hasFreshAccessToken function treats tokens with undefined expiration as
fresh, which is risky; change hasFreshAccessToken to treat undefined expiration
as not fresh (return false) or perform an additional verification step instead
of assuming freshness: update the function (hasFreshAccessToken) to return false
when decodeJwtExpirationMs(token) === undefined, and optionally add a
debug/error log indicating a malformed/missing exp claim so callers can trigger
a token refresh path; ensure callers of hasFreshAccessToken (e.g., whatever uses
CursorUsage) continue to handle refresh when false.

163-179: Consider adding request timeout and error logging for API key exchange.

The exchangeCursorApiKey function doesn't have a timeout and silently returns undefined on failure. While this may be acceptable for a best-effort operation, logging failed attempts would aid debugging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CursorUsage.ts` around lines 163 - 179, The
exchangeCursorApiKey function lacks a request timeout and does not log failures;
wrap the fetch in a try/catch, use an AbortController with a short timeout
(e.g., 5s) to cancel slow requests, and on any non-ok response or caught error
log a clear message including CURSOR_API_BASE_URL and the error/response status
before returning undefined; keep the return behavior (use
normalizeNonEmptyString(parsed.accessToken)) but ensure errors/timeouts are
logged (use the module's logger if available, otherwise console.error).

146-161: Auth data merging may cause unexpected behavior with stale tokens.

When hasFreshAccessToken(fileAuth.accessToken) is false, the code fetches from Keychain but then spreads fileAuth first followed by Keychain values. This means Keychain values override file values, which is the intended behavior. However, if Keychain returns undefined for accessToken, the stale fileAuth.accessToken will be used via the first spread.

This seems intentional as a fallback strategy, but the comment could clarify this behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CursorUsage.ts` around lines 146 - 161,
readCursorAuthData currently falls back to a stale fileAuth.accessToken when
Keychain returns undefined; change the merge so that fileAuth.accessToken is
only used if it is fresh (use hasFreshAccessToken(fileAuth.accessToken)) and
otherwise prefer Keychain values or undefined—update the return object in
readCursorAuthData to explicitly set accessToken to accessToken ??
(hasFreshAccessToken(fileAuth.accessToken) ? fileAuth.accessToken : undefined)
and apiKey to apiKey ?? fileAuth.apiKey, or add a clarifying comment explaining
this exact fallback behavior if the fallback is intentional.
apps/web/src/components/PlanSidebar.tsx (1)

100-130: Consider using .finally() instead of double .then() for cleanup.

The current pattern with two .then() callbacks (one for success, one for rejection of the catch) works but is less idiomatic than using .finally().

♻️ Proposed fix
     void api.projects
       .writeFile({
         cwd: workspaceRoot,
         relativePath: filename,
         contents: normalizePlanMarkdownForExport(planMarkdown),
       })
       .then((result) => {
         toastManager.add({
           type: "success",
           title: "Plan saved",
           description: result.relativePath,
         });
       })
       .catch((error) => {
         toastManager.add({
           type: "error",
           title: "Could not save plan",
           description:
             error instanceof Error ? error.message : "An error occurred.",
         });
       })
-      .then(
-        () => setIsSavingToWorkspace(false),
-        () => setIsSavingToWorkspace(false),
-      );
+      .finally(() => setIsSavingToWorkspace(false));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/PlanSidebar.tsx` around lines 100 - 130, In
handleSaveToWorkspace, replace the current promise chain on
api.projects.writeFile (used with .then(...).catch(...).then(...)) with a chain
that uses .then(...) for success, .catch(...) for errors, and a .finally(...) to
call setIsSavingToWorkspace(false) for cleanup; keep the success toast logic
inside the first .then, error toast logic inside .catch, and ensure you still
early-return when !api || !workspaceRoot || !planMarkdown while preserving
buildProposedPlanMarkdownFilename, normalizePlanMarkdownForExport, and
readNativeApi usage.
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts (1)

523-536: Consider the memory implications of using Map for unbounded segment state.

assistantSegmentStateByKey and assistantSegmentKeysByTurnKey are plain Map objects without TTL or capacity limits, unlike the Cache instances used elsewhere. While cleanup functions exist, a failure to clean up (e.g., due to an error before turn.completed) could cause memory growth.

The existing cleanup on session.exited via clearTurnStateForSession mitigates this, but consider whether the lifetime matches expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts` around
lines 523 - 536, The two plain Maps assistantSegmentStateByKey and
assistantSegmentKeysByTurnKey can grow unbounded and risk memory leaks; replace
them with bounded caches (use Cache.make) or wrap them with TTL/capacity logic
similar to bufferedProposedPlanById so entries auto-evict, or at minimum add
defensive eviction on errors and ensure clearTurnStateForSession is always
invoked (e.g., use finally hooks around turn lifecycle); update references to
assistantSegmentStateByKey and assistantSegmentKeysByTurnKey accordingly so
lookups/updates use the new Cache API and preserve existing cleanup semantics.
apps/server/src/git/Layers/SessionTextGeneration.ts (1)

150-165: Consider adding event filtering before forking to reduce unnecessary queue operations.

The current implementation forks a scoped fiber that filters events by threadId inside the loop. While functionally correct, events for other threads are still queued before being discarded. This is acceptable given the short-lived nature of these sessions, but could be optimized if high event throughput becomes an issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/SessionTextGeneration.ts` around lines 150 - 165,
The Stream.runForEach is currently checking event.threadId inside the loop which
still invokes Queue.offer for every event; instead create a filtered stream
before forking so only matching events are processed: use Stream.filter on
providerService.streamEvents with (e) => e.threadId === threadId and then
runForEach to Queue.offer(eventQueue, event). Keep the same
Effect.asVoid/Effect.forkScoped usage and reference Stream.runForEach,
providerService.streamEvents, event.threadId, threadId, Queue.offer, eventQueue
and Effect.forkScoped when making the change.
apps/server/src/provider/Layers/CopilotAdapter.ts (1)

1625-1626: Consider documenting the internal API dependency.

The function accesses an undocumented internal RPC API via complex type assertion. This is brittle and could break with Copilot SDK updates.

📝 Suggested improvement

Consider adding a comment documenting this dependency and the fallback behavior:

 try {
   await client.start();
+  // NOTE: getQuota is an internal RPC endpoint; may break on SDK updates.
+  // Gracefully returns partial result on failure.
   const quota = await (client as unknown as { rpc: { account: { getQuota: () => Promise<{ quotaSnapshots?: unknown }> } } }).rpc.account.getQuota().catch(() => undefined);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CopilotAdapter.ts` around lines 1625 - 1626,
The code in CopilotAdapter.ts uses a brittle internal RPC access via the
expression (client as unknown as { rpc: { account: { getQuota: () => Promise<{
quotaSnapshots?: unknown }> } } }).rpc.account.getQuota() and silently falls
back to returning { provider: "copilot" } when that call fails; add an inline
comment immediately above this expression that documents the internal API
dependency (that we rely on client.rpc.account.getQuota returning
quotaSnapshots), why the complex type assertion is necessary, and clearly state
the intended fallback behavior when the RPC is absent or throws so future
maintainers know this is an internal, unstable API and why we return the default
provider object.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4abd7b9a-3c0c-4815-ae63-208255ae49ac

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd5b85 and 34b1e34.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (96)
  • .github/VOUCHED.td
  • .github/pull_request_template.md
  • .github/workflows/pr-vouch.yml
  • .github/workflows/release.yml
  • CONTRIBUTING.md
  • README.md
  • apps/desktop/src/main.ts
  • apps/server/scripts/cli.ts
  • apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts
  • apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts
  • apps/server/src/checkpointing/Utils.ts
  • apps/server/src/git/Layers/GitManager.test.ts
  • apps/server/src/git/Layers/GitManager.ts
  • apps/server/src/git/Layers/SessionTextGeneration.test.ts
  • apps/server/src/git/Layers/SessionTextGeneration.ts
  • apps/server/src/git/Services/SessionTextGeneration.ts
  • apps/server/src/git/Services/TextGeneration.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/main.test.ts
  • apps/server/src/open.test.ts
  • apps/server/src/open.ts
  • apps/server/src/opencodeServerManager.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
  • apps/server/src/orchestration/decider.ts
  • apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
  • apps/server/src/provider/Layers/CodexAdapter.ts
  • apps/server/src/provider/Layers/CopilotAdapter.ts
  • apps/server/src/provider/Layers/CursorAdapter.test.ts
  • apps/server/src/provider/Layers/CursorAdapter.ts
  • apps/server/src/provider/Layers/CursorUsage.test.ts
  • apps/server/src/provider/Layers/CursorUsage.ts
  • apps/server/src/provider/Layers/ProviderHealth.ts
  • apps/server/src/provider/Layers/ProviderService.ts
  • apps/server/src/provider/Layers/copilotCliPath.ts
  • apps/server/src/provider/Layers/copilotTurnTracking.test.ts
  • apps/server/src/provider/Layers/copilotTurnTracking.ts
  • apps/server/src/provider/Services/CursorAdapter.test.ts
  • apps/server/src/provider/Services/CursorAdapter.ts
  • apps/server/src/provider/claude-agent-sdk.d.ts
  • apps/server/src/serverLayers.ts
  • apps/server/src/wsServer.test.ts
  • apps/server/src/wsServer.ts
  • apps/web/index.html
  • apps/web/package.json
  • apps/web/src/appSettings.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.tsx
  • apps/web/src/components/DiffPanel.tsx
  • apps/web/src/components/GhosttyTerminalSplitView.tsx
  • apps/web/src/components/GitActionsControl.logic.test.ts
  • apps/web/src/components/GitActionsControl.logic.ts
  • apps/web/src/components/GitActionsControl.tsx
  • apps/web/src/components/Icons.tsx
  • apps/web/src/components/PlanSidebar.tsx
  • apps/web/src/components/ProjectScriptsControl.tsx
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/ThreadTerminalDrawer.tsx
  • apps/web/src/components/ui/input.tsx
  • apps/web/src/composerDraftStore.ts
  • apps/web/src/hooks/useProjectThreadNavigation.ts
  • apps/web/src/index.css
  • apps/web/src/keybindings.test.ts
  • apps/web/src/keybindings.ts
  • apps/web/src/lib/gitReactQuery.ts
  • apps/web/src/proposedPlan.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/session-logic.test.ts
  • apps/web/src/session-logic.ts
  • apps/web/src/store.test.ts
  • apps/web/src/store.ts
  • apps/web/src/wsTransport.ts
  • apps/web/vite.config.ts
  • package.json
  • packages/contracts/src/editor.ts
  • packages/contracts/src/git.ts
  • packages/contracts/src/keybindings.test.ts
  • packages/contracts/src/keybindings.ts
  • packages/contracts/src/model.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/provider.test.ts
  • packages/contracts/src/provider.ts
  • packages/contracts/src/server.ts
  • packages/shared/src/model.test.ts
  • packages/shared/src/model.ts
  • scripts/build-desktop-artifact.ts
  • scripts/dev-runner.ts
  • scripts/install.sh
  • scripts/merge-mac-update-manifests.test.ts
  • scripts/merge-mac-update-manifests.ts
  • scripts/release-smoke.ts
  • scripts/update-release-package-versions.ts
  • update

readonly model?: string;
readonly modelOptions?: ProviderModelOptions;
readonly serviceTier?: ProviderServiceTier | null;
readonly providerOptions?: ProviderStartOptions;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provider option updates never reach active sessions.

These options are only consumed by startSession(). If a thread already has a live session and only providerOptions change, ensureSessionForThread() keeps the existing session and sendTurn() has no way to apply the new values. The update silently does nothing until some unrelated restart happens.

Also applies to: 220-222, 236-240, 255-255

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/orchestration/Layers/ProviderCommandReactor.ts` at line 210,
The ProviderStartOptions stored on the reactor are only applied in
startSession(), so when providerOptions changes for an already-active thread
ensureSessionForThread() keeps the old session and sendTurn() never sees the
update; change the flow so updates to providerOptions propagate to live sessions
by detecting option changes in ensureSessionForThread() (or a new
applyProviderOptionsToSession(session, providerOptions) helper) and
reconfiguring or restarting the existing session as needed, then call that
helper from places that currently only call startSession() (including sendTurn()
entry paths) so active sessions get the new ProviderStartOptions immediately;
reference ProviderStartOptions, startSession(), ensureSessionForThread(),
sendTurn() and the providerOptions property when implementing.

- ProviderCommandReactor: don't mark session stopped after failed stop;
  document provider options only apply at session start
- ClaudeCodeAdapter: move executable path resolution inside Effect.try
  for proper error mapping in packaged environments
- ProviderHealth: replace unsafe getQuotaInfo cast with documented
  client.rpc.account.getQuota() SDK API
- ProviderService: preserve providerOptions across all directory.upsert
  calls (sendTurn, recovery, stopAll) to prevent silent data loss
- ChatView: add removeKeybinding API to clean up stale shortcuts on
  script deletion; document snapshot resync trade-offs
- CommandPalette: catch async onSelect failures to prevent unhandled
  promise rejections
- ProjectScriptsControl: await onDeleteScript and surface errors via
  validation state instead of fire-and-forget
- Settings: add aria-label to provider accent color pickers
- wsTransport: close sockets that finish connecting after disposal
- orchestration.ts: redact credentials (username/password) from
  persisted event payloads; use PositiveInt for port validation
- install.sh: read prompts from /dev/tty for curl|bash compatibility;
  fix word-splitting vulnerability in find_bin path probing
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/components/ChatView.tsx (1)

2611-2641: ⚠️ Potential issue | 🟡 Minor

The drag-over indicator can get stuck after hovering nested children.

Line 2616 increments dragDepthRef for bubbled dragenters from descendants, but Lines 2634-2641 only decrement when the pointer leaves the composer entirely. After moving across children, the counter can remain above zero and keep isDragOverComposer stuck true. Either rely only on the containment check here or make the enter/leave bookkeeping symmetrical.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ChatView.tsx` around lines 2611 - 2641, The drag
enter/leave bookkeeping is asymmetric: onComposerDragEnter increments
dragDepthRef for bubbled enters, but onComposerDragLeave only decrements when
leaving the composer entirely, which can leave dragDepthRef > 0 and keep
isDragOverComposer true; fix by making leave decrement symmetrically — in
onComposerDragLeave decrement dragDepthRef.current (clamped to >=0) for every
leave event, then check event.relatedTarget containment (using
event.currentTarget.contains(event.relatedTarget)) and only call
setIsDragOverComposer(false) when dragDepthRef.current === 0 and the
relatedTarget is outside; keep the preventDefault/Files type guard and ensure no
negative counts.
♻️ Duplicate comments (2)
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts (1)

240-244: ⚠️ Potential issue | 🟠 Major

Apply providerOptions diffs to active sessions.

Lines 240-244 update the cache, but Lines 295-297 reuse the current session unless the provider/runtime/model changed. A providerOptions-only change is still a silent no-op until some unrelated restart. Treat option changes as a restart/reconfigure trigger too.

Also applies to: 295-297

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/orchestration/Layers/ProviderCommandReactor.ts` around lines
240 - 244, The code updates threadProviderOptions but does not treat
providerOptions-only changes as a reconfigure trigger; compare the new
effectiveProviderOptions (from options?.providerOptions ??
threadProviderOptions.get(threadId)) against the previously stored
providerOptions for threadId and, if different, force a session
restart/reconfiguration rather than reusing the current session. Modify the
session-reuse logic (the check around lines that reuse the current session
unless provider/runtime/model changed) to include providerOptions in its
equality check, or clear/reset the cached session entry for threadId when
threadProviderOptions.set(threadId, options.providerOptions) is executed so that
the code path that creates a new session runs. Ensure you reference the same
threadId and threadProviderOptions keys when performing the comparison/reset.
apps/web/src/components/ChatView.tsx (1)

3328-3359: ⚠️ Potential issue | 🔴 Critical

Don't roll back a live implementation thread because a client-side resync/navigation step failed.

The chain on Lines 3291-3339 puts getSnapshot(), syncServerReadModel(), and navigate() under the same catch that deletes nextThreadId on Lines 3345-3351. If thread.create and thread.turn.start succeed but the snapshot fetch, local sync, or navigation fails, this will still delete a valid server thread and discard the newly-started implementation run. Keep rollback scoped to failures before the server-side create/start succeeds, and let the orchestration.domainEvent websocket unblock client visibility/navigation instead of snapshot resyncs. As per coding guidelines Web app must consume orchestration domain events via WebSocket push on channel orchestration.domainEvent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ChatView.tsx` around lines 3328 - 3359, The catch
currently deletes the server thread (dispatchCommand type "thread.delete") for
any failure including client-side snapshot sync/navigation; change this so
rollback only happens if the server-side create/start did not succeed: introduce
a local flag (e.g., threadCreated or threadStarted) that's set when the
create/start RPCs return successfully and only invoke
api.orchestration.dispatchCommand({type: "thread.delete", commandId:
newCommandId(), threadId: nextThreadId}) inside the catch when that flag is
false; keep the snapshot re-sync (api.orchestration.getSnapshot ->
syncServerReadModel) and planSidebarOpenOnNextThreadRef/navigate logic as
separate flows and rely on orchestration.domainEvent websocket updates for
eventual visibility instead of deleting a live thread on client-side
navigation/sync failures.
🧹 Nitpick comments (2)
scripts/install.sh (1)

274-275: NODE_BIN is assigned but never used.

The variable is set here but the script never references it afterward. Unlike GIT_BIN (used in step_clone), Node.js is only checked for presence. Consider removing the assignment or documenting why it's retained for future use.

♻️ Option 1: Remove the unused assignment
   log_ok "Node.js found: ${BOLD}${node_bin}${RESET} (${node_ver})"
-  NODE_BIN="$node_bin"
   return 0

Also remove from line 405:

-  GIT_BIN="" NODE_BIN="" BUN_BIN=""
+  GIT_BIN="" BUN_BIN=""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.sh` around lines 274 - 275, The NODE_BIN variable is assigned
from node_bin but never used; remove the unused assignment(s) to NODE_BIN (the
initial NODE_BIN="$node_bin" and the later duplicate assignment) and any related
dead code, or if you intend to keep it for future use, add a brief comment
explaining why it’s retained; reference the variables NODE_BIN and node_bin and
note that GIT_BIN is different (used in step_clone) for context.
apps/server/src/provider/Layers/ProviderHealth.ts (1)

491-491: Fix semantically incorrect return value in error handler.

The catch(() => []) returns an empty array, which is semantically incorrect for a stop() operation. While this doesn't affect runtime behavior since the result is discarded, it's misleading.

🔧 Proposed fix
       } finally {
-        await client.stop().catch(() => []);
+        await client.stop().catch(() => undefined);
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ProviderHealth.ts` at line 491, The error
handler currently returns an empty array from the promise chain for
client.stop(), which is semantically wrong; update the call that does await
client.stop().catch(() => []) to instead swallow the error with a void/undefined
return (e.g., .catch(() => undefined) or .catch(() => {}) ), or log the error
and return undefined, so the catch reflects a no-value stop failure for the
client.stop() invocation in ProviderHealth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/orchestration/decider.ts`:
- Around line 21-35: The redaction in redactProviderStartOptions currently
strips opencode/kilo credentials too early, preventing ProviderCommandReactor
(which reads thread.turn-start-requested and calls providerService.startSession)
from seeing providerOptions; change the flow so redactProviderStartOptions is
only used when creating the persisted event shape (materializing events) and do
not modify the in-memory event passed to ProviderCommandReactor or any code that
calls providerService.startSession; keep the raw providerOptions (with
opencode/kilo) on the live event and move calls to redactProviderStartOptions to
the persistence/serialization path (also update the other instance referenced at
the 317-325 location to follow the same pattern).

In `@apps/server/src/orchestration/Layers/ProviderCommandReactor.ts`:
- Around line 699-701: In the "thread.deleted" case inside
ProviderCommandReactor, stop any active provider session for that thread before
deleting its options to avoid orphaning resources: call the existing
session-teardown helper (e.g., stopProviderSession(threadId) or
stopActiveSession(threadId) — whichever function is defined in this module)
using event.payload.threadId, wait for it to complete (await if async), then
call threadProviderOptions.delete(event.payload.threadId) and return; this
ensures any running provider is cleanly stopped prior to removing its config.

In `@apps/server/src/provider/Layers/ClaudeCodeAdapter.ts`:
- Around line 266-279: The desktop diagnostic helpers diagnosticEnvKeys and
logDesktopClaudeDiagnostic currently expose too much sensitive data; restrict
diagnostic_env collection to a safe allowlist of specific non-secret keys (e.g.,
only include approved keys like APP_VERSION, PLATFORM, CLAUDE_CLIENT_VERSION)
instead of returning all DESKTOP_DIAGNOSTIC_ENV_PREFIXES matches, and change
logDesktopClaudeDiagnostic to never log raw stderr by default—either redact
sensitive text or require an explicit opt-in flag (e.g., a redactStderr=false
option) before emitting full stderr; update the functions (diagnosticEnvKeys,
logDesktopClaudeDiagnostic) and any callers to use the allowlist and
opt-in/redaction behavior and ensure only minimal non-sensitive fields and
summarized/error codes are written to console.warn.

In `@apps/web/src/components/ChatView.tsx`:
- Around line 6691-6702: The copyPath callback sets copiedPath before
navigator.clipboard.writeText resolves, causing false-positive UI on failures;
update the useCallback (copyPath) to await
navigator.clipboard.writeText(openInCwd) and only call setCopiedPath(true) after
the promise resolves, move the timeout setup/clearTimeout logic inside the
success path, and add a catch to handle/write failures (e.g., log or surface an
error) so failed writes do not show the success state.

In `@scripts/install.sh`:
- Around line 131-136: The case block handling OS_RAW contains overlapping
patterns where "MINGW*" precedes "MINGW64*", making "MINGW64*" unreachable;
update the case patterns in the OS detection switch (the case for OS_RAW) so the
more specific "MINGW64*" is either removed or moved before the more general
"MINGW*" (or simply keep a single "MINGW*" pattern) to avoid unreachable
alternatives while preserving the mapping to OS="windows".

---

Outside diff comments:
In `@apps/web/src/components/ChatView.tsx`:
- Around line 2611-2641: The drag enter/leave bookkeeping is asymmetric:
onComposerDragEnter increments dragDepthRef for bubbled enters, but
onComposerDragLeave only decrements when leaving the composer entirely, which
can leave dragDepthRef > 0 and keep isDragOverComposer true; fix by making leave
decrement symmetrically — in onComposerDragLeave decrement dragDepthRef.current
(clamped to >=0) for every leave event, then check event.relatedTarget
containment (using event.currentTarget.contains(event.relatedTarget)) and only
call setIsDragOverComposer(false) when dragDepthRef.current === 0 and the
relatedTarget is outside; keep the preventDefault/Files type guard and ensure no
negative counts.

---

Duplicate comments:
In `@apps/server/src/orchestration/Layers/ProviderCommandReactor.ts`:
- Around line 240-244: The code updates threadProviderOptions but does not treat
providerOptions-only changes as a reconfigure trigger; compare the new
effectiveProviderOptions (from options?.providerOptions ??
threadProviderOptions.get(threadId)) against the previously stored
providerOptions for threadId and, if different, force a session
restart/reconfiguration rather than reusing the current session. Modify the
session-reuse logic (the check around lines that reuse the current session
unless provider/runtime/model changed) to include providerOptions in its
equality check, or clear/reset the cached session entry for threadId when
threadProviderOptions.set(threadId, options.providerOptions) is executed so that
the code path that creates a new session runs. Ensure you reference the same
threadId and threadProviderOptions keys when performing the comparison/reset.

In `@apps/web/src/components/ChatView.tsx`:
- Around line 3328-3359: The catch currently deletes the server thread
(dispatchCommand type "thread.delete") for any failure including client-side
snapshot sync/navigation; change this so rollback only happens if the
server-side create/start did not succeed: introduce a local flag (e.g.,
threadCreated or threadStarted) that's set when the create/start RPCs return
successfully and only invoke api.orchestration.dispatchCommand({type:
"thread.delete", commandId: newCommandId(), threadId: nextThreadId}) inside the
catch when that flag is false; keep the snapshot re-sync
(api.orchestration.getSnapshot -> syncServerReadModel) and
planSidebarOpenOnNextThreadRef/navigate logic as separate flows and rely on
orchestration.domainEvent websocket updates for eventual visibility instead of
deleting a live thread on client-side navigation/sync failures.

---

Nitpick comments:
In `@apps/server/src/provider/Layers/ProviderHealth.ts`:
- Line 491: The error handler currently returns an empty array from the promise
chain for client.stop(), which is semantically wrong; update the call that does
await client.stop().catch(() => []) to instead swallow the error with a
void/undefined return (e.g., .catch(() => undefined) or .catch(() => {}) ), or
log the error and return undefined, so the catch reflects a no-value stop
failure for the client.stop() invocation in ProviderHealth.

In `@scripts/install.sh`:
- Around line 274-275: The NODE_BIN variable is assigned from node_bin but never
used; remove the unused assignment(s) to NODE_BIN (the initial
NODE_BIN="$node_bin" and the later duplicate assignment) and any related dead
code, or if you intend to keep it for future use, add a brief comment explaining
why it’s retained; reference the variables NODE_BIN and node_bin and note that
GIT_BIN is different (used in step_clone) for context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1ff86c89-7d23-4b55-90fa-06cef11329ec

📥 Commits

Reviewing files that changed from the base of the PR and between 34b1e34 and 7e919c6.

📒 Files selected for processing (18)
  • apps/server/src/keybindings.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
  • apps/server/src/orchestration/decider.ts
  • apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
  • apps/server/src/provider/Layers/ProviderHealth.ts
  • apps/server/src/provider/Layers/ProviderService.ts
  • apps/server/src/wsServer.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.tsx
  • apps/web/src/components/ProjectScriptsControl.tsx
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/wsNativeApi.ts
  • apps/web/src/wsTransport.ts
  • packages/contracts/src/ipc.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/server.ts
  • packages/contracts/src/ws.ts
  • scripts/install.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/components/ProjectScriptsControl.tsx
  • apps/web/src/components/CommandPalette.tsx

Comment on lines +266 to +279
function diagnosticEnvKeys(env: Readonly<Record<string, string | undefined>>): ReadonlyArray<string> {
return Object.keys(env)
.filter((key) => DESKTOP_DIAGNOSTIC_ENV_PREFIXES.some((prefix) => key.startsWith(prefix)))
.sort();
}

function logDesktopClaudeDiagnostic(message: string, data?: Record<string, unknown>): void {
if (!isDesktopRuntime()) return;
if (data) {
console.warn("[claudeCode][desktop]", message, data);
return;
}
console.warn("[claudeCode][desktop]", message);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tighten the new desktop diagnostics before they start carrying user data.

This now logs cwd, every matching CLAUDE*/CODEX*/T3CODE* env key name, and raw Claude stderr. That combination can leak workspace paths, credential-related env metadata, or prompt/file content into persistent desktop logs. Please restrict diagnostics to a small safe allowlist and make raw stderr opt-in or redacted.

🔐 Possible tightening
-const DESKTOP_DIAGNOSTIC_ENV_PREFIXES = ["CLAUDE", "ELECTRON", "CODEX", "T3CODE"] as const;
+const DESKTOP_DIAGNOSTIC_ENV_KEYS = [
+  "ELECTRON_RUN_AS_NODE",
+  "ELECTRON_RENDERER_PORT",
+  "CLAUDECODE",
+  "T3CODE_MODE",
+] as const;

 function diagnosticEnvKeys(env: Readonly<Record<string, string | undefined>>): ReadonlyArray<string> {
-  return Object.keys(env)
-    .filter((key) => DESKTOP_DIAGNOSTIC_ENV_PREFIXES.some((prefix) => key.startsWith(prefix)))
-    .sort();
+  return DESKTOP_DIAGNOSTIC_ENV_KEYS.filter((key) => env[key] !== undefined);
 }
@@
                 stderr: (message: string) => {
                   const trimmed = message.trimEnd();
-                  if (trimmed.length > 0) {
-                    console.warn("[claudeCode][stderr]", trimmed);
-                  }
+                  if (trimmed.length === 0) return;
+                  if (process.env.T3CODE_DEBUG_CLAUDE_STDERR === "1") {
+                    console.warn("[claudeCode][stderr]", trimmed);
+                    return;
+                  }
+                  console.warn("[claudeCode][stderr]", "Claude CLI wrote to stderr.");
                 },

Also applies to: 1741-1760

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ClaudeCodeAdapter.ts` around lines 266 - 279,
The desktop diagnostic helpers diagnosticEnvKeys and logDesktopClaudeDiagnostic
currently expose too much sensitive data; restrict diagnostic_env collection to
a safe allowlist of specific non-secret keys (e.g., only include approved keys
like APP_VERSION, PLATFORM, CLAUDE_CLIENT_VERSION) instead of returning all
DESKTOP_DIAGNOSTIC_ENV_PREFIXES matches, and change logDesktopClaudeDiagnostic
to never log raw stderr by default—either redact sensitive text or require an
explicit opt-in flag (e.g., a redactStderr=false option) before emitting full
stderr; update the functions (diagnosticEnvKeys, logDesktopClaudeDiagnostic) and
any callers to use the allowlist and opt-in/redaction behavior and ensure only
minimal non-sensitive fields and summarized/error codes are written to
console.warn.

CodeRabbit round 3:
- decider.ts: move credential redaction to persistence/WS boundaries
  so ProviderCommandReactor receives full providerOptions at runtime
- ProviderCommandReactor: stop active session on thread.deleted before
  cleanup; don't mark session stopped after failed stop
- ChatView: await clipboard.writeText before showing success indicator;
  fix asymmetric drag enter/leave bookkeeping; scope thread rollback to
  pre-start failures only
- ProviderHealth: fix catch(() => []) to catch(() => undefined)
- install.sh: remove unreachable MINGW64* pattern; remove unused NODE_BIN

Internal specialist audit:
- Security: redact credentials in ProviderService runtimePayload before
  SQLite persistence (all 3 upsert boundaries)
- Remove 6 unused imports in ChatView.tsx (lint warnings eliminated)
- Extract toMessage() to shared module (deduplicated across 4 adapters)
- CursorAdapter: early return in extractCursorChunkText to prevent
  duplicate text when both text and content fields are populated
- SessionTextGeneration: handle session.exited event to fail fast
  instead of blocking 180s on provider process crash
- CopilotAdapter: eliminate double trimToUndefined call
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.

1 participant