Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c2c4185
fix(web): onboarding installs agents without needing Node or npm (#10…
t3dotgg Sep 6, 2026
7ac93e3
fix(server): allow settling threads with unanswered async questions (…
t3dotgg Sep 6, 2026
001f06d
feat(ci): ship stable releases from the latest nightly commit (#10410)
t3dotgg Sep 6, 2026
075a86e
feat(marketing): add a nightly channel to the download page (#10408)
t3dotgg Sep 6, 2026
9c96ac2
fix(web): keep settings inputs focused during IME composition (#10262)
Lucenx9 Sep 6, 2026
f1a0811
fix(server): preserve Codex reset credits during usage updates (#10308)
yashranaway Sep 6, 2026
e15ffb9
docs: link the repository security reporting policy (#10303)
yashranaway Sep 6, 2026
2d6a379
fix(web): only show auto balance errors after failed checks (#10407)
maria-rcks Sep 6, 2026
3941c2a
fix(web): improve preview recording frame delivery (#10403)
maria-rcks Sep 6, 2026
3cd2cbb
Modify model and budget settings in ui-consistency.md
juliusmarminge Sep 6, 2026
f5a1ec5
Update model and effort in effect-service-conventions
juliusmarminge Sep 6, 2026
0032892
Fix duplicate maxBudgetPerRun entry in UI consistency
juliusmarminge Sep 6, 2026
ac4f1a2
fix(server): preserve inline provider secrets on redacted saves (#10054)
maxwellyoung Sep 6, 2026
d57bdf3
Fix formatting of maxBudgetPerPR in conventions file
juliusmarminge Sep 6, 2026
2108996
fix(web, mobile): replace Apple desktop machine labels (#10396)
extoci Sep 6, 2026
7e8ae6b
fix(web): hide browser when the right panel starts closing (#10385)
Neel2107 Sep 6, 2026
4538770
fix(web): keep settings section headings description-free (#10415)
maria-rcks Sep 6, 2026
0a89364
fix(usage): read and redeem hub reset credits through CLIProxyAPI (#1…
juliusmarminge Sep 6, 2026
7939415
fix(web): deduplicate expanded tool labels and keep errors expandable…
Yash-Singh1 Sep 6, 2026
86070cb
fix(server): skip git status scans while the index is locked (#9845)
Gigioxx Sep 6, 2026
29c5ecd
fix(mcp): allow text-only preview snapshots (#10232)
juliusmarminge Sep 6, 2026
3d00cfd
fix(claude): name the expired login or usage limit instead of a gener…
vitalyiegorov Sep 6, 2026
66a24d6
feat(mobile): queue a message while its attachment is still uploading…
juliusmarminge Sep 6, 2026
d6aa179
feat(mobile): show when an existing thread has a message waiting in t…
juliusmarminge Sep 6, 2026
9513925
fix(codex): accept misalignment policy errors on thread resume (#10373)
realbakari Sep 6, 2026
d3d4ea4
fix(server): skip disabled settlement lookups (#10424)
t3dotgg Sep 6, 2026
9ab0635
fix(server): run OpenCode CLI commands sequentially (#10427)
t3dotgg Sep 7, 2026
5b0c923
feat(web): name the drop action while dragging sidebar threads (#10378)
SunkenInTime Sep 7, 2026
252df77
perf(web): keep the sidebar responsive during bulk thread updates (#1…
t3dotgg Sep 7, 2026
ec36176
fix(web): onboarding wizard now supports light mode (#10432)
t3dotgg Sep 7, 2026
7112697
feat(threads): dismiss async questions without replying (#10431)
t3dotgg Sep 7, 2026
a12589d
fix(web): stop collapsing the composer when it loses focus (#10437)
t3dotgg Sep 7, 2026
1abc717
fix(server): keep interrupted threads resumable after restarts (#10421)
maria-rcks Sep 7, 2026
efeac14
fix(web): show load balancing note for a single machine (#10433)
maria-rcks Sep 7, 2026
1e740e4
fix(server): follow placeholder branches after checkout updates (#10441)
Yash-Singh1 Sep 7, 2026
8c9a49a
fix(mobile): expand single-line tool details in work logs (#10442)
Yash-Singh1 Sep 7, 2026
95f9b14
fix(server): import transcripts with oversized tool records (#10430)
Yash-Singh1 Sep 7, 2026
0860cea
fix(marketing): deploy site with nightly releases (#10443)
t3dotgg Sep 7, 2026
a07715c
fix(web): preserve multiline composer drafts during timeline scrollin…
Yash-Singh1 Sep 7, 2026
52b2bf7
fix(server): handle JSON-wrapped titles and verbose Claude output (#1…
Noojuno Sep 7, 2026
da976cf
fix(marketing): restore continuous endorsement scrolling (#10450)
t3dotgg Sep 7, 2026
bd280de
Revert "fix(marketing): restore continuous endorsement scrolling" (#1…
t3dotgg Sep 7, 2026
b22646c
fix(marketing): bring back the endorsement marquee (#10455)
t3dotgg Sep 7, 2026
de28fa1
chore: enable CodeRabbit automatic reviews (#10457)
t3dotgg Sep 7, 2026
1c1d38f
fix(codex): keep Spark limits from replacing the main allowance (#10458)
Yash-Singh1 Sep 7, 2026
7e03dcf
fix(marketing): send 95 nightly downloads to the downloads page (#10460)
t3dotgg Sep 7, 2026
ecf3716
fix(web): composer regains focus when you tab back into T3 Code (#10463)
t3dotgg Sep 7, 2026
4e969f3
fix(web): keep sidebar drag dividers clear and gestures smooth (#10453)
juliusmarminge Sep 7, 2026
6134b90
fix(server): mark Cursor transport error answers as failed (#10337)
shivamhwp Sep 7, 2026
f5fb056
fix(web): clear stuck panel resize cursor (#10461)
t3dotgg Sep 7, 2026
490eb17
fix(web): clarify sidebar drag dividers and empty targets (#10464)
juliusmarminge Sep 7, 2026
f729e8f
fix(web): make onboarding a shared multi-computer wizard (#10465)
juliusmarminge Sep 7, 2026
6abdf37
fix(shared): redeem reset credits through the hub when it holds the a…
juliusmarminge Sep 7, 2026
a2f25bb
feat(agents): checkpoint native cross-provider dispatch
agiontheway Sep 7, 2026
b3a77cc
chore(agents): remove inactive Effect diagnostic suppressions
agiontheway Sep 7, 2026
01b6686
chore(release): align native package versions to 0.0.39
agiontheway Sep 7, 2026
80c01ae
fix(server): preserve Claude child model on follow-up
agiontheway Sep 7, 2026
b68655c
fix(agents): checkpoint automatic delivery and live callback turns
agiontheway Sep 10, 2026
9d0bfec
chore(fork): reconcile previously rebased dispatch ancestry
agiontheway Sep 10, 2026
ef18a43
fix(agents): deliver child results through native hidden input
agiontheway Sep 10, 2026
811c589
fix(server): show accepted native question answers in chat
agiontheway Sep 10, 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 .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
reviews:
review_status: false
auto_review:
enabled: false
enabled: true
8 changes: 8 additions & 0 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Security policy

Report security vulnerabilities affecting T3 Code or T3 Tools-operated infrastructure to
[security@ping.gg](mailto:security@ping.gg). Please do not disclose them publicly until we have had
a reasonable opportunity to investigate and remediate them.

See the [full security policy](https://t3.codes/security-policy) for reporting details, scope,
and safe harbor terms for good-faith research.
43 changes: 33 additions & 10 deletions .github/scripts/check-nightly-release.cjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
const MINIMUM_RELEASE_GAP_MS = 6 * 60 * 60 * 1000;

// Runs after the workflow acquires the nightly concurrency lock.
async function shouldReleaseNightly({ github, context, core, now = Date.now() }) {
const isNightlyTag = (tag) => /^v.*-nightly\./.test(tag) || tag.startsWith("nightly-v");

// Newest published nightly by publication time, or undefined when none exists.
async function findLatestNightly({ github, context }) {
const releases = await github.paginate(github.rest.repos.listReleases, {
...context.repo,
per_page: 100,
});
const lastNightly = releases
.filter(
(release) =>
!release.draft &&
release.published_at &&
(/^v.*-nightly\./.test(release.tag_name) || release.tag_name.startsWith("nightly-v")),
)
return releases
.filter((release) => !release.draft && release.published_at && isNightlyTag(release.tag_name))
.sort((a, b) => Date.parse(b.published_at) - Date.parse(a.published_at))[0];
}

// Runs after the workflow acquires the nightly concurrency lock.
async function shouldReleaseNightly({ github, context, core, now = Date.now() }) {
const lastNightly = await findLatestNightly({ github, context });

if (!lastNightly) {
core.info("No published nightly found. Proceeding with release.");
Expand Down Expand Up @@ -41,4 +43,25 @@ async function shouldReleaseNightly({ github, context, core, now = Date.now() })
return true;
}

module.exports = { shouldReleaseNightly };
// Stable releases build the commit the latest nightly shipped, so the stable
// build is one nightly users already ran. Returns the nightly tag, its commit,
// and the stable version that nightly was a preview of.
async function resolveLatestNightlyCommit({ github, context, core }) {
const lastNightly = await findLatestNightly({ github, context });
if (!lastNightly) {
throw new Error("No published nightly found. Stable releases build the latest nightly commit.");
}

const tag = lastNightly.tag_name;
// repos.getCommit dereferences annotated tags, so this is the commit either way.
const { data: commit } = await github.rest.repos.getCommit({ ...context.repo, ref: tag });
const version = /^(?:nightly-)?v(\d+\.\d+\.\d+)-nightly\./.exec(tag)?.[1];
if (!version) {
throw new Error(`Cannot derive a stable version from nightly tag ${tag}.`);
}

core.info(`Latest nightly ${tag} shipped ${commit.sha} as a preview of ${version}.`);
return { tag, sha: commit.sha, version };
}

module.exports = { shouldReleaseNightly, resolveLatestNightlyCommit };
42 changes: 42 additions & 0 deletions .github/scripts/check-nightly-release.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,45 @@ for (const status of ["behind", "diverged"]) {
assert.equal(await shouldReleaseNightly(options), false);
});
}

const { resolveLatestNightlyCommit } = require("./check-nightly-release.cjs");

function nightlyCommitFixture({ releases, commitSha = "abc123" }) {
const refs = [];
const { options } = fixture({ releases });
options.github.rest.repos.getCommit = async ({ ref }) => {
refs.push(ref);
return { data: { sha: commitSha } };
};
return { options, refs };
}

test("stable releases resolve the commit of the newest published nightly", async () => {
const { options, refs } = nightlyCommitFixture({
releases: [
nightly(10, { tag_name: "v1.0.1-nightly.20260905.100" }),
nightly(1, { tag_name: "v1.0.1-nightly.20260905.123" }),
nightly(0, { tag_name: "v1.0.0" }),
nightly(0, { draft: true, tag_name: "v1.0.1-nightly.20260905.999" }),
],
commitSha: "deadbeef",
});
assert.deepEqual(await resolveLatestNightlyCommit(options), {
tag: "v1.0.1-nightly.20260905.123",
sha: "deadbeef",
version: "1.0.1",
});
assert.deepEqual(refs, ["v1.0.1-nightly.20260905.123"]);
});

test("stable releases derive the version from legacy nightly tags", async () => {
const { options } = nightlyCommitFixture({
releases: [nightly(1, { tag_name: "nightly-v0.9.0-nightly.20260905.5" })],
});
assert.equal((await resolveLatestNightlyCommit(options)).version, "0.9.0");
});

test("stable releases fail without a published nightly", async () => {
const { options } = nightlyCommitFixture({ releases: [nightly(0, { tag_name: "v1.0.0" })] });
await assert.rejects(resolveLatestNightlyCommit(options), /No published nightly/);
});
135 changes: 105 additions & 30 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ on:
- stable
- nightly
version:
description: "Release version (for example 1.2.3 or v1.2.3)"
description: "Stable version override (for example 1.2.3). Defaults to the version the latest nightly previewed."
required: false
type: string

Expand All @@ -39,33 +39,54 @@ permissions:
id-token: none

jobs:
check_changes:
name: Check automatic nightly release
if: github.event_name == 'schedule'
# Picks the commit every later job builds. Nightlies and tag pushes build the
# triggering commit. Manual stable releases build the commit of the latest
# published nightly, so stable only ever ships a build that nightly users
# have already run. Scheduled runs also decide here whether a nightly is due.
resolve_commit:
name: Resolve release commit
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
has_changes: ${{ steps.check.outputs.result }}
ref: ${{ steps.resolve.outputs.ref }}
nightly_version: ${{ steps.resolve.outputs.nightly_version }}
has_changes: ${{ steps.resolve.outputs.has_changes }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: .github/scripts

- id: check
name: Check release gap and new commits
- id: resolve
name: Resolve release commit
uses: actions/github-script@v8
env:
DISPATCH_CHANNEL: ${{ inputs.channel }}
with:
script: |
const { shouldReleaseNightly } = require('./.github/scripts/check-nightly-release.cjs');
return await shouldReleaseNightly({ github, context, core });
const {
shouldReleaseNightly,
resolveLatestNightlyCommit,
} = require('./.github/scripts/check-nightly-release.cjs');

if (context.eventName === 'schedule') {
core.setOutput('has_changes', await shouldReleaseNightly({ github, context, core }));
core.setOutput('ref', context.sha);
} else if (context.eventName === 'workflow_dispatch' && process.env.DISPATCH_CHANNEL !== 'nightly') {
const { tag, sha, version } = await resolveLatestNightlyCommit({ github, context, core });
core.notice(`Stable release builds ${sha}, the commit shipped by ${tag}.`);
core.setOutput('ref', sha);
core.setOutput('nightly_version', version);
} else {
core.setOutput('ref', context.sha);
}

preflight:
name: Preflight
needs: [check_changes]
needs: [resolve_commit]
if: |
!failure() && !cancelled() &&
(github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true')
needs.resolve_commit.result == 'success' &&
(github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true')
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
outputs:
Expand All @@ -78,11 +99,12 @@ jobs:
cli_dist_tag: ${{ steps.release_meta.outputs.cli_dist_tag }}
is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }}
make_latest: ${{ steps.release_meta.outputs.make_latest }}
ref: ${{ github.sha }}
ref: ${{ needs.resolve_commit.outputs.ref }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.resolve_commit.outputs.ref }}
fetch-depth: 0
sparse-checkout: |
/*
Expand All @@ -104,8 +126,9 @@ jobs:
env:
DISPATCH_CHANNEL: ${{ github.event.inputs.channel }}
DISPATCH_VERSION: ${{ github.event.inputs.version }}
NIGHTLY_VERSION: ${{ needs.resolve_commit.outputs.nightly_version }}
NIGHTLY_DATE: ${{ github.run_started_at }}
NIGHTLY_SHA: ${{ github.sha }}
NIGHTLY_SHA: ${{ needs.resolve_commit.outputs.ref }}
NIGHTLY_RUN_NUMBER: ${{ github.run_number }}
run: |
if [[ "${GITHUB_EVENT_NAME}" == "schedule" || ( "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${DISPATCH_CHANNEL:-stable}" == "nightly" ) ]]; then
Expand All @@ -123,9 +146,9 @@ jobs:
echo "make_latest=false" >> "$GITHUB_OUTPUT"
else
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
raw="${DISPATCH_VERSION}"
raw="${DISPATCH_VERSION:-$NIGHTLY_VERSION}"
if [[ -z "$raw" ]]; then
echo "workflow_dispatch stable releases require the version input." >&2
echo "workflow_dispatch stable releases need a version input or a published nightly." >&2
exit 1
fi
else
Expand Down Expand Up @@ -210,14 +233,12 @@ jobs:

relay_public_config:
name: Resolve T3 Connect public config
# Consumes only the commit SHA, not preflight's resolved version, so it runs
# alongside preflight instead of after it. The condition mirrors preflight's:
# check_changes is skipped on manual and tag releases (skipped is neither failure
# nor success, so success() would be wrong here).
needs: [check_changes]
# Consumes only the release commit, not preflight's resolved version, so it
# runs alongside preflight instead of after it. The condition mirrors preflight's.
needs: [resolve_commit]
if: |
!failure() && !cancelled() &&
(github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true')
needs.resolve_commit.result == 'success' &&
(github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true')
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 5
environment:
Expand All @@ -239,7 +260,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
ref: ${{ needs.resolve_commit.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
Expand Down Expand Up @@ -312,19 +333,19 @@ jobs:
# machine. node-pty is N-API, so one binary works across all WSL Node versions.
build_wsl_node_pty:
name: Build WSL node-pty (linux-x64)
# Same gating as relay_public_config: only the commit SHA is needed, so this
# runs alongside preflight. See the condition comment there.
needs: [check_changes]
# Same gating as relay_public_config: only the release commit is needed, so
# this runs alongside preflight. See the condition comment there.
needs: [resolve_commit]
if: |
!failure() && !cancelled() &&
(github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true')
needs.resolve_commit.result == 'success' &&
(github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true')
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
ref: ${{ needs.resolve_commit.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
Expand Down Expand Up @@ -1036,6 +1057,60 @@ jobs:
"${vercel_scope_args[@]}"
fi

deploy_marketing:
name: Deploy marketing site
needs: [preflight, release]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'nightly' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_TEAM_SLUG: ${{ vars.VERCEL_TEAM_SLUG }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: |
args:
- --filter=@t3tools/marketing...

- name: Deploy marketing site to Vercel
shell: bash
run: |
set -euo pipefail

if [[ -z "${VERCEL_TOKEN:-}" || -z "${VERCEL_ORG_ID:-}" ]]; then
echo "Missing one or more required Vercel secrets: VERCEL_TOKEN, VERCEL_ORG_ID." >&2
exit 1
fi

VERCEL_PROJECT_ID="$(
curl --fail --silent --show-error \
--header "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v9/projects/t3code-marketing?teamId=$VERCEL_ORG_ID" \
| jq --exit-status --raw-output '.id'
)"
export VERCEL_PROJECT_ID

vp dlx vercel@53.1.1 deploy \
--archive=tgz \
--prod \
--yes \
--token "$VERCEL_TOKEN" \
--scope "${VERCEL_TEAM_SLUG:-$VERCEL_ORG_ID}"

finalize:
name: Finalize release
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }}
Expand Down
5 changes: 3 additions & 2 deletions .macroscope/check-run-agents/effect-service-conventions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Effect Service Conventions
model: claude-opus-5
effort: high
model: gpt-5-6-sol
effort: medium
input: full_diff
tools:
- browse_code
Expand All @@ -16,6 +16,7 @@ labels:
- vouch:trusted
requires:
- Check
maxBudgetPerRun: 5
maxBudgetPerPR: 25
conclusion: failure
showToolCalls: true
Expand Down
6 changes: 3 additions & 3 deletions .macroscope/check-run-agents/ui-consistency.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: UI Consistency
model: claude-opus-5
model: gpt-5-6-terra
effort: medium
input: full_diff
tools:
Expand All @@ -15,9 +15,9 @@ labels:
- vouch:trusted
requires:
- Check
maxBudgetPerPR: 25
maxBudgetPerRun: 2
maxBudgetPerPR: 10
conclusion: failure
maxBudgetPerRun: 10
---

# UI consistency review
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.38",
"version": "0.0.39",
"private": true,
"type": "module",
"main": "dist-electron/main.cjs",
Expand Down
Loading
Loading