Skip to content

Commit 3dedd41

Browse files
authored
Merge branch 'main' into t3code/ignore-test-files-change-size
2 parents 592620f + 9e29c9d commit 3dedd41

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2615
-1415
lines changed

.docs/scripts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
- `bun run dev:web` — Starts just the Vite dev server for the web app.
66
- Dev commands default `T3CODE_STATE_DIR` to `~/.t3/dev` to keep dev state isolated from desktop/prod state.
77
- Override server CLI-equivalent flags from root dev commands with `--`, for example:
8-
`bun run dev -- --state-dir ~/.t3/another-dev-state`
8+
`bun run dev -- --base-dir ~/.t3-2`
99
- `bun run start` — Runs the production server (serves built web app as static files).
1010
- `bun run build` — Builds contracts, web app, and server through Turbo.
1111
- `bun run typecheck` — Strict TypeScript checks for all packages.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ release/
1717
apps/web/.playwright
1818
apps/web/playwright-report
1919
apps/web/src/components/__screenshots__
20-
.vitest-*
20+
.vitest-*
21+
__screenshots__/

REMOTE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The T3 Code CLI accepts the following configuration options, available either as
1111
| `--mode <web\|desktop>` | `T3CODE_MODE` | Runtime mode. |
1212
| `--port <number>` | `T3CODE_PORT` | HTTP/WebSocket port. |
1313
| `--host <address>` | `T3CODE_HOST` | Bind interface/address. |
14-
| `--state-dir <path>` | `T3CODE_STATE_DIR` | State directory. |
14+
| `--base-dir <path>` | `T3CODE_HOME` | Base directory. |
1515
| `--dev-url <url>` | `VITE_DEV_SERVER_URL` | Dev web URL redirect/proxy target. |
1616
| `--no-browser` | `T3CODE_NO_BROWSER` | Disable auto-open browser. |
1717
| `--auth-token <token>` | `T3CODE_AUTH_TOKEN` | WebSocket auth token. |

apps/desktop/src/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
5656
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
5757
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
5858
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
59-
const STATE_DIR =
60-
process.env.T3CODE_STATE_DIR?.trim() || Path.join(OS.homedir(), ".t3", "userdata");
59+
const BASE_DIR = process.env.T3CODE_HOME?.trim() || Path.join(OS.homedir(), ".t3");
60+
const STATE_DIR = Path.join(BASE_DIR, "userdata");
6161
const DESKTOP_SCHEME = "t3";
6262
const ROOT_DIR = Path.resolve(__dirname, "../../..");
6363
const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
@@ -924,7 +924,7 @@ function backendEnv(): NodeJS.ProcessEnv {
924924
T3CODE_MODE: "desktop",
925925
T3CODE_NO_BROWSER: "1",
926926
T3CODE_PORT: String(backendPort),
927-
T3CODE_STATE_DIR: STATE_DIR,
927+
T3CODE_HOME: BASE_DIR,
928928
T3CODE_AUTH_TOKEN: backendAuthToken,
929929
};
930930
}

apps/server/integration/OrchestrationEngineHarness.integration.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import fs from "node:fs";
2-
import os from "node:os";
3-
import path from "node:path";
41
import { execFileSync } from "node:child_process";
52

