Skip to content

Commit e9c611f

Browse files
juliusmarmingecodex
andcommitted
Merge origin/main into t3code/greeting
Co-authored-by: codex <codex@users.noreply.github.com>
2 parents 146e102 + fb72607 commit e9c611f

17 files changed

+656
-58
lines changed

apps/server/src/open.test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
4040
args: ["/tmp/workspace"],
4141
});
4242

43+
const vscodeInsidersLaunch = yield* resolveEditorLaunch(
44+
{ cwd: "/tmp/workspace", editor: "vscode-insiders" },
45+
"darwin",
46+
);
47+
assert.deepEqual(vscodeInsidersLaunch, {
48+
command: "code-insiders",
49+
args: ["/tmp/workspace"],
50+
});
51+
52+
const vscodiumLaunch = yield* resolveEditorLaunch(
53+
{ cwd: "/tmp/workspace", editor: "vscodium" },
54+
"darwin",
55+
);
56+
assert.deepEqual(vscodiumLaunch, {
57+
command: "codium",
58+
args: ["/tmp/workspace"],
59+
});
60+
4361
const zedLaunch = yield* resolveEditorLaunch(
4462
{ cwd: "/tmp/workspace", editor: "zed" },
4563
"darwin",
@@ -80,6 +98,24 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
8098
args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
8199
});
82100

101+
const vscodeInsidersLineAndColumn = yield* resolveEditorLaunch(
102+
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode-insiders" },
103+
"darwin",
104+
);
105+
assert.deepEqual(vscodeInsidersLineAndColumn, {
106+
command: "code-insiders",
107+
args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
108+
});
109+
110+
const vscodiumLineAndColumn = yield* resolveEditorLaunch(
111+
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscodium" },
112+
"darwin",
113+
);
114+
assert.deepEqual(vscodiumLineAndColumn, {
115+
command: "codium",
116+
args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
117+
});
118+
83119
const zedLineAndColumn = yield* resolveEditorLaunch(
84120
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "zed" },
85121
"darwin",
@@ -220,13 +256,14 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
220256
const path = yield* Path.Path;
221257
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });
222258

223-
yield* fs.writeFileString(path.join(dir, "cursor.CMD"), "@echo off\r\n");
259+
yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");
260+
yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");
224261
yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");
225262
const editors = resolveAvailableEditors("win32", {
226263
PATH: dir,
227264
PATHEXT: ".COM;.EXE;.BAT;.CMD",
228265
});
229-
assert.deepEqual(editors, ["cursor", "file-manager"]);
266+
assert.deepEqual(editors, ["vscode-insiders", "vscodium", "file-manager"]);
230267
}),
231268
);
232269
});

