Skip to content

Commit 356ca61

Browse files
committed
Merge remote-tracking branch 'github/main' into fix/claude-context-window-meter
# Conflicts: # apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
2 parents 5ea686c + 6577c6b commit 356ca61

1,075 files changed

Lines changed: 55110 additions & 10568 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/SECURITY.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Security policy
22

33
Report security vulnerabilities affecting T3 Code or T3 Tools-operated infrastructure to
4-
[security@ping.gg](mailto:security@ping.gg). Please do not disclose them publicly until we have had
4+
[security@t3.codes](mailto:security@t3.codes). Please do not disclose them publicly until we have had
55
a reasonable opportunity to investigate and remediate them.
66

77
See the [full security policy](https://t3.codes/security-policy) for reporting details, scope,

‎.github/actions/setup-ci-deps/action.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,12 @@ runs:
123123
key: ${{ steps.pnpm_verified.outputs.cache-primary-key }}
124124

125125
# A filtered install that started from the current full store adds nothing
126-
# worth a second copy of it.
126+
# worth a second copy of it. A stacked PR, based on a branch other than the
127+
# default, saves a ~1 GB store only its own reruns can read, so it only restores.
127128
- name: Save pnpm store
128129
if: >-
129130
inputs.install == 'true' &&
131+
(github.event_name != 'pull_request' || github.base_ref == github.event.repository.default_branch) &&
130132
steps.pnpm_cache.outputs.cache-hit != 'true' &&
131133
(steps.pnpm_store.outputs.namespace == 'full' ||
132134
steps.pnpm_cache.outputs.cache-matched-key != format('pnpm-full-v1-{0}-{1}-{2}', runner.os, runner.arch, hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json')))

‎.github/workflows/ci.yml‎

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,41 @@ jobs:
192192
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
193193
run: node apps/desktop/scripts/verify-preload-bundle.mjs
194194

195+
# Browser tests run source modules; this boots the production chunk graph. The
196+
# desktop build above already produced apps/web/dist, and the client waits about
197+
# 15s for a session that never comes, so this runs here instead of in Test Web,
198+
# which gates the whole run. Chromium comes from the cache Test Web shares.
199+
- name: Resolve Playwright version
200+
id: playwright_version
201+
if: ${{ !cancelled() && steps.build.outcome == 'success' && needs.changes.outputs.test_web != 'false' }}
202+
run: echo "version=$(node -p "require('./apps/web/node_modules/playwright/package.json').version")" >> "$GITHUB_OUTPUT"
203+
204+
- name: Cache Chromium
205+
id: chromium_cache
206+
if: ${{ !cancelled() && steps.build.outcome == 'success' && needs.changes.outputs.test_web != 'false' }}
207+
uses: actions/cache@v6
208+
with:
209+
path: ~/.cache/ms-playwright
210+
key: playwright-chromium-shell-${{ runner.os }}-${{ runner.arch }}-${{ steps.playwright_version.outputs.version }}
211+
212+
- name: Install Chromium for the built web client boot check
213+
if: ${{ !cancelled() && steps.build.outcome == 'success' && needs.changes.outputs.test_web != 'false' && steps.chromium_cache.outputs.cache-hit != 'true' }}
214+
run: node apps/web/node_modules/playwright/cli.js install --only-shell chromium
215+
216+
- name: Install missing Chromium system dependencies
217+
if: ${{ !cancelled() && steps.build.outcome == 'success' && needs.changes.outputs.test_web != 'false' }}
218+
run: |
219+
browser=$(find "$HOME/.cache/ms-playwright" -name chrome-headless-shell -type f -print -quit)
220+
test -n "$browser"
221+
if ldd "$browser" | grep '=> not found'; then
222+
node apps/web/node_modules/playwright/cli.js install-deps chromium
223+
fi
224+
225+
- name: Boot the built web client
226+
if: ${{ !cancelled() && steps.build.outcome == 'success' && needs.changes.outputs.test_web != 'false' }}
227+
timeout-minutes: 3
228+
run: node apps/web/scripts/verify-built-boot.ts
229+
195230
- name: Typecheck
196231
if: ${{ !cancelled() && steps.typecheck.outcome == 'success' }}
197232
timeout-minutes: 5
@@ -340,12 +375,6 @@ jobs:
340375
- name: Test
341376
run: vp run --filter @t3tools/web test
342377

343-
# Browser tests run source modules; this boots the production chunk graph.
344-
- name: Boot the built web client
345-
run: |
346-
vp run --filter @t3tools/web build
347-
node apps/web/scripts/verify-built-boot.ts
348-
349378
# apps/server sets `fileParallelism: false`, so its test files run strictly
350379
# one at a time. Sharding spreads them over separate runners instead of
351380
# separate workers, so no two server test files ever share a machine and the

‎.github/workflows/desktop-macos-preview.yml‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,27 @@ jobs:
7373
# Public T3 Connect identifiers (Clerk publishable key, relay URL).
7474
cp .env.example .env
7575
76-
- uses: ./.github/actions/setup-apt-mirrors
76+
# PR heads from before build:desktop:bundle use the full build, which
77+
# compiles the Linux browser secret helper and needs libsecret.
78+
- name: Check bundle-only build support
79+
id: bundle_build
80+
run: |
81+
script="$(node -p "require('./package.json').scripts['build:desktop:bundle'] ? 'build:desktop:bundle' : 'build:desktop'")"
82+
echo "script=$script" >> "$GITHUB_OUTPUT"
83+
84+
- if: steps.bundle_build.outputs.script == 'build:desktop'
85+
uses: ./.github/actions/setup-apt-mirrors
7786

7887
- name: Install browser secret helper build libraries
88+
if: steps.bundle_build.outputs.script == 'build:desktop'
7989
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
8090

81-
# desktop-macos-preview-publish.yml packages this bundle as the preview channel.
91+
# desktop-macos-preview-publish.yml packages this bundle as the preview
92+
# channel. The Linux browser secret helper is not part of a macOS app.
8293
- name: Build JS bundle
8394
env:
8495
T3CODE_TELEMETRY_CHANNEL: ${{ github.repository == 'pingdotgg/t3code' && 'preview' || 'dev' }}
85-
run: vp run build:desktop
96+
run: vp run ${{ steps.bundle_build.outputs.script }}
8697

8798
# Same layout as release.yml's js-bundle so release-desktop.yml can
8899
# package it unchanged.

‎.github/workflows/release-desktop.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ jobs:
449449
name: cli-${{ inputs.platform }}-${{ inputs.arch }}
450450
path: release-cli/*
451451
if-no-files-found: error
452+
# The archive is already gzip or zip; deflating it again saves under 2%.
453+
compression-level: 0
452454

453455
- name: Install Linux desktop build libraries
454456
if: inputs.platform == 'linux'
@@ -761,6 +763,8 @@ jobs:
761763
name: desktop-${{ inputs.platform }}-${{ inputs.arch }}
762764
path: release-publish/*
763765
if-no-files-found: error
766+
# Installers, zips and blockmaps are already compressed; deflating them again saves under 0.3%.
767+
compression-level: 0
764768

765769
- name: Upload resource monitor
766770
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7

‎.github/workflows/release.yml‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -565,22 +565,32 @@ jobs:
565565
- name: Align package versions to release version
566566
run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}"
567567

568-
- uses: ./.github/actions/setup-apt-mirrors
568+
# Older release refs lack build:desktop:bundle and use the full build,
569+
# which compiles the Linux browser secret helper and needs libsecret.
570+
- name: Check bundle-only build support
571+
id: bundle_build
572+
run: |
573+
script="$(node -p "require('./package.json').scripts['build:desktop:bundle'] ? 'build:desktop:bundle' : 'build:desktop'")"
574+
echo "script=$script" >> "$GITHUB_OUTPUT"
575+
576+
- if: steps.bundle_build.outputs.script == 'build:desktop'
577+
uses: ./.github/actions/setup-apt-mirrors
569578

570-
# @t3tools/desktop#build compiles the Linux browser secret helper on a
571-
# Linux host before packing, and that needs libsecret headers.
572579
- name: Install browser secret helper build libraries
580+
if: steps.bundle_build.outputs.script == 'build:desktop'
573581
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
574582

575-
# Runs t3#build (which depends on @t3tools/web#build) and
576-
# @t3tools/desktop#build, so apps/server/dist holds the server bundle
577-
# plus the web client and apps/desktop/dist-electron the Electron main.
583+
# Runs t3#build (which depends on @t3tools/web#build) and the desktop
584+
# bundle, so apps/server/dist holds the server bundle plus the web client
585+
# and apps/desktop/dist-electron the Electron main. build:desktop:bundle
586+
# skips the Linux browser secret helper, which this artifact does not
587+
# carry; each Linux package job compiles its own per arch.
578588
# The release channel is already stable, nightly, or preview. Builds in
579589
# other repositories report "dev" telemetry.
580590
- name: Build JS bundle
581591
env:
582592
T3CODE_TELEMETRY_CHANNEL: ${{ github.repository == 'pingdotgg/t3code' && needs.preflight.outputs.release_channel || 'dev' }}
583-
run: vp run build:desktop
593+
run: vp run ${{ steps.bundle_build.outputs.script }}
584594

585595
# Two paths under apps/ so the artifact root is apps/; consumers download
586596
# into `apps` to restore both at their original locations.

‎.macroscope/ignore.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,7 @@ native/libghostty-vt/include/**
279279
# it needs. Regenerated by apps/mobile/scripts/generate-highlights.mts; review
280280
# that script and cpp/t3_highlights.c instead.
281281
apps/mobile/modules/t3-highlights/Vendor/**
282+
283+
# Nitro bindings for T3Highlights. Regenerated by `vp run generate:nitro` in
284+
# apps/mobile from modules/t3-highlights/src/T3Highlights.nitro.ts.
285+
apps/mobile/modules/t3-highlights/nitrogen/generated/**

‎apps/desktop/src/app/DesktopApp.test.ts‎

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { assert, it } from "@effect/vitest";
22
import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
33
import * as Deferred from "effect/Deferred";
44
import * as Effect from "effect/Effect";
5+
import * as Exit from "effect/Exit";
56
import * as Layer from "effect/Layer";
67
import * as FileSystem from "effect/FileSystem";
78
import * as Path from "effect/Path";
@@ -69,14 +70,19 @@ import * as ElectronWindow from "../electron/ElectronWindow.ts";
6970
import * as MacPermissions from "../permissions/MacPermissions.ts";
7071
import * as PreviewManager from "../preview/Manager.ts";
7172

72-
it.effect(
73-
"starts the backend and window while Linux registration is stalled and cancels it on shutdown",
74-
() =>
73+
it.effect.each([false, true])(
74+
"stops backends together before flushing history and cancels Linux registration, stop defect=%s",
75+
(stopDefect) =>
7576
Effect.gen(function* () {
7677
const registrationStarted = yield* Deferred.make<void>();
7778
const releaseRegistration = yield* Deferred.make<void>();
7879
const registrationStopped = yield* Deferred.make<void>();
7980
const windowStarted = yield* Deferred.make<void>();
81+
const firstStopStarted = yield* Deferred.make<void>();
82+
const secondStopStarted = yield* Deferred.make<void>();
83+
const releaseStops = yield* Deferred.make<void>();
84+
const historyStarted = yield* Deferred.make<void>();
85+
const releaseHistory = yield* Deferred.make<void>();
8086
const quitting = yield* Ref.make(false);
8187
const events: string[] = [];
8288
const record = (event: string) =>
@@ -169,7 +175,21 @@ it.effect(
169175
start: record("backend"),
170176
currentConfig: Effect.succeedSome({ preflightFailure: Option.none() }),
171177
}),
172-
list: Effect.succeed([]),
178+
list: Effect.succeed([
179+
{
180+
stop: () =>
181+
Deferred.succeed(firstStopStarted, undefined).pipe(
182+
Effect.andThen(Deferred.await(releaseStops)),
183+
Effect.andThen(stopDefect ? Effect.die("stop defect") : Effect.void),
184+
),
185+
},
186+
{
187+
stop: () =>
188+
Deferred.succeed(secondStopStarted, undefined).pipe(
189+
Effect.andThen(Deferred.await(releaseStops)),
190+
),
191+
},
192+
]),
173193
} as unknown as DesktopBackendPool.DesktopBackendPool["Service"]),
174194
Effect.provideService(DesktopServerExposure.DesktopServerExposure, {
175195
configureFromSettings: () => Effect.succeed({ mode: "local-only" }),
@@ -182,7 +202,10 @@ it.effect(
182202
start: Effect.void,
183203
} as unknown as DesktopAppActivation.DesktopAppActivation["Service"]),
184204
Effect.provideService(DesktopRendererHistory.DesktopRendererHistory, {
185-
shutdown: Effect.void,
205+
shutdown: Deferred.succeed(historyStarted, undefined).pipe(
206+
Effect.andThen(Deferred.await(releaseHistory)),
207+
Effect.asVoid,
208+
),
186209
} as DesktopRendererHistory.DesktopRendererHistory["Service"]),
187210
);
188211
const app = yield* runApp.pipe(
@@ -226,8 +249,19 @@ it.effect(
226249
yield* Deferred.await(windowStarted);
227250
assert.deepEqual(events, ["identity", "ready", "identity", "backend", "window"]);
228251
assert.isFalse(yield* Deferred.isDone(registrationStopped));
229-
yield* (yield* DesktopShutdown.DesktopShutdown).request;
230-
yield* Fiber.join(app);
252+
const shutdown = yield* DesktopShutdown.DesktopShutdown;
253+
yield* shutdown.request;
254+
yield* Deferred.await(firstStopStarted);
255+
yield* Deferred.await(secondStopStarted);
256+
assert.isFalse(yield* Deferred.isDone(historyStarted));
257+
assert.isFalse(yield* shutdown.isComplete);
258+
yield* Deferred.succeed(releaseStops, undefined);
259+
yield* Deferred.await(historyStarted);
260+
assert.isFalse(yield* shutdown.isComplete);
261+
yield* Deferred.succeed(releaseHistory, undefined);
262+
const result = yield* Fiber.await(app);
263+
assert.equal(Exit.isFailure(result), stopDefect);
264+
assert.isTrue(yield* shutdown.isComplete);
231265
assert.isTrue(yield* Deferred.isDone(registrationStopped));
232266
}).pipe(Effect.provide(DesktopShutdown.layer)),
233267
);

‎apps/desktop/src/app/DesktopApp.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as Cause from "effect/Cause";
22
import * as Config from "effect/Config";
3-
import * as Duration from "effect/Duration";
43
import * as Effect from "effect/Effect";
54
import * as Option from "effect/Option";
65
import * as Ref from "effect/Ref";
@@ -19,6 +18,7 @@ import * as DesktopClerk from "./DesktopClerk.ts";
1918
import * as DesktopApplicationMenu from "../window/DesktopApplicationMenu.ts";
2019
import * as DesktopWindow from "../window/DesktopWindow.ts";
2120
import * as PreviewPasskeys from "../preview/Passkeys.ts";
21+
import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts";
2222
import * as DesktopBackendPool from "../backend/DesktopBackendPool.ts";
2323
import * as DesktopEnvironment from "./DesktopEnvironment.ts";
2424
import * as DesktopLegacyLocalStorage from "./DesktopLegacyLocalStorage.ts";
@@ -160,7 +160,7 @@ const stopAllPoolInstances = Effect.fn("desktop.app.stopAllPoolInstances")(
160160
const instances = yield* pool.list;
161161
yield* Effect.forEach(
162162
instances,
163-
(instance) => instance.stop({ timeout: Duration.seconds(5) }),
163+
(instance) => instance.stop({ timeout: DesktopBackendManager.BACKEND_QUIT_STOP_TIMEOUT }),
164164
{ concurrency: "unbounded" },
165165
);
166166
},
@@ -363,6 +363,7 @@ const scopedProgram = Effect.scoped(
363363
stopAllPoolInstances().pipe(
364364
Effect.ensuring(rendererHistory.shutdown),
365365
Effect.ensuring(shutdown.markComplete),
366+
Effect.withSpan("desktop.app.shutdown"),
366367
),
367368
);
368369

‎apps/desktop/src/backend/DesktopBackendManager.test.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,4 +720,58 @@ describe("DesktopBackendManager", () => {
720720
}).pipe(Effect.provide(TestClock.layer())),
721721
),
722722
);
723+
724+
it.effect("the quit stop deadline waits for a backend that only exits at the force kill", () =>
725+
Effect.scoped(
726+
Effect.gen(function* () {
727+
const spawned = yield* Deferred.make<void>();
728+
const teardownStarted = yield* Deferred.make<void>();
729+
const teardownFinished = yield* Deferred.make<void>();
730+
// The real spawner sends SIGTERM, waits the grace, then SIGKILLs. A backend that
731+
// ignores SIGTERM therefore finishes tearing down just after the grace.
732+
const instance = yield* makeTestInstance({
733+
spawnerLayer: Layer.succeed(
734+
ChildProcessSpawner.ChildProcessSpawner,
735+
ChildProcessSpawner.make(() =>
736+
Effect.gen(function* () {
737+
const scope = yield* Scope.Scope;
738+
yield* Deferred.succeed(spawned, undefined);
739+
yield* Scope.addFinalizer(
740+
scope,
741+
Deferred.succeed(teardownStarted, undefined).pipe(
742+
Effect.andThen(Effect.sleep(Duration.seconds(6))),
743+
Effect.andThen(Deferred.succeed(teardownFinished, undefined)),
744+
Effect.asVoid,
745+
),
746+
);
747+
return makeProcess({
748+
exitCode: Deferred.await(teardownFinished).pipe(
749+
Effect.as(ChildProcessSpawner.ExitCode(0)),
750+
),
751+
});
752+
}),
753+
),
754+
),
755+
httpClientLayer: layerHttpClient(() => Effect.never),
756+
});
757+
yield* instance.start;
758+
yield* Deferred.await(spawned);
759+
760+
// App quit and the updater stop every backend with this deadline.
761+
const quitDone = yield* Deferred.make<void>();
762+
const quitFiber = yield* instance
763+
.stop({ timeout: DesktopBackendManager.BACKEND_QUIT_STOP_TIMEOUT })
764+
.pipe(Effect.andThen(Deferred.succeed(quitDone, undefined)), Effect.forkChild);
765+
766+
yield* Deferred.await(teardownStarted);
767+
// Past the SIGTERM grace but before the force kill lands: quit must still be waiting.
768+
yield* TestClock.adjust(Duration.millis(5_500));
769+
assert.isFalse(yield* Deferred.isDone(quitDone));
770+
771+
yield* TestClock.adjust(Duration.millis(500));
772+
yield* Fiber.join(quitFiber);
773+
assert.isTrue(yield* Deferred.isDone(teardownFinished));
774+
}).pipe(Effect.provide(TestClock.layer())),
775+
),
776+
);
723777
});

0 commit comments

Comments
 (0)