63
import * as NodeServices from "@effect/platform-node/NodeServices";
@@ -13,9 +10,11 @@ import {
1310
import {
1411
Effect,
1512
Exit,
13+
FileSystem,
1614
Layer,
1715
ManagedRuntime,
1816
Option,
17+
Path,
1918
Ref,
2019
Schedule,
2120
Schema,
@@ -66,7 +65,7 @@ import {
6665
makeTestProviderAdapterHarness,
6766
type TestProviderAdapterHarness,
6867
} from "./TestProviderAdapter.integration.ts";
69-
import { ServerConfig } from "../src/config.ts";
68+
import { deriveServerPaths, ServerConfig } from "../src/config.ts";
7069

7170
function runGit(cwd: string, args: ReadonlyArray<string>) {
7271
return execFileSync("git", args, {
@@ -76,14 +75,16 @@ function runGit(cwd: string, args: ReadonlyArray<string>) {
7675
});
7776
}
7877

79-
function initializeGitWorkspace(cwd: string) {
78+
const initializeGitWorkspace = Effect.fn(function* (cwd: string) {
8079
runGit(cwd, ["init", "--initial-branch=main"]);
8180
runGit(cwd, ["config", "user.email", "[email protected]"]);
8281
runGit(cwd, ["config", "user.name", "Test User"]);
83-
fs.writeFileSync(path.join(cwd, "README.md"), "v1\n", "utf8");
82+
const fileSystem = yield* FileSystem.FileSystem;
83+
const { join } = yield* Path.Path;
84+
yield* fileSystem.writeFileString(join(cwd, "README.md"), "v1\n");
8485
runGit(cwd, ["add", "."]);
8586
runGit(cwd, ["commit", "-m", "Initial"]);
86-
}
87+
});
8788

8889
export function gitRefExists(cwd: string, ref: string): boolean {
8990
try {
@@ -214,7 +215,9 @@ export const makeOrchestrationIntegrationHarness = (
214215
options?: MakeOrchestrationIntegrationHarnessOptions,
215216
) =>
216217
Effect.gen(function* () {
217-
const sleep = (ms: number) => Effect.sleep(ms);
218+
const path = yield* Path.Path;
219+
const fileSystem = yield* FileSystem.FileSystem;
220+
218221
const provider = options?.provider ?? "codex";
219222
const useRealCodex = options?.realCodex === true;
220223
const adapterHarness = useRealCodex
@@ -231,13 +234,16 @@ export const makeOrchestrationIntegrationHarness = (
231234
listProviders: () => Effect.succeed([adapterHarness.provider]),
232235
} as typeof ProviderAdapterRegistry.Service)
233236
: null;
234-
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-orchestration-integration-"));
237+
const rootDir = yield* fileSystem.makeTempDirectoryScoped({
238+
prefix: "t3-orchestration-integration-",
239+
});
235240
const workspaceDir = path.join(rootDir, "workspace");
236-
const stateDir = path.join(rootDir, "state");
237-
const dbPath = path.join(stateDir, "state.sqlite");
238-
fs.mkdirSync(workspaceDir, { recursive: true });
239-
fs.mkdirSync(stateDir, { recursive: true });
240-
initializeGitWorkspace(workspaceDir);
241+
const { stateDir, dbPath } = yield* deriveServerPaths(rootDir, undefined).pipe(
242+
Effect.provideService(Path.Path, path),
243+
);
244+
yield* fileSystem.makeDirectory(workspaceDir, { recursive: true });
245+
yield* fileSystem.makeDirectory(stateDir, { recursive: true });
246+
yield* initializeGitWorkspace(workspaceDir);
241247

242248
const persistenceLayer = makeSqlitePersistenceLive(dbPath);
243249
const orchestrationLayer = OrchestrationEngineLive.pipe(
@@ -262,7 +268,7 @@ export const makeOrchestrationIntegrationHarness = (
262268
}),
263269
).pipe(
264270
Layer.provide(makeCodexAdapterLive()),
265-
Layer.provideMerge(ServerConfig.layerTest(workspaceDir, stateDir)),
271+
Layer.provideMerge(ServerConfig.layerTest(workspaceDir, rootDir)),
266272
Layer.provideMerge(NodeServices.layer),
267273
Layer.provideMerge(providerSessionDirectoryLayer),
268274
);
@@ -312,7 +318,7 @@ export const makeOrchestrationIntegrationHarness = (
312318
);
313319
const layer = orchestrationReactorLayer.pipe(
314320
Layer.provide(persistenceLayer),
315-
Layer.provideMerge(ServerConfig.layerTest(workspaceDir, stateDir)),
321+
Layer.provideMerge(ServerConfig.layerTest(workspaceDir, rootDir)),
316322
Layer.provideMerge(NodeServices.layer),
317323
);
318324

@@ -352,7 +358,7 @@ export const makeOrchestrationIntegrationHarness = (
352358
yield* Stream.runForEach(runtimeReceiptBus.stream, (receipt) =>
353359
Ref.update(receiptHistory, (history) => [...history, receipt]).pipe(Effect.asVoid),
354360
).pipe(Effect.forkIn(scope));
355-
yield* sleep(10);
361+
yield* Effect.sleep(10);
356362

357363
const waitForThread: OrchestrationIntegrationHarness["waitForThread"] = (
358364
threadId,
@@ -469,13 +475,7 @@ export const makeOrchestrationIntegrationHarness = (
469475
}
470476
});
471477

472-
yield* shutdown.pipe(
473-
Effect.ensuring(
474-
Effect.sync(() => {
475-
fs.rmSync(rootDir, { recursive: true, force: true });
476-
}),
477-
),
478-
);
478+
yield* shutdown;
479479
});
480480

481481
return {

apps/server/integration/orchestrationEngine.integration.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type {
2727
CheckpointDiffFinalizedReceipt,
2828
TurnProcessingQuiescedReceipt,
2929
} from "../src/orchestration/Services/RuntimeReceiptBus.ts";
30+
import * as NodeServices from "@effect/platform-node/NodeServices";
3031

3132
const asMessageId = (value: string): MessageId => MessageId.makeUnsafe(value);
3233
const asProjectId = (value: string): ProjectId => ProjectId.makeUnsafe(value);
@@ -51,8 +52,6 @@ class IntegrationWaitTimeoutError extends Schema.TaggedErrorClass<IntegrationWai
5152
},
5253
) {}
5354

54-
const sleep = (ms: number) => Effect.sleep(ms);
55-
5655
function waitForSync<A>(
5756
read: () => A,
5857
predicate: (value: A) => boolean,
@@ -70,7 +69,7 @@ function waitForSync<A>(
7069
if (Date.now() >= deadline) {
7170
return yield* Effect.die(new IntegrationWaitTimeoutError({ description }));
7271
}
73-
yield* sleep(10);
72+
yield* Effect.sleep(10);
7473
}
7574
});
7675
}
@@ -91,7 +90,7 @@ function withHarness<A, E>(
9190
makeOrchestrationIntegrationHarness({ provider }),
9291
use,
9392
(harness) => harness.dispose,
94-
);
93+
).pipe(Effect.provide(NodeServices.layer));
9594
}
9695

