Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
125b619
fix(server): release completed pull request sweep data (#25177)
juliusmarminge Oct 28, 2026
d5f1d0a
test(evals): device manager cases for mcp-tool-use (#25850)
gabrielelpidio Oct 28, 2026
d07c0a7
fix(server): agents drive simulators through the device tools instead…
gabrielelpidio Oct 28, 2026
30eb7e7
fix(mobile): overlay sidebar header blurs content scrolling under it …
juliusmarminge Oct 28, 2026
fec83a1
fix(web): numbered composer lists nest and lift correctly (#17138)
michal-billtech Oct 28, 2026
612a8e5
feat(mobile): add dynamic workflows to the Agents sheet (#18642)
Bil0000 Oct 28, 2026
237498c
fix(ci): Android merges stop cutting a new Play Store build every tim…
juliusmarminge Oct 28, 2026
886d8f6
fix(mobile): dictation confirm button no longer hugs the small compos…
juliusmarminge Oct 28, 2026
be6ff17
feat(devices): lease simulators and emulators from a Simlock pool (al…
juliusmarminge Oct 28, 2026
ba18706
fix(mobile): use a native split button for Android merges (#26373)
PixPMusic Oct 28, 2026
9e09926
docs: settings paths name the Environments and Sharing & access pages…
juliusmarminge Oct 28, 2026
9826dbb
docs(agents): agents verify in a browser or simulator without asking …
juliusmarminge Oct 28, 2026
5b89a53
feat(pull-requests): manage GitHub maintainer edits (#25588)
Bil0000 Oct 28, 2026
d464631
fix(web): image chips drop the pill and match other chips (#26379)
flamboh Oct 28, 2026
e34067e
fix(mobile): align settled and active thread rows (#26622)
PixPMusic Oct 28, 2026
e677e97
fix(server): publish local git status before remote lookup (#21046)
JustYannicc Oct 29, 2026
26caea2
fix(server): outside MCP clients get thread links they can open (#26642)
t3dotgg Oct 29, 2026
725573f
fix(web): double-click selects the whole inline code span (#26639)
t3dotgg Oct 29, 2026
b287102
fix(acp): ID-only command approvals keep the tracked command and gran…
juliusmarminge Oct 29, 2026
37a9ca0
fix(cursor): subagent ls, lint and MCP results no longer fail the par…
juliusmarminge Oct 29, 2026
29f33af
perf(web): long-thread timeline rebuilds 45 → 9 ms, tool updates 12 →…
juliusmarminge Oct 29, 2026
a42bdd4
fix(mobile): a thread opened while reconnecting no longer turns into …
shivamhwp Oct 29, 2026
221610f
fix(web): keep settled drop hint inside the sidebar viewport (#26736)
Yash-Singh1 Oct 29, 2026
be7d86f
feat(devices): Simlock setup uses each machine's agent-device token, …
juliusmarminge Oct 29, 2026
a5448f4
fix(web): pull request diff follows the file tree, with a hide-tests …
juliusmarminge Oct 29, 2026
8ff9ae1
fix(mobile): delivered messages no longer reappear at the bottom of l…
juliusmarminge Oct 29, 2026
d8f4980
ci(mobile): fork PRs now get the Native Change label (#26755)
juliusmarminge Oct 29, 2026
5eebf77
fix(claude): command approvals show the command, not Claude's descrip…
maria-rcks Oct 29, 2026
7b87466
docs: add nixpkgs installation instructions (#26750)
0x4bs3nt Oct 29, 2026
861a51a
feat(server,web,mobile): fall back when the default provider is out o…
maria-rcks Oct 29, 2026
783581d
fix(ci): fork PRs now resolve in workflow_run PR lookups (#26779)
juliusmarminge Oct 29, 2026
8298664
fix(server,cursor,opencode): refresh provider usage when a turn hits …
maria-rcks Oct 29, 2026
fe219c4
fix(web): keep tool groups collapsed after a usage-limit pause (#24937)
shivamhwp Oct 29, 2026
7632c3d
fix(mobile): photo-library PNGs no longer fail after resizing (#26522)
shivamhwp Oct 29, 2026
ab1e221
fix(server): guest links no longer return file diffs (#26538)
nexxeln Oct 29, 2026
eb565e1
fix(claude): Open diff shows Claude edits as a diff instead of raw JS…
nexxeln Oct 29, 2026
dac8b11
chore: vouch nexxeln (#26812)
t3dotgg Oct 29, 2026
e256a26
Merge commit 'dac8b11ddc25bd5da62a367fdb6c3de777ae2986' into fix/bots…
fireboltdude1357 Oct 29, 2026
573f019
fix(bots): the bot launch test provides upstream's ProviderRegistry
fireboltdude1357 Oct 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
4 changes: 2 additions & 2 deletions .agents/skills/test-t3-mobile/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ This installs the dev client for the checkout's native fingerprint: from the
device, the local cache, or the shared cache in seconds, building once only
when no machine has that fingerprint yet (5-15 minutes, logged to the file it
names). The device id may be omitted when exactly one device is booted. It
prints one JSON line; on failure it names the next command. Authorized mobile
verification includes that step unless the user prohibits it.
prints one JSON line; on failure it names the next command. Mobile verification
includes that step unless the user prohibits it.

For a device you opened in an agent-device session elsewhere (a device pool,
cloud, or remote host), run `vp run mobile:install <ios|android> --session <s>`
Expand Down
1 change: 1 addition & 0 deletions .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ github:myacoub91
github:naMqe-h
github:nateEc
github:naveed949
github:nexxeln
github:nmggithub
github:Noojuno
github:notkainoa
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/desktop-macos-preview-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,19 @@ jobs:
return;
}
// workflow_run.pull_requests is empty for fork PRs, so resolve the
// PR from the built commit instead and require exactly one open PR
// from the same head repository and branch. The build baked its
// workflow_run.pull_requests and the commit-to-PR lookup are both
// empty for fork PRs, so list open PRs by head owner and branch
// and require exactly one at the built commit. The build baked its
// PR number into the version, so two candidates would mean the
// asset name could belong to either.
const associated = await github.paginate(
github.rest.repos.listPullRequestsAssociatedWithCommit,
{ owner, repo, commit_sha: run.head_sha, per_page: 100 },
);
const matching = associated.filter(
const open = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
head: `${run.head_repository?.owner?.login}:${run.head_branch}`,
per_page: 100,
});
const matching = open.filter(
(candidate) =>
candidate.state === "open" &&
candidate.head.sha === run.head_sha &&
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/mobile-eas-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,20 @@ jobs:
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
run: eas env:pull production --non-interactive
run: |
eas env:pull production --non-interactive
# EAS commands skip dotenv, and the Android fingerprint hashes this
# file. Without it every merge misses the build's hash and rebuilds.
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from "node:fs";
import { parseEnv } from "node:util";
const env = parseEnv(readFileSync(".env.local", "utf8"));
const googleServicesFile = env.T3CODE_ANDROID_GOOGLE_SERVICES_FILE;
if (!googleServicesFile) {
throw new Error("T3CODE_ANDROID_GOOGLE_SERVICES_FILE is missing from the production environment");
}
appendFileSync(process.env.GITHUB_ENV, `T3CODE_ANDROID_GOOGLE_SERVICES_FILE=${googleServicesFile}\n`);
NODE
- name: Apply manual version override
if: steps.version_app_token.outcome == 'success'
Expand Down
106 changes: 15 additions & 91 deletions .github/workflows/mobile-fingerprint-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: Mobile Fingerprint Check

# Detects whether a PR changes the native fingerprint — i.e. whether merging
# it would leave main un-OTA-able until a new store build ships. Native-change
# PRs get the "📱 Native Change" label so they can be held and merged as a
# PRs get the "📱 Native Change" label (applied by mobile-fingerprint-label.yml,
# which runs from main so fork PRs get it too) so they can be held and merged as a
# batch right before the next store submission, keeping main OTA-able for
# everything else in between. (Once one native PR merges, every later merge
# inherits the drifted fingerprint and loses OTA reach too — that is why the
Expand All @@ -27,8 +28,6 @@ jobs:
runs-on: blacksmith-8vcpu-ubuntu-2404
permissions:
contents: read
issues: write
pull-requests: write
env:
APP_VARIANT: production
NODE_OPTIONS: --max-old-space-size=8192
Expand Down Expand Up @@ -127,94 +126,19 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"
echo "changed_platforms=${changed# }" >> "$GITHUB_OUTPUT"
- name: Sync native change label
# Fork PRs get a read-only token under pull_request; the check stays
# advisory there (summary only). This workflow must not move to
# pull_request_target — it installs and runs PR code.
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v8
# mobile-fingerprint-label.yml reads this from main with a write token
# and syncs the label; this job's token is read-only for fork PRs.
- name: Record changed platforms
env:
CHANGED_PLATFORMS: ${{ steps.compare.outputs.changed_platforms }}
with:
script: |
const managedLabel = {
name: "📱 Native Change",
color: "d93f0b",
description:
"Changes the native fingerprint; merging blocks production OTAs until a new store build ships.",
};
const nativeChanged = (process.env.CHANGED_PLATFORMS ?? "").trim() !== "";
const issueNumber = context.payload.pull_request.number;
try {
const { data: existing } = await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
});
if (
existing.color !== managedLabel.color ||
(existing.description ?? "") !== managedLabel.description
) {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
color: managedLabel.color,
description: managedLabel.description,
});
}
} catch (error) {
if (error.status !== 404) {
throw error;
}
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
color: managedLabel.color,
description: managedLabel.description,
});
} catch (createError) {
if (createError.status !== 422) {
throw createError;
}
}
}
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100,
});
const hasLabel = currentLabels.some((label) => label.name === managedLabel.name);
if (nativeChanged && !hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [managedLabel.name],
});
} else if (!nativeChanged && hasLabel) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: managedLabel.name,
});
} catch (removeError) {
if (removeError.status !== 404) {
throw removeError;
}
}
}
run: |
mkdir -p "$RUNNER_TEMP/fingerprint-result"
printf '%s\n' "$CHANGED_PLATFORMS" > "$RUNNER_TEMP/fingerprint-result/changed-platforms"
core.info(
`PR #${issueNumber}: native fingerprint ${nativeChanged ? `changed (${process.env.CHANGED_PLATFORMS})` : "unchanged"}`,
);
- name: Upload changed platforms
uses: actions/upload-artifact@v7
with:
name: fingerprint-result
path: ${{ runner.temp }}/fingerprint-result
if-no-files-found: error
retention-days: 1
189 changes: 189 additions & 0 deletions .github/workflows/mobile-fingerprint-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
name: Mobile Fingerprint Label

# Trusted half of mobile-fingerprint-check.yml. That workflow runs PR code
# (fork PRs included) with a read-only token and records which platforms'
# fingerprints changed. This one runs from main with a write token and syncs
# the "📱 Native Change" label. It never checks out or runs PR code.
#
# A PR can edit the check workflow it runs under, so the recorded result is
# untrusted data: it is validated, and at worst a PR mislabels itself.

on:
workflow_run:
workflows: [Mobile Fingerprint Check]
types: [completed]

permissions:
contents: read

jobs:
label:
name: Sync native change label
# A cancelled run was superseded by a newer push, whose run labels instead.
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
actions: read
contents: read
issues: write
pull-requests: write
steps:
- id: pr
name: Resolve the pull request behind the check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const run = context.payload.workflow_run;
const { owner, repo } = context.repo;
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
owner,
repo,
run_id: run.id,
per_page: 100,
});
const results = artifacts.filter(
(artifact) => artifact.name === "fingerprint-result" && !artifact.expired,
);
if (results.length !== 1) {
core.info(`Expected one fingerprint-result artifact; found ${results.length}. Skipping.`);
return;
}
// workflow_run.pull_requests and the commit-to-PR lookup are both
// empty for fork PRs, so list open PRs by head owner and branch.
// Requiring the PR's current head to be the checked commit keeps a
// slow run for an older push from overwriting a newer result.
const open = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
head: `${run.head_repository?.owner?.login}:${run.head_branch}`,
per_page: 100,
});
const matching = open.filter(
(candidate) =>
candidate.state === "open" &&
candidate.head.sha === run.head_sha &&
candidate.head.ref === run.head_branch &&
candidate.head.repo?.full_name === run.head_repository?.full_name,
);
if (matching.length !== 1) {
core.info(`Expected one open PR at ${run.head_sha}; found ${matching.length}. Skipping.`);
return;
}
core.setOutput("artifact_id", String(results[0].id));
core.setOutput("pr_number", String(matching[0].number));
# Read the archive as bytes; never extract PR-produced files into a
# workspace this job runs code from.
- id: result
name: Read changed platforms
if: steps.pr.outputs.pr_number != ''
shell: bash
env:
GH_TOKEN: ${{ github.token }}
ARTIFACT_ID: ${{ steps.pr.outputs.artifact_id }}
run: |
set -euo pipefail
gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}/zip" > "$RUNNER_TEMP/fingerprint-result.zip"
changed="$(unzip -p "$RUNNER_TEMP/fingerprint-result.zip" changed-platforms)"
if [[ ! "$changed" =~ ^((ios|android)( (ios|android))?)?$ ]]; then
echo "Unexpected changed-platforms value '$changed'." >&2
exit 1
fi
echo "changed_platforms=$changed" >> "$GITHUB_OUTPUT"
- name: Sync native change label
if: steps.pr.outputs.pr_number != ''
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
CHANGED_PLATFORMS: ${{ steps.result.outputs.changed_platforms }}
with:
script: |
const managedLabel = {
name: "📱 Native Change",
color: "d93f0b",
description:
"Changes the native fingerprint; merging blocks production OTAs until a new store build ships.",
};
const nativeChanged = (process.env.CHANGED_PLATFORMS ?? "").trim() !== "";
const issueNumber = Number(process.env.PR_NUMBER);
try {
const { data: existing } = await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
});
if (
existing.color !== managedLabel.color ||
(existing.description ?? "") !== managedLabel.description
) {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
color: managedLabel.color,
description: managedLabel.description,
});
}
} catch (error) {
if (error.status !== 404) {
throw error;
}
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
color: managedLabel.color,
description: managedLabel.description,
});
} catch (createError) {
if (createError.status !== 422) {
throw createError;
}
}
}
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100,
});
const hasLabel = currentLabels.some((label) => label.name === managedLabel.name);
if (nativeChanged && !hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [managedLabel.name],
});
} else if (!nativeChanged && hasLabel) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: managedLabel.name,
});
} catch (removeError) {
if (removeError.status !== 404) {
throw removeError;
}
}
}
core.info(
`PR #${issueNumber}: native fingerprint ${nativeChanged ? `changed (${process.env.CHANGED_PLATFORMS})` : "unchanged"}`,
);
Loading
Loading