Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
e55e731
fix: update OpenAI logo to current brand asset (#13611)
aaditagrawal Sep 25, 2026
1c12706
fix(mobile): render assigned project icons in chat list (#12810)
SunkenInTime Sep 25, 2026
e5a46d6
feat(usage): read cursor, opencode, and antigravity history (#10409)
maria-rcks Sep 25, 2026
d06f0ff
fix(sqlite): retry failed statement preparations (#10584)
yashranaway Sep 25, 2026
7a12aff
fix(mobile): scale Android controls with appearance text size (#13356)
none23 Sep 25, 2026
a4f6078
fix(web): return focus to the composer after saving a citation note (…
mackinleysmith Sep 25, 2026
fd996d1
feat(observability): honor the standard OTLP endpoint, headers, and p…
yordis Sep 25, 2026
1a0c915
fix(terminal): settling a thread closes its idle shells (#13673)
t3dotgg Sep 25, 2026
f5bd2fd
fix(server): load Cursor keyring with createRequire (#13678)
Yash-Singh1 Sep 25, 2026
4293433
perf(server): avoid rereading unchanged files in review previews (#13…
shivamhwp Sep 25, 2026
2598ce7
feat(desktop): honor the standard OTLP variables in the main process …
yordis Sep 25, 2026
ed809f7
fix(web): restore compact provider instance badges (#13700)
t3-code[bot] Sep 25, 2026
525af2d
perf(mobile): lighter Home rows away from the viewport (#13702)
AKolenda Sep 26, 2026
aade3a6
fix(usage): price Cursor cache savings by base model (#13731)
Yash-Singh1 Sep 26, 2026
595a1e1
perf(mobile): render Home rows further ahead while scrolling (#13705)
AKolenda Sep 26, 2026
2a9832b
refactor(observability): name each service after its application (#13…
yordis Sep 26, 2026
c216ba4
perf(server): stop remapping every thread on each thread event (#13720)
t3dotgg Sep 26, 2026
5660ab5
Remove unused items tracking from Claude adapter state (#13718)
t3dotgg Sep 26, 2026
20885ce
feat(observability): name the command on subprocess spans (#13701)
t3dotgg Sep 26, 2026
7c901a3
fix(cli): t3 triage points agents at log files that exist (#13685)
t3dotgg Sep 26, 2026
8aa5be2
fix(server): the SQLite WAL file shrinks back after large writes (#13…
t3dotgg Sep 26, 2026
75bf92e
feat(cli): summarize the server trace file from the command line (#13…
t3dotgg Sep 26, 2026
6530de0
perf(server): pull request sync reads only threads with linked pull r…
t3dotgg Sep 26, 2026
1dc8cbe
feat(observability): write a server heap snapshot on SIGUSR2 (#13694)
t3dotgg Sep 26, 2026
574b180
perf(server): shutdown no longer rewrites every stopped session row (…
t3dotgg Sep 26, 2026
b6eefc9
perf(server): build the thread list snapshot without decoding it twic…
t3dotgg Sep 26, 2026
b2577d6
fix(client): slow servers finish loading the thread list instead of l…
t3dotgg Sep 26, 2026
6989856
perf(web): hidden terminal drawers no longer keep full thread history…
t3dotgg Sep 26, 2026
3b0a495
perf(server): per-thread settlement and PR checks no longer rebuild t…
t3dotgg Sep 26, 2026
ecd3237
fix(mobile): running threads open at the latest message (#13530)
AKolenda Sep 26, 2026
ee18e56
feat(observability): record event loop stalls in the server trace (#1…
t3dotgg Sep 26, 2026
999161e
perf(server): stop re-running git for every project each minute (#13689)
t3dotgg Sep 26, 2026
94c4216
fix(usage): hide the Cursor keychain prompt when Cursor isn't set up …
Gigioxx Sep 26, 2026
04c15f3
feat(web): add chat width setting for wide screens (#11594)
otavio Sep 26, 2026
4408bf5
fix(opencode): accept v2 serve ready line when spawning server (#13651)
shirishpothi Sep 26, 2026
1e844a3
fix(editors): stop treating the agy CLI as the Antigravity IDE (#7079)
ishaanko Sep 26, 2026
eeea71a
fix(web): make the empty workspace draggable on desktop (#13713)
otavio Sep 26, 2026
dd8332d
fix(server): installed editors no longer vanish when discovery is slo…
bfowler Sep 26, 2026
29a3266
fix(git): exclude SSH ports from provider URLs (#12537)
GaMeRaM Sep 26, 2026
5d707bb
fix(web): Mod+B bolds on non-Latin layouts (#13409)
ValeraZSD Sep 26, 2026
a21b42c
fix(server): prune expired replay-protection files from the secrets d…
t3dotgg Sep 26, 2026
8bc9b78
fix(web): terminal links drop a trailing colon (#13408)
ValeraZSD Sep 26, 2026
4923ff4
fix(server): bump node-pty to 1.2.0-beta.15 for linux-arm64 prebuild …
Ephraim-9 Sep 26, 2026
92f0af2
Show a focus ring on sidebar thread and draft rows (#13344)
ryanilano Sep 26, 2026
dab9561
fix(mobile): keep composer within folded screen after resume (#13310)
PixPMusic Sep 26, 2026
2679d27
fix(server): let OpenCode generate session titles (#13368)
macodev00 Sep 26, 2026
def34c2
fix(server): let Antigravity inspect unsupported files by path (#13339)
Bil0000 Sep 26, 2026
fe6388f
fix(mobile): link URLs with ports and single-label hosts (#13795)
Yash-Singh1 Sep 26, 2026
daafcc4
feat(web): add keyboard navigation for usage (#10158)
tris203 Sep 26, 2026
99efaea
perf(observability): stop writing empty spans on spawns, projected ev…
t3dotgg Sep 26, 2026
8b873ea
perf(server): opening Diagnostics no longer loads the whole trace rin…
t3dotgg Sep 26, 2026
6f97b0f
perf(clients): sort projects and settled threads without re-parsing d…
t3dotgg Sep 26, 2026
294dd12
fix(observability): the renderer trace proxy stops tracing itself (#1…
t3dotgg Sep 26, 2026
1d6f23b
perf(server): background sweeps only read threads that can still sett…
t3dotgg Sep 26, 2026
9151ea4
perf(clients): saving the thread list cache no longer freezes the UI …
t3dotgg Sep 26, 2026
8872666
perf(server): cut idle wakeups from the Connect relay and session rea…
t3dotgg Sep 26, 2026
81e0491
fix(mobile): keep trailing underscores and tildes in autolinked URLs …
Yash-Singh1 Sep 26, 2026
295d7cb
fix(web): queued messages send while their thread is not open (#13764)
t3dotgg Sep 26, 2026
95030dc
fix(server): background git status fetches no longer fill the disk wi…
t3dotgg Sep 26, 2026
75d63d6
fix(mobile): thread list shows the pull request icon instead of # (#1…
flamboh Sep 26, 2026
393d595
fix(accessibility): correct control announcements and sidebar travers…
blinding-pixels Sep 26, 2026
10bb59b
fix(usage): tolerate newer provider variants (#10076)
tris203 Sep 26, 2026
d6802b4
fix(usage): omit Cursor warning when no login is saved (#13820)
tris203 Sep 26, 2026
3dae78f
fix(usage): identify client version mismatches (#8208)
tris203 Sep 26, 2026
dd582de
fix(web): stop mistaking offline servers for updates (#13083)
tris203 Sep 26, 2026
d110f98
test(usage): assert contract mismatch details (#13861)
Yash-Singh1 Sep 26, 2026
ea7d46a
fix(build): validate Linux node-pty prebuilds in Windows artifacts (#…
Yash-Singh1 Sep 26, 2026
679c34c
fix(clients): hide duplicate Cursor Keychain prompts (#13870)
Yash-Singh1 Sep 26, 2026
74ee515
feat(observability): honor OTEL_*_EXPORTER=none per signal (#13736)
yordis Sep 26, 2026
c9a0e8a
fix(web): align reasoning arrows with tool calls (#13850)
Adamulek123 Sep 26, 2026
cb97415
fix(server): let agents use simctl and adb alongside device tools (#1…
juliusmarminge Sep 27, 2026
ab09917
fix(web): environment status tooltip resizes when the status changes …
BearHuddleston Sep 27, 2026
6b50eb0
fix(mobile): don't open Android markdown links on text-selection drag
cursoragent Sep 27, 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: 3 additions & 2 deletions .github/triage/PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ different code depending on it:

Then work from evidence, not assumption. In rough order of value:

- The server log and the trace file (`server.trace.ndjson`) around the time of the
problem. Recent failures usually leave a trail here.
- The trace file (`server.trace.ndjson`) around the time of the problem, plus the
service log or desktop backend logs from the context file if they exist. Recent
failures usually leave a trail here.
- The provider event log, for problems with claude/codex/cursor sessions.
- The SQLite database. Read it freely, but only write when a write is necessary
to fix the problem the user described, and get their explicit permission
Expand Down
148 changes: 140 additions & 8 deletions apps/desktop/src/app/DesktopObservability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ const collectorLayer = (requests: Array<ExportedRequest>) =>
),
);

// A developer's own OTEL_* variables would otherwise pick the endpoints.
const emptyEnv = ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} }));

const encodeObservabilitySettingsFile = Schema.encodeSync(
Schema.fromJsonString(
Schema.Struct({ observability: Schema.Record(Schema.String, Schema.String) }),
Expand Down Expand Up @@ -182,7 +185,7 @@ describe("DesktopObservability", () => {
assert.isFalse(yield* fileSystem.exists(logPath));
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
),
);

Expand Down Expand Up @@ -260,7 +263,7 @@ describe("DesktopObservability", () => {
);
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
),
);

Expand Down Expand Up @@ -300,7 +303,7 @@ describe("DesktopObservability", () => {
assert.equal(records.at(-1)?.annotations.details, "code=1");
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
),
);

Expand Down Expand Up @@ -344,7 +347,7 @@ describe("DesktopObservability", () => {
assert.isFalse(text.includes("y"));
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
),
);

Expand Down Expand Up @@ -378,7 +381,7 @@ describe("DesktopObservability", () => {
assert.equal(lines.length, 258);
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),
Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
),
);

Expand Down Expand Up @@ -424,7 +427,136 @@ describe("DesktopObservability", () => {
assert.lengthOf(record?.events ?? [], 0);
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))),
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)),
);
});

it.effect("exports to an OTEL endpoint over Settings, with its own headers and protocol", () => {
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_HEADERS: "x-scope=desktop",
});
yield* writeObservabilitySettings(environmentLayer, {
otlpLogsUrl: "https://settings.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.logInfo("desktop otel export").pipe(
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.lengthOf(requests, 1);
const [request] = requests;
assert.strictEqual(request?.url, "https://collector.example.com/v1/logs");
assert.strictEqual(request?.headers["x-otel"], "desktop");
assert.strictEqual(request?.headers["x-scope"], undefined);
assert.strictEqual(request?.headers["content-type"], "application/json");
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(
ConfigProvider.fromEnv({
env: {
OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com",
OTEL_EXPORTER_OTLP_HEADERS: "x-otel=desktop",
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: "http/json",
},
}),
),
),
),
);
});

it.effect("keeps its service name while OTEL resource attributes add dimensions", () => {
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.logInfo("desktop service name").pipe(
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.lengthOf(requests, 1);
const body = requests[0]?.body ?? "";
assert.include(body, '"stringValue":"t3code-desktop"');
assert.include(body, "deployment.environment.name");
assert.include(body, '"key":"service.namespace","value":{"stringValue":"t3code"}');
assert.notInclude(body, "renamed");
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(
ConfigProvider.fromEnv({
env: {
OTEL_SERVICE_NAME: "renamed",
OTEL_RESOURCE_ATTRIBUTES:
"service.name=renamed,service.namespace=renamed,deployment.environment.name=development",
},
}),
),
),
),
);
});

it.effect("exports nothing to Settings for logs an unusable OTEL endpoint claimed", () => {
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_HEADERS: "x-scope=desktop",
});
yield* writeObservabilitySettings(environmentLayer, {
otlpLogsUrl: "https://settings.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.logInfo("desktop otel off").pipe(
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_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com",
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: "grpc",
},
}),
),
),
),
);
});

Expand Down Expand Up @@ -495,7 +627,7 @@ describe("DesktopObservability", () => {
);
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))),
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)),
);
});

Expand All @@ -519,7 +651,7 @@ describe("DesktopObservability", () => {
assert.lengthOf(requests, 0);
}).pipe(
Effect.scoped,
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))),
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)),
);
});

Expand Down
67 changes: 47 additions & 20 deletions apps/desktop/src/app/DesktopObservability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
makeLocalFileTracer,
makeTraceSink,
otlpSerializationLayer,
type SignalExport,
} from "@t3tools/shared/observability";
import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
import {
Expand Down Expand Up @@ -350,8 +351,10 @@ const readPersistedObservabilitySettings: Effect.Effect<
});

/**
* Settings is read once for every signal, so the main process cannot
* resolve traces against one revision of the file and logs against another.
* Resolved as the server resolves them, with persisted Settings as the
* fallback. Settings is read once for every signal, so the main process
* cannot resolve traces against one revision of the file and logs against
* another.
*/
const resolveOtlpEndpoints = Effect.gen(function* () {
const otel = yield* OtelEnvironment.load;
Expand All @@ -367,10 +370,30 @@ const resolveOtlpEndpoints = Effect.gen(function* () {

const environment = yield* DesktopEnvironment.DesktopEnvironment;
const persisted = yield* readPersistedObservabilitySettings;
const signalExport: SignalExport = {
protocol: environment.otlpProtocol,
headers: Option.getOrUndefined(environment.otlpHeaders),
exportIntervalMs: environment.otlpExportIntervalMs,
};
return {
traces: Option.getOrUndefined(environment.otlpTracesUrl) ?? persisted.otlpTracesUrl,
metrics: Option.getOrUndefined(environment.otlpMetricsUrl) ?? persisted.otlpMetricsUrl,
logs: Option.getOrUndefined(environment.otlpLogsUrl) ?? persisted.otlpLogsUrl,
traces: OtelEnvironment.resolveSignalEndpoint(
otel,
"traces",
{ url: Option.getOrUndefined(environment.otlpTracesUrl), export: signalExport },
persisted.otlpTracesUrl,
),
metrics: OtelEnvironment.resolveSignalEndpoint(
otel,
"metrics",
{ url: Option.getOrUndefined(environment.otlpMetricsUrl), export: signalExport },
persisted.otlpMetricsUrl,
),
logs: OtelEnvironment.resolveSignalEndpoint(
otel,
"logs",
{ url: Option.getOrUndefined(environment.otlpLogsUrl), export: signalExport },
persisted.otlpLogsUrl,
),
warnings: otel.warnings,
resourceAttributes: otel.resourceAttributes,
};
Expand Down Expand Up @@ -602,11 +625,10 @@ const telemetryLayer = Layer.unwrap(
Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const endpoints = yield* resolveOtlpEndpoints;
const headers = Option.getOrUndefined(environment.otlpHeaders);
const serializationLayer = otlpSerializationLayer(environment.otlpProtocol);
const resource = {
serviceName: "desktop",
serviceName: "t3code-desktop",
attributes: {
"service.namespace": "t3code",
"service.runtime": "desktop",
"service.mode": environment.isDevelopment ? "development" : "packaged",
},
Expand All @@ -629,14 +651,19 @@ const telemetryLayer = Layer.unwrap(
: [
Logger.consolePretty(),
OtlpLogger.make({
url: endpoints.logs,
exportInterval: `${environment.otlpExportIntervalMs} millis`,
headers,
url: endpoints.logs.url,
exportInterval: `${endpoints.logs.export.exportIntervalMs} millis`,
headers: endpoints.logs.export.headers,
resource,
}),
],
{ mergeWithExisting: false },
).pipe(Layer.provide(OtlpExporter.layerFlusher), Layer.provide(serializationLayer));
).pipe(
Layer.provide(OtlpExporter.layerFlusher),
Layer.provide(
otlpSerializationLayer(endpoints.logs?.export.protocol ?? environment.otlpProtocol),
),
);

const tracerLayer = Layer.unwrap(
Effect.gen(function* () {
Expand All @@ -651,11 +678,11 @@ const telemetryLayer = Layer.unwrap(
endpoints.traces === undefined
? undefined
: yield* OtlpTracer.make({
url: endpoints.traces,
exportInterval: `${environment.otlpExportIntervalMs} millis`,
headers,
url: endpoints.traces.url,
exportInterval: `${endpoints.traces.export.exportIntervalMs} millis`,
headers: endpoints.traces.export.headers,
resource,
}).pipe(Effect.provide(serializationLayer));
}).pipe(Effect.provide(otlpSerializationLayer(endpoints.traces.export.protocol)));
const tracer = yield* makeLocalFileTracer({
filePath: tracePath,
maxBytes: DESKTOP_LOG_FILE_MAX_BYTES,
Expand All @@ -679,11 +706,11 @@ const telemetryLayer = Layer.unwrap(
// endpoints.metrics === undefined
// ? Layer.empty
// : OtlpMetrics.layer({
// url: endpoints.metrics,
// exportInterval: `${environment.otlpExportIntervalMs} millis`,
// headers,
// url: endpoints.metrics.url,
// exportInterval: `${endpoints.metrics.export.exportIntervalMs} millis`,
// headers: endpoints.metrics.export.headers,
// resource,
// }).pipe(Layer.provide(serializationLayer));
// }).pipe(Layer.provide(otlpSerializationLayer(endpoints.metrics.export.protocol)));

// Logged once the loggers above are installed, so the warnings use them.
const otelWarningsLayer = Layer.effectDiscard(
Expand Down
Loading
Loading