Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b225dfa
Make server settings authoritative
juliusmarminge Mar 25, 2026
3f089a4
layers
juliusmarminge Mar 25, 2026
a9aa5c4
Centralize provider settings under server authority
juliusmarminge Mar 25, 2026
f942c34
mv
juliusmarminge Mar 25, 2026
14e81b9
lint
juliusmarminge Mar 25, 2026
69e5d81
Default server settings providers object
juliusmarminge Mar 25, 2026
d6099ed
fix expectation
juliusmarminge Mar 25, 2026
5b750e2
use more lenient json parse
juliusmarminge Mar 25, 2026
007bd17
kewl
juliusmarminge Mar 25, 2026
7b0c9c0
kewl
juliusmarminge Mar 25, 2026
da0eaff
rm reexport shi
juliusmarminge Mar 25, 2026
c91bd26
fix
juliusmarminge Mar 26, 2026
619fd20
mv to contract package
juliusmarminge Mar 26, 2026
6715932
mv
juliusmarminge Mar 26, 2026
57ff0ae
fix
juliusmarminge Mar 26, 2026
19f09b7
nit
juliusmarminge Mar 26, 2026
5372668
pass configured binary to health checks
juliusmarminge Mar 26, 2026
b4b3b2b
rm reexport
juliusmarminge Mar 26, 2026
7757947
Move provider settings authority into server settings
juliusmarminge Mar 26, 2026
89e7550
Make server settings test layer stateful
juliusmarminge Mar 26, 2026
9320fc8
Centralize provider checks on server settings
juliusmarminge Mar 26, 2026
e57fec3
kewl
juliusmarminge Mar 26, 2026
6f34ca0
fix race
juliusmarminge Mar 26, 2026
9934b6e
Preserve environment when launching Codex text generation
juliusmarminge Mar 26, 2026
e39cf66
Make server settings startup atomic
juliusmarminge Mar 26, 2026
37c30f5
Avoid publishing unchanged provider snapshots
juliusmarminge Mar 26, 2026
9d390e3
restrcit objects for deepMerge
juliusmarminge Mar 26, 2026
dc7465a
Add provider refresh action to settings
juliusmarminge Mar 26, 2026
11d5a70
Use server settings when generating worktree branch names
juliusmarminge Mar 26, 2026
21838dc
Clean up temp files after atomic settings writes
juliusmarminge Mar 26, 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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { ProjectionPendingApprovalRepository } from "../src/persistence/Services
import { ProviderUnsupportedError } from "../src/provider/Errors.ts";
import { ProviderAdapterRegistry } from "../src/provider/Services/ProviderAdapterRegistry.ts";
import { ProviderSessionDirectoryLive } from "../src/provider/Layers/ProviderSessionDirectory.ts";
import { ServerSettingsService } from "../src/serverSettings.ts";
import { makeProviderServiceLive } from "../src/provider/Layers/ProviderService.ts";
import { makeCodexAdapterLive } from "../src/provider/Layers/CodexAdapter.ts";
import { CodexAdapter } from "../src/provider/Services/CodexAdapter.ts";
Expand Down Expand Up @@ -295,8 +296,10 @@ export const makeOrchestrationIntegrationHarness = (
providerLayer,
RuntimeReceiptBusLive,
);
const serverSettingsLayer = ServerSettingsService.layerTest();
const runtimeIngestionLayer = ProviderRuntimeIngestionLive.pipe(
Layer.provideMerge(runtimeServicesLayer),
Layer.provideMerge(serverSettingsLayer),
);
const gitCoreLayer = Layer.succeed(GitCore, {
renameBranch: (input: Parameters<GitCoreShape["renameBranch"]>[0]) =>
Expand All @@ -309,6 +312,7 @@ export const makeOrchestrationIntegrationHarness = (
Layer.provideMerge(runtimeServicesLayer),
Layer.provideMerge(gitCoreLayer),
Layer.provideMerge(textGenerationLayer),
Layer.provideMerge(serverSettingsLayer),
);
const checkpointReactorLayer = CheckpointReactorLive.pipe(
Layer.provideMerge(runtimeServicesLayer),
Expand All @@ -320,6 +324,7 @@ export const makeOrchestrationIntegrationHarness = (
);
const layer = orchestrationReactorLayer.pipe(
Layer.provide(persistenceLayer),
Layer.provideMerge(ServerSettingsService.layerTest()),
Layer.provideMerge(ServerConfig.layerTest(workspaceDir, rootDir)),
Layer.provideMerge(NodeServices.layer),
);
Expand Down
3 changes: 3 additions & 0 deletions apps/server/integration/providerService.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ProviderRuntimeEvent } from "@t3tools/contracts";
import { ThreadId } from "@t3tools/contracts";
import { DEFAULT_SERVER_SETTINGS } from "@t3tools/contracts/settings";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { it, assert } from "@effect/vitest";
import { Effect, FileSystem, Layer, Path, Queue, Stream } from "effect";
Expand All @@ -12,6 +13,7 @@ import {
ProviderService,
type ProviderServiceShape,
} from "../src/provider/Services/ProviderService.ts";
import { ServerSettingsService } from "../src/serverSettings.ts";
import { AnalyticsService } from "../src/telemetry/Services/AnalyticsService.ts";
import { SqlitePersistenceMemory } from "../src/persistence/Layers/Sqlite.ts";
import { ProviderSessionRuntimeRepositoryLive } from "../src/persistence/Layers/ProviderSessionRuntime.ts";
Expand Down Expand Up @@ -60,6 +62,7 @@ const makeIntegrationFixture = Effect.gen(function* () {
const shared = Layer.mergeAll(
directoryLayer,
Layer.succeed(ProviderAdapterRegistry, registry),
ServerSettingsService.layerTest(DEFAULT_SERVER_SETTINGS),
AnalyticsService.layerTest,
).pipe(Layer.provide(SqlitePersistenceMemory));

Expand Down
18 changes: 6 additions & 12 deletions apps/server/src/codexAppServerManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ describe("startSession", () => {
manager.startSession({
threadId: asThreadId("thread-1"),
provider: "codex",
binaryPath: "codex",
runtimeMode: "full-access",
}),
).rejects.toThrow("cwd missing");
Expand Down Expand Up @@ -390,6 +391,7 @@ describe("startSession", () => {
manager.startSession({
threadId: asThreadId("thread-1"),
provider: "codex",
binaryPath: "codex",
runtimeMode: "full-access",
}),
).rejects.toThrow(
Expand Down Expand Up @@ -948,12 +950,8 @@ describe.skipIf(!process.env.CODEX_BINARY_PATH)("startSession live Codex resume"
provider: "codex",
cwd: workspaceDir,
runtimeMode: "full-access",
providerOptions: {
codex: {
...(process.env.CODEX_BINARY_PATH ? { binaryPath: process.env.CODEX_BINARY_PATH } : {}),
...(process.env.CODEX_HOME_PATH ? { homePath: process.env.CODEX_HOME_PATH } : {}),
},
},
binaryPath: process.env.CODEX_BINARY_PATH!,
...(process.env.CODEX_HOME_PATH ? { homePath: process.env.CODEX_HOME_PATH } : {}),
});

const firstTurn = await manager.sendTurn({
Expand Down Expand Up @@ -983,12 +981,8 @@ describe.skipIf(!process.env.CODEX_BINARY_PATH)("startSession live Codex resume"
cwd: workspaceDir,
runtimeMode: "approval-required",
resumeCursor: firstSession.resumeCursor,
providerOptions: {
codex: {
...(process.env.CODEX_BINARY_PATH ? { binaryPath: process.env.CODEX_BINARY_PATH } : {}),
...(process.env.CODEX_HOME_PATH ? { homePath: process.env.CODEX_HOME_PATH } : {}),
},
},
binaryPath: process.env.CODEX_BINARY_PATH!,
...(process.env.CODEX_HOME_PATH ? { homePath: process.env.CODEX_HOME_PATH } : {}),
});

expect(resumedSession.threadId).toBe(originalThreadId);
Expand Down
29 changes: 9 additions & 20 deletions apps/server/src/codexAppServerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
type ProviderApprovalDecision,
type ProviderEvent,
type ProviderSession,
type ProviderSessionStartInput,
type ProviderTurnStartResult,
RuntimeMode,
ProviderInteractionMode,
Expand Down Expand Up @@ -131,7 +130,8 @@ export interface CodexAppServerStartSessionInput {
readonly model?: string;
readonly serviceTier?: string;
readonly resumeCursor?: unknown;
readonly providerOptions?: ProviderSessionStartInput["providerOptions"];
readonly binaryPath: string;
readonly homePath?: string;
readonly runtimeMode: RuntimeMode;
}

Expand Down Expand Up @@ -541,9 +541,8 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
updatedAt: now,
};

const codexOptions = readCodexProviderOptions(input);
const codexBinaryPath = codexOptions.binaryPath ?? "codex";
const codexHomePath = codexOptions.homePath;
const codexBinaryPath = input.binaryPath;
const codexHomePath = input.homePath;
this.assertSupportedCodexCliVersion({
binaryPath: codexBinaryPath,
cwd: resolvedCwd,
Expand Down Expand Up @@ -1591,20 +1590,6 @@ function normalizeProviderThreadId(value: string | undefined): string | undefine
return brandIfNonEmpty(value, (normalized) => normalized);
}

function readCodexProviderOptions(input: CodexAppServerStartSessionInput): {
readonly binaryPath?: string;
readonly homePath?: string;
} {
const options = input.providerOptions?.codex;
if (!options) {
return {};
}
return {
...(options.binaryPath ? { binaryPath: options.binaryPath } : {}),
...(options.homePath ? { homePath: options.homePath } : {}),
};
}

function assertSupportedCodexCliVersion(input: {
readonly binaryPath: string;
readonly cwd: string;
Expand Down Expand Up @@ -1658,7 +1643,11 @@ function readResumeCursorThreadId(resumeCursor: unknown): string | undefined {
return typeof rawThreadId === "string" ? normalizeProviderThreadId(rawThreadId) : undefined;
}

function readResumeThreadId(input: CodexAppServerStartSessionInput): string | undefined {
function readResumeThreadId(input: {
readonly resumeCursor?: unknown;
readonly threadId?: ThreadId;
readonly runtimeMode?: RuntimeMode;
}): string | undefined {
return readResumeCursorThreadId(input.resumeCursor);
}

Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface ServerDerivedPaths {
readonly stateDir: string;
readonly dbPath: string;
readonly keybindingsConfigPath: string;
readonly settingsPath: string;
readonly worktreesDir: string;
readonly attachmentsDir: string;
readonly logsDir: string;
Expand Down Expand Up @@ -60,6 +61,7 @@ export const deriveServerPaths = Effect.fn(function* (
stateDir,
dbPath,
keybindingsConfigPath: join(stateDir, "keybindings.json"),
settingsPath: join(stateDir, "settings.json"),
worktreesDir: join(baseDir, "worktrees"),
attachmentsDir,
logsDir,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/git/Layers/ClaudeTextGeneration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { expect } from "vitest";
import { ServerConfig } from "../../config.ts";
import { TextGeneration } from "../Services/TextGeneration.ts";
import { ClaudeTextGenerationLive } from "./ClaudeTextGeneration.ts";
import { ServerSettingsService } from "../../serverSettings.ts";

const ClaudeTextGenerationTestLayer = ClaudeTextGenerationLive.pipe(
Layer.provideMerge(ServerSettingsService.layerTest()),
Layer.provideMerge(
ServerConfig.layerTest(process.cwd(), {
prefix: "t3code-claude-text-generation-test-",
Expand Down
11 changes: 9 additions & 2 deletions apps/server/src/git/Layers/ClaudeTextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Effect, Layer, Option, Schema, Stream } from "effect";
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";

import { ClaudeModelSelection } from "@t3tools/contracts";
import { normalizeClaudeModelOptions } from "@t3tools/shared/model";
import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git";

import { TextGenerationError } from "../Errors.ts";
Expand All @@ -27,6 +26,8 @@ import {
sanitizePrTitle,
toJsonSchemaObject,
} from "../Utils.ts";
import { normalizeClaudeModelOptions } from "../../provider/Layers/ClaudeProvider.ts";
import { ServerSettingsService } from "../../serverSettings.ts";

const CLAUDE_TIMEOUT_MS = 180_000;

Expand All @@ -40,6 +41,7 @@ const ClaudeOutputEnvelope = Schema.Struct({

const makeClaudeTextGeneration = Effect.gen(function* () {
const commandSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const serverSettingsService = yield* Effect.service(ServerSettingsService);

const readStreamAsString = <E>(
operation: string,
Expand Down Expand Up @@ -86,9 +88,14 @@ const makeClaudeTextGeneration = Effect.gen(function* () {
...(normalizedOptions?.fastMode ? { fastMode: true } : {}),
};

const claudeSettings = yield* Effect.map(
serverSettingsService.getSettings,
(settings) => settings.providers.claudeAgent,
).pipe(Effect.catch(() => Effect.undefined));

const runClaudeCommand = Effect.gen(function* () {
const command = ChildProcess.make(
"claude",
claudeSettings?.binaryPath || "claude",
[
"-p",
"--output-format",
Expand Down
Loading