apps/server/src/open.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,8 @@ interface CommandAvailabilityOptions {
3939

4040
const LINE_COLUMN_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
4141

42-
function shouldUseGotoFlag(editorId: EditorId, target: string): boolean {
43-
return (
44-
(editorId === "cursor" || editorId === "vscode") && LINE_COLUMN_SUFFIX_PATTERN.test(target)
45-
);
42+
function shouldUseGotoFlag(editor: (typeof EDITORS)[number], target: string): boolean {
43+
return editor.supportsGoto && LINE_COLUMN_SUFFIX_PATTERN.test(target);
4644
}
4745

4846
function fileManagerCommandForPlatform(platform: NodeJS.Platform): string {
@@ -213,7 +211,7 @@ export const resolveEditorLaunch = Effect.fnUntraced(function* (
213211
}
214212

215213
if (editorDef.command) {
216-
return shouldUseGotoFlag(editorDef.id, input.cwd)
214+
return shouldUseGotoFlag(editorDef, input.cwd)
217215
? { command: editorDef.command, args: ["--goto", input.cwd] }
218216
: { command: editorDef.command, args: [input.cwd] };
219217
}

apps/server/src/provider/Layers/ClaudeAdapter.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,6 +2384,85 @@ describe("ClaudeAdapterLive", () => {
23842384
);
23852385
});
23862386

2387+
it.effect(
2388+
"does not re-set the Claude model when the session already uses the same effective API model",
2389+
() => {
2390+
const harness = makeHarness();
2391+
return Effect.gen(function* () {
2392+
const adapter = yield* ClaudeAdapter;
2393+
const modelSelection = {
2394+
provider: "claudeAgent" as const,
2395+
model: "claude-opus-4-6",
2396+
};
2397+
2398+
const session = yield* adapter.startSession({
2399+
threadId: THREAD_ID,
2400+
provider: "claudeAgent",
2401+
modelSelection,
2402+
runtimeMode: "full-access",
2403+
});
2404+
2405+
yield* adapter.sendTurn({
2406+
threadId: session.threadId,
2407+
input: "hello",
2408+
modelSelection,
2409+
attachments: [],
2410+
});
2411+
yield* adapter.sendTurn({
2412+
threadId: session.threadId,
2413+
input: "hello again",
2414+
modelSelection,
2415+
attachments: [],
2416+
});
2417+
2418+
assert.deepEqual(harness.query.setModelCalls, []);
2419+
}).pipe(
2420+
Effect.provideService(Random.Random, makeDeterministicRandomService()),
2421+
Effect.provide(harness.layer),
2422+
);
2423+
},
2424+
);
2425+
2426+
it.effect("re-sets the Claude model when the effective API model changes", () => {
2427+
const harness = makeHarness();
2428+
return Effect.gen(function* () {
2429+
const adapter = yield* ClaudeAdapter;
2430+
2431+
const session = yield* adapter.startSession({
2432+
threadId: THREAD_ID,
2433+
provider: "claudeAgent",
2434+
runtimeMode: "full-access",
2435+
});
2436+
2437+
yield* adapter.sendTurn({
2438+
threadId: session.threadId,
2439+
input: "hello",
2440+
modelSelection: {
2441+
provider: "claudeAgent",
2442+
model: "claude-opus-4-6",
2443+
options: {
2444+
contextWindow: "1m",
2445+
},
2446+
},
2447+
attachments: [],
2448+
});
2449+
yield* adapter.sendTurn({
2450+
threadId: session.threadId,
2451+
input: "hello again",
2452+
modelSelection: {
2453+
provider: "claudeAgent",
2454+
model: "claude-opus-4-6",
2455+
},
2456+
attachments: [],
2457+
});
2458+
2459+
assert.deepEqual(harness.query.setModelCalls, ["claude-opus-4-6[1m]", "claude-opus-4-6"]);
2460+
}).pipe(
2461+
Effect.provideService(Random.Random, makeDeterministicRandomService()),
2462+
Effect.provide(harness.layer),
2463+
);
2464+
});
2465+
23872466
it.effect("sets plan permission mode on sendTurn when interactionMode is plan", () => {
23882467
const harness = makeHarness();
23892468
return Effect.gen(function* () {

apps/server/src/provider/Layers/ClaudeAdapter.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ interface ClaudeSessionContext {
143143
streamFiber: Fiber.Fiber<void, Error> | undefined;
144144
readonly startedAt: string;
145145
readonly basePermissionMode: PermissionMode | undefined;
146+
currentApiModelId: string | undefined;
146147
resumeSessionId: string | undefined;
147148
readonly pendingApprovals: Map<ApprovalRequestId, PendingApproval>;
148149
readonly pendingUserInputs: Map<ApprovalRequestId, PendingUserInput>;
@@ -2804,6 +2805,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
28042805
streamFiber: undefined,
28052806
startedAt,
28062807
basePermissionMode: permissionMode,
2808+
currentApiModelId: apiModelId,
28072809
resumeSessionId: sessionId,
28082810
pendingApprovals,
28092811
pendingUserInputs,
@@ -2893,10 +2895,17 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
28932895

28942896
if (modelSelection?.model) {
28952897
const apiModelId = resolveClaudeApiModelId(modelSelection);
2896-
yield* Effect.tryPromise({
2897-
try: () => context.query.setModel(apiModelId),
2898-
catch: (cause) => toRequestError(input.threadId, "turn/setModel", cause),
2899-
});
2898+
if (context.currentApiModelId !== apiModelId) {
2899+
yield* Effect.tryPromise({
2900+
try: () => context.query.setModel(apiModelId),
2901+
catch: (cause) => toRequestError(input.threadId, "turn/setModel", cause),
2902+
});
2903+
context.currentApiModelId = apiModelId;
2904+
}
2905+
context.session = {
2906+
...context.session,
2907+
model: modelSelection.model,
2908+
};
29002909
}
29012910

29022911
// Apply interaction mode by switching the SDK's permission mode.

apps/web/src/components/BranchToolbar.logic.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
resolveBranchSelectionTarget,
77
resolveDraftEnvModeAfterBranchChange,
88
resolveBranchToolbarValue,
9+
shouldIncludeBranchPickerItem,
910
} from "./BranchToolbar.logic";
1011

1112
describe("resolveDraftEnvModeAfterBranchChange", () => {
@@ -267,3 +268,38 @@ describe("resolveBranchSelectionTarget", () => {
267268
});
268269
});
269270
});
271+
272+
describe("shouldIncludeBranchPickerItem", () => {
273+
it("keeps the synthetic checkout PR item visible for gh pr checkout input", () => {
274+
expect(
275+
shouldIncludeBranchPickerItem({
276+
itemValue: "__checkout_pull_request__:1359",
277+
normalizedQuery: "gh pr checkout 1359",
278+
createBranchItemValue: "__create_new_branch__:gh pr checkout 1359",
279+
checkoutPullRequestItemValue: "__checkout_pull_request__:1359",
280+
}),
281+
).toBe(true);
282+
});
283+
284+
it("keeps the synthetic create-branch item visible for arbitrary branch input", () => {
285+
expect(
286+
shouldIncludeBranchPickerItem({
287+
itemValue: "__create_new_branch__:feature/demo",
288+
normalizedQuery: "feature/demo",
289+
createBranchItemValue: "__create_new_branch__:feature/demo",
290+
checkoutPullRequestItemValue: null,
291+
}),
292+
).toBe(true);
293+
});
294+
295+
it("still filters ordinary branch items by query text", () => {
296+
expect(
297+
shouldIncludeBranchPickerItem({
298+
itemValue: "main",
299+
normalizedQuery: "gh pr checkout 1359",
300+
createBranchItemValue: "__create_new_branch__:gh pr checkout 1359",
301+
checkoutPullRequestItemValue: "__checkout_pull_request__:1359",
302+
}),
303+
).toBe(false);
304+
});
305+
});

apps/web/src/components/BranchToolbar.logic.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,26 @@ export function resolveBranchSelectionTarget(input: {
123123
reuseExistingWorktree: false,
124124
};
125125
}
126+
127+
export function shouldIncludeBranchPickerItem(input: {
128+
itemValue: string;
129+
normalizedQuery: string;
130+
createBranchItemValue: string | null;
131+
checkoutPullRequestItemValue: string | null;
132+
}): boolean {
133+
const { itemValue, normalizedQuery, createBranchItemValue, checkoutPullRequestItemValue } = input;
134+
135+
if (normalizedQuery.length === 0) {
136+
return true;
137+
}
138+
139+
if (createBranchItemValue && itemValue === createBranchItemValue) {
140+
return true;
141+
}
142+
143+
if (checkoutPullRequestItemValue && itemValue === checkoutPullRequestItemValue) {
144+
return true;
145+
}
146+
147+
return itemValue.toLowerCase().includes(normalizedQuery);
148+
}

apps/web/src/components/BranchToolbarBranchSelector.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
EnvMode,
2929
resolveBranchSelectionTarget,
3030
resolveBranchToolbarValue,
31+
shouldIncludeBranchPickerItem,
3132
} from "./BranchToolbar.logic";
3233
import { Button } from "./ui/button";
3334
import {
@@ -134,11 +135,20 @@ export function BranchToolbarBranchSelector({
134135
() =>
135136
normalizedDeferredBranchQuery.length === 0
136137
? branchPickerItems
137-
: branchPickerItems.filter((itemValue) => {
138-
if (createBranchItemValue && itemValue === createBranchItemValue) return true;
139-
return itemValue.toLowerCase().includes(normalizedDeferredBranchQuery);
140-
}),
141-
[branchPickerItems, createBranchItemValue, normalizedDeferredBranchQuery],
138+
: branchPickerItems.filter((itemValue) =>
139+
shouldIncludeBranchPickerItem({
140+
itemValue,
141+
normalizedQuery: normalizedDeferredBranchQuery,
142+
createBranchItemValue,
143+
checkoutPullRequestItemValue,
144+
}),
145+
),
146+
[
147+
branchPickerItems,
148+
checkoutPullRequestItemValue,
149+
createBranchItemValue,
150+
normalizedDeferredBranchQuery,
151+
],
142152
);
143153
const [resolvedActiveBranch, setOptimisticBranch] = useOptimistic(
144154
canonicalActiveBranch,

0 commit comments

Comments
 (0)