9796
function withRealCodexHarness<A, E>(
@@ -101,7 +100,7 @@ function withRealCodexHarness<A, E>(
101100
makeOrchestrationIntegrationHarness({ provider: "codex", realCodex: true }),
102101
use,
103102
(harness) => harness.dispose,
104-
);
103+
).pipe(Effect.provide(NodeServices.layer));
105104
}
106105

107106
const seedProjectAndThread = (harness: OrchestrationIntegrationHarness) =>

apps/server/src/attachmentPaths.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ export function normalizeAttachmentRelativePath(rawRelativePath: string): string
1111
}
1212

1313
export function resolveAttachmentRelativePath(input: {
14-
readonly stateDir: string;
14+
readonly attachmentsDir: string;
1515
readonly relativePath: string;
1616
}): string | null {
1717
const normalizedRelativePath = normalizeAttachmentRelativePath(input.relativePath);
1818
if (!normalizedRelativePath) {
1919
return null;
2020
}
2121

22-
const attachmentsRoot = path.resolve(path.join(input.stateDir, "attachments"));
22+
const attachmentsRoot = path.resolve(input.attachmentsDir);
2323
const filePath = path.resolve(path.join(attachmentsRoot, normalizedRelativePath));
2424
if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) {
2525
return null;

apps/server/src/attachmentStore.test.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,34 +44,32 @@ describe("attachmentStore", () => {
4444
});
4545

4646
it("resolves attachment path by id using the extension that exists on disk", () => {
47-
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-attachment-store-"));
47+
const attachmentsDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-attachment-store-"));
4848
try {
4949
const attachmentId = "thread-1-attachment";
50-
const attachmentsDir = path.join(stateDir, "attachments");
51-
fs.mkdirSync(attachmentsDir, { recursive: true });
5250
const pngPath = path.join(attachmentsDir, `${attachmentId}.png`);
5351
fs.writeFileSync(pngPath, Buffer.from("hello"));
5452

5553
const resolved = resolveAttachmentPathById({
56-
stateDir,
54+
attachmentsDir,
5755
attachmentId,
5856
});
5957
expect(resolved).toBe(pngPath);
6058
} finally {
61-
fs.rmSync(stateDir, { recursive: true, force: true });
59+
fs.rmSync(attachmentsDir, { recursive: true, force: true });
6260
}
6361
});
6462

6563
it("returns null when no attachment file exists for the id", () => {
66-
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-attachment-store-"));
64+
const attachmentsDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-attachment-store-"));
6765
try {
6866
const resolved = resolveAttachmentPathById({
69-
stateDir,
67+
attachmentsDir,
7068
attachmentId: "thread-1-missing",
7169
});
7270
expect(resolved).toBeNull();
7371
} finally {
74-
fs.rmSync(stateDir, { recursive: true, force: true });
72+
fs.rmSync(attachmentsDir, { recursive: true, force: true });
7573
}
7674
});
7775
});

apps/server/src/attachmentStore.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,17 @@ export function attachmentRelativePath(attachment: ChatAttachment): string {
6666
}
6767

6868
export function resolveAttachmentPath(input: {
69-
readonly stateDir: string;
69+
readonly attachmentsDir: string;
7070
readonly attachment: ChatAttachment;
7171
}): string | null {
7272
return resolveAttachmentRelativePath({
73-
stateDir: input.stateDir,
73+
attachmentsDir: input.attachmentsDir,
7474
relativePath: attachmentRelativePath(input.attachment),
7575
});
7676
}
7777

7878
export function resolveAttachmentPathById(input: {
79-
readonly stateDir: string;
79+
readonly attachmentsDir: string;
8080
readonly attachmentId: string;
8181
}): string | null {
8282
const normalizedId = normalizeAttachmentRelativePath(input.attachmentId);
@@ -85,7 +85,7 @@ export function resolveAttachmentPathById(input: {
8585
}
8686
for (const extension of ATTACHMENT_FILENAME_EXTENSIONS) {
8787
const maybePath = resolveAttachmentRelativePath({
88-
stateDir: input.stateDir,
88+
attachmentsDir: input.attachmentsDir,
8989
relativePath: `${normalizedId}${extension}`,
9090
});
9191
if (maybePath && existsSync(maybePath)) {

0 commit comments

Comments
 (0)