Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: SDK stream fiber is detached and unmanaged
- Replaced
Effect.runForkwithEffect.forkChildto keep the fiber in the managed runtime, stored the fiber reference in session context, and added explicitFiber.interruptinstopSessionInternalbefore queue teardown to eliminate the race window.
- Replaced
- ✅ Fixed: Identity function adds unnecessary indirection
- Removed the no-op
asCanonicalTurnIdidentity function and inlined theTurnIdvalue directly at all 10 call sites.
- Removed the no-op
Or push these changes by commenting:
@cursor push 2efc9d6c0c
Preview (2efc9d6c0c)
diff --git a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
--- a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
+++ b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
@@ -34,7 +34,7 @@
ThreadId,
TurnId,
} from "@t3tools/contracts";
-import { Cause, DateTime, Deferred, Effect, Layer, Queue, Random, Ref, Stream } from "effect";
+import { Cause, DateTime, Deferred, Effect, Fiber, Layer, Queue, Random, Ref, Stream } from "effect";
import {
ProviderAdapterProcessError,
@@ -106,6 +106,7 @@
lastAssistantUuid: string | undefined;
lastThreadStartedId: string | undefined;
stopped: boolean;
+ streamFiber: Fiber.Fiber<void, never> | undefined;
}
interface ClaudeQueryRuntime extends AsyncIterable<SDKMessage> {
@@ -144,10 +145,6 @@
return RuntimeItemId.makeUnsafe(value);
}
-function asCanonicalTurnId(value: TurnId): TurnId {
- return value;
-}
-
function asRuntimeRequestId(value: ApprovalRequestId): RuntimeRequestId {
return RuntimeRequestId.makeUnsafe(value);
}
@@ -505,7 +502,7 @@
...(typeof message.session_id === "string"
? { providerThreadId: message.session_id }
: {}),
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
...(itemId ? { itemId: ProviderItemId.makeUnsafe(itemId) } : {}),
payload: message,
},
@@ -613,7 +610,7 @@
provider: PROVIDER,
createdAt: stamp.createdAt,
threadId: context.session.threadId,
- ...(turnState ? { turnId: asCanonicalTurnId(turnState.turnId) } : {}),
+ ...(turnState ? { turnId: turnState.turnId } : {}),
payload: {
message,
class: "provider_error",
@@ -640,7 +637,7 @@
provider: PROVIDER,
createdAt: stamp.createdAt,
threadId: context.session.threadId,
- ...(turnState ? { turnId: asCanonicalTurnId(turnState.turnId) } : {}),
+ ...(turnState ? { turnId: turnState.turnId } : {}),
payload: {
message,
...(detail !== undefined ? { detail } : {}),
@@ -855,7 +852,7 @@
provider: PROVIDER,
createdAt: stamp.createdAt,
threadId: context.session.threadId,
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
itemId: asRuntimeItemId(tool.itemId),
payload: {
itemType: tool.itemType,
@@ -896,7 +893,7 @@
provider: PROVIDER,
createdAt: stamp.createdAt,
threadId: context.session.threadId,
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
itemId: asRuntimeItemId(tool.itemId),
payload: {
itemType: tool.itemType,
@@ -1006,7 +1003,7 @@
provider: PROVIDER,
createdAt: stamp.createdAt,
threadId: context.session.threadId,
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
providerRefs: {
...providerThreadRef(context),
...(context.turnState ? { providerTurnId: context.turnState.turnId } : {}),
@@ -1165,7 +1162,7 @@
provider: PROVIDER,
createdAt: stamp.createdAt,
threadId: context.session.threadId,
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
providerRefs: {
...providerThreadRef(context),
...(context.turnState ? { providerTurnId: context.turnState.turnId } : {}),
@@ -1295,6 +1292,11 @@
context.stopped = true;
+ if (context.streamFiber) {
+ yield* Fiber.interrupt(context.streamFiber);
+ context.streamFiber = undefined;
+ }
+
for (const [requestId, pending] of context.pendingApprovals) {
yield* Deferred.succeed(pending.decision, "cancel");
const stamp = yield* makeEventStamp();
@@ -1304,7 +1306,7 @@
provider: PROVIDER,
createdAt: stamp.createdAt,
threadId: context.session.threadId,
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
requestId: asRuntimeRequestId(requestId),
payload: {
requestType: pending.requestType,
@@ -1442,7 +1444,7 @@
provider: PROVIDER,
createdAt: requestedStamp.createdAt,
threadId: context.session.threadId,
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
requestId: asRuntimeRequestId(requestId),
payload: {
requestType,
@@ -1494,7 +1496,7 @@
provider: PROVIDER,
createdAt: resolvedStamp.createdAt,
threadId: context.session.threadId,
- ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
+ ...(context.turnState ? { turnId: context.turnState.turnId } : {}),
requestId: asRuntimeRequestId(requestId),
payload: {
requestType,
@@ -1610,6 +1612,7 @@
lastAssistantUuid: resumeState?.resumeSessionAt,
lastThreadStartedId: undefined,
stopped: false,
+ streamFiber: undefined,
};
yield* Ref.set(contextRef, context);
sessions.set(threadId, context);
@@ -1658,7 +1661,7 @@
providerRefs: {},
});
- Effect.runFork(runSdkStream(context));
+ context.streamFiber = yield* Effect.forkChild(runSdkStream(context));
return {
...session,2b53034 to
1beeff2
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Native event logger missing threadId in event payload
- Added
threadId: context.session.threadIdto the event object and passedcontext.session.threadIdinstead ofnullas the second argument tonativeEventLogger.write(), enabling per-thread log routing consistent with the Codex adapter.
- Added
Or push these changes by commenting:
@cursor push 8489584240
Preview (8489584240)
diff --git a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
--- a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
+++ b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
@@ -502,6 +502,7 @@
provider: PROVIDER,
createdAt: observedAt,
method: sdkNativeMethod(message),
+ threadId: context.session.threadId,
...(typeof message.session_id === "string"
? { providerThreadId: message.session_id }
: {}),
@@ -510,7 +511,7 @@
payload: message,
},
},
- null,
+ context.session.threadId,
);
});4afd04a to
3af67f5
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Redundant duplicate
threadIdassignment in session construction- Removed the redundant
...(threadId ? { threadId } : {})spread at line 1587 sincethreadIdis already set directly at line 1581 and is always defined.
- Removed the redundant
Or push these changes by commenting:
@cursor push 1970102289
Preview (1970102289)
diff --git a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
--- a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
+++ b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts
@@ -1584,7 +1584,6 @@
runtimeMode: input.runtimeMode,
...(input.cwd ? { cwd: input.cwd } : {}),
...(input.model ? { model: input.model } : {}),
- ...(threadId ? { threadId } : {}),
resumeCursor: {
...(threadId ? { threadId } : {}),
...(resumeState?.resume ? { resume: resumeState.resume } : {}),| async respondToUserInput( | ||
| threadId: ThreadId, | ||
| requestId: ApprovalRequestId, | ||
| answers: ProviderUserInputAnswers, | ||
| ): Promise<void> { | ||
| const context = this.requireSession(threadId); | ||
| const pendingRequest = context.pendingUserInputs.get(requestId); | ||
| if (!pendingRequest) { | ||
| throw new Error(`Unknown pending user input request: ${requestId}`); | ||
| } | ||
|
|
||
| context.pendingUserInputs.delete(requestId); | ||
| const codexAnswers = toCodexUserInputAnswers(answers); | ||
| this.writeMessage(context, { |
There was a problem hiding this comment.
🟡 Medium src/codexAppServerManager.ts:807
In respondToUserInput, context.pendingUserInputs.delete(requestId) is called before validating answers via toCodexUserInputAnswers. If conversion throws, the request record is permanently lost but no response reaches the provider, leaving the session corrupted and blocking retries. Move the deletion after successful validation and conversion.
async respondToUserInput(
threadId: ThreadId,
requestId: ApprovalRequestId,
answers: ProviderUserInputAnswers,
): Promise<void> {
const context = this.requireSession(threadId);
const pendingRequest = context.pendingUserInputs.get(requestId);
if (!pendingRequest) {
throw new Error(`Unknown pending user input request: ${requestId}`);
}
- context.pendingUserInputs.delete(requestId);
const codexAnswers = toCodexUserInputAnswers(answers);
+ context.pendingUserInputs.delete(requestId);
this.writeMessage(context, {
id: pendingRequest.jsonRpcId,
result: {🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/codexAppServerManager.ts around lines 807-820:
In `respondToUserInput`, `context.pendingUserInputs.delete(requestId)` is called before validating `answers` via `toCodexUserInputAnswers`. If conversion throws, the request record is permanently lost but no response reaches the provider, leaving the session corrupted and blocking retries. Move the deletion after successful validation and conversion.
Evidence trail:
apps/server/src/codexAppServerManager.ts lines 807-825 (REVIEWED_COMMIT): `respondToUserInput` method showing delete at line 818, conversion at line 819
apps/server/src/codexAppServerManager.ts lines 358-381 (REVIEWED_COMMIT): `toCodexUserInputAnswer` function that throws Error at line 370, and `toCodexUserInputAnswers` that calls it
Line 818: `context.pendingUserInputs.delete(requestId);`
Line 819: `const codexAnswers = toCodexUserInputAnswers(answers);`
Line 370: `throw new Error("User input answers must be strings or arrays of strings.");`
| this.runPromise = services ? Effect.runPromiseWith(services) : Effect.runPromise; | ||
| } | ||
|
|
||
| async startSession(input: CodexAppServerStartSessionInput): Promise<ProviderSession> { |
There was a problem hiding this comment.
🟠 High src/codexAppServerManager.ts:428
startSession overwrites this.sessions.set(threadId, context) without checking if a session already exists for that threadId. This orphans the previous process (which keeps running) and leaves its exit handler active. When the orphaned process later exits, that handler calls this.sessions.delete(threadId), which removes the new session from the map. Subsequent calls like sendTurn then fail with "Unknown session" because the entry was deleted by the stale handler.
Also found in 1 other location(s)
apps/server/src/provider/Layers/CodexAdapter.ts:1262
The
nativeEventLoggercreated whenoptions.nativeEventLogPathis provided is never closed. Whilemanageris wrapped inEffect.acquireReleasefor cleanup,nativeEventLoggeris instantiated separately and itsclose()method is never called. This causes file handles opened by the logger to leak every time theCodexAdapterlayer is released (e.g., during configuration reloads or tests).
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/codexAppServerManager.ts around line 428:
`startSession` overwrites `this.sessions.set(threadId, context)` without checking if a session already exists for that `threadId`. This orphans the previous process (which keeps running) and leaves its `exit` handler active. When the orphaned process later exits, that handler calls `this.sessions.delete(threadId)`, which removes the *new* session from the map. Subsequent calls like `sendTurn` then fail with "Unknown session" because the entry was deleted by the stale handler.
Evidence trail:
apps/server/src/codexAppServerManager.ts: lines 428-471 show `startSession` calls `this.sessions.set(threadId, context)` without checking for existing session; line 943 shows exit handler calls `this.sessions.delete(context.session.threadId)` where `context` is captured in closure; lines 890-893 show `requireSession` throws 'Unknown session' when entry not in map; lines 606-607 show `sendTurn` uses `requireSession`.
Also found in 1 other location(s):
- apps/server/src/provider/Layers/CodexAdapter.ts:1262 -- The `nativeEventLogger` created when `options.nativeEventLogPath` is provided is never closed. While `manager` is wrapped in `Effect.acquireRelease` for cleanup, `nativeEventLogger` is instantiated separately and its `close()` method is never called. This causes file handles opened by the logger to leak every time the `CodexAdapter` layer is released (e.g., during configuration reloads or tests).
| export const OpenCodeIcon: Icon = (props) => ( | ||
| <svg {...props} viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| <g clipPath="url(#opencode__clip0_1311_94969)"> | ||
| <path d="M24 32H8V16H24V32Z" fill="#BCBBBB" /> | ||
| <path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#211E1E" /> | ||
| </g> | ||
| <defs> | ||
| <clipPath id="opencode__clip0_1311_94969"> | ||
| <rect width="32" height="40" fill="white" /> | ||
| </clipPath> | ||
| </defs> | ||
| </svg> | ||
| ); |
There was a problem hiding this comment.
🟢 Low components/Icons.tsx:329
The OpenCodeIcon component uses hardcoded fill attributes (#211E1E and #BCBBBB) on its SVG paths. In dark mode, the #211E1E dark grey fill renders the icon nearly invisible due to low contrast against dark backgrounds. Unlike GitHubIcon and OpenAI, which use currentColor to inherit the surrounding text color, this icon fails to adapt to the theme.
- <svg {...props} viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <svg {...props} viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<g clipPath="url(#opencode__clip0_1311_94969)">
- <path d="M24 32H8V16H24V32Z" fill="#BCBBBB" />
- <path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#211E1E" />
+ <path d="M24 32H8V16H24V32Z" fill="currentColor" />
+ <path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="currentColor" />🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/web/src/components/Icons.tsx around lines 329-341:
The `OpenCodeIcon` component uses hardcoded `fill` attributes (`#211E1E` and `#BCBBBB`) on its SVG paths. In dark mode, the `#211E1E` dark grey fill renders the icon nearly invisible due to low contrast against dark backgrounds. Unlike `GitHubIcon` and `OpenAI`, which use `currentColor` to inherit the surrounding text color, this icon fails to adapt to the theme.
Evidence trail:
apps/web/src/components/Icons.tsx lines 329-342 (OpenCodeIcon with hardcoded fills #211E1E and #BCBBBB), lines 5-15 (GitHubIcon with fill="currentColor" at line 12), lines 146-150 (OpenAI with fill="currentColor" at line 147). Verified at commit REVIEWED_COMMIT.
|
I prepared a CI fix PR targeting this branch: #243\n\nIt updates stale ClaudeCodeAdapter test expectations for thread identity/providerThreadId behavior and aligns with current adapter semantics. |
Fix detached SDK stream fiber by replacing Effect.runFork with Effect.forkChild and adding explicit Fiber.interrupt on session stop. Add missing threadId to native event logger for per-thread log routing. Remove no-op asCanonicalTurnId identity function. Remove redundant threadId spread in session construction. Fix stale test expectations for thread identity behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…apters Merge the Claude Code adapter (PR pingdotgg#179) into main, resolving 45 conflicts caused by the deliberate split of provider stacks on March 5. Key additions: - ClaudeCodeAdapter with full session, turn, and resume lifecycle - Cursor provider support (model catalog, UI, routing) - ProviderKind expanded from "codex" to "codex" | "claudeCode" | "cursor" - Provider model catalogs, aliases, and slug resolution across all providers - UI support for Claude Code and Cursor in ChatView, settings, and composer Conflict resolution strategy: - Kept HEAD's refactored patterns (scoped finalizers, telemetry, serviceTier) - Added PR's new provider routing, adapters, and UI components - Fixed duplicate declarations, missing props, and type mismatches Typecheck: 7/7 packages pass Lint: 0 warnings, 0 errors Tests: 419/422 pass (3 pre-existing failures in ClaudeCodeAdapter.test.ts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove filtro que escondia o Claude Code do seletor de providers, tornando-o selecionável após o merge do adapter (PR pingdotgg#179). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add first-class Claude Code support alongside existing Codex provider: - Add ClaudeCodeAdapter (1857 lines) backed by @anthropic-ai/claude-agent-sdk - Extend ProviderKind to accept "codex" | "claudeCode" - Add Claude model catalog (Opus 4.6, Sonnet 4.6, Haiku 4.5) - Register ClaudeCodeAdapter in provider registry and server layers - Add Claude SDK event sources to provider runtime events - Enable Claude Code in UI provider picker - Add ClaudeCodeProviderStartOptions to contracts - Update all tests for multi-provider support Based on upstream PR pingdotgg#179 by juliusmarminge, surgically applied to current main. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merges codething/648ca884-claude branch which adds full Claude Code provider adapter, orchestration enhancements, and UI surface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| }); | ||
| return; | ||
| } | ||
| const tool = context.inFlightTools.get(index); |
There was a problem hiding this comment.
🟢 Low Layers/ClaudeAdapter.ts:1365
In the content_block_stop handler (line 1354), when the stopped block corresponds to a tool (not an assistant text block), the tool is fetched from context.inFlightTools at line 1365 but execution silently falls through without emitting any event. This means the final accumulated tool input from input_json_delta streaming is never published as an item.updated event when the content block finishes — the UI will show stale/partial tool input until the tool result message arrives later. The input_json_delta handler only emits updates when the fingerprint changes, so any final delta that didn't alter the fingerprint is lost. Add an item.updated emission (following the same pattern as the input_json_delta handler at lines 1256–1281) after line 1368 to publish the tool's final input state.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/ClaudeAdapter.ts around line 1365:
In the `content_block_stop` handler (line 1354), when the stopped block corresponds to a tool (not an assistant text block), the tool is fetched from `context.inFlightTools` at line 1365 but execution silently falls through without emitting any event. This means the final accumulated tool input from `input_json_delta` streaming is never published as an `item.updated` event when the content block finishes — the UI will show stale/partial tool input until the tool result message arrives later. The `input_json_delta` handler only emits updates when the fingerprint changes, so any final delta that didn't alter the fingerprint is lost. Add an `item.updated` emission (following the same pattern as the `input_json_delta` handler at lines 1256–1281) after line 1368 to publish the tool's final input state.
Evidence trail:
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 1354-1369: `content_block_stop` handler shows the tool is fetched at line 1365 (`const tool = context.inFlightTools.get(index);`), then lines 1366-1368 only check `if (!tool) { return; }` with no code to handle when tool exists - the handler ends at line 1369.
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 1242-1249: `input_json_delta` handler shows the fingerprint check that prevents emission when fingerprint is unchanged: `if (!parsedInput || !nextFingerprint || tool.lastEmittedInputFingerprint === nextFingerprint) { return; }`
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 1256-1281: Shows the `item.updated` event emission pattern in `input_json_delta` handler that is missing from the `content_block_stop` tool handling.
|
@juliusmarminge thanks for all the work you’ve put into this! Do you have an estimate for when it might be ready to land? |
- Add dedicated Claude traits picker with model-aware effort, thinking, and fast mode controls - Treat Claude Ultrathink as a prompt keyword instead of session effort - Normalize provider model options in composer flow and adapter, with tests for unsupported effort/thinking cases
There was a problem hiding this comment.
🟡 Medium
t3code/apps/web/src/components/ChatView.tsx
Line 2339 in a75a9b4
When sending only images, the optimistic user message displays the internal IMAGE_ONLY_BOOTSTRAP_PROMPT text ("[User attached one or more images...]") in the chat bubble instead of showing only the attached images. This happens because onSend sets text: outgoingMessageText on line 2344, where outgoingMessageText falls back to the bootstrap prompt when the user provided no text. Previously the optimistic message used an empty text field for image-only sends, which correctly hid the internal instruction from users. Consider using an empty string for the optimistic message's text field when the original trimmed prompt was empty, keeping only the attachments visible.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/web/src/components/ChatView.tsx around line 2339:
When sending only images, the optimistic user message displays the internal `IMAGE_ONLY_BOOTSTRAP_PROMPT` text (`"[User attached one or more images...]"`) in the chat bubble instead of showing only the attached images. This happens because `onSend` sets `text: outgoingMessageText` on line 2344, where `outgoingMessageText` falls back to the bootstrap prompt when the user provided no text. Previously the optimistic message used an empty `text` field for image-only sends, which correctly hid the internal instruction from users. Consider using an empty string for the optimistic message's `text` field when the original `trimmed` prompt was empty, keeping only the attachments visible.
Evidence trail:
apps/web/src/components/ChatView.tsx lines 172-173: IMAGE_ONLY_BOOTSTRAP_PROMPT definition; lines 2319-2320: outgoingMessageText uses `trimmed || IMAGE_ONLY_BOOTSTRAP_PROMPT` fallback; line 2344: optimistic message sets `text: outgoingMessageText`
There was a problem hiding this comment.
🟢 Low
t3code/apps/web/src/components/ChatView.tsx
Line 181 in a75a9b4
formatOutgoingPrompt checks params.effort === "ultrathink" to apply the prompt prefix, but selectedPromptEffort is computed from selectedClaudeBaseEffort which explicitly excludes "ultrathink". This branch is unreachable — the ultrathink prompt prefix is never applied during send, only when ClaudeTraitsMenuContent directly edits the prompt. Consider removing the dead branch or ensuring the prefix is applied consistently when ultrathink is active.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/web/src/components/ChatView.tsx around line 181:
`formatOutgoingPrompt` checks `params.effort === "ultrathink"` to apply the prompt prefix, but `selectedPromptEffort` is computed from `selectedClaudeBaseEffort` which explicitly excludes `"ultrathink"`. This branch is unreachable — the ultrathink prompt prefix is never applied during send, only when `ClaudeTraitsMenuContent` directly edits the prompt. Consider removing the dead branch or ensuring the prefix is applied consistently when ultrathink is active.
Evidence trail:
apps/web/src/components/ChatView.tsx:181-189 (formatOutgoingPrompt checks params.effort === "ultrathink"), apps/web/src/components/ChatView.tsx:570 (selectedPromptEffort = selectedCodexEffort ?? selectedClaudeBaseEffort), apps/web/src/components/ChatView.tsx:539-543 (selectedCodexEffort is null when provider is claudeAgent), apps/web/src/components/ChatView.tsx:548-565 (selectedClaudeBaseEffort explicitly excludes "ultrathink" at line 557: draftEffort !== "ultrathink"), apps/web/src/components/ChatView.tsx:2317-2320,2723-2726,2839-2842 (all calls pass selectedPromptEffort as effort)
|
@akarabach you can just clone this branch, and build the application from here. it works. i have been using it. |
|
@juliusmarminge when actually using it as a DMG i noticed that there is a small bug that it doesnt use the system claude binaries but instead the bundles sdks binaries. I filled #1189 into this branch. |
- Merge upstream Claude traits/model/UI changes from pingdotgg#179. - Add ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN overrides and wire them through WS, server, Codex spawn env, and Claude env. - Skip claude auth probe when external Anthropic token is configured. Made-with: Cursor
…esolution Merge origin/codething/648ca884-claude into main, resolving conflicts in: - apps/web/src/appSettings.ts (keep both textGenerationModel and customClaudeModels) - apps/web/src/components/ChatView.tsx (take PR's restructured compositor layout) - apps/web/src/composerDraftStore.ts (take PR's modelOptions migration, restore prompt/terminalContexts processing) - packages/contracts/src/model.ts (keep both git text generation model and backward compat exports) Additional fixes on top of the merge: - Add terminalContexts to browser test mock drafts (ClaudeTraitsPicker, CodexTraitsPicker, CompactComposerControlsMenu) - Restore ensureInlineTerminalContextPlaceholders call in draft deserialization - Add terminalContexts and onRemoveTerminalContext props to ComposerPromptEditor - Use DEFAULT_MODEL_BY_PROVIDER[selectedProvider] instead of hardcoded .codex - Persist provider/model before clearComposerDraftContent to prevent reset Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merges latest commits from pingdotgg#179 including: - Storage refactor (composerDraftStore extracted resolveModelOptions) - setState pattern refactored in browser test fixtures - Upstream main merged in (terminal context, sidebar fixes, git text gen) - Button overflow fix Conflict resolution: - Keep Haven Code fork identity (Bedrock settings, enableCodexProvider, claude-haiku-4-5 as default git text gen model, Bedrock badge in picker) - Accept upstream refactors (draftsByThreadId variable pattern, resolveModelOptions callback, CSS improvements) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Im not sure if this is ready for reviews and such yet, but these errors when testing it out. On NixOS btw |
Co-authored-by: codex <codex@users.noreply.github.com>
| const sendTurn: ClaudeAdapterShape["sendTurn"] = (input) => | ||
| Effect.gen(function* () { | ||
| const context = yield* requireSession(input.threadId); | ||
|
|
||
| if (context.turnState) { | ||
| // Auto-close a stale synthetic turn (from background agent responses | ||
| // between user prompts) to prevent blocking the user's next turn. | ||
| yield* completeTurn(context, "completed"); | ||
| } |
There was a problem hiding this comment.
🟡 Medium Layers/ClaudeAdapter.ts:2511
In sendTurn, the code at lines 2515-2519 calls completeTurn(context, "completed") whenever context.turnState exists, without checking whether the turn is a synthetic background turn or an active user-initiated turn. This causes any ongoing user turn to be incorrectly marked as completed when the user sends a new message, even if the previous turn was still processing. The comment claims this only closes "stale synthetic turns," but the condition if (context.turnState) matches all turns. Consider adding a flag to distinguish synthetic turns from user turns, or checking that the turn is actually synthetic before auto-completing it.
- if (context.turnState) {
- // Auto-close a stale synthetic turn (from background agent responses
- // between user prompts) to prevent blocking the user's next turn.
- yield* completeTurn(context, "completed");
- }🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/ClaudeAdapter.ts around lines 2511-2519:
In `sendTurn`, the code at lines 2515-2519 calls `completeTurn(context, "completed")` whenever `context.turnState` exists, without checking whether the turn is a synthetic background turn or an active user-initiated turn. This causes any ongoing user turn to be incorrectly marked as completed when the user sends a new message, even if the previous turn was still processing. The comment claims this only closes "stale synthetic turns," but the condition `if (context.turnState)` matches all turns. Consider adding a flag to distinguish synthetic turns from user turns, or checking that the turn is actually synthetic before auto-completing it.
Evidence trail:
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 2515-2518 (REVIEWED_COMMIT): shows `if (context.turnState)` condition with comment claiming it's for synthetic turns only.
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 85-93 (REVIEWED_COMMIT): `ClaudeTurnState` interface has no flag to distinguish synthetic vs user turns.
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 1592-1606 (REVIEWED_COMMIT): synthetic turn creation - same ClaudeTurnState structure as user turns.
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 2545-2554 (REVIEWED_COMMIT): user turn creation - identical ClaudeTurnState structure with no distinguishing flag.
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 2043-2053 (REVIEWED_COMMIT): `requireSession` only checks session existence, not status, so `sendTurn` can be called while a turn is active.
|
@JustYannicc If I'm looking to build the desktop app using this branch should I rather use the one for #1189 ? |
- add decoding defaults in `AppSettingsSchema` so older persisted settings load safely - export shared `Schema.Literals` types for `EnvMode` and `TimestampFormat` - add a regression test covering pre-new-key settings hydration
|
@TaylorJonesTRT if you just want to use the build command. It doesn't matter. But if you want to build a DMG then you need to use the other Branch I created. It's just a small bug that only happens in the DMG but not with build commands. |
…update Merge upstream PR pingdotgg#179 updates (Claude adapter + storage refactor)
|
how are you building it for macOS? It’s failing when I try to package it. @JustYannicc, could you share the scripts you’re using? |
- Map unknown pending approval/user-input errors to explicit stale-request details - Clear stale pending approvals and user-input prompts in session derivation logic - Treat reused Claude text block indexes as new assistant messages to avoid item ID reuse - Persist latest runtime metadata before provider stop-all and expand regression coverage
| @@ -498,6 +526,17 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => | |||
| const runStopAll = () => | |||
There was a problem hiding this comment.
🟡 Medium Layers/ProviderService.ts:526
In runStopAll, the loop at lines 534-539 saves active session state via upsertSessionBinding, but the subsequent loop at lines 543-553 immediately overwrites every thread binding with a minimal runtimePayload that only contains activeTurnId, lastRuntimeEvent, and lastRuntimeEventAt. This drops cwd, model, modelOptions, and providerOptions that were just preserved, breaking session recovery after restart. Consider removing the second loop or merging it with the first to preserve the full session state.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/ProviderService.ts around line 526:
In `runStopAll`, the loop at lines 534-539 saves active session state via `upsertSessionBinding`, but the subsequent loop at lines 543-553 immediately overwrites every thread binding with a minimal `runtimePayload` that only contains `activeTurnId`, `lastRuntimeEvent`, and `lastRuntimeEventAt`. This drops `cwd`, `model`, `modelOptions`, and `providerOptions` that were just preserved, breaking session recovery after restart. Consider removing the second loop or merging it with the first to preserve the full session state.
Evidence trail:
- apps/server/src/provider/Layers/ProviderService.ts lines 526-557 (the `runStopAll` function showing both loops)
- apps/server/src/provider/Layers/ProviderService.ts lines 171-188 (`upsertSessionBinding` function calling `directory.upsert` with `toRuntimePayloadFromSession`)
- apps/server/src/provider/Layers/ProviderService.ts lines 89-109 (`toRuntimePayloadFromSession` function returning `cwd`, `model`, `activeTurnId`, `lastError` from session)
- First loop (lines 534-539) saves session state including `cwd`, `model` via `upsertSessionBinding`
- Second loop (lines 543-553) calls `directory.upsert` directly with minimal `runtimePayload` containing only `activeTurnId`, `lastRuntimeEvent`, `lastRuntimeEventAt`
- Both loops use same `directory.upsert`, so second loop overwrites first loop's data
| return typeof value === "number" && Number.isFinite(value) ? value : undefined; | ||
| } | ||
|
|
||
| function toTurnId(value: string | undefined): TurnId | undefined { |
There was a problem hiding this comment.
🟢 Low Layers/CodexAdapter.ts:112
toTurnId checks value?.trim() for truthiness but then passes the original untrimmed value to TurnId.makeUnsafe. If the input contains leading/trailing whitespace (e.g., " abc "), the check passes but a TurnId containing whitespace is created. Consider using TurnId.makeUnsafe(value.trim()) to match the validation logic.
-function toTurnId(value: string | undefined): TurnId | undefined {
- return value?.trim() ? TurnId.makeUnsafe(value) : undefined;
-}🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/CodexAdapter.ts around line 112:
`toTurnId` checks `value?.trim()` for truthiness but then passes the original untrimmed `value` to `TurnId.makeUnsafe`. If the input contains leading/trailing whitespace (e.g., `" abc "`), the check passes but a `TurnId` containing whitespace is created. Consider using `TurnId.makeUnsafe(value.trim())` to match the validation logic.
Evidence trail:
apps/server/src/provider/Layers/CodexAdapter.ts lines 112-113 at REVIEWED_COMMIT - shows `toTurnId` function checking `value?.trim()` but passing untrimmed `value` to `makeUnsafe`.
packages/contracts/src/baseSchemas.ts lines 14-29 at REVIEWED_COMMIT - shows `TurnId` is created via `makeEntityId` using `TrimmedNonEmptyString` schema, confirming values should be trimmed.
- Reuse persisted `resumeCursor` when restarting a Claude session for the same thread/provider - Generate a fresh Claude `sessionId` only for new sessions; avoid reusing stale checkpoint fields - Keep resume state when only model options change, with tests covering restart and resume behavior
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>


Summary
This PR adds the Claude Code adapter on top of the core orchestration branch in #103.
It includes:
Stack
codething/648ca884Validation
bun lintbun typecheckcd apps/server && bun run test -- --run src/provider/Layers/ProviderAdapterRegistry.test.tscd apps/web && bun run test -- --run src/session-logic.test.tsNote
High Risk
Adds a new
claudeAgentprovider with session lifecycle, resume, approvals/user-input bridging, and rollback behavior, plus changes to provider/session validation and restart rules; mistakes could break turn execution, approval gating, or checkpoint reverts across providers.Overview
Adds first-class support for the
claudeAgentprovider end-to-end, including a newClaudeAdapterLivebacked by@anthropic-ai/claude-agent-sdkthat streams canonicalProviderRuntimeEvents, supports interrupts, approvals/user-input requests, resume cursors, and thread rollback.Updates orchestration/provider plumbing to be provider-aware:
ProviderKindand model catalogs now include Claude models and options,ProviderCommandReactorenforces provider/model compatibility, restarts Claude sessions when ClaudemodelOptionschange, and surfaces turn-start failures as thread activities instead of crashing.Extends ingestion and checkpointing tests/behavior for Claude events (turn lifecycle, task progress summaries, checkpoint capture/revert), and adds new integration coverage for first-turn Claude selection, stopAll recovery via persisted resume state, approval response forwarding, and interrupt forwarding. Also refreshes docs/README to reflect Claude as supported.
Written by Cursor Bugbot for commit a75a9b4. This will update automatically on new commits. Configure here.
Note
Add Claude (claudeAgent) provider adapter with full session, model options, and health check support
claudeAgentprovider adapter (ClaudeAdapter.ts) wrapping@anthropic-ai/claude-agent-sdkthat handles session start/stop, event streaming, approvals, and user input.claudeAgentalongsidecodexinProviderAdapterRegistryandProviderHealth, so both adapters are discoverable and health-checked by default.ProviderKind,ProviderStartOptions,ProviderModelOptions) to enumerate Claude models, aliases, effort options (includingultrathink), and per-model capability flags (supportsClaudeFastMode,supportsClaudeAdaptiveReasoning, etc.).ClaudeTraitsPickerUI component and wires it intoChatViewalongside the existing Codex picker; composer drafts are migrated from per-field codex flags to a provider-scopedmodelOptionsstructure (store version bumped to v2).sourceProposedPlanthrough the projection pipeline, snapshot query, andOrchestrationLatestTurncontract so the sidebar can prefer a running turn's source plan.effort/codexFastMode/serviceTierfields are translated on load, but malformed or incompatible persisted state is silently dropped.Macroscope summarized e41830f.