Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7f3387a
Annotate Git core Effect.fn helpers (#1511)
juliusmarminge Mar 29, 2026
00f3efb
Make DrainableWorker drain off outstanding count (#1514)
juliusmarminge Mar 29, 2026
a1c428b
Refactor projection pipeline side effects (#1512)
juliusmarminge Mar 29, 2026
bc1024c
Inline sqlite error classification (#1515)
juliusmarminge Mar 29, 2026
a60daa1
Wrap orchestration reactor starts in Effect.fn (#1513)
juliusmarminge Mar 29, 2026
40e7dbd
Fix for broken Codex model selection for 'Text Generation Model' (#1543)
Alexx999 Mar 29, 2026
d5f2333
Show hidden thread status in sidebar (#1517)
juliusmarminge Mar 29, 2026
7b676b7
fix: show empty project thread state in sidebar (#1546)
shivamhwp Mar 29, 2026
bf9c828
Make provider model lists dynamic based on auth context (#1556)
juliusmarminge Mar 30, 2026
fc65070
fix(windows): hover-effect-fix-in-the-branch-ui (#1559)
shivamhwp Mar 30, 2026
d417819
fix(web): preserve scroll when collapsing large changed-files trees (…
CodeZeno Mar 30, 2026
792ad4b
Fix model settings getting stuck (#1538)
Alexx999 Mar 30, 2026
6358444
fix(web): Fix duplicate highlight in composer slash command menu (#1445)
AbdulelahHajjar Mar 30, 2026
32dca3b
[codex] debounce thread jump hint pills (#1526)
juliusmarminge Mar 30, 2026
5b58f74
fix(desktop): default confirm dialog selection to "No" (#1400)
Efroim-Propel Mar 30, 2026
4a4795e
Scroll active item into view in composer menu (#1557)
naufalw Mar 30, 2026
59f23d7
Show archive action on hover with confirm focus (#1561)
juliusmarminge Mar 30, 2026
409cdf5
Merge upstream/main: integrate 17 post-v0.0.15 commits
aaditagrawal Mar 30, 2026
538e411
Address code review feedback and fix CI browser test failures
aaditagrawal Mar 30, 2026
7361c47
Address round 2 review feedback and remaining CI failure
aaditagrawal Mar 30, 2026
c207311
Address round 3 review: unskip archive test, inline readConfigValueNu…
aaditagrawal Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/src/confirmDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function showDesktopConfirmDialog(
const options = {
type: "question" as const,
buttons: ["No", "Yes"],
defaultId: CONFIRM_BUTTON_INDEX,
defaultId: 0,
cancelId: 0,
noLink: true,
message: normalizedMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ export const makeOrchestrationIntegrationHarness = (

const scope = yield* Scope.make("sequential");
yield* tryRuntimePromise("start OrchestrationReactor", () =>
runtime.runPromise(reactor.start.pipe(Scope.provide(scope))),
runtime.runPromise(reactor.start().pipe(Scope.provide(scope))),
).pipe(Effect.orDie);
const receiptHistory = yield* Ref.make<ReadonlyArray<OrchestrationRuntimeReceipt>>([]);
yield* Stream.runForEach(runtimeReceiptBus.stream, (receipt) =>
Expand Down
27 changes: 25 additions & 2 deletions apps/server/src/codexAppServerManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,15 +339,28 @@ describe("readCodexAccountSnapshot", () => {
});
});

it("keeps spark enabled for api key accounts", () => {
it("disables spark for api key accounts", () => {
expect(
readCodexAccountSnapshot({
type: "apiKey",
}),
).toEqual({
type: "apiKey",
planType: null,
sparkEnabled: true,
sparkEnabled: false,
});
});

it("disables spark for unknown chatgpt plans", () => {
expect(
readCodexAccountSnapshot({
type: "chatgpt",
email: "unknown@example.com",
}),
).toEqual({
type: "chatgpt",
planType: "unknown",
sparkEnabled: false,
});
});
});
Expand All @@ -372,6 +385,16 @@ describe("resolveCodexModelForAccount", () => {
}),
).toBe("gpt-5.3-codex-spark");
});

it("falls back from spark to default for api key auth", () => {
expect(
resolveCodexModelForAccount("gpt-5.3-codex-spark", {
type: "apiKey",
planType: null,
sparkEnabled: false,
}),
).toBe("gpt-5.3-codex");
});
});

describe("startSession", () => {
Expand Down
104 changes: 10 additions & 94 deletions apps/server/src/codexAppServerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ import {
parseCodexCliVersion,
} from "./provider/codexCliVersion";
import { createLogger } from "./logger";
import {
readCodexAccountSnapshot,
resolveCodexModelForAccount,
type CodexAccountSnapshot,
} from "./provider/codexAccount";
import { buildCodexInitializeParams, killCodexChildProcess } from "./provider/codexAppServer";

export { buildCodexInitializeParams } from "./provider/codexAppServer";
export { readCodexAccountSnapshot, resolveCodexModelForAccount } from "./provider/codexAccount";

type PendingRequestKey = string;

Expand Down Expand Up @@ -97,23 +106,6 @@ interface JsonRpcNotification {
params?: unknown;
}

type CodexPlanType =
| "free"
| "go"
| "plus"
| "pro"
| "team"
| "business"
| "enterprise"
| "edu"
| "unknown";

interface CodexAccountSnapshot {
readonly type: "apiKey" | "chatgpt" | "unknown";
readonly planType: CodexPlanType | null;
readonly sparkEnabled: boolean;
}

export interface CodexAppServerSendTurnInput {
readonly threadId: ThreadId;
readonly input?: string;
Expand Down Expand Up @@ -163,50 +155,6 @@ const RECOVERABLE_THREAD_RESUME_ERROR_SNIPPETS = [
"unknown thread",
"does not exist",
];
const CODEX_DEFAULT_MODEL = "gpt-5.3-codex";
const CODEX_SPARK_MODEL = "gpt-5.3-codex-spark";
const CODEX_SPARK_DISABLED_PLAN_TYPES = new Set<CodexPlanType>(["free", "go", "plus"]);

function asObject(value: unknown): Record<string, unknown> | undefined {
if (!value || typeof value !== "object") {
return undefined;
}
return value as Record<string, unknown>;
}

function asString(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined;
}

export function readCodexAccountSnapshot(response: unknown): CodexAccountSnapshot {
const record = asObject(response);
const account = asObject(record?.account) ?? record;
const accountType = asString(account?.type);

if (accountType === "apiKey") {
return {
type: "apiKey",
planType: null,
sparkEnabled: true,
};
}

if (accountType === "chatgpt") {
const planType = (account?.planType as CodexPlanType | null) ?? "unknown";
return {
type: "chatgpt",
planType,
sparkEnabled: !CODEX_SPARK_DISABLED_PLAN_TYPES.has(planType),
};
}

return {
type: "unknown",
planType: null,
sparkEnabled: true,
};
}

export const CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS = `<collaboration_mode># Plan Mode (Conversational)

You work in 3 phases, and you should *chat your way* to a great plan before finalizing it. A great plan is very detailed-intent- and implementation-wise-so that it can be handed to another engineer or agent to be implemented right away. It must be **decision complete**, where the implementer does not need to make any decisions.
Expand Down Expand Up @@ -359,32 +307,13 @@ function mapCodexRuntimeMode(runtimeMode: RuntimeMode): {
};
}

export function resolveCodexModelForAccount(
model: string | undefined,
account: CodexAccountSnapshot,
): string | undefined {
if (model !== CODEX_SPARK_MODEL || account.sparkEnabled) {
return model;
}

return CODEX_DEFAULT_MODEL;
}

/**
* On Windows with `shell: true`, `child.kill()` only terminates the `cmd.exe`
* wrapper, leaving the actual command running. Use `taskkill /T` to kill the
* entire process tree instead.
*/
function killChildTree(child: ChildProcessWithoutNullStreams): void {
if (process.platform === "win32" && child.pid !== undefined) {
try {
spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
return;
} catch {
// fallback to direct kill
}
}
child.kill();
killCodexChildProcess(child);
}

export function normalizeCodexModelSlug(
Expand All @@ -403,19 +332,6 @@ export function normalizeCodexModelSlug(
return normalized;
}

export function buildCodexInitializeParams() {
return {
clientInfo: {
name: "t3code_desktop",
title: "T3 Code Desktop",
version: "0.1.0",
},
capabilities: {
experimentalApi: true,
},
} as const;
}

function buildCodexCollaborationMode(input: {
readonly interactionMode?: "default" | "plan";
readonly model?: string;
Expand Down
7 changes: 4 additions & 3 deletions apps/server/src/git/Layers/ClaudeTextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ import {
sanitizeThreadTitle,
toJsonSchemaObject,
} from "../Utils.ts";
import { normalizeClaudeModelOptions } from "../../provider/Layers/ClaudeProvider.ts";
import { normalizeClaudeModelOptionsWithCapabilities } from "@t3tools/shared/model";
import { ServerSettingsService } from "../../serverSettings.ts";
import { getClaudeModelCapabilities } from "../../provider/Layers/ClaudeProvider.ts";

const CLAUDE_TIMEOUT_MS = 180_000;

Expand Down Expand Up @@ -84,8 +85,8 @@ const makeClaudeTextGeneration = Effect.gen(function* () {
}): Effect.Effect<S["Type"], TextGenerationError, S["DecodingServices"]> =>
Effect.gen(function* () {
const jsonSchemaStr = JSON.stringify(toJsonSchemaObject(outputSchemaJson));
const normalizedOptions = normalizeClaudeModelOptions(
modelSelection.model,
const normalizedOptions = normalizeClaudeModelOptionsWithCapabilities(
getClaudeModelCapabilities(modelSelection.model),
modelSelection.options,
);
const settings = {
Expand Down
7 changes: 4 additions & 3 deletions apps/server/src/git/Layers/CodexTextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ import {
sanitizeThreadTitle,
toJsonSchemaObject,
} from "../Utils.ts";
import { normalizeCodexModelOptions } from "../../provider/Layers/CodexProvider.ts";
import { getCodexModelCapabilities } from "../../provider/Layers/CodexProvider.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { normalizeCodexModelOptionsWithCapabilities } from "@t3tools/shared/model";

const CODEX_GIT_TEXT_GENERATION_REASONING_EFFORT = "low";
const CODEX_TIMEOUT_MS = 180_000;
Expand Down Expand Up @@ -156,8 +157,8 @@ const makeCodexTextGeneration = Effect.gen(function* () {
).pipe(Effect.catch(() => Effect.undefined));

const runCodexCommand = Effect.gen(function* () {
const normalizedOptions = normalizeCodexModelOptions(
modelSelection.model,
const normalizedOptions = normalizeCodexModelOptionsWithCapabilities(
getCodexModelCapabilities(modelSelection.model),
modelSelection.options,
);
const reasoningEffort =
Expand Down
Loading
Loading