Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e64221a
feat(web): improve command and settings discovery
rynfar Sep 24, 2026
8cbd23d
fix(web): sort equally relevant thread matches by activity
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
340ab0d
Merge pylon and preserve command search recency
rynfar Sep 24, 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: 5 additions & 0 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/);
});
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