Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
5cf5f39
fix(deps): update desktop packaging and security parents
rynfar Sep 24, 2026
762792d
fix(server): preserve pending turn admission during provider bind (#752)
rynfar Sep 24, 2026
a8d9041
feat(chat): deliver buffered prose at natural boundaries (#757)
rynfar Sep 24, 2026
9cadbaa
fix(server): show Claude subagent tool progress (#745)
rynfar Sep 24, 2026
a675fa8
fix(server): show Relay workers from installed CLI results (#744)
rynfar Sep 24, 2026
90129e7
fix(relay): accept delegated thread IDs in activity routes (#756)
rynfar Sep 24, 2026
29e57cf
feat(prime): show child progress notes in Agents view (#748)
rynfar Sep 24, 2026
ccb8854
fix(server): bypass provider caches on explicit refresh (#743)
rynfar Sep 24, 2026
91e5166
fix(git): prevent stale ref checkout from restoring files (#751)
rynfar Sep 24, 2026
7cd8bb3
fix(shared): preserve final quoted empty CSV records (#761)
rynfar Sep 24, 2026
b7f9179
fix(server): reduce background GitHub PR quota use (#749)
rynfar Sep 24, 2026
b264cfb
fix(cursor): preserve internal agent errors (#754)
rynfar Sep 24, 2026
e82750e
fix(ci): compare transfer ceilings only within a scenario (#759)
rynfar Sep 24, 2026
298d788
fix(usage): retain saved totals after transcript cleanup (#762)
rynfar Sep 24, 2026
e4434e5
fix(ssh): find Linuxbrew Node on WSL hosts (#769)
rynfar Sep 24, 2026
08e6f51
fix(server): generate PR diffs from branch changes (#13170) (#742)
rynfar Sep 24, 2026
dbc8eeb
feat(observability): honor OpenTelemetry export kill switch (#746)
rynfar Sep 24, 2026
961c4dc
fix(grok): show monitor and background shell lifecycle (#753)
rynfar Sep 24, 2026
6bd4401
fix(agents): suppress stale awareness alerts after restart (#758)
rynfar Sep 24, 2026
d576ab3
fix(usage): attribute shared homes by transcript source (#784)
rynfar Sep 24, 2026
0c7c00f
Start provider event worker before service exposure (#828)
rynfar Sep 24, 2026
e94af91
fix(server): bind retained usage to verified accounts (#833)
rynfar Sep 24, 2026
22d2964
Sync linked PR changes and settle configured threads on events (#834)
rynfar Sep 24, 2026
bfd5560
fix(devices): identify legacy Android SDK command-line tools (#780)
rynfar Sep 24, 2026
1313084
feat(server): configure worktree submodule initialization (#800)
rynfar Sep 24, 2026
11260d3
perf(server): scope worktree fetch and parallel checkout (#820)
rynfar Sep 24, 2026
47f9f16
fix(desktop): bound WSL runtime cache retries and scratch cleanup (#741)
rynfar Sep 24, 2026
66396e0
fix(desktop): bound backend shutdown during quit (#763)
rynfar Sep 24, 2026
dae8134
docs(claude): clarify OpenRouter model selection (#816)
rynfar Sep 24, 2026
65a80a3
fix(web): label message copy action accurately (#779)
rynfar Sep 24, 2026
06c628b
fix(web): keep PR checkout command during detail loading (#785)
rynfar Sep 24, 2026
b10720f
fix(web): expose selected menu choices to assistive tech (#806)
rynfar Sep 24, 2026
0847708
fix(web): keep provider account emails readable (#810)
rynfar Sep 24, 2026
416a302
fix(web): expose switch state to screen readers (#755)
rynfar Sep 24, 2026
06df08d
fix(mobile): show Move down icon on Android (#776)
rynfar Sep 24, 2026
3ef8369
fix(web): allow selecting text while renaming a thread (#807)
rynfar Sep 24, 2026
629a985
fix(server): preserve sibling checkpoint refs during rollback (#847)
rynfar Sep 24, 2026
2e23652
fix(server): keep checkpoint failures out of public output (#851)
rynfar Sep 24, 2026
febfd6b
fix(server): reject stale Prime rollback responses after reconnect (#…
rynfar Sep 24, 2026
4bee9a5
feat(web): improve command and settings discovery (#801)
rynfar Sep 24, 2026
9f63f66
fix(web): keep settled threads closed when adding projects (#792)
rynfar Sep 24, 2026
68d775c
fix(web): keep selection actions dismissed after button presses (#808)
rynfar Sep 24, 2026
19b3a00
fix(usage): distinguish unavailable limits from empty limits (#781)
rynfar Sep 24, 2026
b03cc6f
fix(mobile): send terminal delete from software keyboard (#770)
rynfar Sep 24, 2026
8167c58
feat(mobile): copy thread IDs from list actions (#795)
rynfar Sep 24, 2026
f7a03a6
fix(mobile): retain archive list during iOS search (#797)
rynfar Sep 24, 2026
14163d3
fix(web): sort equally relevant thread search results by activity (#809)
rynfar Sep 24, 2026
a27cedf
fix(usage): refresh limits when the tab opens (#789)
rynfar Sep 24, 2026
669ae5d
fix(web): keep sidebar shelf rows moving together (#793)
rynfar Sep 24, 2026
4173c3b
fix(web): dismiss host menus on browser guest focus (#775)
rynfar Sep 24, 2026
fd36791
fix(web): restore composer focus after option menus close (#782)
rynfar Sep 24, 2026
1e1ebc4
Use PR summary for hover previews (#831)
rynfar Sep 24, 2026
d2cbef1
fix(web): match notification icons to thread events (#813)
rynfar Sep 24, 2026
ebeb937
fix(web): reveal composer context labels in tooltips (#829)
rynfar Sep 24, 2026
eca6de6
fix(agents): keep orphaned shell tasks out of the subagent cards (#853)
rynfar Sep 24, 2026
68fe9ea
fix(mobile): expose connection trace copy actions accessibly (#827)
rynfar Sep 24, 2026
cb6b239
feat(web): show timestamps on timeline work rows (#825)
rynfar Sep 24, 2026
f66c3b8
fix(mobile): ignore bare wake links without navigation routes (#772)
rynfar Sep 24, 2026
21247b7
fix(web): show pull request edit actions on touch (#802)
rynfar Sep 24, 2026
bf06be8
docs(upstream): correct deferred thought preview disposition (#773)
rynfar Sep 24, 2026
dcee43c
fix(web): make provider accent color controls keyboard accessible (#805)
rynfar Sep 25, 2026
f3881cc
fix(web): show pull request state in context chips (#812)
rynfar Sep 25, 2026
ecbf4e5
fix(web): keep media controls clear and restore gallery arrows (#814)
rynfar Sep 25, 2026
abf753d
fix(web): preserve browser tab shortcuts (#791)
rynfar Sep 25, 2026
b6f89c0
fix(mobile): keep screen awake during dictation (#799)
rynfar Sep 25, 2026
543a297
feat(web): allow manual disconnect of offline remote threads (#823)
rynfar Sep 25, 2026
a3150e1
fix(codex): explain a writer-locked conversation instead of the raw r…
rynfar Sep 25, 2026
63a7805
chore(mobile): check indexed access and overrides (#822)
rynfar Sep 25, 2026
36af785
fix(web): keep PR panel handoffs in the current thread (#826)
rynfar Sep 25, 2026
36ce2f8
feat(web): filter sidebar from thread menu (#821)
rynfar Sep 25, 2026
ea7fa44
feat(web): add bounded route history shortcuts on desktop (#803)
rynfar Sep 25, 2026
70b4b3b
fix(web): add local dropped folders as path chips (#818)
rynfar Sep 25, 2026
ddd0f75
feat(mobile): search projects when starting a task (#796)
rynfar Sep 25, 2026
a5a776d
feat(mobile): favorite models in thread settings (#790)
rynfar Sep 25, 2026
0a4fa62
test(server): stabilize daemon and Git fixture timing (#855)
rynfar Sep 25, 2026
4e15146
fix(desktop): keep SnapShot shortcut helper out of Dock (#778)
rynfar Sep 25, 2026
c6a2aca
fix(mobile): wait for deep-link thread hydration (#764)
rynfar Sep 25, 2026
47bf17d
fix(relay): stale Relay workers no longer flood a reopened thread (#852)
rynfar Sep 25, 2026
9611fac
fix(mobile): keep ordinary offline outbox retries out of warnings (#783)
rynfar Sep 25, 2026
b34fe34
fix(web): respect panel motion and nested timeline scrolling (#750)
rynfar Sep 25, 2026
edebab3
fix(preview): prefer visible browser for new leases (#767)
rynfar Sep 25, 2026
10c3bac
fix(preview): preserve picking and align animated surfaces (#839)
rynfar Sep 25, 2026
9d95224
feat(mobile): rename threads from list menus (#786)
rynfar Sep 25, 2026
fe8a8e5
fix(mobile): keep chat rows aligned after measurement (#777)
rynfar Sep 25, 2026
afdbde5
feat(web): add file context actions across diff and browser (#836)
rynfar Sep 25, 2026
24da335
feat(client): preview thread lifecycle actions until shell receipts (…
rynfar Sep 25, 2026
45f58ec
feat(web): choose themes from command palette (#837)
rynfar Sep 25, 2026
c720b0f
fix(mobile): recover from screen render failures (#768)
rynfar Sep 25, 2026
307c6e7
fix(marketing): update Astro image dependency chain (#774)
rynfar Sep 25, 2026
0988c44
Merge pylon into desktop dependency remediation
rynfar Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
47 changes: 26 additions & 21 deletions .agents/upstream-review.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions .github/scripts/thread-transfer-report.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function validateResult(value) {
}

assertExactKeys(value.scenario, SCENARIO_KEYS, "result.scenario");
if (value.scenario.id !== "thread-transfer-v1") {
if (!["thread-transfer-v1", "thread-transfer-v2"].includes(value.scenario.id)) {
throw new Error("result.scenario.id is not supported");
}
for (const key of SCENARIO_KEYS.slice(1)) {
Expand Down Expand Up @@ -157,7 +157,7 @@ function renderComment(input) {
`| ${provider === "codex" ? "Codex" : "Claude"} | ${metric.label} | ${baselineObserved === undefined ? "—" : formatValue(baselineObserved, metric.kind)} | ${formatValue(observed, metric.kind)} | ${formatImpact(observed, baselineObserved, metric.kind)} | ${formatValue(ceiling, metric.kind)} | ${pass ? "✅" : "❌"} |`,
);

if (baseline && baseline.providers[provider].ceiling[metric.key] !== ceiling) {
if (comparable && baseline.providers[provider].ceiling[metric.key] !== ceiling) {
ceilingChanges.push(
`- ${provider === "codex" ? "Codex" : "Claude"} ${metric.label}: ${formatValue(baseline.providers[provider].ceiling[metric.key], metric.kind)} → ${formatValue(ceiling, metric.kind)}`,
);
Expand Down
18 changes: 18 additions & 0 deletions .github/scripts/thread-transfer-report.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,21 @@ test("preserves a successful result when a same-SHA rerun has no artifact", asyn
assert.equal(published, true);
assert.equal(updatedComment, false);
});

test("accepts V2 without comparing it to the V1 scenario", () => {
const current = result();
current.scenario.id = "thread-transfer-v2";
current.providers.codex.ceiling = { ...current.providers.codex.ceiling, totalWireBytes: 3000000 };
assert.equal(validateResult(current), current);
const comment = renderComment({
current,
baseline: result(),
currentRun: { sha: "bbbbbbbb", conclusion: "success", url: "https://example.com/current" },
baselineRun: { sha: "aaaaaaaa", matchesBase: true, url: "https://example.com/baseline" },
});
assert.match(comment, /fixture changed/);
assert.doesNotMatch(comment, /This PR changes transfer ceilings/);
assert.doesNotMatch(comment, /[+-]\d+\.\d+%/);
current.scenario.id = "unrecognized";
assert.throws(() => validateResult(current), /not supported/);
});
4 changes: 2 additions & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
"@t3tools/tailscale": "workspace:*",
"dbus-next": "0.10.2",
"effect": "catalog:",
"electron": "44.1.0",
"electron": "44.4.2",
"electron-store": "^8.2.0",
"electron-updater": "^6.6.2",
"electron-updater": "^6.8.9",
"ffi-rs": "1.3.2",
"playwright-core": "1.60.0",
"react-grab": "^0.1.32"
Expand Down
33 changes: 21 additions & 12 deletions apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Cause from "effect/Cause";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Ref from "effect/Ref";
Expand Down Expand Up @@ -142,6 +143,20 @@ const handleFatalStartupError = Effect.fn("desktop.startup.handleFatalStartupErr
const fatalStartupCause = <E>(stage: string, cause: Cause.Cause<E>) =>
handleFatalStartupError(stage, Cause.pretty(cause)).pipe(Effect.andThen(Effect.failCause(cause)));

export const stopAllPoolInstances = Effect.fn("desktop.app.stopAllPoolInstances")(
function* (): Effect.fn.Return<void, never, DesktopBackendPool.DesktopBackendPool> {
// Stop every backend in the pool with a timeout to guarantee the quit
// path makes progress even if a backend hangs during teardown.
const pool = yield* DesktopBackendPool.DesktopBackendPool;
const instances = yield* pool.list;
yield* Effect.forEach(
instances,
(instance) => instance.stop({ timeout: Duration.seconds(5) }),
{ concurrency: "unbounded" },
);
},
);

const bootstrap = Effect.gen(function* () {
const pool = yield* DesktopBackendPool.DesktopBackendPool;
const primaryBackend = yield* pool.primary;
Expand Down Expand Up @@ -313,18 +328,12 @@ const scopedProgram = Effect.scoped(
const shutdown = yield* DesktopShutdown.DesktopShutdown;

yield* Effect.addFinalizer(() =>
Effect.gen(function* () {
const pool = yield* DesktopBackendPool.DesktopBackendPool;
// Stop every backend in the pool, not just the primary. The
// electronApp.quit() path can race ahead of the layer-scope
// cascade, so leaving the WSL instance for its parent scope
// finalizer means it gets hard-killed by the OS instead of
// receiving SIGTERM + grace. Stops run concurrently.
const instances = yield* pool.list;
yield* Effect.forEach(instances, (instance) => instance.stop(), {
concurrency: "unbounded",
});
}).pipe(Effect.ensuring(shutdown.markComplete)),
// Stop every backend in the pool, not just the primary. The
// electronApp.quit() path can race ahead of the layer-scope
// cascade, so leaving the WSL instance for its parent scope
// finalizer means it gets hard-killed by the OS instead of
// receiving SIGTERM + grace.
stopAllPoolInstances().pipe(Effect.ensuring(shutdown.markComplete)),
);

yield* startup;
Expand Down
64 changes: 64 additions & 0 deletions apps/desktop/src/app/DesktopObservability.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
Expand Down Expand Up @@ -427,6 +428,36 @@ describe("DesktopObservability", () => {
);
});

it.effect("exports kill switch warnings through the configured logger", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-observability-test-",
});
const environmentLayer = makeEnvironmentLayer(baseDir, true, {
T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.void.pipe(
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.include(requests[0]?.body ?? "", "OTEL_SDK_DISABLED=1 was read as false");
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "1" } })),
),
),
);
});

it.effect("reads every signal endpoint from Settings when the environment names none", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
Expand Down Expand Up @@ -491,4 +522,37 @@ describe("DesktopObservability", () => {
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))),
);
});

it.effect("stops every export when the OpenTelemetry SDK is disabled", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-observability-test-",
});
const environmentLayer = makeEnvironmentLayer(baseDir);
yield* writeObservabilitySettings(environmentLayer, {
otlpTracesUrl: "https://settings.example.com/v1/traces",
otlpLogsUrl: "https://settings.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.logInfo("desktop log stays local when disabled").pipe(
Effect.withSpan("desktop-disabled-test"),
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.lengthOf(requests, 0);
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "true" } })),
),
),
);
});
});
14 changes: 13 additions & 1 deletion apps/desktop/src/app/DesktopObservability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
makeTraceSink,
otlpSerializationLayer,
} from "@t3tools/shared/observability";
import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
import {
parsePersistedServerObservabilitySettings,
type PersistedServerObservabilitySettings,
Expand Down Expand Up @@ -353,12 +354,18 @@ const readPersistedObservabilitySettings: Effect.Effect<
* resolve traces against one revision of the file and logs against another.
*/
const resolveOtlpEndpoints = Effect.gen(function* () {
const otel = yield* OtelEnvironment.load;
if (otel.disabled) {
return { traces: undefined, metrics: undefined, logs: undefined, warnings: otel.warnings };
}

const environment = yield* DesktopEnvironment.DesktopEnvironment;
const persisted = yield* readPersistedObservabilitySettings;
return {
traces: Option.getOrUndefined(environment.otlpTracesUrl) ?? persisted.otlpTracesUrl,
metrics: Option.getOrUndefined(environment.otlpMetricsUrl) ?? persisted.otlpMetricsUrl,
logs: Option.getOrUndefined(environment.otlpLogsUrl) ?? persisted.otlpLogsUrl,
warnings: otel.warnings,
};
});

Expand Down Expand Up @@ -671,7 +678,12 @@ const telemetryLayer = Layer.unwrap(
// resource,
// }).pipe(Layer.provide(serializationLayer));

return Layer.mergeAll(loggerLayer, tracerLayer);
// Logged once the loggers above are installed, so the warnings use them.
const otelWarningsLayer = Layer.effectDiscard(
Effect.forEach(endpoints.warnings, (warning) => Effect.logWarning(warning)),
);

return otelWarningsLayer.pipe(Layer.provideMerge(Layer.mergeAll(loggerLayer, tracerLayer)));
}),
);

Expand Down
43 changes: 43 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,49 @@ describe("DesktopBackendConfiguration", () => {
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("resolveWsl carries the kill switch into the distro", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-backend-config-test-",
});

const previousWslEnv = process.env.WSLENV;
const previousDisabled = process.env.OTEL_SDK_DISABLED;
try {
delete process.env.WSLENV;
process.env.OTEL_SDK_DISABLED = "true";

yield* Effect.gen(function* () {
const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
const config = yield* configuration.resolveWsl({ port: 5050, distro: null });

assert.equal(config.env.OTEL_SDK_DISABLED, "true");
assert.include((config.env.WSLENV ?? "").split(":"), "OTEL_SDK_DISABLED");
}).pipe(
Effect.provide(
DesktopBackendConfiguration.layer.pipe(
Layer.provideMerge(serverExposureLayer),
Layer.provideMerge(DesktopAppSettings.layerTest()),
Layer.provideMerge(DesktopWslServerTree.layerTest()),
Layer.provideMerge(
DesktopWslEnvironment.layerTest({
isAvailable: true,
windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
getDistroIp: () => Option.some("172.27.0.99"),
}),
),
Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
),
),
);
} finally {
restoreEnv("WSLENV", previousWslEnv);
restoreEnv("OTEL_SDK_DISABLED", previousDisabled);
}
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("resolveWsl preserves existing WSLENV entries when forwarding backend secrets", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ const DESKTOP_BACKEND_ENV_NAMES = [
const WSL_FORWARDED_ENV_NAMES = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
// Otherwise the WSL server keeps exporting to endpoints from the bootstrap.
"T3CODE_OTEL_SDK_DISABLED",
"OTEL_SDK_DISABLED",
"T3CODE_OTLP_HEADERS",
"T3CODE_OTLP_PROTOCOL",
] as const;
Expand Down
Loading
Loading