Skip to content

Commit e6254a8

Browse files
JustYanniccclaude
andauthored
feat: claude effort controls, ultrathink UI, and adapter robustness (#1146)
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
1 parent b04132d commit e6254a8

22 files changed

+1355
-543
lines changed

apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,56 @@ describe("ProviderCommandReactor", () => {
351351
});
352352
});
353353

354+
it("forwards claude effort options through session start and turn send", async () => {
355+
const harness = await createHarness();
356+
const now = new Date().toISOString();
357+
358+
await Effect.runPromise(
359+
harness.engine.dispatch({
360+
type: "thread.turn.start",
361+
commandId: CommandId.makeUnsafe("cmd-turn-start-claude-effort"),
362+
threadId: ThreadId.makeUnsafe("thread-1"),
363+
message: {
364+
messageId: asMessageId("user-message-claude-effort"),
365+
role: "user",
366+
text: "hello with effort",
367+
attachments: [],
368+
},
369+
provider: "claudeAgent",
370+
model: "claude-sonnet-4-6",
371+
modelOptions: {
372+
claudeAgent: {
373+
effort: "max",
374+
},
375+
},
376+
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
377+
runtimeMode: "approval-required",
378+
createdAt: now,
379+
}),
380+
);
381+
382+
await waitFor(() => harness.startSession.mock.calls.length === 1);
383+
await waitFor(() => harness.sendTurn.mock.calls.length === 1);
384+
expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({
385+
provider: "claudeAgent",
386+
model: "claude-sonnet-4-6",
387+
modelOptions: {
388+
claudeAgent: {
389+
effort: "max",
390+
},
391+
},
392+
});
393+
expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({
394+
threadId: ThreadId.makeUnsafe("thread-1"),
395+
model: "claude-sonnet-4-6",
396+
modelOptions: {
397+
claudeAgent: {
398+
effort: "max",
399+
},
400+
},
401+
});
402+
});
403+
354404
it("forwards plan interaction mode to the provider turn request", async () => {
355405
const harness = await createHarness();
356406
const now = new Date().toISOString();
@@ -531,6 +581,73 @@ describe("ProviderCommandReactor", () => {
531581
expect(harness.stopSession.mock.calls.length).toBe(0);
532582
});
533583

584+
it("restarts claude sessions when claude effort changes", async () => {
585+
const harness = await createHarness();
586+
const now = new Date().toISOString();
587+
588+
await Effect.runPromise(
589+
harness.engine.dispatch({
590+
type: "thread.turn.start",
591+
commandId: CommandId.makeUnsafe("cmd-turn-start-claude-effort-1"),
592+
threadId: ThreadId.makeUnsafe("thread-1"),
593+
message: {
594+
messageId: asMessageId("user-message-claude-effort-1"),
595+
role: "user",
596+
text: "first claude turn",
597+
attachments: [],
598+
},
599+
provider: "claudeAgent",
600+
model: "claude-sonnet-4-6",
601+
modelOptions: {
602+
claudeAgent: {
603+
effort: "medium",
604+
},
605+
},
606+
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
607+
runtimeMode: "approval-required",
608+
createdAt: now,
609+
}),
610+
);
611+
612+
await waitFor(() => harness.startSession.mock.calls.length === 1);
613+
await waitFor(() => harness.sendTurn.mock.calls.length === 1);
614+
615+
await Effect.runPromise(
616+
harness.engine.dispatch({
617+
type: "thread.turn.start",
618+
commandId: CommandId.makeUnsafe("cmd-turn-start-claude-effort-2"),
619+
threadId: ThreadId.makeUnsafe("thread-1"),
620+
message: {
621+
messageId: asMessageId("user-message-claude-effort-2"),
622+
role: "user",
623+
text: "second claude turn",
624+
attachments: [],
625+
},
626+
provider: "claudeAgent",
627+
model: "claude-sonnet-4-6",
628+
modelOptions: {
629+
claudeAgent: {
630+
effort: "max",
631+
},
632+
},
633+
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
634+
runtimeMode: "approval-required",
635+
createdAt: now,
636+
}),
637+
);
638+
639+
await waitFor(() => harness.startSession.mock.calls.length === 2);
640+
await waitFor(() => harness.sendTurn.mock.calls.length === 2);
641+
expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({
642+
provider: "claudeAgent",
643+
modelOptions: {
644+
claudeAgent: {
645+
effort: "max",
646+
},
647+
},
648+
});
649+
});
650+
534651
it("restarts the provider session when runtime mode is updated on the thread", async () => {
535652
const harness = await createHarness();
536653
const now = new Date().toISOString();

apps/server/src/orchestration/Layers/ProviderCommandReactor.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";
7575
const WORKTREE_BRANCH_PREFIX = "t3code";
7676
const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`);
7777

78+
const sameModelOptions = (
79+
left: ProviderModelOptions | undefined,
80+
right: ProviderModelOptions | undefined,
81+
): boolean => JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
82+
7883
function isUnknownPendingApprovalRequestError(cause: Cause.Cause<ProviderServiceError>): boolean {
7984
const error = Cause.squash(cause);
8085
if (Schema.is(ProviderAdapterRequestError)(error)) {
@@ -137,6 +142,7 @@ const make = Effect.gen(function* () {
137142
);
138143

139144
const threadProviderOptions = new Map<string, ProviderStartOptions>();
145+
const threadModelOptions = new Map<string, ProviderModelOptions>();
140146

141147
const appendProviderFailureActivity = (input: {
142148
readonly threadId: ThreadId;
@@ -289,13 +295,23 @@ const make = Effect.gen(function* () {
289295
: (yield* providerService.getCapabilities(currentProvider)).sessionModelSwitch;
290296
const modelChanged = options?.model !== undefined && options.model !== activeSession?.model;
291297
const shouldRestartForModelChange = modelChanged && sessionModelSwitch === "restart-session";
298+
const previousModelOptions = threadModelOptions.get(threadId);
299+
const shouldRestartForModelOptionsChange =
300+
currentProvider === "claudeAgent" &&
301+
options?.modelOptions !== undefined &&
302+
!sameModelOptions(previousModelOptions, options.modelOptions);
292303

293-
if (!runtimeModeChanged && !providerChanged && !shouldRestartForModelChange) {
304+
if (
305+
!runtimeModeChanged &&
306+
!providerChanged &&
307+
!shouldRestartForModelChange &&
308+
!shouldRestartForModelOptionsChange
309+
) {
294310
return existingSessionThreadId;
295311
}
296312

297313
const resumeCursor =
298-
providerChanged || shouldRestartForModelChange
314+
providerChanged || shouldRestartForModelChange || shouldRestartForModelOptionsChange
299315
? undefined
300316
: (activeSession?.resumeCursor ?? undefined);
301317
yield* Effect.logInfo("provider command reactor restarting provider session", {
@@ -309,6 +325,7 @@ const make = Effect.gen(function* () {
309325
providerChanged,
310326
modelChanged,
311327
shouldRestartForModelChange,
328+
shouldRestartForModelOptionsChange,
312329
hasResumeCursor: resumeCursor !== undefined,
313330
});
314331
const restartedSession = yield* startProviderSession({
@@ -348,15 +365,18 @@ const make = Effect.gen(function* () {
348365
if (!thread) {
349366
return;
350367
}
351-
if (input.providerOptions !== undefined) {
352-
threadProviderOptions.set(input.threadId, input.providerOptions);
353-
}
354368
yield* ensureSessionForThread(input.threadId, input.createdAt, {
355369
...(input.provider !== undefined ? { provider: input.provider } : {}),
356370
...(input.model !== undefined ? { model: input.model } : {}),
357371
...(input.modelOptions !== undefined ? { modelOptions: input.modelOptions } : {}),
358372
...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}),
359373
});
374+
if (input.providerOptions !== undefined) {
375+
threadProviderOptions.set(input.threadId, input.providerOptions);
376+
}
377+
if (input.modelOptions !== undefined) {
378+
threadModelOptions.set(input.threadId, input.modelOptions);
379+
}
360380
const normalizedInput = toNonEmptyProviderInput(input.messageText);
361381
const normalizedAttachments = input.attachments ?? [];
362382
const activeSession = yield* providerService
@@ -657,13 +677,13 @@ const make = Effect.gen(function* () {
657677
return;
658678
}
659679
const cachedProviderOptions = threadProviderOptions.get(event.payload.threadId);
660-
yield* ensureSessionForThread(
661-
event.payload.threadId,
662-
event.occurredAt,
663-
cachedProviderOptions !== undefined
680+
const cachedModelOptions = threadModelOptions.get(event.payload.threadId);
681+
yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, {
682+
...(cachedProviderOptions !== undefined
664683
? { providerOptions: cachedProviderOptions }
665-
: undefined,
666-
);
684+
: {}),
685+
...(cachedModelOptions !== undefined ? { modelOptions: cachedModelOptions } : {}),
686+
});
667687
return;
668688
}
669689
case "thread.turn-start-requested":

apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,6 +1412,7 @@ describe("ProviderRuntimeIngestion", () => {
14121412
payload: {
14131413
taskId: "turn-task-1",
14141414
description: "Comparing the desktop rollout chunks to the app-server stream.",
1415+
summary: "Code reviewer is validating the desktop rollout chunks.",
14151416
},
14161417
});
14171418

@@ -1474,8 +1475,9 @@ describe("ProviderRuntimeIngestion", () => {
14741475
expect(started?.kind).toBe("task.started");
14751476
expect(started?.summary).toBe("Plan task started");
14761477
expect(progress?.kind).toBe("task.progress");
1477-
expect(progressPayload?.detail).toBe(
1478-
"Comparing the desktop rollout chunks to the app-server stream.",
1478+
expect(progressPayload?.detail).toBe("Code reviewer is validating the desktop rollout chunks.");
1479+
expect(progressPayload?.summary).toBe(
1480+
"Code reviewer is validating the desktop rollout chunks.",
14791481
);
14801482
expect(completed?.kind).toBe("task.completed");
14811483
expect(completedPayload?.detail).toBe("<proposed_plan>\n# Plan title\n</proposed_plan>");

apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,8 @@ function runtimeEventToActivities(
370370
summary: "Reasoning update",
371371
payload: {
372372
taskId: event.payload.taskId,
373-
detail: truncateDetail(event.payload.description),
373+
detail: truncateDetail(event.payload.summary ?? event.payload.description),
374+
...(event.payload.summary ? { summary: truncateDetail(event.payload.summary) } : {}),
374375
...(event.payload.lastToolName ? { lastToolName: event.payload.lastToolName } : {}),
375376
...(event.payload.usage !== undefined ? { usage: event.payload.usage } : {}),
376377
},

0 commit comments

Comments
 (0)