Skip to content
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
9403429
Make server settings authoritative for runtime behavior (#1421)
juliusmarminge Mar 26, 2026
add5f34
Add Claude context window selection support (#1422)
juliusmarminge Mar 26, 2026
648f067
Add VS Code Insiders and VSCodium to Open In editor picker (#1392)
nassimna Mar 27, 2026
02989fe
fix(web): improve chat header badge and title flex distribution (#1309)
GuilhermeVieiraDev Mar 27, 2026
d8a485e
Support `gh pr checkout` pull request references (#1457)
juliusmarminge Mar 27, 2026
fb72607
fix(claude): avoid resetting the Claude model on every turn (#1466)
harshit97 Mar 27, 2026
59a383e
Add Effect.fn refactor checklist (#1476)
juliusmarminge Mar 28, 2026
83eb396
fix(threads): Keep active-turn runtime errors from ending sessions (#…
Snowy7 Mar 28, 2026
e08cea3
Improve drain semantics via STM in `DrainableWorker` (#1474)
IMax153 Mar 28, 2026
23b3f0c
Refactor Codex adapter lifecycle helpers (#1478)
juliusmarminge Mar 28, 2026
d2e6c82
Skip a unit test that is using a bash script on Windows (#1490)
Alexx999 Mar 28, 2026
42ea7cf
Detect command not found when checking codex/claude CLI on Windows (#…
Alexx999 Mar 28, 2026
80a176f
Add eol=lf to .gitattributes for Windows users (#1491)
Alexx999 Mar 28, 2026
48196e6
fix(web): linkify Windows C:/ paths in terminal output (#1483)
MaansenV Mar 28, 2026
f0cdaf9
Add thread archiving and settings navigation (#1359)
shivamhwp Mar 28, 2026
3e2df5a
Normalize typed provider runtime ingestion (#1475)
juliusmarminge Mar 28, 2026
5851b8a
fix: trust node-pty install scripts on linux (#1451)
smalitobules Mar 28, 2026
32c1f98
Fix Linux desktop Codex CLI detection at startup (#1100)
Ryan-D-Gast Mar 28, 2026
61f9830
fix(web): allow switching away from Ultrathink without manual prompt …
Marve10s Mar 28, 2026
0429896
Fix server fallback to direct fd read on /proc/self/fd failure (#1488)
rishi-chauhan Mar 28, 2026
52fa6a7
fix(desktop): ensure all windows are destroyed before launching the N…
danielss-dev Mar 28, 2026
ce463a5
fix(web): add project path action to sidebar menu (#1436)
FllipEis Mar 28, 2026
73b2f25
fix: add noopener to external chat markdown links (#1315)
sabraman Mar 28, 2026
c426452
Tighten settings nav and add Escape back navigation (#1503)
juliusmarminge Mar 28, 2026
f4617e7
feat: add mock update server (#1180)
nmggithub Mar 28, 2026
5b1d5c1
fix(ci): use whitespace-insensitive PR size diff (#1500)
maria-rcks Mar 28, 2026
5210a83
Fix sidebar thread panel weird states. (#1497)
shivamhwp Mar 28, 2026
64d21bd
fix: Prevent sidebar project dragging when using macos control+click …
Noojuno Mar 28, 2026
33773ff
Harden Claude stream exit handling (#1504)
juliusmarminge Mar 28, 2026
5513845
feat(threads): auto-generate first-turn thread titles (#1375)
maria-rcks Mar 29, 2026
58ba4f1
Add keyboard shortcuts for jumping to sidebar threads (#1456)
t3dotgg Mar 29, 2026
afc807a
Add new GitHub users to VOUCHED.td (#1508)
juliusmarminge Mar 29, 2026
9e60597
Truncate oversized git diffs instead of failing (#1499)
juliusmarminge Mar 29, 2026
f82bae1
Update system overhaul (#1505)
shivamhwp Mar 29, 2026
5462d50
chore(release): prepare v0.0.15
t3-code[bot] Mar 29, 2026
6e587ca
Integrate upstream runtime sync foundations
aaditagrawal Mar 29, 2026
bcc1237
Merge upstream/main with fork-preserving integrations
aaditagrawal Mar 29, 2026
270ca4e
Align Copilot provider probing with runtime fallback
aaditagrawal Mar 29, 2026
70baa7c
Address review feedback and fix CI regressions
aaditagrawal Mar 29, 2026
abc6ec8
Address latest review feedback
aaditagrawal Mar 29, 2026
6ea4f4e
Make provider cache merges atomic
aaditagrawal Mar 29, 2026
b5916a7
Stabilize provider snapshot change detection
aaditagrawal Mar 29, 2026
c080a2a
Fix provider snapshot cache test baseline
aaditagrawal Mar 29, 2026
ed67a23
Fix SqlError constructor for effect@4.0.0-beta.42
aaditagrawal Mar 29, 2026
4cc9592
Format NodeSqliteClient.ts
aaditagrawal Mar 29, 2026
b134e5a
Fix ultrathink warning text in browser test
aaditagrawal Mar 29, 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: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# git autocrlf=true converts LF to CRLF on Windows, causing issues with oxfmt
* text=auto eol=lf
3 changes: 2 additions & 1 deletion .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ github:adityavardhansharma
github:binbandit
github:chuks-qua
github:cursoragent
github:eggfriedrice24
github:gbarros-dev
github:github-actions[bot]
github:hwanseoc
Expand All @@ -28,4 +27,6 @@ github:PatrickBauer
github:realAhmedRoach
github:shiroyasha9
github:Yash-Singh1
github:eggfriedrice24
github:Ymit24
github:shivamhwp
112 changes: 71 additions & 41 deletions .github/workflows/pr-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,25 +124,49 @@ jobs:
group: pr-size-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
- name: Checkout base repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Sync PR size label
uses: actions/github-script@v8
env:
PR_SIZE_LABELS_JSON: ${{ needs.prepare-config.outputs.labels_json }}
with:
script: |
const { execFileSync } = require("node:child_process");

const issueNumber = context.payload.pull_request.number;
const baseSha = context.payload.pull_request.base.sha;
const headSha = context.payload.pull_request.head.sha;
const headTrackingRef = `refs/remotes/pr-size/${issueNumber}`;
const managedLabels = JSON.parse(process.env.PR_SIZE_LABELS_JSON ?? "[]");
const managedLabelNames = new Set(managedLabels.map((label) => label.name));
// Keep this aligned with the repo's test entrypoints and test-only support files.
const testFilePatterns = [
/(^|\/)__tests__(\/|$)/,
/(^|\/)tests?(\/|$)/,
/^apps\/server\/integration\//,
/\.(test|spec|browser|integration)\.[^.\/]+$/,
const testExcludePathspecs = [
":(glob,exclude)**/__tests__/**",
":(glob,exclude)**/test/**",
":(glob,exclude)**/tests/**",
":(glob,exclude)apps/server/integration/**",
":(glob,exclude)**/*.test.*",
":(glob,exclude)**/*.spec.*",
":(glob,exclude)**/*.browser.*",
":(glob,exclude)**/*.integration.*",
];

const isTestFile = (filename) =>
testFilePatterns.some((pattern) => pattern.test(filename));
const sumNumstat = (text) =>
text
.split("\n")
.filter(Boolean)
.reduce((total, line) => {
const [insertionsRaw = "0", deletionsRaw = "0"] = line.split("\t");
const additions =
insertionsRaw === "-" ? 0 : Number.parseInt(insertionsRaw, 10) || 0;
const deletions =
deletionsRaw === "-" ? 0 : Number.parseInt(deletionsRaw, 10) || 0;

return total + additions + deletions;
}, 0);

const resolveSizeLabel = (totalChangedLines) => {
if (totalChangedLines < 10) {
Expand All @@ -168,50 +192,56 @@ jobs:
return "size:XXL";
};

const files = await github.paginate(
github.rest.pulls.listFiles,
execFileSync("git", ["fetch", "--no-tags", "origin", baseSha], {
stdio: "inherit",
});

execFileSync(
"git",
["fetch", "--no-tags", "origin", `+refs/pull/${issueNumber}/head:${headTrackingRef}`],
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: issueNumber,
per_page: 100,
stdio: "inherit",
},
(response) => response.data,
);

const isFileListTruncated = files.length >= 3000;
if (isFileListTruncated) {
core.warning(
"The GitHub pull request files API may truncate results at 3,000 files; forcing size:XXL.",
const resolvedHeadSha = execFileSync("git", ["rev-parse", headTrackingRef], {
encoding: "utf8",
}).trim();

if (resolvedHeadSha !== headSha) {
throw new Error(
`Fetched head SHA ${resolvedHeadSha} does not match pull request head SHA ${headSha}; retry once refs/pull/${issueNumber}/head catches up.`,
);
}

let testChangedLines = 0;
let nonTestChangedLines = 0;

if (!isFileListTruncated) {
for (const file of files) {
const changedLinesForFile = (file.additions ?? 0) + (file.deletions ?? 0);

if (changedLinesForFile === 0) {
continue;
}
execFileSync("git", ["cat-file", "-e", `${baseSha}^{commit}`], {
stdio: "inherit",
});

if (isTestFile(file.filename)) {
testChangedLines += changedLinesForFile;
continue;
}
const diffArgs = [
"diff",
"--numstat",
"--ignore-all-space",
"--ignore-blank-lines",
`${baseSha}...${resolvedHeadSha}`,
];

nonTestChangedLines += changedLinesForFile;
}
}
const totalChangedLines = sumNumstat(
execFileSync(
"git",
diffArgs,
{ encoding: "utf8" },
),
);
const nonTestChangedLines = sumNumstat(
execFileSync("git", [...diffArgs, "--", ".", ...testExcludePathspecs], {
encoding: "utf8",
}),
);
const testChangedLines = Math.max(0, totalChangedLines - nonTestChangedLines);

const changedLines = isFileListTruncated
? 1000
: (nonTestChangedLines === 0 ? testChangedLines : nonTestChangedLines);
const nextLabelName = isFileListTruncated
? "size:XXL"
: resolveSizeLabel(changedLines);
const changedLines = nonTestChangedLines === 0 ? testChangedLines : nonTestChangedLines;
const nextLabelName = resolveSizeLabel(changedLines);

const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ packages/*/dist
build/
.logs/
release/
release-mock/
.t3
.idea/
apps/web/.playwright
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.0.14",
"version": "0.0.15",
"private": true,
"main": "dist-electron/main.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
const MENU_ACTION_CHANNEL = "desktop:menu-action";
const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_CHECK_CHANNEL = "desktop:update-check";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const UPDATE_CHECK_CHANNEL = "desktop:update-check";
const LOG_DIR_CHANNEL = "desktop:log-dir";
const LOG_LIST_CHANNEL = "desktop:log-list";
const LOG_READ_CHANNEL = "desktop:log-read";
Expand Down
28 changes: 24 additions & 4 deletions apps/desktop/src/syncShellEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,13 @@ describe("syncShellEnvironment", () => {
expect(env.SSH_AUTH_SOCK).toBe("/tmp/inherited.sock");
});

it("does nothing outside macOS", () => {
it("hydrates PATH and missing SSH_AUTH_SOCK from the login shell on linux", () => {
const env: NodeJS.ProcessEnv = {
SHELL: "/bin/zsh",
PATH: "/usr/bin",
SSH_AUTH_SOCK: "/tmp/inherited.sock",
};
const readEnvironment = vi.fn(() => ({
PATH: "/opt/homebrew/bin:/usr/bin",
PATH: "/home/linuxbrew/.linuxbrew/bin:/usr/bin",
SSH_AUTH_SOCK: "/tmp/secretive.sock",
}));

Expand All @@ -78,8 +77,29 @@ describe("syncShellEnvironment", () => {
readEnvironment,
});

expect(readEnvironment).toHaveBeenCalledWith("/bin/zsh", ["PATH", "SSH_AUTH_SOCK"]);
expect(env.PATH).toBe("/home/linuxbrew/.linuxbrew/bin:/usr/bin");
expect(env.SSH_AUTH_SOCK).toBe("/tmp/secretive.sock");
});

it("does nothing outside macOS and linux", () => {
const env: NodeJS.ProcessEnv = {
SHELL: "C:/Program Files/Git/bin/bash.exe",
PATH: "C:\\Windows\\System32",
SSH_AUTH_SOCK: "/tmp/inherited.sock",
};
const readEnvironment = vi.fn(() => ({
PATH: "/usr/local/bin:/usr/bin",
SSH_AUTH_SOCK: "/tmp/secretive.sock",
}));

syncShellEnvironment(env, {
platform: "win32",
readEnvironment,
});

expect(readEnvironment).not.toHaveBeenCalled();
expect(env.PATH).toBe("/usr/bin");
expect(env.PATH).toBe("C:\\Windows\\System32");
expect(env.SSH_AUTH_SOCK).toBe("/tmp/inherited.sock");
});
});
3 changes: 2 additions & 1 deletion apps/desktop/src/syncShellEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export function syncShellEnvironment(
readEnvironment?: ShellEnvironmentReader;
} = {},
): void {
if ((options.platform ?? process.platform) !== "darwin") return;
const platform = options.platform ?? process.platform;
if (platform !== "darwin" && platform !== "linux") return;

try {
const shell = env.SHELL?.trim() || "/bin/zsh";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ function waitFor<A, E>(
read: Effect.Effect<A, E>,
predicate: (value: A) => boolean,
description: string,
timeoutMs = 10_000,
timeoutMs = 60_000,
): Effect.Effect<A, never> {
const RETRY_SIGNAL = "wait_for_retry";
const retryIntervalMs = 10;
Expand Down Expand Up @@ -306,7 +306,8 @@ export const makeOrchestrationIntegrationHarness = (
Effect.succeed({ branch: input.newBranch }),
} as unknown as GitCoreShape);
const textGenerationLayer = Layer.succeed(TextGeneration, {
generateBranchName: () => Effect.succeed({ branch: null }),
generateBranchName: () => Effect.succeed({ branch: "update" }),
generateThreadTitle: () => Effect.succeed({ title: "New thread" }),
} as unknown as TextGenerationShape);
const providerCommandReactorLayer = ProviderCommandReactorLive.pipe(
Layer.provideMerge(runtimeServicesLayer),
Expand Down Expand Up @@ -364,7 +365,7 @@ export const makeOrchestrationIntegrationHarness = (
const receiptHistory = yield* Ref.make<ReadonlyArray<OrchestrationRuntimeReceipt>>([]);
yield* Stream.runForEach(runtimeReceiptBus.stream, (receipt) =>
Ref.update(receiptHistory, (history) => [...history, receipt]).pipe(Effect.asVoid),
).pipe(Effect.forkIn(scope));
).pipe(Effect.forkIn(scope, { startImmediately: true }));
yield* Effect.sleep(10);

const waitForThread: OrchestrationIntegrationHarness["waitForThread"] = (
Expand Down
27 changes: 25 additions & 2 deletions apps/server/integration/orchestrationEngine.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function waitForSync<A>(
read: () => A,
predicate: (value: A) => boolean,
description: string,
timeoutMs = 3000,
timeoutMs = 10_000,
): Effect.Effect<A, never> {
return Effect.gen(function* () {
const deadline = Date.now() + timeoutMs;
Expand Down Expand Up @@ -745,6 +745,18 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git
messageId: "msg-user-revert-1",
text: "First edit",
});
yield* harness.waitForReceipt(
(receipt): receipt is CheckpointDiffFinalizedReceipt =>
receipt.type === "checkpoint.diff.finalized" &&
receipt.threadId === THREAD_ID &&
receipt.checkpointTurnCount === 1,
);
yield* harness.waitForReceipt(
(receipt): receipt is TurnProcessingQuiescedReceipt =>
receipt.type === "turn.processing.quiesced" &&
receipt.threadId === THREAD_ID &&
receipt.checkpointTurnCount === 1,
);

yield* harness.waitForThread(
THREAD_ID,
Expand Down Expand Up @@ -803,14 +815,25 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git
messageId: "msg-user-revert-2",
text: "Second edit",
});
yield* harness.waitForReceipt(
(receipt): receipt is CheckpointDiffFinalizedReceipt =>
receipt.type === "checkpoint.diff.finalized" &&
receipt.threadId === THREAD_ID &&
receipt.checkpointTurnCount === 2,
);
yield* harness.waitForReceipt(
(receipt): receipt is TurnProcessingQuiescedReceipt =>
receipt.type === "turn.processing.quiesced" &&
receipt.threadId === THREAD_ID &&
receipt.checkpointTurnCount === 2,
);

yield* harness.waitForThread(
THREAD_ID,
(entry) =>
entry.latestTurn?.turnId === "turn-2" &&
entry.checkpoints.length === 2 &&
entry.activities.some((activity) => activity.turnId === "turn-2"),
8000,
);

yield* harness.engine.dispatch({
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.0.14",
"version": "0.0.15",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
Loading
Loading