Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6a8f4d3
refactor(mobile): remove unused pairing redaction wrapper (#10147)
juliusmarminge Sep 5, 2026
47e250a
test(web): drop provider banner styling assertions (#10148)
juliusmarminge Sep 5, 2026
29c3a54
refactor(client-runtime): remove unused relay token waiter (#10151)
juliusmarminge Sep 5, 2026
a324cab
test(web): drop sidebar artwork styling snapshots (#10152)
juliusmarminge Sep 5, 2026
3fbc497
refactor(ssh): keep package internals private (#10144)
juliusmarminge Sep 5, 2026
62ed748
ci: reject unused SSH exports with Knip (#10145)
juliusmarminge Sep 5, 2026
cb9a694
refactor(acp): keep protocol implementation exports private (#10165)
juliusmarminge Sep 5, 2026
2c301fd
fix(shared): validate cloudflared with the version subcommand (#9880)
maria-rcks Sep 5, 2026
60e1b73
fix(desktop): separate LAN and Tailscale pairing endpoints (#9882)
maria-rcks Sep 5, 2026
311f05c
fix(server): install pinned runtime when pnpm node lacks npm (#9923)
maria-rcks Sep 5, 2026
8d3c56b
fix(web): hide sidebar search shortcut on mobile (#9932)
maria-rcks Sep 5, 2026
89bd637
fix(web): align tool disclosure chevrons with expanded state (#9935)
maria-rcks Sep 5, 2026
6349a0e
fix(antigravity): distinguish session initialization auth failures (#…
maria-rcks Sep 5, 2026
0d8a91a
fix(cursor): cache successful model discovery between refreshes (#9918)
maria-rcks Sep 5, 2026
4ca7146
fix(opencode): revert from the first removed assistant message (#9924)
maria-rcks Sep 5, 2026
d92dca7
fix(web): resume imported custom-provider threads (#10184)
juliusmarminge Sep 5, 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
5 changes: 4 additions & 1 deletion apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ const bootstrap = Effect.gen(function* () {
yield* logBootstrapInfo("bootstrap enabled network access", {
endpointUrl: serverExposureState.endpointUrl,
});
} else if (settings.serverExposureMode === "network-accessible") {
} else if (
settings.serverExposureMode === "network-accessible" &&
serverExposureState.mode === "local-only"
) {
yield* logBootstrapWarning(
"bootstrap fell back to local-only because no advertised network host was available",
);
Expand Down
40 changes: 33 additions & 7 deletions apps/desktop/src/backend/DesktopServerExposure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,9 @@ describe("DesktopServerExposure", () => {
);
});

it.effect("resolves advertised endpoints from the scoped runtime state", () =>
it.effect("keeps LAN and Tailscale endpoints distinct when Tailscale is enumerated first", () =>
withHarness(
{ ...lanNetworkInterfaces, ...tailnetNetworkInterfaces },
{ ...tailnetNetworkInterfaces, ...lanNetworkInterfaces },
Effect.gen(function* () {
const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
yield* serverExposure.configureFromSettings({ port: 4173 });
Expand All @@ -321,6 +321,32 @@ describe("DesktopServerExposure", () => {
),
);

it.effect("keeps Tailscale-only hosts network-accessible", () =>
withHarness(
tailnetNetworkInterfaces,
Effect.gen(function* () {
const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
const settings = yield* DesktopAppSettings.DesktopAppSettings;
yield* settings.setServerExposureMode("network-accessible");

const state = yield* serverExposure.configureFromSettings({ port: 4173 });
assert.equal(state.mode, "network-accessible");
assert.equal(state.advertisedHost, null);
assert.equal(state.endpointUrl, null);
assert.equal((yield* serverExposure.backendConfig).bindHost, "0.0.0.0");

const endpoints = yield* serverExposure.getAdvertisedEndpoints;
assert.deepEqual(
endpoints.map((endpoint) => [endpoint.reachability, endpoint.httpBaseUrl]),
[
["loopback", "http://127.0.0.1:4173/"],
["private-network", "http://100.90.1.2:4173/"],
],
);
}),
),
);

it.effect("does not spawn the tailscale CLI while server exposure is local-only", () =>
withHarness(
lanNetworkInterfaces,
Expand All @@ -342,25 +368,25 @@ describe("DesktopServerExposure", () => {
),
);

it.effect("uses ConfigProvider desktop exposure overrides", () =>
it.effect("preserves explicit Tailscale exposure overrides", () =>
withHarness(
lanNetworkInterfaces,
Effect.gen(function* () {
const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
yield* serverExposure.configureFromSettings({ port: 4173 });
const change = yield* serverExposure.setMode("network-accessible");

assert.equal(change.state.advertisedHost, "10.0.0.7");
assert.equal(change.state.endpointUrl, "http://10.0.0.7:4173");
assert.equal(change.state.advertisedHost, "100.90.1.2");
assert.equal(change.state.endpointUrl, "http://100.90.1.2:4173");

const endpoints = yield* serverExposure.getAdvertisedEndpoints;
assert.deepEqual(
endpoints.map((endpoint) => endpoint.httpBaseUrl),
["http://127.0.0.1:4173/", "http://10.0.0.7:4173/", "https://public.example.test/"],
["http://127.0.0.1:4173/", "http://100.90.1.2:4173/", "https://public.example.test/"],
);
}),
{
T3CODE_DESKTOP_LAN_HOST: "10.0.0.7",
T3CODE_DESKTOP_LAN_HOST: "100.90.1.2",
T3CODE_DESKTOP_HTTPS_ENDPOINTS: "https://public.example.test",
},
),
Expand Down
15 changes: 12 additions & 3 deletions apps/desktop/src/backend/DesktopServerExposure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type DesktopServerExposureMode,
type DesktopServerExposureState,
} from "@t3tools/contracts";
import { readTailscaleStatus } from "@t3tools/tailscale";
import { isTailscaleIpv4Address, readTailscaleStatus } from "@t3tools/tailscale";
import * as Context from "effect/Context";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -65,7 +65,9 @@ const normalizeOptionalHost = (value: string | undefined): string | undefined =>
};

const isUsableLanIpv4Address = (address: string): boolean =>
!address.startsWith("127.") && !address.startsWith("169.254.");
!address.startsWith("127.") &&
!address.startsWith("169.254.") &&
!isTailscaleIpv4Address(address);

const isHttpsEndpointUrl = (value: string): boolean => {
try {
Expand Down Expand Up @@ -376,7 +378,14 @@ function resolveRuntimeState(input: {
...(advertisedHostOverride ? { advertisedHostOverride } : {}),
});
const unavailable =
input.requestedMode === "network-accessible" && requestedExposure.endpointUrl === null;
input.requestedMode === "network-accessible" &&
requestedExposure.endpointUrl === null &&
!Object.values(input.networkInterfaces).some((addresses) =>
addresses?.some(
(address) =>
!address.internal && address.family === "IPv4" && isTailscaleIpv4Address(address.address),
),
);
const exposure = unavailable
? resolveDesktopServerExposure({
mode: "local-only",
Expand Down
23 changes: 1 addition & 22 deletions apps/mobile/src/lib/connection.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
import { EnvironmentId } from "@t3tools/contracts";

import {
isRelayManagedConnection,
redactPairingCredential,
toStableSavedRemoteConnection,
} from "./connection";
import { isRelayManagedConnection, toStableSavedRemoteConnection } from "./connection";
import { authClientMetadata } from "./authClientMetadata";

const mobilePlatform = vi.hoisted(() => ({ OS: "ios" as "ios" | "android" }));
Expand Down Expand Up @@ -83,23 +79,6 @@ describe("mobile remote connection records", () => {
});
});

it("removes one-time bootstrap credentials before persisting pairing URLs", () => {
expect(redactPairingCredential("https://desktop.example/#token=bootstrap-token")).toBe(
"https://desktop.example/",
);
expect(redactPairingCredential("https://desktop.example/?token=bootstrap-token")).toBe(
"https://desktop.example/",
);
});

it("removes hosted pairing credentials while keeping the advertised host", () => {
expect(
redactPairingCredential(
"https://app.t3.codes/pair?host=https%3A%2F%2Fdesktop.example&token=bootstrap-token&label=Desktop",
),
).toBe("https://app.t3.codes/pair?host=https%3A%2F%2Fdesktop.example&label=Desktop");
});

it("recognizes explicitly managed relay connections", () => {
expect(isRelayManagedConnection({ relayManaged: true })).toBe(true);
});
Expand Down
10 changes: 0 additions & 10 deletions apps/mobile/src/lib/connection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { EnvironmentId } from "@t3tools/contracts";
import { stripPairingTokenFromUrl } from "@t3tools/shared/remote";
import { type EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection";

export interface SavedRemoteConnection {
Expand All @@ -17,15 +16,6 @@ export interface SavedRemoteConnection {

export type RemoteClientConnectionState = EnvironmentConnectionPhase;

export function redactPairingCredential(pairingUrl: string): string {
const trimmed = pairingUrl.trim();
try {
return stripPairingTokenFromUrl(new URL(trimmed)).toString();
} catch {
return trimmed;
}
}

export function isRelayManagedConnection(
connection: Pick<SavedRemoteConnection, "authenticationMethod" | "relayManaged">,
): boolean {
Expand Down
79 changes: 79 additions & 0 deletions apps/server/src/cloud/pinnedRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Fiber from "effect/Fiber";
import * as Path from "effect/Path";
import * as PlatformError from "effect/PlatformError";
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";

import * as ProcessRunner from "../processRunner.ts";
Expand Down Expand Up @@ -38,6 +39,84 @@ const successfulRunner = (fs: FileSystem.FileSystem, path: Path.Path) =>
});

it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
it.effect("installs through pnpm when its Node runtime has no npm executable", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-pnpm-" });
const commands: Array<ProcessRunner.ProcessRunInput> = [];
const install = successfulRunner(fs, path);
const paths = yield* ensurePinnedRuntimeInstalled({
baseDir,
version: "1.2.3",
fs,
path,
runner: ProcessRunner.ProcessRunner.of({
run: (input) => {
commands.push(input);
return input.command === "npm"
? Effect.fail(
new ProcessRunner.ProcessSpawnError({
command: "npm",
argumentCount: input.args.length,
cause: PlatformError.systemError({
_tag: "NotFound",
module: "ChildProcess",
method: "spawn",
}),
}),
)
: install.run(input);
},
}),
validate: (staging) =>
fs.exists(staging.entryPath).pipe(
Effect.flatMap((exists) => (exists ? Effect.void : Effect.die("missing runtime"))),
Effect.orDie,
),
});
assert.deepEqual(
commands.map((command) => command.command),
["npm", "pnpm"],
);
assert.deepEqual(commands[1]!.args, ["--package=npm@11", "dlx", "npm", ...commands[0]!.args]);
assert.equal(yield* fs.readFileString(paths.sentinelPath), "1.2.3\n");
}),
);

it.effect("does not try a different installer for npm permission failures", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-permission-" });
const commands: string[] = [];
yield* ensurePinnedRuntimeInstalled({
baseDir,
version: "1.2.3",
fs,
path,
runner: ProcessRunner.ProcessRunner.of({
run: (input) => {
commands.push(input.command);
return Effect.fail(
new ProcessRunner.ProcessSpawnError({
command: input.command,
argumentCount: input.args.length,
cause: PlatformError.systemError({
_tag: "PermissionDenied",
module: "ChildProcess",
method: "spawn",
}),
}),
);
},
}),
validate: () => Effect.die("must not validate a failed install"),
}).pipe(Effect.flip);
assert.deepEqual(commands, ["npm"]);
}),
);

it.effect("validates a staging tree before atomically publishing it", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
Expand Down
24 changes: 23 additions & 1 deletion apps/server/src/cloud/pinnedRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as PlatformError from "effect/PlatformError";
import * as Schema from "effect/Schema";
import * as Option from "effect/Option";
import * as Semaphore from "effect/Semaphore";
Expand Down Expand Up @@ -152,14 +153,35 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(

return yield* Effect.gen(function* () {
const installStep = "installing the pinned t3 runtime (this can take a few minutes)";
const installArgs = [
"install",
"--prefix",
stagingDir,
"--no-fund",
"--no-audit",
`t3@${input.version}`,
];
yield* runner
.run({
command: "npm",
args: ["install", "--prefix", stagingDir, "--no-fund", "--no-audit", `t3@${input.version}`],
args: installArgs,
// Native dependencies may compile from source on slower machines.
timeout: PINNED_RUNTIME_INSTALL_TIMEOUT,
})
.pipe(
Effect.catchTags({
ProcessSpawnError: (error) =>
error.cause instanceof PlatformError.PlatformError &&
error.cause.reason._tag === "NotFound"
? // pnpm-managed Node installations do not include npm. Keep npm
// installation semantics for the pinned runtime and native builds.
runner.run({
command: "pnpm",
args: ["--package=npm@11", "dlx", "npm", ...installArgs],
timeout: PINNED_RUNTIME_INSTALL_TIMEOUT,
})
: Effect.fail(error),
}),
Effect.mapError((cause) => new PinnedRuntimeInstallError({ step: installStep, cause })),
Effect.filterOrFail(
(result) => result.code === 0,
Expand Down
29 changes: 28 additions & 1 deletion apps/server/src/provider/AntigravityAuth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const makeHarness = Effect.fn("makeAuthTestHarness")(function* (
} = {},
) {
const authenticated = yield* Deferred.make<void, AcpErrors.AcpError>();
const discovered = yield* Deferred.make<void>();
const discovered = yield* Deferred.make<void, AcpErrors.AcpError>();
const closed = yield* Deferred.make<void>();
const events: string[] = [];
let receiveAuthorizationUrl:
Expand Down Expand Up @@ -224,6 +224,33 @@ it.layer(NodeServices.layer)("AntigravityAuth", (it) => {
}),
);

it.effect(
"distinguishes a post-authentication session failure without exposing its payload",
() =>
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.auth.controller.start(owner);
yield* phase(harness.auth, "waiting");
yield* Deferred.succeed(harness.authenticated, undefined);
yield* Deferred.fail(
harness.discovered,
new AcpErrors.AcpRequestError({
code: -32603,
errorMessage: `Internal error ${callbackUrl}`,
method: "session/new",
}),
);
const failed = yield* phase(harness.auth, "failed");
assert.equal(
failed.message,
"Antigravity authenticated, but could not initialize a session or load models.",
);
assert.isNull(failed.authorizationUrl);
assert.deepEqual(harness.catalog(), ["previous-account-model"]);
yield* Deferred.await(harness.closed);
}),
);

it.effect("does not call callback HTTP success a successful Google sign-in", () =>
Effect.gen(function* () {
const harness = yield* makeHarness();
Expand Down
3 changes: 3 additions & 0 deletions apps/server/src/provider/AntigravityAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ function safeAuthFailure(cause: Cause.Cause<unknown>, usesBrowser: boolean): str
if (/access_denied|denied access|cancelled/i.test(error.value.errorMessage)) {
return "Google sign-in was not approved. Start sign-in again.";
}
if (error.value.method === "session/new" && error.value.code === -32603) {
return "Antigravity authenticated, but could not initialize a session or load models.";
}
if (!usesBrowser && error.value.code === -32602) {
return "Antigravity rejected the configured credentials. Check the provider settings.";
}
Expand Down
8 changes: 7 additions & 1 deletion apps/server/src/provider/Drivers/CursorDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { makeCursorAdapter } from "../Layers/CursorAdapter.ts";
import {
buildInitialCursorProviderSnapshot,
checkCursorProviderStatus,
makeCursorModelDiscovery,
enrichCursorSnapshot,
} from "../Layers/CursorProvider.ts";
import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
Expand Down Expand Up @@ -136,7 +137,12 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = {
});
const textGeneration = yield* makeCursorTextGeneration(effectiveConfig, processEnv);

const checkProvider = checkCursorProviderStatus(effectiveConfig, processEnv).pipe(
const discoverModels = yield* makeCursorModelDiscovery(effectiveConfig, processEnv);
const checkProvider = checkCursorProviderStatus(
effectiveConfig,
processEnv,
discoverModels,
).pipe(
Effect.map(stampIdentity),
Effect.provideService(Crypto.Crypto, crypto),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Expand Down
Loading
Loading