From d2bdd58eef0410f08de0cfd1e48de51c6893acd0 Mon Sep 17 00:00:00 2001 From: Craig Davison <5341072+davisonio@users.noreply.github.com> Date: Sat, 14 Nov 2026 16:38:59 +0800 Subject: [PATCH 001/192] fix(server): npm provider updates only offer versions npm will install (#24062) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../Drivers/ClaudeMaintenance.test.ts | 54 ++++++-- .../src/provider/Drivers/CodexDriver.test.ts | 19 ++- .../src/server/maintenanceResolver.test.ts | 118 +++++++++++++++--- .../src/server/maintenanceResolver.ts | 92 +++++++++----- 4 files changed, 226 insertions(+), 57 deletions(-) diff --git a/apps/server/src/provider/Drivers/ClaudeMaintenance.test.ts b/apps/server/src/provider/Drivers/ClaudeMaintenance.test.ts index eccf421dd7c18..33f9f901c8731 100644 --- a/apps/server/src/provider/Drivers/ClaudeMaintenance.test.ts +++ b/apps/server/src/provider/Drivers/ClaudeMaintenance.test.ts @@ -24,6 +24,8 @@ import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; import * as HttpClient from "effect/http/HttpClient"; import * as HttpClientResponse from "effect/http/HttpClientResponse"; +import * as Sink from "effect/Sink"; +import * as ChildProcess from "effect/process/ChildProcess"; import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; import { makeClaudeMaintenanceResolution } from "./ClaudeMaintenance.ts"; @@ -77,6 +79,13 @@ const fixture = Effect.fn("ClaudeMaintenance.test.fixture")(function* ( : platform === "darwin" ? "/Library/Application Support/ClaudeCode" : "/etc/claude-code"; + const npmPath = path.join(directory, "bin/npm"); + if (install === "npm") { + yield* fs.makeDirectory(path.dirname(npmPath), { recursive: true }); + yield* fs.writeFileString(npmPath, "#!/bin/sh\n"); + yield* fs.chmod(npmPath, 0o755); + } + const npmProbes: Array> = []; let installationReads = 0; const testFs = FileSystem.makeNoop({ ...fs, @@ -105,11 +114,37 @@ const fixture = Effect.fn("ClaudeMaintenance.test.fixture")(function* ( managed, managedDirectory, settingsPath, + npmProbes, installationReads: () => installationReads, - makeResolution: makeClaudeMaintenanceResolution({ binaryPath, env: { PATH: "" } }).pipe( + makeResolution: makeClaudeMaintenanceResolution({ + binaryPath, + env: { PATH: install === "npm" ? path.dirname(npmPath) : "" }, + }).pipe( Effect.provideService( ChildProcessSpawner.ChildProcessSpawner, - ChildProcessSpawner.make(() => Effect.die("version checks must not run a subprocess")), + ChildProcessSpawner.make((command) => { + if (!ChildProcess.isStandardCommand(command) || command.command !== npmPath) { + return Effect.die("version checks may only ask npm"); + } + npmProbes.push(command.args.map(String)); + return Effect.succeed( + ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(1), + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(1)), + isRunning: Effect.succeed(false), + kill: () => Effect.void, + unref: Effect.succeed(Effect.void), + stdin: Sink.drain, + stdout: Stream.encodeText( + Stream.make(JSON.stringify({ "@anthropic-ai/claude-code": { latest: "2.1.316" } })), + ), + stderr: Stream.empty, + all: Stream.empty, + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + }), + ); + }), ), Effect.provideService(FileSystem.FileSystem, testFs), Effect.provideService(HostProcess.Platform, platform), @@ -233,12 +268,17 @@ it.layer(NodeServices.layer)("ClaudeMaintenance", (it) => { status: row.advisory, latestVersion: row.expected, }); - expect(requests).toHaveLength(1); - expect(requests[0]).toMatch( - row.npm ? /registry\.npmjs\.org/ : new RegExp(`claude-code-releases/${row.channel}$`), - ); + // npm installs ask `npm outdated`, which applies npm's release-age settings. + const lookups = row.npm ? f.npmProbes : requests; + expect(lookups).toHaveLength(1); + if (row.npm) { + expect(requests).toEqual([]); + expect(f.npmProbes[0]?.[0]).toBe("outdated"); + } else { + expect(requests[0]).toMatch(new RegExp(`claude-code-releases/${row.channel}$`)); + } yield* enrich; - expect(requests).toHaveLength(1); + expect(lookups).toHaveLength(1); }), ); diff --git a/apps/server/src/provider/Drivers/CodexDriver.test.ts b/apps/server/src/provider/Drivers/CodexDriver.test.ts index 01d44f3d3d1e8..1462d051346f1 100644 --- a/apps/server/src/provider/Drivers/CodexDriver.test.ts +++ b/apps/server/src/provider/Drivers/CodexDriver.test.ts @@ -729,6 +729,7 @@ it.layer(layerTest)("CodexDriver", (it) => { const shimDir = NodePath.join(tempDir, fixture.dataRoot, "shims"); const npmPrefix = NodePath.join(tempDir, "mise", "installs", "node", "24.13.0"); const npmBin = NodePath.join(npmPrefix, "bin"); + const npmPath = NodePath.join(npmBin, "npm"); const npmEntry = NodePath.join( npmPrefix, "lib", @@ -738,7 +739,7 @@ it.layer(layerTest)("CodexDriver", (it) => { "bin", "codex.js", ); - for (const file of [brewPath, misePath, npmEntry]) { + for (const file of [brewPath, misePath, npmPath, npmEntry]) { yield* fs.makeDirectory(NodePath.dirname(file), { recursive: true }); yield* fs.writeFileString(file, "#!/bin/sh\n"); yield* fs.chmod(file, 0o755); @@ -753,12 +754,20 @@ it.layer(layerTest)("CodexDriver", (it) => { ].join(NodePath.delimiter); const probes: Array> = []; const metadataSpawner = ChildProcessSpawner.make((command) => { - if (!ChildProcess.isStandardCommand(command) || command.command !== brewPath) { + const isNpmOutdated = + ChildProcess.isStandardCommand(command) && + command.command === npmPath && + command.args[0] === "outdated"; + if ( + !ChildProcess.isStandardCommand(command) || + (command.command !== brewPath && !isNpmOutdated) + ) { return Effect.die("Provider resolution must not execute a provider or updater"); } - probes.push(command.args.map(String)); - const stdout = - command.args[0] === "--prefix" + if (!isNpmOutdated) probes.push(command.args.map(String)); + const stdout = isNpmOutdated + ? JSON.stringify({ "@openai/codex": { latest: "0.153.4" } }) + : command.args[0] === "--prefix" ? brewPrefix : JSON.stringify({ formulae: [{ versions: { stable: "2026.9.1" } }] }); return Effect.succeed( diff --git a/packages/provider-core/src/server/maintenanceResolver.test.ts b/packages/provider-core/src/server/maintenanceResolver.test.ts index 0899713ed9602..c9fcc71d9c66d 100644 --- a/packages/provider-core/src/server/maintenanceResolver.test.ts +++ b/packages/provider-core/src/server/maintenanceResolver.test.ts @@ -356,12 +356,11 @@ it.layer( ), ); + // npm-global installs ask `npm outdated` instead; see the npm-global cases below. it.effect.each([ { install: "native", stdout: '"1.2.0"', exitCode: 0, latest: "1.2.0" }, { install: "manual", stdout: '["1.2.0"]', exitCode: 0, latest: "1.2.0" }, - { install: "npm", stdout: '"1.2.0"', exitCode: 0, latest: "1.2.0" }, { install: "native", stdout: "", exitCode: 1, latest: null }, - { install: "npm", stdout: '"1.2.0"', exitCode: 1, latest: null }, { install: "native", stdout: "not json", exitCode: 0, latest: null }, { install: "manual", stdout: '["1.2.0", "1.3.0"]', exitCode: 0, latest: null }, { install: "manual", stdout: '""', exitCode: 0, latest: null }, @@ -372,20 +371,12 @@ it.layer( const tempDir = yield* makeTempDir("t3-registry-version"); const npmBinDir = NodePath.join(tempDir, "npm-bin"); writeExecutable(NodePath.join(npmBinDir, "npm")); - const binaryPath = - fixture.install === "npm" - ? linkIntoPackage(tempDir, "package-tool", [ - "lib", - "node_modules", - "@example", - "package-tool", - ]) - : NodePath.join(tempDir, ".local/bin/native-package-tool"); - if (fixture.install !== "npm") writeExecutable(binaryPath); + const binaryPath = NodePath.join(tempDir, ".local/bin/native-package-tool"); + writeExecutable(binaryPath); let probes = 0; const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect( fixture.install === "native" ? nativePackageToolUpdate : packageToolUpdate, - { binaryPath, env: { PATH: fixture.install === "npm" ? npmBinDir : "" } }, + { binaryPath, env: { PATH: "" } }, ).pipe( Effect.provideService(HostProcess.Environment, { PATH: npmBinDir }), Effect.provideService( @@ -397,7 +388,6 @@ it.layer( expect(args).toEqual([ "view", "--global", - ...(fixture.install === "npm" ? ["--prefix", tempDir] : []), "--json", `${capabilities.packageName}@latest`, "version", @@ -540,7 +530,7 @@ it.layer( ChildProcessSpawner.ChildProcessSpawner, stdoutSpawner(() => { probes++; - return JSON.stringify(version); + return JSON.stringify({ "@example/package-tool": { latest: version } }); }), ), Effect.tap((capabilities) => @@ -596,7 +586,7 @@ it.layer( expect(advisoryFor(pnpmTool, "1.1.0-beta.1")).toBe( "pnpm add -g --allow-build=@example/package-tool @example/package-tool@latest", ); - // Only pnpm's minimumReleaseAge skips fresh releases; npm keeps @latest. + // npm keeps @latest; its outdated probe already reports what that resolves to. expect( advisoryFor( makeProviderMaintenanceCapabilities({ @@ -710,6 +700,9 @@ it.layer( expect(capabilities).toEqual({ provider: driver("packageTool"), packageName: "@example/package-tool", + latestVersion: null, + lookupLatestVersion: expect.anything(), + latestVersionCacheKey: expect.any(String), update: { command: `npm install -g --prefix ${tempDir} --allow-scripts=@example/package-tool @example/package-tool@latest`, executable: "npm", @@ -1424,6 +1417,99 @@ it.layer( { skip: !symlinksSupported || windowsHost }, ); + it.effect.each([ + { + case: "the eligible release", + outdated: { "@example/package-tool": { current: "1.0.0", wanted: "1.1.0", latest: "1.1.0" } }, + exitCode: 1, + status: "behind_latest", + latestVersion: "1.1.0", + }, + { + case: "no update during cooldown", + outdated: {}, + exitCode: 0, + status: "current", + latestVersion: "1.0.0", + }, + { + case: "no update during cooldown beside a broken global", + outdated: {}, + exitCode: 0, + listExitCode: 1, + status: "current", + latestVersion: "1.0.0", + }, + { + case: "no registry version when npm cannot reach the registry", + outdated: { error: { code: "ECONNREFUSED" } }, + exitCode: 1, + status: "unknown", + latestVersion: null, + }, + ])( + "advertises $case for npm-global installs", + (fixture) => + Effect.gen(function* () { + const tempDir = yield* makeTempDir("t3-npm-latest"); + const npmBinDir = NodePath.join(tempDir, "npm-bin"); + const npmPath = NodePath.join(npmBinDir, "npm"); + writeExecutable(npmPath); + const link = linkIntoPackage(tempDir, "package-tool", [ + "lib", + "node_modules", + "@example", + "package-tool", + ]); + + const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect( + packageToolUpdate, + { binaryPath: link, env: { PATH: npmBinDir } }, + ).pipe( + // The updater's npm comes from the provider's PATH. + Effect.provideService(HostProcess.Environment, { PATH: "" }), + Effect.provideService( + ChildProcessSpawner.ChildProcessSpawner, + stdoutSpawner( + (command, args) => { + expect(command).toBe(npmPath); + expect(args.slice(1, 4)).toEqual(["-g", "--prefix", tempDir]); + return JSON.stringify( + args[0] === "outdated" + ? fixture.outdated + : { + dependencies: { "@example/package-tool": { version: "1.0.0" } }, + }, + ); + }, + (args) => (args[0] === "outdated" ? fixture.exitCode : (fixture.listExitCode ?? 0)), + ), + ), + ); + + const provider = yield* enrichProviderSnapshotWithVersionAdvisory( + installedPackageToolProvider, + capabilities, + ).pipe( + Effect.provideServiceEffect( + ProviderLatestVersions.ProviderLatestVersions, + ProviderLatestVersions.make([["@example/package-tool", "9.9.9"]]), + ), + Effect.provideService( + HttpClient.HttpClient, + HttpClient.make(() => Effect.die("the npm version is already cached")), + ), + ); + expect(provider.versionAdvisory).toMatchObject({ + status: fixture.status, + currentVersion: "1.0.0", + latestVersion: fixture.latestVersion, + updateCommand: expect.stringMatching(/@example\/package-tool@latest$/), + }); + }), + { skip: !symlinksSupported || windowsHost }, + ); + it.effect.each([ { case: "the eligible release under minimumReleaseAge", diff --git a/packages/provider-core/src/server/maintenanceResolver.ts b/packages/provider-core/src/server/maintenanceResolver.ts index 96645fd1685fd..d83d7375cf646 100644 --- a/packages/provider-core/src/server/maintenanceResolver.ts +++ b/packages/provider-core/src/server/maintenanceResolver.ts @@ -431,24 +431,22 @@ const runProbe = Effect.fn("runProbe")(function* ( ); }); -const decodePnpmOutdated = Schema.decodeUnknownOption( +const decodeOutdated = Schema.decodeUnknownOption( Schema.fromJsonString( Schema.Record(Schema.String, Schema.Struct({ latest: Schema.optional(Schema.String) })), ), ); -const decodePnpmList = Schema.decodeUnknownOption( - Schema.fromJsonString( - Schema.Array( - Schema.Struct({ - dependencies: Schema.optional( - Schema.Record(Schema.String, Schema.Struct({ version: Schema.optional(Schema.String) })), - ), - }), - ), +const GlobalPackageList = Schema.Struct({ + dependencies: Schema.optional( + Schema.Record(Schema.String, Schema.Struct({ version: Schema.optional(Schema.String) })), ), +}); +const decodePnpmList = Schema.decodeUnknownOption( + Schema.fromJsonString(Schema.Array(GlobalPackageList)), ); +const decodeNpmList = Schema.decodeUnknownOption(Schema.fromJsonString(GlobalPackageList)); -// Unlike npm's latest tag, pnpm outdated respects minimumReleaseAge and its excludes. +// Unlike the registry's latest tag, pnpm outdated respects minimumReleaseAge and its excludes. const resolvePnpmGlobalLatestVersion = Effect.fn("resolvePnpmGlobalLatestVersion")(function* ( packageName: string, env: NodeJS.ProcessEnv, @@ -466,9 +464,7 @@ const resolvePnpmGlobalLatestVersion = Effect.fn("resolvePnpmGlobalLatestVersion // pnpm outdated exits 1 when updates are available. [0, 1], ); - const outdatedEntries = outdated - ? Option.getOrUndefined(decodePnpmOutdated(outdated)) - : undefined; + const outdatedEntries = outdated ? Option.getOrUndefined(decodeOutdated(outdated)) : undefined; if (!outdatedEntries) return null; const latest = nonEmptyString(outdatedEntries[packageName]?.latest); if (latest) return latest; @@ -482,6 +478,41 @@ const resolvePnpmGlobalLatestVersion = Effect.fn("resolvePnpmGlobalLatestVersion return nonEmptyString(listedEntries?.[0]?.dependencies?.[packageName]?.version); }); +// Unlike `npm view`, `outdated` applies min-release-age, before and their excludes. +const resolveNpmGlobalLatestVersion = Effect.fn("resolveNpmGlobalLatestVersion")(function* ( + packageName: string, + npmPrefix: string, + env: NodeJS.ProcessEnv, +) { + // Match the updater's npm and environment. + const npmPath = yield* resolveCommandPath("npm", { + env: { ...(yield* HostProcess.Environment), ...env }, + }); + if (!npmPath) return null; + const outdated = yield* runProbe( + npmPath, + ["outdated", "-g", "--prefix", npmPrefix, "--json", packageName], + env, + // npm outdated exits 1 when updates are available. + [0, 1], + ); + const outdatedEntries = outdated ? Option.getOrUndefined(decodeOutdated(outdated)) : undefined; + // npm prints failures such as an unreachable registry as `{"error": ...}`, also exiting 1. + if (!outdatedEntries || "error" in outdatedEntries) return null; + const latest = nonEmptyString(outdatedEntries[packageName]?.latest); + if (latest) return latest; + // An empty result means the installed version is still the eligible release. + const listed = yield* runProbe( + npmPath, + ["ls", "-g", "--prefix", npmPrefix, "--depth", "0", "--json", packageName], + env, + // npm ls still prints the tree but exits 1 when other globals have problems. + [0, 1], + ); + const listedEntries = listed ? Option.getOrUndefined(decodeNpmList(listed)) : undefined; + return nonEmptyString(listedEntries?.dependencies?.[packageName]?.version); +}); + // npm names the tarball `${name}-${version}.tgz`, dropping a scope's `@` and its `/`. function parseNpmPackVersion(stdout: string, packageName: string): string | null { const prefix = `${packageName.replace(/^@/, "").replace("/", "-")}-`; @@ -933,8 +964,9 @@ export const resolvePackageManagedProviderMaintenance = Effect.fn( // npm proof outranks a containing Homebrew keg; Homebrew Node globals still belong to npm. const npmPrefix = yield* resolveNpmGlobalPrefix(context, packageName); if (npmPrefix) { + const services = yield* Effect.context(); // npm 12 requires script approval to install native binaries; older npm only warns about the flag. - return makeProviderMaintenanceCapabilities({ + const capabilities = makeProviderMaintenanceCapabilities({ provider: definition.provider, packageName, updateExecutable: "npm", @@ -948,7 +980,22 @@ export const resolvePackageManagedProviderMaintenance = Effect.fn( ], updateLockKey: `npm-global:${normalizeCommandPath(npmPrefix)}`, env: context.env, + // Inside min-release-age `@latest` installs an older release than the + // registry's tag, so only npm's own answer is worth advertising. + latestVersion: null, + lookupLatestVersion: resolveNpmGlobalLatestVersion(packageName, npmPrefix, context.env).pipe( + Effect.provideContext(services), + ), }); + return { + ...capabilities, + latestVersionCacheKey: [ + "npm-global", + normalizeCommandPath(npmPrefix), + packageName, + environmentCacheKey(context.env), + ].join("\0"), + }; } const homebrew = homebrewOwnershipFromCommandPath(context.realCommandPath); @@ -1146,21 +1193,9 @@ const withConfiguredNpmVersionLookup = Effect.fn("withConfiguredNpmVersionLookup const npmPath = yield* resolveCommandPath("npm", { env }); if (!npmPath) return capabilities; const services = yield* Effect.context(); - const update = capabilities.update; - const prefixIndex = update?.lockKey.startsWith("npm-global:") - ? update.args.indexOf("--prefix") - : -1; - const prefix = prefixIndex >= 0 ? update?.args[prefixIndex + 1] : undefined; const lookup = runProbe( npmPath, - [ - "view", - "--global", - ...(prefix ? ["--prefix", prefix] : []), - "--json", - `${packageName}@latest`, - "version", - ], + ["view", "--global", "--json", `${packageName}@latest`, "version"], env, ).pipe( Effect.map((stdout) => { @@ -1182,7 +1217,6 @@ const withConfiguredNpmVersionLookup = Effect.fn("withConfiguredNpmVersionLookup "configured-npm", npmPath, packageName, - prefix ?? "", environmentCacheKey(env), installerLatestVersionKey(capabilities) ?? "", ].join("\0"), From 5e06246d24b028f5bfa1543b78cf7e55bf98746e Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 09:42:57 +0100 Subject: [PATCH 002/192] fix(web): recovered queued edits keep their mentions and context (#36578) Co-authored-by: Julius Marminge --- .../components/chat/queuedMessageEdit.test.ts | 52 +++++++-- .../src/components/chat/queuedMessageEdit.ts | 2 +- apps/web/src/composerDraftStore.ts | 106 ++++-------------- .../queued-edit-keeps-thread-mention.e2e.ts | 64 +++++++++++ 4 files changed, 134 insertions(+), 90 deletions(-) create mode 100644 scripts/scenarios/queued-edit-keeps-thread-mention.e2e.ts diff --git a/apps/web/src/components/chat/queuedMessageEdit.test.ts b/apps/web/src/components/chat/queuedMessageEdit.test.ts index c22b3983c8a6a..0f839f414f74f 100644 --- a/apps/web/src/components/chat/queuedMessageEdit.test.ts +++ b/apps/web/src/components/chat/queuedMessageEdit.test.ts @@ -1,4 +1,4 @@ -import { EnvironmentId, ThreadId } from "@t3tools/contracts"; +import { EnvironmentId, MessageId, ThreadId, TurnItemId } from "@t3tools/contracts"; import { scopeThreadRef } from "@t3tools/client-runtime/environment"; import { threadContextRecord } from "@t3tools/shared/composerContextReferences"; import { beforeEach, describe, expect, it } from "vite-plus/test"; @@ -8,6 +8,7 @@ import { type ComposerFileAttachment, type ComposerImageAttachment, } from "../../composerDraftStore"; +import { environmentContextRecord, responseContextRecord } from "../../lib/composerContextRecords"; import { prepareQueuedEditAttachments, recoverQueuedMessageEdit } from "./queuedMessageEdit"; const environmentId = EnvironmentId.make("remote-environment"); @@ -78,25 +79,62 @@ describe("queued message file edits", () => { it("keeps file-only edits when another client starts the queued run", () => { const store = useComposerDraftStore.getState(); store.setPrompt(editTarget, "Original message"); - store.addFiles(editTarget, [file]); + const elsewhere: ComposerFileAttachment = { + ...uploadedFile, + id: "file:elsewhere", + name: "elsewhere.pdf", + file: null, + uploadedAttachmentId: "upload:elsewhere", + uploadEnvironmentId: EnvironmentId.make("other-environment"), + }; + store.addFiles(editTarget, [file, elsewhere]); expect( recoverQueuedMessageEdit({ editTarget, threadTarget, originalText: "Original message" }), ).toBe("kept"); expect(store.getComposerDraft(threadTarget)?.prompt).toBe("Original message"); - expect(store.getComposerDraft(threadTarget)?.files).toEqual([file]); + // An upload this environment cannot reach comes back as a file to reattach, not a dropped chip. + const { uploadedAttachmentId: _id, uploadEnvironmentId: _env, ...reattach } = elsewhere; + expect(store.getComposerDraft(threadTarget)?.files).toEqual([file, reattach]); expect(store.getComposerDraft(editTarget)).toBeNull(); }); - it("removes a kept edit draft that held a thread mention", () => { + it("keeps the mentions and reply of a kept edit in the thread draft", () => { const store = useComposerDraftStore.getState(); store.setPrompt(editTarget, "Edited message"); - store.setThreadContexts(editTarget, [ - threadContextRecord(scopeThreadRef(environmentId, ThreadId.make("thread:other")), "Other"), - ]); + const thread = threadContextRecord( + scopeThreadRef(environmentId, ThreadId.make("thread:other")), + "Other", + ); + const machine = environmentContextRecord(EnvironmentId.make("environment-vps"), "vps"); + const reply = responseContextRecord({ + citation: { + version: 1, + environmentId, + threadId: ThreadId.make("thread:edit"), + messageId: MessageId.make("message:answer"), + text: "Done.", + start: 0, + end: 5, + prefix: "", + suffix: "", + }, + itemId: TurnItemId.make("item:answer"), + subagents: [], + }); + store.addThreadContexts(editTarget, [thread]); + store.addEnvironmentContexts(editTarget, [machine]); + store.setReplyTo(editTarget, reply); + const editedPrompt = store.getComposerDraft(editTarget)!.prompt; expect( recoverQueuedMessageEdit({ editTarget, threadTarget, originalText: "Original message" }), ).toBe("kept"); expect(store.getComposerDraft(editTarget)).toBeNull(); + expect(store.getComposerDraft(threadTarget)).toMatchObject({ + prompt: editedPrompt, + threadContexts: [thread], + environmentContexts: [machine], + replyTo: reply, + }); }); it("reports a removed queued attachment as an edit when the run leaves", () => { diff --git a/apps/web/src/components/chat/queuedMessageEdit.ts b/apps/web/src/components/chat/queuedMessageEdit.ts index ee7d954d3b4a4..b5e576a36bff7 100644 --- a/apps/web/src/components/chat/queuedMessageEdit.ts +++ b/apps/web/src/components/chat/queuedMessageEdit.ts @@ -27,7 +27,7 @@ export function recoverQueuedMessageEdit(input: { !composerDraftHasUserContent(store.getComposerDraft(input.threadTarget)), }); if (recovery === "kept") { - store.moveComposerPromptAndImages(input.editTarget, input.threadTarget); + store.moveComposerContent(input.editTarget, input.threadTarget); } store.clearComposerContent(input.editTarget); return recovery; diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index 857ab86164499..ee337af7b07e6 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -1,4 +1,3 @@ -import { stripInlineContextReferences } from "./lib/composerContextReferences"; import { composerFileAttachmentMimeType, composerImageDedupKey, @@ -276,9 +275,10 @@ export function composerFileNeedsReattach(file: ComposerFileAttachment): boolean return file.file === null && file.uploadedAttachmentId === undefined; } +/** Strips upload ids another environment owns; local bytes re-upload, byte-less files need reattaching. */ function clearStaleFileUploadMetadata( draft: ComposerThreadDraftState, - environmentId: EnvironmentId, + environmentId: EnvironmentId | null, ): ComposerThreadDraftState { let changed = false; const files = draft.files.map((file) => { @@ -757,8 +757,8 @@ interface ComposerDraftStoreState { clearComposerContent: (threadRef: ComposerThreadTarget) => void; /** Clears text and attachments, preserving terminal/element contexts, preview annotations, and review comments. */ clearComposerPromptAndImages: (threadRef: ComposerThreadTarget) => void; - /** Keeps attachments over destination limits, other environments' uploads, terminal/element contexts, annotations, and review comments in source. */ - moveComposerPromptAndImages: (from: ComposerThreadTarget, to: ComposerThreadTarget) => void; + /** Moves all user content into a destination that has none; otherwise leaves both drafts alone. */ + moveComposerContent: (from: ComposerThreadTarget, to: ComposerThreadTarget) => void; } export interface EffectiveComposerModelState { @@ -913,6 +913,18 @@ function createEmptyThreadDraft(): ComposerThreadDraftState { }; } +/** Keep these content fields aligned with composerDraftHasUserContent. */ +function composerDraftContent({ + modelSelectionByProvider: _modelSelectionByProvider, + activeProvider: _activeProvider, + modelSelectionExplicit: _modelSelectionExplicit, + runtimeMode: _runtimeMode, + interactionMode: _interactionMode, + ...content +}: ComposerThreadDraftState) { + return content; +} + export function composerFileDedupKey( file: Pick, ): string { @@ -4288,17 +4300,7 @@ const composerDraftStore = create()( } const nextDraft: ComposerThreadDraftState = { ...current, - prompt: "", - images: [], - files: [], - nonPersistedImageIds: [], - persistedAttachments: [], - terminalContexts: [], - previewAnnotations: [], - reviewComments: [], - threadContexts: [], - environmentContexts: [], - replyTo: null, + ...composerDraftContent(createEmptyThreadDraft()), }; const nextDraftsByThreadKey = { ...state.draftsByThreadKey }; if (shouldRemoveDraft(nextDraft)) { @@ -4345,7 +4347,7 @@ const composerDraftStore = create()( return { draftsByThreadKey: nextDraftsByThreadKey }; }); }, - moveComposerPromptAndImages: (from, to) => { + moveComposerContent: (from, to) => { const fromKey = resolveComposerDraftKey(get(), from) ?? ""; const toKey = resolveComposerDraftKey(get(), to) ?? ""; if (fromKey.length === 0 || toKey.length === 0 || fromKey === toKey) { @@ -4353,84 +4355,24 @@ const composerDraftStore = create()( } set((state) => { const source = state.draftsByThreadKey[fromKey]; - if (!source) { + const destination = state.draftsByThreadKey[toKey] ?? createEmptyThreadDraft(); + if (!source || composerDraftHasUserContent(destination)) { return state; } - const destination = state.draftsByThreadKey[toKey] ?? createEmptyThreadDraft(); const destinationEnvironmentId = typeof to === "string" ? (state.draftThreadsByThreadKey[toKey]?.environmentId ?? parseScopedThreadKey(toKey)?.environmentId ?? null) : to.environmentId; - const destinationFileIds = new Set(destination.files.map((file) => file.id)); - const destinationFileKeys = new Set(destination.files.map(composerFileDedupKey)); - const transferableFiles = source.files.filter( - (file) => - (file.file !== null || file.uploadEnvironmentId === destinationEnvironmentId) && - !destinationFileIds.has(file.id) && - !destinationFileKeys.has(composerFileDedupKey(file)), - ); - const remainingAttachmentSlots = Math.max( - 0, - PROVIDER_SEND_TURN_MAX_ATTACHMENTS - - destination.images.length - - destination.files.length, + const nextDestination = clearStaleFileUploadMetadata( + { ...destination, ...composerDraftContent(source) }, + destinationEnvironmentId, ); - const movedImages = source.images.slice(0, remainingAttachmentSlots); - const movedImageIds = new Set(movedImages.map((image) => image.id)); - const retainedImages = source.images.filter((image) => !movedImageIds.has(image.id)); - const movedFiles = transferableFiles - .slice(0, remainingAttachmentSlots - movedImages.length) - .map((file) => { - // Re-upload local bytes across environments; the destination cannot reach source upload ids. - if ( - file.file === null || - file.uploadEnvironmentId === undefined || - file.uploadEnvironmentId === destinationEnvironmentId - ) { - return file; - } - const { uploadedAttachmentId: _a, uploadEnvironmentId: _e, ...rest } = file; - return rest; - }); - const movedFileIds = new Set(movedFiles.map((file) => file.id)); - const retainedFiles = source.files.filter((file) => !movedFileIds.has(file.id)); - const movedPrompt = ensureInlineContextReferences( - stripInlineContextReferences(source.prompt), - destination.terminalContexts.map(terminalContextReference), - ); - const nextDestination: ComposerThreadDraftState = { - ...destination, - prompt: movedPrompt, - images: [...destination.images, ...movedImages], - files: [...destination.files, ...movedFiles], - nonPersistedImageIds: [ - ...destination.nonPersistedImageIds, - ...source.nonPersistedImageIds.filter((imageId) => movedImageIds.has(imageId)), - ], - persistedAttachments: [ - ...destination.persistedAttachments, - ...source.persistedAttachments.filter((attachment) => - movedImageIds.has(attachment.id), - ), - ], - }; // Moved images still use these blob URLs at the destination; do not revoke them. const nextSource: ComposerThreadDraftState = { ...source, - prompt: ensureInlineContextReferences( - "", - source.terminalContexts.map(terminalContextReference), - ), - images: retainedImages, - files: retainedFiles, - nonPersistedImageIds: source.nonPersistedImageIds.filter( - (imageId) => !movedImageIds.has(imageId), - ), - persistedAttachments: source.persistedAttachments.filter( - (attachment) => !movedImageIds.has(attachment.id), - ), + ...composerDraftContent(createEmptyThreadDraft()), }; const nextDraftsByThreadKey = { ...state.draftsByThreadKey }; if (shouldRemoveDraft(nextSource)) { diff --git a/scripts/scenarios/queued-edit-keeps-thread-mention.e2e.ts b/scripts/scenarios/queued-edit-keeps-thread-mention.e2e.ts new file mode 100644 index 0000000000000..e0d928a8777ea --- /dev/null +++ b/scripts/scenarios/queued-edit-keeps-thread-mention.e2e.ts @@ -0,0 +1,64 @@ +import { expect } from "e2e"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; + +import { T3_HOME } from "../e2e.config.ts"; +import { makeState, runPromise, test, WEB } from "./t3.ts"; + +const SettingsJson = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)); +const decodeSettings = Schema.decodeEffect(SettingsJson); +const encodeSettings = Schema.encodeEffect(SettingsJson); + +const threadRuntimeSettings = Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const settingsPath = (yield* Path.Path).join(T3_HOME, "userdata/settings.json"); + const original = yield* fs.readFileString(settingsPath); + const settings = yield* decodeSettings(original); + return { + supervise: fs.writeFileString( + settingsPath, + yield* encodeSettings({ ...settings, defaultRuntimeMode: "approval-required" }), + ), + restore: fs.writeFileString(settingsPath, original), + }; +}); + +test( + "An unsaved queued edit keeps its thread mention when the queued run starts", + WEB, + async ({ t3, app, screen }) => { + const settings = await runPromise(threadRuntimeSettings); + try { + // Supervised mode makes the approval recording wait for an answer, which holds its turn open. + await runPromise(settings.supervise); + const project = await t3.project("demo"); + await t3.thread({ project, title: "Release notes", messages: ["Draft the notes"] }); + const thread = await t3.thread({ + project, + title: "Waiting work", + messages: ["Change the file"], + model: "approval", + }); + const { cli } = makeState(T3_HOME); + await runPromise(cli(["thread", "send", thread.threadId, "Queued message"])); + await t3.open(thread); + + await screen.getByRole("button", "Edit queued message").tap(); + const composer = screen.getByRole("textbox"); + await composer.press("End"); + await composer.pressSequentially(" @Release"); + await screen.getByRole("option", "Release notes", { exact: false }).tap(); + await expect(composer).toContainText("Release notes"); + + await screen.getByRole("button", "Approve", { exact: true }).tap(); + await expect(screen.getByText("was kept in the composer", { exact: false })).toBeVisible(); + await expect(screen.getByRole("button", "Cancel editing queued message")).not.toBeVisible(); + await app.screenshot("recovered-edit"); + await expect(composer).toContainText("Release notes"); + } finally { + await runPromise(settings.restore); + } + }, +); From 35d01dc291a0552bfd26577d9d6721d18d8f974f Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 02:45:09 -0600 Subject: [PATCH 003/192] perf(acp): long ACP threads reopen without per-chunk history copies (#23724) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../Adapters/AcpAdapterV2.test.ts | 81 +++++++++++++++++++ packages/provider-acp/src/server/adapter.ts | 28 +++---- 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index 28b1897e58d81..b7808ef897ee7 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -3180,6 +3180,87 @@ describe("AcpAdapterV2", () => { }).pipe(Effect.provide(layerTest)), ); + it.effect("replays history in order, with stable earlier snapshots", () => + Effect.gen(function* () { + const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const path = yield* Path.Path; + const selfInvocation = yield* resolveSelfInvocation(); + const mockAgentPath = yield* path.fromFileUrl( + new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), + ); + type RuntimeService = AcpSessionRuntime.AcpSessionRuntime["Service"]; + let sessionUpdateHandler: Parameters[0] | undefined; + const instanceId = ProviderInstanceId.make("acp-test-v2-replay-order"); + const adapter = yield* makeAcpAdapterV2({ + instanceId, + flavor: { + driver: ACP_TEST_DRIVER, + capabilities: AcpProviderCapabilitiesV2, + makeRuntime: makeMockRuntime({ + childProcessSpawner, + mockAgentPath, + wrapRuntime: (runtime) => ({ + ...runtime, + handleSessionUpdate: (handler) => + Effect.sync(() => { + sessionUpdateHandler = handler; + }).pipe(Effect.andThen(runtime.handleSessionUpdate(handler))), + }), + }), + }, + selfInvocation, + }); + const threadId = ThreadId.make("thread-acp-v2-replay-order"); + const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ + runtimeMode: "full-access", + interactionMode: "default", + cwd: process.cwd(), + }); + const modelSelection = { instanceId, model: "default" } as const; + const runtime = yield* adapter.openSession({ + threadId, + providerSessionId: ProviderSessionId.make("provider-session-acp-v2-replay-order"), + modelSelection, + runtimePolicy, + }); + const providerThread = yield* runtime.ensureThread({ + threadId, + modelSelection, + runtimePolicy, + }); + assert.isDefined(sessionUpdateHandler); + const replay = ( + sessionUpdate: "user_message_chunk" | "agent_message_chunk", + messageId: string, + text: string, + ) => + sessionUpdateHandler!({ + sessionId: "mock-session-1", + update: { sessionUpdate, messageId, content: { type: "text", text } }, + }); + + yield* replay("user_message_chunk", "u1", "hel"); + yield* replay("user_message_chunk", "u1", "lo"); + yield* replay("agent_message_chunk", "a1", "hi"); + yield* replay("user_message_chunk", "u2", "next"); + yield* replay("agent_message_chunk", "a1", " there"); + const first = yield* runtime.readThreadSnapshot({ providerThread }); + yield* replay("agent_message_chunk", "a2", "done"); + yield* replay("user_message_chunk", "u2", " one"); + const second = yield* runtime.readThreadSnapshot({ providerThread }); + + const texts = (snapshot: typeof first) => + snapshot.messages.map((message) => `${message.role}:${message.text}`); + assert.deepEqual(texts(first), ["user:hello", "assistant:hi there", "user:next"]); + assert.deepEqual(texts(second), [ + "user:hello", + "assistant:hi there", + "user:next one", + "assistant:done", + ]); + }).pipe(Effect.provide(layerTest), Effect.scoped), + ); + it.effect("persists a bounded subset of streamed root and child tool updates", () => Effect.gen(function* () { const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index 24cde9fd967ba..332c4bee95ec6 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -1729,6 +1729,7 @@ type PendingRuntimeRequest = { } ); +/** Writers update `order` and `messages` in place; readers copy them out. */ interface SnapshotMessageState { readonly order: Array; readonly messages: Map; @@ -2781,14 +2782,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( const rememberSnapshotMessage = (message: OrchestrationV2ConversationMessage) => Ref.update(snapshot, (current) => { const key = String(message.id); - const exists = current.messages.has(key); - const messages = new Map(current.messages); - messages.set(key, message); - return { - ...current, - order: exists ? current.order : [...current.order, key], - messages, - }; + if (!current.messages.has(key)) current.order.push(key); + current.messages.set(key, message); + return current; }); const textStreamFor = ( @@ -4664,8 +4660,8 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( const messageId = providerMessageId(nativeItemId); const key = String(messageId); const previous = current.messages.get(key); - const messages = new Map(current.messages); - messages.set(key, { + if (previous === undefined) current.order.push(key); + current.messages.set(key, { createdBy: previous?.createdBy ?? (role === "user" ? "user" : "agent"), creationSource: previous?.creationSource ?? "provider", id: messageId, @@ -4680,8 +4676,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( updatedAt: now, }); return { - order: current.order.includes(key) ? current.order : [...current.order, key], - messages, + ...current, reasoningIds: role === "thought" && !current.reasoningIds.has(messageId) ? new Set(current.reasoningIds).add(messageId) @@ -9517,6 +9512,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( yield* Ref.set(activeInteractionMode, null); } const state = yield* Ref.get(snapshot); + const messages = state.order.flatMap((key) => { + const message = state.messages.get(key); + return message === undefined ? [] : [message]; + }); const now = yield* DateTime.now; return { providerThread: { @@ -9526,10 +9525,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( updatedAt: now, }, providerTurns: [...(yield* Ref.get(providerTurns)).values()], - messages: state.order.flatMap((key) => { - const message = state.messages.get(key); - return message === undefined ? [] : [message]; - }), + messages, reasoningMessageIds: [...state.reasoningIds], runtimeRequests: [], providerPayload: { protocol: ACP_PROTOCOL, sessionId }, From 4d5d5f89d418c92a1478e15dace1e428f0fc13e8 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 09:48:52 +0100 Subject: [PATCH 004/192] fix(source-control): preserve .git in Azure DevOps repository names (#36576) Co-authored-by: Julius Marminge --- .../RepositoryIdentityResolver.test.ts | 25 ++++++++++++++++++ .../src/project/RepositoryIdentityResolver.ts | 7 ++++- packages/shared/src/git.ts | 25 +++++++++--------- .../src/shared/definition.test.ts | 26 +++++++++++++++++++ .../src/shared/definition.ts | 23 +++++++++++----- .../src/shared/definition.ts | 12 ++++----- 6 files changed, 91 insertions(+), 27 deletions(-) diff --git a/apps/server/src/project/RepositoryIdentityResolver.test.ts b/apps/server/src/project/RepositoryIdentityResolver.test.ts index 4377caf55c524..50d749c3fb1b0 100644 --- a/apps/server/src/project/RepositoryIdentityResolver.test.ts +++ b/apps/server/src/project/RepositoryIdentityResolver.test.ts @@ -8,6 +8,7 @@ import * as Path from "effect/Path"; import { vi } from "vite-plus/test"; import * as ProcessRunner from "../processRunner.ts"; +import { sourceControlDefinitions } from "../sourceControl/sourceControlDefinitions.ts"; import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts"; const normalizePathSeparators = (value: string) => value.replaceAll("\\", "/"); @@ -71,6 +72,30 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { }).pipe(Effect.provide(RepositoryIdentityResolver.layer)), ); + it.effect("keeps a literal .git in an Azure DevOps repository name", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const cwd = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-repository-identity-azure-git-suffix-test-", + }); + + yield* git(cwd, ["init"]); + yield* git(cwd, [ + "remote", + "add", + "origin", + "https://dev.azure.com/Acme/Platform/_git/Tool.git", + ]); + + const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; + const identity = yield* resolver.resolve(cwd); + + expect(identity?.displayName).toBe("Acme/Platform/_git/Tool.git"); + expect(identity?.name).toBe("Tool.git"); + expect(sourceControlDefinitions.repositorySelector(identity)).toBe("Tool.git"); + }).pipe(Effect.provide(RepositoryIdentityResolver.layer)), + ); + it.effect("resolves the fetch remote of a partial clone", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; diff --git a/apps/server/src/project/RepositoryIdentityResolver.ts b/apps/server/src/project/RepositoryIdentityResolver.ts index ebd63a957d5b5..4bb4e024ae151 100644 --- a/apps/server/src/project/RepositoryIdentityResolver.ts +++ b/apps/server/src/project/RepositoryIdentityResolver.ts @@ -123,7 +123,12 @@ function pickPrimaryRemote( function repositoryPathOf(canonicalKey: string, remoteUrl: string): string { const canonicalPath = canonicalKey.split("/").slice(1).join("/"); const displayPath = repositoryPathFromRemoteUrl(remoteUrl); - return displayPath?.toLowerCase() === canonicalPath ? displayPath : canonicalPath; + if (displayPath === null) return canonicalPath; + // A host that names repositories literally keeps the `.git` the display path drops. + return ( + [displayPath, `${displayPath}.git`].find((path) => path.toLowerCase() === canonicalPath) ?? + canonicalPath + ); } function buildRepositoryOrigin( diff --git a/packages/shared/src/git.ts b/packages/shared/src/git.ts index cb551bff37aa7..477130187037c 100644 --- a/packages/shared/src/git.ts +++ b/packages/shared/src/git.ts @@ -122,30 +122,29 @@ export function isTemporaryWorktreeBranch(refName: string): boolean { return TEMP_WORKTREE_BRANCH_PATTERN.test(refName.trim().toLowerCase()); } -function lowerCaseRemote(value: string): string { - return value - .trim() - .replace(/\/+$/g, "") - .replace(/\.git$/i, "") - .toLowerCase(); +const stripGitSuffix = (path: string) => path.replace(/\.git$/i, ""); + +function lowerCaseRemote(value: string, dropSuffix = stripGitSuffix): string { + return dropSuffix(value.trim().replace(/\/+$/g, "")).toLowerCase(); } /** * The host and repository path a remote names, lower case and without a port, or null for a - * remote that names no host, such as a local path. + * remote that names no host, such as a local path. Most hosts serve `name.git` as `name`, so the + * path drops that suffix unless `keepGitSuffix` asks for the name as written. */ export function parseGitRemoteRepository( value: string, + options?: { readonly keepGitSuffix?: boolean }, ): { readonly host: string; readonly path: string } | null { - const normalized = lowerCaseRemote(value); + const dropSuffix = options?.keepGitSuffix ? (path: string) => path : stripGitSuffix; + const normalized = lowerCaseRemote(value, dropSuffix); if (/^(?:ssh|https?|git):\/\//i.test(normalized)) { try { - // Parse the raw remote: `.git` may sit before a query or fragment, so strip it from the path once. + // Apply suffix policy to the path: `.git` may sit before a query or fragment. const url = new URL(value.trim().toLowerCase()); - const repositorySegments = url.pathname - .replace(/\/+$/, "") - .replace(/\.git$/, "") + const repositorySegments = dropSuffix(url.pathname.replace(/\/+$/, "")) .split("/") .filter((segment) => segment.length > 0); if (url.hostname && repositorySegments.length > 1) { @@ -176,7 +175,7 @@ export function parseGitRemoteRepository( /** * `host/path` for a remote that names a host, otherwise the remote itself, lower case. A host that - * spells repositories differently over SSH has its source control registry's + * spells repositories differently over SSH or treats `.git` literally has its source control registry's * `repositoryKeyFromRemote` rewrite this, so persisted keys come from that, not from here. */ export function normalizeGitRemoteUrl(value: string): string { diff --git a/packages/source-control-azure-devops/src/shared/definition.test.ts b/packages/source-control-azure-devops/src/shared/definition.test.ts index 4c3f3a0ec8f11..c373d6cb30df7 100644 --- a/packages/source-control-azure-devops/src/shared/definition.test.ts +++ b/packages/source-control-azure-devops/src/shared/definition.test.ts @@ -137,6 +137,32 @@ describe("Azure DevOps repository keys", () => { } }); + it.each([ + "git@ssh.dev.azure.com:v3/acme/platform/tool.git", + "https://dev.azure.com/acme/platform/_git/tool.git", + ])("preserves a literal .git in an Azure repository name: %s", (remoteUrl) => { + const canonicalKey = registry.repositoryKeyFromRemote(remoteUrl); + expect(canonicalKey).toBe("dev.azure.com/acme/platform/_git/tool.git"); + const identity: ChangeRequestProjectIdentity = { + canonicalKey, + locator: { source: "git-remote", remoteName: "origin", remoteUrl }, + }; + const link = parse("https://dev.azure.com/acme/platform/_git/tool.git/pullrequest/7")!; + expect(definition.isChangeRequestInRepository(identity, link)).toBe(true); + expect( + definition.isChangeRequestInRepository(identity, { + ...link, + repository: "acme/platform/_git/tool", + }), + ).toBe(false); + }); + + it("still drops .git from other hosts' repository keys", () => { + expect(registry.repositoryKeyFromRemote("git@github.com:acme/tool.git")).toBe( + "github.com/acme/tool", + ); + }); + it("keeps the organization in the host for the legacy visualstudio.com remotes", () => { for (const remote of [ "T3Tools@vs-ssh.visualstudio.com:v3/T3Tools/Platform/T3Code", diff --git a/packages/source-control-azure-devops/src/shared/definition.ts b/packages/source-control-azure-devops/src/shared/definition.ts index 5f6fca54f089c..83cd2d317da12 100644 --- a/packages/source-control-azure-devops/src/shared/definition.ts +++ b/packages/source-control-azure-devops/src/shared/definition.ts @@ -17,6 +17,17 @@ const CHECKOUT_COMMAND = /^az\s+repos\s+pr\s+checkout\s+(.+)$/i; const CHANGE_REQUEST_REFERENCE = /^https:\/\/(?:dev\.azure\.com\/[^/\s]+\/[^/\s]+|[^/\s]+\.visualstudio\.com\/(?:DefaultCollection\/)?[^/\s]+)\/_git\/[^/\s]+\/pullrequest\/(\d+)(?:[/?#].*)?$/i; +// `ssh.dev.azure.com` is the default SSH clone host (git@ssh.dev.azure.com:v3/org/project/repo), +// so any `*.dev.azure.com` subdomain is Azure. Legacy hosts stay under `.visualstudio.com`, +// including `vs-ssh.visualstudio.com`. +function isAzureHostname(hostname: string): boolean { + return ( + hostname === "dev.azure.com" || + hostname.endsWith(".dev.azure.com") || + hostname.endsWith(".visualstudio.com") + ); +} + /** * Azure alone addresses one repository under two names that share no part: `ssh.dev.azure.com` and * `v3/{org}/{project}/{repo}` over SSH, against `dev.azure.com` and `{org}/{project}/_git/{repo}` @@ -26,7 +37,10 @@ const CHANGE_REQUEST_REFERENCE = function repositoryKeyFromRemote(key: string): string | null { const match = /^(ssh\.dev\.azure\.com|vs-ssh\.visualstudio\.com)\/v3\/([^/]+)\/([^/]+)\/([^/]+)$/u.exec(key); - if (match === null) return null; + if (match === null) { + // Azure takes a name as written, so its keys keep the `.git` other hosts drop. + return isAzureHostname(key.split("/", 1)[0]!) ? key : null; + } const [, host, organization, project, repository] = match; return host === "ssh.dev.azure.com" ? `dev.azure.com/${organization}/${project}/_git/${repository}` @@ -125,13 +139,8 @@ export const definition = defineSourceControl({ }, isChangeRequestReference: (url) => CHANGE_REQUEST_REFERENCE.test(url), checkoutChangeRequestHost: () => null, - // `ssh.dev.azure.com` is the default SSH clone host (git@ssh.dev.azure.com:v3/org/project/repo), - // so any `*.dev.azure.com` subdomain is Azure. Legacy hosts stay under `.visualstudio.com`, - // including `vs-ssh.visualstudio.com`. recognizeRemote: ({ host, hostname }) => - hostname === "dev.azure.com" || - hostname.endsWith(".dev.azure.com") || - hostname.endsWith(".visualstudio.com") + isAzureHostname(hostname) ? { kind: KIND, name: "Azure DevOps", baseUrl: `https://${host}` } : null, /** diff --git a/packages/source-control-core/src/shared/definition.ts b/packages/source-control-core/src/shared/definition.ts index 280cea5a62b2e..00e1e045f0b79 100644 --- a/packages/source-control-core/src/shared/definition.ts +++ b/packages/source-control-core/src/shared/definition.ts @@ -338,10 +338,9 @@ export interface SourceControlDefinition { */ readonly repositorySelector?: (identity: RepositorySelectorIdentity) => string | null; /** - * For a host that spells one repository differently over SSH and on the web: the web spelling - * of the `host/path` key `normalizeGitRemoteUrl` made from a remote that names a host. Null when - * the key is not this host's. Persisted repository keys are built with it, so its output must - * not change. + * The key of a remote's lower case `host/path`, `.git` suffix kept, for a host that spells it + * apart from `normalizeGitRemoteUrl`: over SSH and the web, or with `.git` as part of the name. + * Null when the key is not this host's. Persisted keys are built with it: output must not change. */ readonly repositoryKeyFromRemote?: (key: string) => string | null; /** @@ -804,9 +803,10 @@ export function makeSourceControlDefinitionRegistry( }, repositoryKeyFromRemote: (remoteUrl) => { const key = normalizeGitRemoteUrl(remoteUrl); - if (parseGitRemoteRepository(remoteUrl) === null) return key; + const written = parseGitRemoteRepository(remoteUrl, { keepGitSuffix: true }); + if (written === null) return key; for (const definition of definitions) { - const spelled = definition.repositoryKeyFromRemote?.(key); + const spelled = definition.repositoryKeyFromRemote?.(`${written.host}/${written.path}`); if (spelled != null) return spelled; } return key; From 4652544f33ccfa2fda12745438067a3b4e55c269 Mon Sep 17 00:00:00 2001 From: Kolja Date: Sat, 14 Nov 2026 09:50:31 +0100 Subject: [PATCH 005/192] fix(server): interrupted checkpoints no longer leak temporary packs (#23715) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriver.test.ts | 345 ++++++++++++++++++ apps/server/src/vcs/GitVcsDriver.ts | 126 ++++++- packages/contracts/src/vcs.ts | 15 + .../src/server/GitLabCli.ts | 1 + 4 files changed, 486 insertions(+), 1 deletion(-) diff --git a/apps/server/src/vcs/GitVcsDriver.test.ts b/apps/server/src/vcs/GitVcsDriver.test.ts index a8c0052452ed7..3e95a3658ef69 100644 --- a/apps/server/src/vcs/GitVcsDriver.test.ts +++ b/apps/server/src/vcs/GitVcsDriver.test.ts @@ -1,3 +1,5 @@ +import * as NodeNet from "node:net"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeServices from "@effect/platform-node/NodeServices"; import * as Cause from "effect/Cause"; @@ -173,6 +175,349 @@ const makeCheckpointFixture = Effect.fn("makeCheckpointFixture")(function* ( return { git, checkpointRef }; }); +it.effect.each(["interrupt", "timeout", "success"] as const)( + "checkpoint bulk staging cleans operation-owned packs after $0 and can retry", + (stop) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.makeVcsDriverShape(); + const liveProcess = yield* VcsProcess.VcsProcess; + const sandbox = yield* fs.makeTempDirectoryScoped({ prefix: "t3-bulk-checkpoint-" }); + const cwd = path.join(sandbox, "repo"); + yield* fs.makeDirectory(cwd); + const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd); + yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); + const originalCheckpoint = (yield* git(["rev-parse", checkpointRef])).stdout; + const originalIndex = yield* fs.readFile(path.join(cwd, ".git/index")); + const originalHead = (yield* git(["rev-parse", "HEAD"])).stdout; + yield* git(["config", "core.bigFileThreshold", "1m"]); + const crypto = yield* SecureRandom.SecureRandom; + const payload = yield* crypto.randomBytes(2 * 1024 * 1024); + yield* fs.writeFile(path.join(cwd, "a-media.bin"), payload); + + // Git keeps bulk-checkin's pack open while the next path's clean filter runs. + // The socket is a barrier, not a race against the speed of compression. + const connected = Promise.withResolvers(); + const sockets = new Set(); + let released = false; + const server = yield* Effect.acquireRelease( + Effect.sync(() => + NodeNet.createServer((socket) => { + sockets.add(socket); + connected.resolve(socket); + // Git can run the filter again while refreshing the staged entry. + if (released) socket.end(); + }), + ), + (server) => + Effect.sync(() => { + for (const socket of sockets) socket.destroy(); + server.close(); + }), + ); + yield* Effect.promise( + () => + new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }), + ); + const address = server.address(); + assert.isTrue(address !== null && typeof address !== "string"); + if (address === null || typeof address === "string") return; + const filter = path.join(sandbox, "filter.cjs"); + yield* fs.writeFileString( + filter, + `const s = require('node:net').connect(${address.port}, '127.0.0.1'); s.resume(); process.stdin.resume(); s.on('end', () => process.exit(0));`, + ); + yield* git(["config", "filter.barrier.clean", `"${process.execPath}" "${filter}"`]); + yield* git(["config", "filter.barrier.required", "true"]); + yield* fs.writeFileString(path.join(cwd, ".gitattributes"), "z-gate filter=barrier\n"); + yield* fs.writeFileString(path.join(cwd, "z-gate"), "gate\n"); + const sharedPack = path.join(cwd, ".git/objects/pack/tmp_pack_other_operation"); + yield* fs.writeFileString(sharedPack, "owned by another Git operation"); + let objectDirectory = path.join(cwd, ".git/objects"); + const captureDriver = yield* GitVcsDriver.makeVcsDriverShape().pipe( + Effect.provideService(VcsProcess.VcsProcess, { + run: (input) => { + if (input.args.includes("add") && input.env?.GIT_OBJECT_DIRECTORY) + objectDirectory = input.env.GIT_OBJECT_DIRECTORY; + return liveProcess.run(input); + }, + }), + ); + const fiber = yield* captureDriver.checkpoints + .captureCheckpoint({ cwd, checkpointRef }) + .pipe(Effect.exit, Effect.forkScoped); + const socket = yield* Effect.promise(() => connected.promise).pipe( + Effect.raceFirst( + Fiber.join(fiber).pipe( + Effect.flatMap((result) => + Exit.isFailure(result) + ? Effect.failCause(result.cause) + : Effect.die("capture ended before the filter connected"), + ), + ), + ), + ); + const packs = (yield* fs.readDirectory(path.join(objectDirectory, "pack"))).filter( + (name) => name.startsWith("tmp_pack_") && name !== "tmp_pack_other_operation", + ); + assert.strictEqual(packs.length, 1); + assert.isAbove( + Number((yield* fs.stat(path.join(objectDirectory, "pack", packs[0]!))).size), + 1024 * 1024, + ); + // An overlapping capture publishes the same large blob while the first + // still owns an unfinished pack. Its objects/ref must survive either exit. + const concurrentRef = CheckpointRef.make("refs/t3/checkpoints/concurrent"); + const concurrentDriver = yield* GitVcsDriver.makeVcsDriverShape().pipe( + Effect.provideService(VcsProcess.VcsProcess, { + run: (input) => + liveProcess.run({ ...input, args: ["-c", "filter.barrier.clean=cat", ...input.args] }), + }), + ); + yield* concurrentDriver.checkpoints.captureCheckpoint({ cwd, checkpointRef: concurrentRef }); + const concurrentOid = (yield* git(["rev-parse", concurrentRef])).stdout; + const sharedPackNames = (yield* fs.readDirectory(path.join(cwd, ".git/objects/pack"))).sort(); + const packName = sharedPackNames.find((name) => name.endsWith(".pack")); + assert.isDefined(packName); + const sharedPackBytes = yield* fs.readFile(path.join(cwd, ".git/objects/pack", packName!)); + if (stop === "interrupt") yield* Fiber.interrupt(fiber); + else if (stop === "timeout") yield* TestClock.adjust("30 seconds"); + released = true; + socket.end(); + const result = yield* Fiber.await(fiber); + if (stop === "interrupt") + assert.isTrue(Exit.isFailure(result) && Cause.hasInterruptsOnly(result.cause)); + else if (stop === "timeout") { + assert.isTrue(Exit.isSuccess(result)); + if (Exit.isSuccess(result)) { + assert.isTrue(Exit.isFailure(result.value)); + if (Exit.isFailure(result.value)) { + const error = Cause.findErrorOption(result.value.cause); + assert.isTrue(Option.isSome(error) && error.value._tag === "VcsProcessTimeoutError"); + } + } + } else assert.isTrue(Exit.isSuccess(result) && Exit.isSuccess(result.value)); + assert.deepEqual( + (yield* fs.readDirectory(path.join(cwd, ".git/objects/pack"))).sort(), + sharedPackNames, + ); + assert.deepEqual( + yield* fs.readFile(path.join(cwd, ".git/objects/pack", packName!)), + sharedPackBytes, + ); + assert.strictEqual((yield* git(["rev-parse", concurrentRef])).stdout, concurrentOid); + assert.isFalse(yield* fs.exists(path.join(objectDirectory, "pack", packs[0]!))); + assert.isFalse(yield* fs.exists(objectDirectory)); + assert.strictEqual(yield* fs.readFileString(sharedPack), "owned by another Git operation"); + if (stop !== "success") + assert.strictEqual((yield* git(["rev-parse", checkpointRef])).stdout, originalCheckpoint); + assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git/index")), originalIndex); + assert.strictEqual((yield* git(["rev-parse", "HEAD"])).stdout, originalHead); + assert.deepEqual(yield* fs.readFile(path.join(cwd, "a-media.bin")), payload); + + yield* git(["config", "filter.barrier.clean", "cat"]); + yield* captureDriver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); + assert.isFalse(yield* fs.exists(objectDirectory)); + const blob = (yield* git(["hash-object", "a-media.bin"])).stdout.trim(); + assert.strictEqual( + (yield* git(["rev-parse", `${checkpointRef}:a-media.bin`])).stdout.trim(), + blob, + ); + yield* fs.remove(sharedPack); + yield* git(["fsck", "--full"]); + }).pipe(Effect.scoped, Effect.provide(layerGitContract)), +); + +it.effect.each(["failure", "interrupt"] as const)( + "checkpoint publication $0 preserves existing refs and permits retry", + (stop) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.makeVcsDriverShape(); + const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-checkpoint-publish-" }); + const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd); + yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); + const previous = (yield* git(["show-ref"])).stdout; + yield* fs.writeFileString(path.join(cwd, "new.txt"), "new checkpoint content\n"); + const published = yield* Deferred.make(); + const captureDriver = yield* GitVcsDriver.makeVcsDriverShape().pipe( + Effect.provideService(FileSystem.FileSystem, { + ...fs, + link: (source, destination) => + fs.link(source, destination).pipe( + Effect.andThen(Deferred.succeed(published, undefined)), + Effect.andThen( + stop === "interrupt" + ? Effect.never + : Effect.fail( + PlatformError.systemError({ + _tag: "BadResource", + module: "FileSystem", + method: "link", + }), + ), + ), + ), + }), + ); + const fiber = yield* captureDriver.checkpoints + .captureCheckpoint({ cwd, checkpointRef }) + .pipe(Effect.exit, Effect.forkScoped); + yield* Deferred.await(published); + if (stop === "interrupt") yield* Fiber.interrupt(fiber); + const result = yield* Fiber.await(fiber); + if (stop === "interrupt") + assert.isTrue(Exit.isFailure(result) && Cause.hasInterruptsOnly(result.cause)); + else { + assert.isTrue(Exit.isSuccess(result) && Exit.isFailure(result.value)); + if (Exit.isSuccess(result) && Exit.isFailure(result.value)) { + const error = Cause.findErrorOption(result.value.cause); + assert.isTrue( + Option.isSome(error) && + error.value._tag === "VcsCheckpointStorageError" && + error.value.stage === "publish", + ); + } + } + assert.strictEqual((yield* git(["show-ref"])).stdout, previous); + assert.deepEqual( + (yield* fs.readDirectory(path.join(cwd, ".git/objects"))).filter((name) => + name.startsWith("tmp_objdir-t3-checkpoint-"), + ), + [], + ); + yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); + assert.strictEqual( + (yield* git(["show", `${checkpointRef}:new.txt`])).stdout, + "new checkpoint content\n", + ); + yield* git(["fsck", "--full"]); + }).pipe(Effect.scoped, Effect.provide(layerGitContract)), +); + +it.effect("checkpoint publication falls back to rename where hardlinks are unavailable", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const crypto = yield* SecureRandom.SecureRandom; + const driver = yield* GitVcsDriver.makeVcsDriverShape(); + const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-publish-no-links-" }); + const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd); + yield* git(["config", "core.bigFileThreshold", "1m"]); + yield* fs.writeFile(path.join(cwd, "media.bin"), yield* crypto.randomBytes(2 * 1024 * 1024)); + const captureDriver = yield* GitVcsDriver.makeVcsDriverShape().pipe( + Effect.provideService(FileSystem.FileSystem, { + ...fs, + link: () => + Effect.fail( + PlatformError.systemError({ + _tag: "PermissionDenied", + module: "FileSystem", + method: "link", + }), + ), + }), + ); + yield* captureDriver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); + const files = yield* fs.readDirectory(path.join(cwd, ".git/objects/pack")); + assert.isTrue(files.some((name) => name.endsWith(".pack"))); + assert.isTrue(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef })); + assert.match((yield* git(["count-objects", "-v"])).stdout, /^garbage: 0$/m); + yield* git(["fsck", "--full"]); + }).pipe(Effect.scoped, Effect.provide(layerGitContract)), +); + +it.effect.skipIf(HostProcess.Platform.defaultValue() === "win32")( + "checkpoint publication preserves Git's shared object directory permissions", + () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.makeVcsDriverShape(); + const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-publish-shared-" }); + const git = (args: ReadonlyArray) => + driver.execute({ operation: "test.shared", cwd, args }); + yield* git(["init", "--shared=0660"]); + const checkpointRef = CheckpointRef.make("refs/t3/checkpoints/shared"); + yield* fs.writeFileString(path.join(cwd, "shared.txt"), "new shared object\n"); + const blob = (yield* git(["hash-object", "-w", "shared.txt"])).stdout.trim(); + const directory = path.join(cwd, ".git/objects", blob.slice(0, 2)); + const expectedMode = (yield* fs.stat(directory)).mode & 0o7777; + yield* fs.remove(directory, { recursive: true }); + assert.isFalse(yield* fs.exists(directory)); + yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); + assert.strictEqual((yield* fs.stat(directory)).mode & 0o7777, expectedMode); + yield* git(["fsck", "--full"]); + }).pipe(Effect.scoped, Effect.provide(layerGitContract)), +); + +it.effect("checkpoint capture ignores an inherited object directory", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.makeVcsDriverShape(); + const sandbox = yield* fs.makeTempDirectoryScoped({ prefix: "t3-inherited-objects-" }); + const cwd = path.join(sandbox, "repo"); + yield* fs.makeDirectory(cwd); + const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd); + const unrelated = path.join(sandbox, "unrelated"); + yield* git(["init", "--bare", unrelated]); + const unrelatedObjects = path.join(unrelated, "objects"); + // VcsProcess clears inherited selectors from git children; only the driver sees this one. + yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef }).pipe( + Effect.provideService(HostProcess.Environment, { + ...(yield* HostProcess.Environment), + GIT_OBJECT_DIRECTORY: unrelatedObjects, + }), + ); + assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "unstaged\n"); + assert.deepEqual( + (yield* fs.readDirectory(unrelatedObjects)).filter( + (name) => name !== "info" && name !== "pack", + ), + [], + ); + yield* git(["fsck", "--full"]); + }).pipe(Effect.scoped, Effect.provide(layerGitContract)), +); + +it.effect.each(["sha1", "sha256"] as const)( + "checkpoint object isolation supports linked worktrees with $0 objects", + (format) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.makeVcsDriverShape(); + const sandbox = yield* fs.makeTempDirectoryScoped({ prefix: "t3-checkpoint-worktree-" }); + // A separator must remain part of the alternate's path, not split the list. + const platform = yield* HostProcess.Platform; + const cwd = path.join(sandbox, platform === "win32" ? "repo;space" : 'repo:space"\u0001'); + yield* fs.makeDirectory(cwd); + yield* driver.execute({ + operation: "test.init", + cwd, + args: ["init", `--object-format=${format}`], + }); + const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd); + const worktree = path.join(sandbox, "linked"); + yield* git(["worktree", "add", "-b", "linked", worktree]); + yield* fs.writeFileString(path.join(worktree, "untracked.txt"), "linked worktree\n"); + const originalIndex = yield* fs.readFile(path.join(cwd, ".git/index")); + yield* driver.checkpoints.captureCheckpoint({ cwd: worktree, checkpointRef }); + assert.strictEqual( + (yield* git(["show", `${checkpointRef}:untracked.txt`])).stdout, + "linked worktree\n", + ); + assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git/index")), originalIndex); + yield* git(["fsck", "--full"]); + }).pipe(Effect.scoped, Effect.provide(layerGitContract)), +); + it.effect("checkpoint capture skips untracked nested repositories without a commit", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts index 4084e531f2302..837600c54d13c 100644 --- a/apps/server/src/vcs/GitVcsDriver.ts +++ b/apps/server/src/vcs/GitVcsDriver.ts @@ -12,6 +12,7 @@ import { ChildProcessSpawner } from "effect/process"; import { GitCommandError, + VcsCheckpointStorageError, VcsProcessExitError, VcsUnsupportedOperationError, type VcsSwitchRefInput, @@ -37,6 +38,7 @@ import { } from "@t3tools/contracts"; import { isMissingExecutable } from "@t3tools/source-control-core/server/discovery"; import * as HostProcess from "@t3tools/shared/HostProcess"; +import { quoteGitPatchPath } from "@t3tools/shared/gitPatchPath"; import { makeGitVcsDriverCore, PATCH_RENDER_PREFIX_ARGS, @@ -1064,8 +1066,42 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( const indexId = yield* random.uuidV4(); const tempIndexPath = path.join(gitCommonDir, `t3-checkpoint-index-${indexId}`); const hostEnv = yield* HostProcess.Environment; + // VcsProcess clears inherited object selectors, so every other command uses this store. + const sharedObjects = path.join(gitCommonDir, "objects"); + const quotedObjects = quoteGitPatchPath(sharedObjects); + // Isolate bulk-checkin packs so cleanup cannot remove another Git writer's objects. + const privateObjects = yield* Effect.acquireRelease( + fileSystem + .makeTempDirectory({ directory: sharedObjects, prefix: "tmp_objdir-t3-checkpoint-" }) + .pipe( + Effect.mapError( + (cause) => + new VcsCheckpointStorageError({ + operation, + cwd: input.cwd, + stage: "isolate", + cause, + }), + ), + ), + (directory) => + fileSystem.remove(directory, { recursive: true, force: true }).pipe( + Effect.catch((cause) => + Effect.logWarning("Could not remove private checkpoint objects", { + reason: cause.reason._tag, + }), + ), + ), + ); const commitEnv: NodeJS.ProcessEnv = { GIT_INDEX_FILE: tempIndexPath, + GIT_OBJECT_DIRECTORY: privateObjects, + // A filter must not publish refs that depend on our disposable store. + GIT_QUARANTINE_PATH: privateObjects, + // Git accepts C-quoted alternate paths, including path-list separators. + GIT_ALTERNATE_OBJECT_DIRECTORIES: quotedObjects.startsWith('"') + ? quotedObjects + : `"${quotedObjects}"`, GIT_AUTHOR_NAME: "T3 Code", GIT_AUTHOR_EMAIL: "t3code@users.noreply.github.com", GIT_COMMITTER_NAME: "T3 Code", @@ -1369,6 +1405,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( GIT_INDEX_FILE: undefined, GIT_OBJECT_DIRECTORY: undefined, GIT_ALTERNATE_OBJECT_DIRECTORIES: undefined, + GIT_QUARANTINE_PATH: undefined, }; const exclusions: Array = []; for (const entry of candidates) { @@ -1566,13 +1603,100 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( }); } + // Git writers have exited; bound publication by fanout directory. + yield* fileSystem.readDirectory(privateObjects).pipe( + Effect.flatMap((directories) => + Effect.forEach( + directories.filter( + (directory) => directory === "pack" || /^[0-9a-f]{2}$/.test(directory), + ), + (directory) => + Effect.gen(function* () { + const source = path.join(privateObjects, directory); + const destination = path.join(sharedObjects, directory); + // Most fanouts already exist; avoid constructing an EEXIST error + // for each of them on every capture. mkdir still handles the race. + if (!(yield* fileSystem.exists(destination))) + yield* fileSystem.makeDirectory(destination).pipe( + Effect.andThen( + Effect.gen(function* () { + // Git has already applied core.sharedRepository to the source. + // chmod bypasses umask, but never changes a pre-existing bucket. + const { mode } = yield* fileSystem.stat(source); + yield* fileSystem.chmod(destination, mode & 0o7777); + }), + ), + Effect.catchIf( + (cause) => cause.reason._tag === "AlreadyExists", + () => Effect.void, + ), + Effect.uninterruptible, + ); + const names = yield* fileSystem.readDirectory(source); + // Per-object spans would swamp the capture span for large workspaces. + const linkObject = Effect.fnUntraced(function* (name: string) { + const from = path.join(source, name); + const to = path.join(destination, name); + yield* fileSystem.link(from, to).pipe( + Effect.catchIf( + (cause) => + cause.reason._tag === "PermissionDenied" || + cause.reason._tag === "Unknown", + () => + Effect.gen(function* () { + if (yield* fileSystem.exists(to)) return; + // Git also falls back to rename without hardlinks; identical object names are interchangeable. + yield* fileSystem.rename(from, to); + }), + ), + Effect.catchIf( + (cause) => cause.reason._tag === "AlreadyExists", + () => Effect.void, + ), + ); + }); + if (directory !== "pack") { + yield* Effect.forEach( + names.filter((name) => /^(?:[0-9a-f]{38}|[0-9a-f]{62})$/.test(name)), + linkObject, + { discard: true }, + ); + return; + } + const files = new Set(names); + for (const name of names) { + if (!/^pack-(?:[0-9a-f]{40}|[0-9a-f]{64})\.pack$/.test(name)) continue; + const base = name.slice(0, -5); + if (!files.has(`${base}.idx`)) continue; + // Link metadata before the pack so an I/O error cannot strand a + // large unindexed pack. Defer cancellation across this short group + // of links. Never roll back shared links: a concurrent writer may + // already depend on them. Ref publication still happens last. + yield* Effect.forEach( + [".keep", ".promisor", ".rev", ".idx", ".pack"] + .map((extension) => `${base}${extension}`) + .filter((file) => files.has(file)), + linkObject, + { discard: true }, + ).pipe(Effect.uninterruptible); + } + }), + { concurrency: 16, discard: true }, + ), + ), + Effect.mapError( + (cause) => + new VcsCheckpointStorageError({ operation, cwd: input.cwd, stage: "publish", cause }), + ), + ); + yield* execute({ operation, cwd: input.cwd, args: [...durableWrite, "update-ref", input.checkpointRef, commitOid], }); }).pipe(Effect.ensuring(cleanupTempIndex)); - }), + }, Effect.scoped), hasCheckpointRef: (input) => resolveCheckpointCommit(input.cwd, input.checkpointRef).pipe( diff --git a/packages/contracts/src/vcs.ts b/packages/contracts/src/vcs.ts index 91cb8fa029022..4d388ad8befa6 100644 --- a/packages/contracts/src/vcs.ts +++ b/packages/contracts/src/vcs.ts @@ -260,7 +260,22 @@ export class VcsUnsupportedOperationError extends Schema.TaggedError()( + "VcsCheckpointStorageError", + { + operation: Schema.String, + cwd: Schema.String, + stage: Schema.Literals(["isolate", "publish"]), + cause: Schema.Defect(), + }, +) { + override get message(): string { + return `Could not ${this.stage} checkpoint objects.`; + } +} + export const VcsError = Schema.Union([ + VcsCheckpointStorageError, VcsProcessSpawnError, VcsProcessExitError, VcsProcessTimeoutError, diff --git a/packages/source-control-gitlab/src/server/GitLabCli.ts b/packages/source-control-gitlab/src/server/GitLabCli.ts index c0d4879d28d43..fbc995441b6f9 100644 --- a/packages/source-control-gitlab/src/server/GitLabCli.ts +++ b/packages/source-control-gitlab/src/server/GitLabCli.ts @@ -154,6 +154,7 @@ export class GitLabCliCommandError extends Schema.TaggedError new GitLabCliCommandError({ ...context, cause }), VcsProcessMissingExitCodeError: (cause) => new GitLabCliCommandError({ ...context, cause }), VcsRepositoryDetectionError: (cause) => new GitLabCliCommandError({ ...context, cause }), + VcsCheckpointStorageError: (cause) => new GitLabCliCommandError({ ...context, cause }), VcsUnsupportedOperationError: (cause) => new GitLabCliCommandError({ ...context, cause }), }); } From 306914f016001cc57c0fc00232d47bdea873d3d7 Mon Sep 17 00:00:00 2001 From: Thomas Nguyen Date: Sat, 14 Nov 2026 03:52:48 -0500 Subject: [PATCH 006/192] fix(client-runtime): grouped projects no longer take one folder's name as their label (#35614) Co-authored-by: Julius Marminge --- .../SettingsProjectOverviewRouteScreen.tsx | 6 ++- .../features/settings/SettingsRouteScreen.tsx | 3 +- .../src/state/projectGrouping.test.ts | 44 +++++++++++++++++++ .../src/state/projectGrouping.ts | 15 +++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx index 89408243ef81a..05ef2af346daa 100644 --- a/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx @@ -138,7 +138,11 @@ function ProjectOverviewContent(props: { }) { const { isRemoving, setIsRemoving, startRemoval, finishRemoval } = props; const representative = props.members[0]!; - const displayName = deriveProjectGroupLabel({ representative, members: props.members }); + const displayName = deriveProjectGroupLabel({ + groupKey: props.group.key, + representative, + members: props.members, + }); const [draftName, setDraftName] = useState(null); const [isSaving, setIsSaving] = useState(false); const threads = useThreadShells(); diff --git a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx index 50ad81383addc..bde67d793aeb4 100644 --- a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx @@ -252,8 +252,9 @@ function useSettingsIndexSections(): SettingsListSection[] { selectedTargets.some((target) => target.environmentId === project.environmentId), ) ?? []; const projectLabel = - scopedProjectMembers.length > 0 + selectedProject && scopedProjectMembers.length > 0 ? deriveProjectGroupLabel({ + groupKey: selectedProject.key, representative: scopedProjectMembers[0]!, members: scopedProjectMembers, }) diff --git a/packages/client-runtime/src/state/projectGrouping.test.ts b/packages/client-runtime/src/state/projectGrouping.test.ts index f4edba2b8d456..a890c852bcbf3 100644 --- a/packages/client-runtime/src/state/projectGrouping.test.ts +++ b/packages/client-runtime/src/state/projectGrouping.test.ts @@ -303,6 +303,50 @@ describe("buildProjectGroups", () => { expect(label(renamed)).toBe("t3code"); }); + it("labels a group by its repository when clones disagree on its casing", () => { + // A subfolder project, its repository root, and a clone whose remote URL is + // lowercased: no shared title or display name, but one repository. + const subfolder = makeProject("design-study", "/work/NewCo/reference/design-study"); + const root = makeProject("NewCo", "/work/NewCo"); + const lowercasedClone = makeProject("NewCo", "/remote/NewCo", { + environmentId: EnvironmentId.make("remote"), + repositoryIdentity: { ...repositoryIdentity, name: "T3CODE", displayName: "t3 code" }, + }); + + const groups = buildProjectGroups({ + projects: [subfolder, root, lowercasedClone], + settings: settings("repository"), + preferredEnvironmentId: environmentId, + }); + expect(groups).toHaveLength(1); + expect(groups[0]?.label).toBe("T3 Code"); + + // A subfolder group in repository_path mode keeps its own title. + const localIdentity = { ...repositoryIdentity, rootPath: "/work/t3code" }; + const pathGroups = buildProjectGroups({ + projects: [ + makeProject("design", "/work/t3code/design", { + title: "Design docs", + hasCustomTitle: true, + repositoryIdentity: localIdentity, + }), + makeProject("design", "/remote/t3code/design", { + environmentId: EnvironmentId.make("remote"), + repositoryIdentity: { + ...repositoryIdentity, + rootPath: "/remote/t3code", + name: "T3CODE", + displayName: "t3 code", + }, + }), + makeProject("t3code", "/work/t3code", { repositoryIdentity: localIdentity }), + ], + settings: settings("repository_path"), + preferredEnvironmentId: environmentId, + }); + expect(pathGroups.map((group) => group.label)).toEqual(["Design docs", "t3code"]); + }); + it("applies a physical-project override without dropping its siblings", () => { const first = makeProject("t3code", "/work/t3code"); const second = makeProject("t3code-2", "/work/t3code-2"); diff --git a/packages/client-runtime/src/state/projectGrouping.ts b/packages/client-runtime/src/state/projectGrouping.ts index c05a9724ce207..b54342b5dfcb6 100644 --- a/packages/client-runtime/src/state/projectGrouping.ts +++ b/packages/client-runtime/src/state/projectGrouping.ts @@ -199,6 +199,7 @@ export function resolveScratchProjectScopeKey( } export function deriveProjectGroupLabel(input: { + readonly groupKey: string; readonly representative: Pick; readonly members: ReadonlyArray< Pick @@ -230,6 +231,19 @@ export function deriveProjectGroupLabel(input: { return sharedRepositoryNames[0]!; } + // A whole-repository group is named for the repository, not one member's folder: + // clones can disagree on its casing. A subfolder group keeps its own title. + const representativeIdentity = input.representative.repositoryIdentity; + if ( + representativeIdentity && + input.groupKey === repositoryGroupingKeyOf(representativeIdentity) + ) { + return ( + repositoryGroupingDisplayNameOf(representativeIdentity) ?? + representativeIdentity.name ?? + input.representative.title + ); + } return input.representative.title; } @@ -369,6 +383,7 @@ export function buildProjectGroups(input: { ? "No project" : members.length > 1 ? deriveProjectGroupLabel({ + groupKey: key, representative, members: members.map((member) => member.project), }) From bebaf3ec04d015ad99260e9be418900f5822fcaf Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 00:54:03 -0800 Subject: [PATCH 007/192] fix(pi): resumed threads run in the worktree after a handoff (#36586) --- .../provider-pi/src/server/adapter.test.ts | 45 ++++++ packages/provider-pi/src/server/adapter.ts | 135 +++++++++++------- 2 files changed, 129 insertions(+), 51 deletions(-) diff --git a/packages/provider-pi/src/server/adapter.test.ts b/packages/provider-pi/src/server/adapter.test.ts index 7005311fff840..7d167faee746a 100644 --- a/packages/provider-pi/src/server/adapter.test.ts +++ b/packages/provider-pi/src/server/adapter.test.ts @@ -2108,6 +2108,51 @@ describe("PiAdapterV2", () => { }).pipe(Effect.scoped, Effect.provide(layerTest)), ); + it.effect("forks a resumed session recorded in another cwd into the runtime cwd", () => + Effect.gen(function* () { + const host = yield* ProviderHost.ProviderHost; + const fileSystem = yield* FileSystem.FileSystem; + const fake = yield* makeFakePi; + const forkFake = yield* makeFakePi; + const forkFile = "/fake/forked-into-worktree.jsonl"; + const { runtime } = yield* openRuntime(fake, "default", THREAD_ID, SESSION_ID, forkFake); + const providerThread = yield* runtime.ensureThread({ + threadId: THREAD_ID, + modelSelection: modelSelection("default"), + runtimePolicy, + }); + const dir = yield* fileSystem.makeTempDirectoryScoped(); + const sessionAt = (sessionCwd: string) => + Effect.gen(function* () { + const file = `${dir}/${sessionCwd.length}.jsonl`; + yield* fileSystem.writeFileString( + file, + `${JSON.stringify({ type: "session", version: 3, id: "s", cwd: sessionCwd })}\n{}\n`, + ); + return { + ...providerThread, + nativeThreadRef: { driver: PI_PROVIDER, nativeId: file, strength: "strong" as const }, + }; + }); + const lastSwitch = () => + fake.allRequests().findLast((request) => request.type === "switch_session")?.sessionPath; + + const sameCwd = yield* sessionAt(host.paths.cwd); + yield* runtime.resumeThread({ providerThread: sameCwd }); + assert.equal(lastSwitch(), sameCwd.nativeThreadRef?.nativeId); + assert.isFalse(forkFake.allRequests().some((request) => request.type === "get_state")); + + const otherCwd = yield* sessionAt(`${host.paths.cwd}-original-checkout`); + forkFake.queueState({ sessionFile: forkFile }); + fake.queueState({ sessionFile: forkFile }); + const resumed = yield* runtime.resumeThread({ providerThread: otherCwd }); + const args = forkFake.lastSpawn().args; + assert.equal(args[args.indexOf("--fork") + 1], otherCwd.nativeThreadRef?.nativeId); + assert.equal(lastSwitch(), forkFile); + assert.equal(resumed.nativeThreadRef?.nativeId, forkFile); + }).pipe(Effect.scoped, Effect.provide(layerTest)), + ); + it.effect("creates a distinct native session after a failed resume", () => Effect.gen(function* () { const fake = yield* makeFakePi; diff --git a/packages/provider-pi/src/server/adapter.ts b/packages/provider-pi/src/server/adapter.ts index a9508868a7c27..26f2611a8454a 100644 --- a/packages/provider-pi/src/server/adapter.ts +++ b/packages/provider-pi/src/server/adapter.ts @@ -2582,6 +2582,82 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( } }); + // CLI --fork uses Pi's own session format and sets the destination cwd. RPC + // switch_session/clone alone would retain the source cwd. + const forkSessionIntoCwd = Effect.fnUntraced(function* ( + sourceFile: string, + boundary: PiForkBoundary, + providerThreadCreatedAt: DateTime.Utc, + ) { + const forkLaunch = buildPiRpcLaunch({ + launchArgs: resolvedLaunchArgs.args, + environment: options.environment, + mcpSession: undefined, + extensionPath: undefined, + shortLived: true, + disableExtensions: true, + disableTools: true, + }); + return yield* Effect.scoped( + Effect.gen(function* () { + const forkConnection = yield* makePiRpcConnection({ + command: options.settings.binaryPath || "pi", + args: [...forkLaunch.args, "--fork", sourceFile], + cwd, + env: forkLaunch.env, + }); + yield* Stream.fromQueue(forkConnection.events).pipe( + Stream.runDrain, + Effect.ignore, + Effect.forkScoped, + ); + const beforeEntry = + boundary.type === "nothing" + ? null + : yield* piForkEntry( + boundary, + providerThreadCreatedAt, + forkConnection.request({ type: "get_entries" }), + ); + if (beforeEntry !== null) { + const result = yield* forkConnection.request({ + type: "fork", + entryId: beforeEntry, + }); + if (recordField(result, "cancelled") === true) { + return yield* protocolError("A Pi extension cancelled the session fork"); + } + } + const state = yield* forkConnection.request({ type: "get_state" }); + const file = recordString(state, "sessionFile"); + if (file === undefined || file === sourceFile) { + return yield* protocolError("Pi fork did not create a distinct session file"); + } + return file; + }), + ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner)); + }); + + // A workspace handoff restarts Pi in a new cwd, but the session header still names the old one. + const sessionFileInCwd = Effect.fnUntraced(function* ( + sessionFile: string, + providerThreadCreatedAt: DateTime.Utc, + ) { + const header = yield* fileSystem.stream(sessionFile).pipe( + Stream.decodeText(), + Stream.splitLines, + Stream.runHead, + Effect.map(Option.getOrUndefined), + Effect.orElseSucceed(() => undefined), + ); + if (header === undefined) return sessionFile; + const sessionCwd = recordString(Option.getOrUndefined(decodeSessionHeader(header)), "cwd"); + if (sessionCwd === undefined || sessionCwd === cwd) return sessionFile; + const realCwd = yield* fileSystem.realPath(cwd).pipe(Effect.orElseSucceed(() => cwd)); + if (sessionCwd === realCwd) return sessionFile; + return yield* forkSessionIntoCwd(sessionFile, { type: "nothing" }, providerThreadCreatedAt); + }); + const registerThread = Effect.fnUntraced(function* ( threadInput: ProviderAdapter.ProviderAdapterV2EnsureThreadInput, publish = true, @@ -2621,8 +2697,11 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( baselineThinking = null; contextWindow = null; const result = yield* lifecycleRequest( - resumeId != null - ? { type: "switch_session", sessionPath: resumeId } + resumeId != null && existing !== undefined + ? { + type: "switch_session", + sessionPath: yield* sessionFileInCwd(resumeId, existing.createdAt), + } : { type: "new_session" }, ); if (recordField(result, "cancelled") === true) { @@ -3474,55 +3553,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( target: { type: "provider_turn", providerTurn: target }, providerThreadTurns: sourceTurns, }); - // CLI --fork uses Pi's own session format and sets the destination cwd. RPC - // switch_session/clone alone would retain the source cwd. - const forkLaunch = buildPiRpcLaunch({ - launchArgs: resolvedLaunchArgs.args, - environment: options.environment, - mcpSession: undefined, - extensionPath: undefined, - shortLived: true, - disableExtensions: true, - disableTools: true, - }); - const nativeId = yield* Effect.scoped( - Effect.gen(function* () { - const forkConnection = yield* makePiRpcConnection({ - command: options.settings.binaryPath || "pi", - args: [...forkLaunch.args, "--fork", sourceFile], - cwd, - env: forkLaunch.env, - }); - yield* Stream.fromQueue(forkConnection.events).pipe( - Stream.runDrain, - Effect.ignore, - Effect.forkScoped, - ); - const beforeEntry = - boundary.type === "nothing" - ? null - : yield* piForkEntry( - boundary, - source.createdAt, - forkConnection.request({ type: "get_entries" }), - ); - if (beforeEntry !== null) { - const result = yield* forkConnection.request({ - type: "fork", - entryId: beforeEntry, - }); - if (recordField(result, "cancelled") === true) { - return yield* protocolError("A Pi extension cancelled the session fork"); - } - } - const state = yield* forkConnection.request({ type: "get_state" }); - const file = recordString(state, "sessionFile"); - if (file === undefined || file === sourceFile) { - return yield* protocolError("Pi fork did not create a distinct session file"); - } - return file; - }), - ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner)); + const nativeId = yield* forkSessionIntoCwd(sourceFile, boundary, source.createdAt); const now = yield* DateTime.now; return yield* registerThread( { @@ -3576,6 +3607,8 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( }); }); +const decodeSessionHeader = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown)); + interface PiSessionEntry { readonly parentId: string | null; readonly isUserMessage: boolean; From d0a494fc0bfc61ef1b9a2d38bb715450b1679a96 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 02:57:26 -0600 Subject: [PATCH 008/192] fix(server): honor the configured push remote on tracked branches (#24047) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/git/GitManager.test.ts | 51 +++++++ apps/server/src/git/GitManager.ts | 30 +++- apps/server/src/vcs/GitVcsDriver.ts | 4 + apps/server/src/vcs/GitVcsDriverCore.test.ts | 146 +++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 108 ++++++++++---- 5 files changed, 303 insertions(+), 36 deletions(-) diff --git a/apps/server/src/git/GitManager.test.ts b/apps/server/src/git/GitManager.test.ts index 3ca4e1bd88020..c63258d187a65 100644 --- a/apps/server/src/git/GitManager.test.ts +++ b/apps/server/src/git/GitManager.test.ts @@ -4759,6 +4759,57 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { }), ); + it.effect( + "create_pr publishes to the push remote when the tracked upstream already has HEAD", + () => + Effect.gen(function* () { + const repoDir = yield* makeTempDir("t3code-git-manager-"); + yield* initRepo(repoDir); + const originDir = yield* createBareRemote(); + const forkDir = yield* createBareRemote(); + yield* runGit(repoDir, ["remote", "add", "origin", originDir]); + yield* runGit(repoDir, ["remote", "add", "fork", forkDir]); + yield* runGit(repoDir, ["checkout", "-b", "topic"]); + NodeFS.writeFileSync(NodePath.join(repoDir, "changes.txt"), "change\n"); + yield* runGit(repoDir, ["add", "changes.txt"]); + yield* runGit(repoDir, ["commit", "-m", "Topic change"]); + yield* runGit(repoDir, ["push", "-u", "origin", "topic"]); + yield* configureVisibleRemoteUrlWithLocalRewrite( + repoDir, + "origin", + "git@github.com:pingdotgg/codething-mvp.git", + originDir, + ); + yield* configureVisibleRemoteUrlWithLocalRewrite( + repoDir, + "fork", + "git@github.com:octocat/codething-mvp.git", + forkDir, + ); + yield* runGit(repoDir, ["config", "remote.pushDefault", "fork"]); + + const { manager, providerCalls } = yield* makeManager(); + const result = yield* runStackedAction(manager, { + cwd: repoDir, + action: "create_pr", + }); + + expect(result.push).toMatchObject({ status: "pushed", upstreamBranch: "fork/topic" }); + expect(result.pr.status).toBe("created"); + expect( + yield* runGit(forkDir, ["--git-dir=.", "log", "-1", "--pretty=%s", "topic"]), + ).toMatchObject({ + stdout: "Topic change\n", + }); + expect(created(providerCalls)).toEqual([ + { baseRefName: "main", headSelector: "octocat:topic" }, + ]); + expect(yield* runGit(repoDir, ["rev-parse", "--abbrev-ref", "@{upstream}"])).toMatchObject({ + stdout: "origin/topic\n", + }); + }), + ); + it.effect("rejects push/pr actions from detached HEAD", () => Effect.gen(function* () { const repoDir = yield* makeTempDir("t3code-git-manager-"); diff --git a/apps/server/src/git/GitManager.ts b/apps/server/src/git/GitManager.ts index b406d431264fd..dfd13a7c06609 100644 --- a/apps/server/src/git/GitManager.ts +++ b/apps/server/src/git/GitManager.ts @@ -2376,6 +2376,7 @@ export const make = Effect.gen(function* () { fallbackBranch: string | null, threadId: ThreadId | undefined, emit: GitActionProgressEmitter, + pushRemoteRef?: { readonly remoteName: string; readonly upstreamRef: string }, ) { const provider = yield* sourceControlProvider(cwd); const terms = sourceControlDefinitions.get(provider.kind).changeRequest; @@ -2396,10 +2397,12 @@ export const make = Effect.gen(function* () { }); } - const headContext = yield* resolveBranchHeadContext(cwd, { - branch, - upstreamRef: details.upstreamRef, - }); + // A configured push remote receives the commits while the branch keeps + // tracking its upstream, so the PR head is the push remote's branch. + const headContext = yield* resolveBranchHeadContext( + cwd, + pushRemoteRef ? { branch, ...pushRemoteRef } : { branch, upstreamRef: details.upstreamRef }, + ); const existing = yield* findOpenPr(cwd, headContext); if (existing) { @@ -3412,12 +3415,26 @@ export const make = Effect.gen(function* () { > { const initialStatus = yield* gitCore.statusDetails(input.cwd); const wantsCommit = isCommitAction(input.action); + const upToDateBranch = + input.action === "create_pr" && + initialStatus.hasUpstream && + initialStatus.aheadCount === 0 + ? initialStatus.branch + : null; + // A current upstream says nothing about a separate push remote, which may lack HEAD. + const pushTarget = upToDateBranch + ? yield* gitCore + .resolvePushTarget(input.cwd, { branch: upToDateBranch, hasUpstream: true }) + .pipe(Effect.orElseSucceed(() => null)) + : null; const wantsPush = input.action === "push" || input.action === "commit_push" || input.action === "commit_push_pr" || (input.action === "create_pr" && - (!initialStatus.hasUpstream || initialStatus.aheadCount > 0)); + (!initialStatus.hasUpstream || + initialStatus.aheadCount > 0 || + pushTarget?.viaPushRemote === true)); const wantsPr = input.action === "create_pr" || input.action === "commit_push_pr"; if (input.featureBranch && !wantsCommit) { @@ -3570,6 +3587,9 @@ export const make = Effect.gen(function* () { currentBranch, input.threadId, progress.emit, + "pushRemote" in push && push.pushRemote && push.upstreamBranch + ? { remoteName: push.pushRemote, upstreamRef: push.upstreamBranch } + : undefined, ), ), ) diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts index 837600c54d13c..14147854a2ddb 100644 --- a/apps/server/src/vcs/GitVcsDriver.ts +++ b/apps/server/src/vcs/GitVcsDriver.ts @@ -179,12 +179,16 @@ export interface GitPushResult { status: "pushed" | "skipped_up_to_date"; branch: string; upstreamBranch?: string | undefined; + /** Set when `upstreamBranch` is on a configured push remote instead of the tracked upstream. */ + pushRemote?: string | undefined; setUpstream?: boolean | undefined; } export interface GitPushTarget { remoteName: string; branchName: string; + /** The tracked upstream is current, yet pushes go to this configured push remote. */ + viaPushRemote: boolean; } export interface GitRangeContext { diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 71e83c784127e..802d0d463cfdf 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -7998,6 +7998,152 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { assert.notEqual(originMain.exitCode, 0); }), ); + + it.effect.each(["branch.topic.pushRemote", "remote.pushDefault"] as const)( + "pushes a branch tracking its upstream head to the %s remote", + (configKey) => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const upstreamRemote = yield* makeTmpDir("git-upstream-remote-"); + const forkRemote = yield* makeTmpDir("git-fork-remote-"); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* git(upstreamRemote, ["init", "--bare"]); + yield* git(forkRemote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "upstream", upstreamRemote]); + yield* git(cwd, ["remote", "add", "fork", forkRemote]); + yield* git(cwd, ["checkout", "-b", "topic"]); + yield* git(cwd, ["push", "upstream", "topic"]); + yield* git(cwd, ["push", "fork", "topic"]); + yield* git(cwd, ["branch", "--set-upstream-to=upstream/topic"]); + yield* git(cwd, ["config", configKey, "fork"]); + const upstreamTopicSha = yield* git(cwd, ["rev-parse", "HEAD"]); + yield* writeTextFile(cwd, "fork.txt", "fork\n"); + yield* git(cwd, ["add", "-A"]); + yield* driver.commit(cwd, "Add fork update", ""); + + const pushed = yield* driver.pushCurrentBranch(cwd, null); + + assert.deepInclude(pushed, { + status: "pushed", + branch: "topic", + upstreamBranch: "fork/topic", + pushRemote: "fork", + setUpstream: false, + }); + assert.equal( + yield* git(forkRemote, ["--git-dir=.", "log", "-1", "--pretty=%s", "topic"]), + "Add fork update", + ); + assert.equal( + yield* git(upstreamRemote, ["--git-dir=.", "rev-parse", "topic"]), + upstreamTopicSha, + ); + assert.equal( + yield* git(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"]), + "upstream/topic", + ); + }), + ); + + it.effect.each(["deleted", "rewound"] as const)( + "pushes an up-to-date tracked branch to the push remote when its branch there is %s", + (forkState) => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const upstreamRemote = yield* makeTmpDir("git-upstream-remote-"); + const forkRemote = yield* makeTmpDir("git-fork-remote-"); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* git(upstreamRemote, ["init", "--bare"]); + yield* git(forkRemote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "upstream", upstreamRemote]); + yield* git(cwd, ["remote", "add", "fork", forkRemote]); + yield* git(cwd, ["checkout", "-b", "topic"]); + yield* writeTextFile(cwd, "upstream.txt", "upstream\n"); + yield* git(cwd, ["add", "-A"]); + yield* driver.commit(cwd, "Add upstream update", ""); + yield* git(cwd, ["push", "-u", "upstream", "topic"]); + yield* git(cwd, ["config", "remote.pushDefault", "fork"]); + // Another checkout changes the fork after this one last fetched it, + // leaving a local fork/topic that still contains HEAD. + yield* git(cwd, ["push", "fork", "topic"]); + yield* git( + forkRemote, + forkState === "deleted" + ? ["--git-dir=.", "update-ref", "-d", "refs/heads/topic"] + : ["--git-dir=.", "update-ref", "refs/heads/topic", "topic~1"], + ); + + const pushed = yield* driver.pushCurrentBranch(cwd, null); + + assert.deepInclude(pushed, { + status: "pushed", + upstreamBranch: "fork/topic", + pushRemote: "fork", + }); + assert.equal( + yield* git(forkRemote, ["--git-dir=.", "log", "-1", "--pretty=%s", "topic"]), + "Add upstream update", + ); + assert.equal( + yield* git(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"]), + "upstream/topic", + ); + }), + ); + + it.effect("skips an up-to-date tracked branch the push remote already has", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const upstreamRemote = yield* makeTmpDir("git-upstream-remote-"); + const forkRemote = yield* makeTmpDir("git-fork-remote-"); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* git(upstreamRemote, ["init", "--bare"]); + yield* git(forkRemote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "upstream", upstreamRemote]); + yield* git(cwd, ["remote", "add", "fork", forkRemote]); + yield* git(cwd, ["checkout", "-b", "topic"]); + yield* git(cwd, ["push", "-u", "upstream", "topic"]); + yield* git(cwd, ["push", "fork", "topic"]); + yield* git(cwd, ["config", "remote.pushDefault", "fork"]); + + const pushed = yield* driver.pushCurrentBranch(cwd, null); + + assert.deepInclude(pushed, { + status: "skipped_up_to_date", + upstreamBranch: "fork/topic", + pushRemote: "fork", + }); + }), + ); + + it.effect("skips an up-to-date branch whose differently named upstream is its base", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const upstreamRemote = yield* makeTmpDir("git-upstream-remote-"); + const forkRemote = yield* makeTmpDir("git-fork-remote-"); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* git(upstreamRemote, ["init", "--bare"]); + yield* git(forkRemote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "upstream", upstreamRemote]); + yield* git(cwd, ["remote", "add", "fork", forkRemote]); + yield* git(cwd, ["push", "upstream", "HEAD:refs/heads/release"]); + yield* git(cwd, ["fetch", "upstream"]); + yield* git(cwd, ["checkout", "-b", "topic", "--track", "upstream/release"]); + yield* git(cwd, ["config", "remote.pushDefault", "fork"]); + + const pushed = yield* driver.pushCurrentBranch(cwd, null); + + assert.deepInclude(pushed, { + status: "skipped_up_to_date", + upstreamBranch: "upstream/release", + }); + assert.equal(yield* git(forkRemote, ["--git-dir=.", "branch", "--list", "topic"]), ""); + }), + ); }); }); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index db3109c1d3ce9..b85416032e177 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1819,7 +1819,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ), ); - const resolvePushRemoteName = Effect.fn("resolvePushRemoteName")(function* ( + const resolveConfiguredPushRemoteName = Effect.fn("resolveConfiguredPushRemoteName")(function* ( cwd: string, refName: string, ) { @@ -1839,13 +1839,32 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ["config", "--get", "remote.pushDefault"], true, ).pipe(Effect.map((stdout) => stdout.trim())); - if (pushDefaultRemote.length > 0) { - return pushDefaultRemote; + return pushDefaultRemote.length > 0 ? pushDefaultRemote : null; + }); + + const resolvePushRemoteName = Effect.fn("resolvePushRemoteName")(function* ( + cwd: string, + refName: string, + ) { + const configuredRemote = yield* resolveConfiguredPushRemoteName(cwd, refName); + if (configuredRemote) { + return configuredRemote; } return yield* resolvePrimaryRemoteName(cwd).pipe(Effect.orElseSucceed(() => null)); }); + const remoteRefContainsHead = (cwd: string, remoteRef: string) => + executeGit( + "GitVcsDriver.pushCurrentBranch.publishContainsHead", + cwd, + ["merge-base", "--is-ancestor", "HEAD", `refs/remotes/${remoteRef}`], + { allowNonZeroExit: true }, + ).pipe( + Effect.map((result) => result.exitCode === 0), + Effect.orElseSucceed(() => false), + ); + const ensureRemote: GitVcsDriver.GitVcsDriver["Service"]["ensureRemote"] = Effect.fn( "ensureRemote", )(function* (input) { @@ -2846,7 +2865,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const remoteName = yield* resolvePushRemoteName(cwd, branch); if (!remoteName) return null; const branchName = yield* resolvePublishBranchName(cwd, branch); - return { remoteName, branchName, publishes: true, upstream: null }; + return { remoteName, branchName, publishes: true, upstream: null, pushRemote: null }; } if ((yield* readConfigValue(cwd, `branch.${branch}.remote`)) === ".") { @@ -2864,6 +2883,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* branchName: branch, publishes: true, upstream: { remoteName: ".", branchName: localUpstream, upstreamRef: localUpstream }, + pushRemote: null, }; } @@ -2874,11 +2894,20 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* branch === upstream.branchName || (branch.endsWith(`/${upstream.branchName}`) && upstream.upstreamRef.endsWith(`/${branch}`)); if (isAliasOfUpstreamHead) { + // A fork workflow pushes to the configured push remote and keeps the upstream for pulls, as plain `git push` does. + const configuredPushRemote = yield* resolveConfiguredPushRemoteName(cwd, branch).pipe( + Effect.orElseSucceed(() => null), + ); + const pushRemote = + configuredPushRemote && configuredPushRemote !== upstream.remoteName + ? configuredPushRemote + : null; return { - remoteName: upstream.remoteName, + remoteName: pushRemote ?? upstream.remoteName, branchName: upstream.branchName, publishes: false, upstream, + pushRemote, }; } const publishRemoteName = yield* resolvePushRemoteName(cwd, branch).pipe( @@ -2890,6 +2919,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* branchName, publishes: true, upstream, + pushRemote: null, }; }); @@ -2898,7 +2928,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* )(function* (cwd, current) { const destination = yield* resolvePushDestination(cwd, current.branch, current.hasUpstream); return destination - ? { remoteName: destination.remoteName, branchName: destination.branchName } + ? { + remoteName: destination.remoteName, + branchName: destination.branchName, + viaPushRemote: destination.pushRemote !== null, + } : null; }); @@ -2936,16 +2970,35 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* } const hasNoLocalDelta = details.aheadCount === 0 && details.behindCount === 0; - if (hasNoLocalDelta && details.hasUpstream) { - return { - status: "skipped_up_to_date" as const, - branch, - ...(details.upstreamRef ? { upstreamBranch: details.upstreamRef } : {}), - }; - } - const destination = yield* resolvePushDestination(cwd, branch, details.hasUpstream); - if (hasNoLocalDelta) { + if (hasNoLocalDelta && details.hasUpstream) { + if (!destination?.pushRemote) { + return { + status: "skipped_up_to_date" as const, + branch, + ...(details.upstreamRef ? { upstreamBranch: details.upstreamRef } : {}), + }; + } + const { pushRemote, branchName } = destination; + const pushRemoteRef = `${pushRemote}/${branchName}`; + // The local ref can be stale when the branch was deleted or rewound elsewhere. + const pushRemoteHasHead = yield* fetchRemoteTrackingBranch({ + cwd, + remoteName: pushRemote, + remoteBranch: branchName, + }).pipe( + Effect.andThen(remoteRefContainsHead(cwd, pushRemoteRef)), + Effect.orElseSucceed(() => false), + ); + if (pushRemoteHasHead) { + return { + status: "skipped_up_to_date" as const, + branch, + upstreamBranch: pushRemoteRef, + pushRemote, + }; + } + } else if (hasNoLocalDelta) { const comparableBaseBranch = yield* resolveBaseBranchForNoUpstream(cwd, branch).pipe( Effect.orElseSucceed(() => null), ); @@ -2958,21 +3011,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* } // The configured diff base can contain HEAD while the publish branch is still behind. - const publishContainsHead = yield* executeGit( - "GitVcsDriver.pushCurrentBranch.publishContainsHead", - cwd, - [ - "merge-base", - "--is-ancestor", - "HEAD", - `refs/remotes/${destination.remoteName}/${destination.branchName}`, - ], - { allowNonZeroExit: true }, - ).pipe( - Effect.map((result) => result.exitCode === 0), - Effect.orElseSucceed(() => false), - ); - if (publishContainsHead) { + if ( + yield* remoteRefContainsHead(cwd, `${destination.remoteName}/${destination.branchName}`) + ) { return { status: "skipped_up_to_date" as const, branch, @@ -3041,7 +3082,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* return { status: "pushed" as const, branch, - upstreamBranch: destination.upstream.upstreamRef, + ...(destination.pushRemote + ? { + upstreamBranch: `${destination.remoteName}/${destination.branchName}`, + pushRemote: destination.pushRemote, + } + : { upstreamBranch: destination.upstream.upstreamRef }), setUpstream: false, }; } From 000d82dc67be2b0b1fcfe3edceeb6032b7e1e553 Mon Sep 17 00:00:00 2001 From: "Aatman Jain (AJ)" Date: Sat, 14 Nov 2026 08:57:39 +0000 Subject: [PATCH 009/192] fix(web): Esc closes the theme editor, not Settings (#25130) Co-authored-by: Aatman Jain Co-authored-by: Julius Marminge --- .../ThemeEditorPanel.browser.test.tsx | 56 +++++++++++++++++++ .../components/settings/ThemeEditorPanel.tsx | 9 +++ 2 files changed, 65 insertions(+) diff --git a/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx b/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx index 881c27cc22a37..9ec6d782cbe81 100644 --- a/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx +++ b/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vite-plus/test"; import { page, userEvent } from "vite-plus/test/browser"; import { act, render } from "../../../test/render"; +import { Dialog, DialogPopup, DialogTitle } from "../ui/dialog"; import { ThemeEditorPanel } from "./ThemeEditorPanel"; function dragHeader(panel: HTMLElement, toX: number, toY: number) { @@ -74,6 +75,41 @@ async function renderPanel() { }; } +// The editor floats beside the Settings dialog, whose Esc handler sits on the document. +async function renderBesideSettings() { + const onSettingsOpenChange = vi.fn(); + const onEditorOpenChange = vi.fn(); + const screen = await render( + <> + + + Settings + + + true} + editingTheme={null} + initialAppearance="light" + restoreTheme={() => {}} + /> + , + ); + return { + onSettingsOpenChange, + onEditorOpenChange, + [Symbol.asyncDispose]: () => screen[Symbol.asyncDispose](), + }; +} + +// The modal Settings dialog marks the editor inert, hiding it from role queries and pointer input. +async function editorElement(selector: string) { + const query = () => document.querySelector(`[data-theme-editor-panel] ${selector}`); + await expect.poll(query).toBeTruthy(); + return query()!; +} + describe("ThemeEditorPanel", () => { it("keeps its left edge when resized after the window shrank", async () => { await using view = await renderPanel(); @@ -148,4 +184,24 @@ describe("ThemeEditorPanel", () => { }); expect((hex.element() as HTMLInputElement).value).toBe(original); }); + + it("closes on Escape without closing Settings", async () => { + await using view = await renderBesideSettings(); + (await editorElement("input")).focus(); + await userEvent.keyboard("{Escape}"); + + expect(view.onEditorOpenChange).toHaveBeenCalledWith(false); + expect(view.onSettingsOpenChange).not.toHaveBeenCalled(); + }); + + it("leaves inspection on Escape without closing the editor or Settings", async () => { + await using view = await renderBesideSettings(); + (await editorElement('[aria-label="Inspect app colors"]')).click(); + await editorElement('[aria-label="Cancel inspecting app colors"]'); + await userEvent.keyboard("{Escape}"); + + await editorElement('[aria-label="Inspect app colors"]'); + expect(view.onEditorOpenChange).not.toHaveBeenCalled(); + expect(view.onSettingsOpenChange).not.toHaveBeenCalled(); + }); }); diff --git a/apps/web/src/components/settings/ThemeEditorPanel.tsx b/apps/web/src/components/settings/ThemeEditorPanel.tsx index 38aed4fd2d185..89abb7b9b6a5e 100644 --- a/apps/web/src/components/settings/ThemeEditorPanel.tsx +++ b/apps/web/src/components/settings/ThemeEditorPanel.tsx @@ -692,7 +692,9 @@ export function ThemeEditorPanel({ }; const cancelInspection = (event: KeyboardEvent) => { if (event.key !== "Escape") return; + // Esc leaves inspection only; Settings must not see it and close too. event.preventDefault(); + event.stopImmediatePropagation(); clearHover(); clearInspectorSelection(); }; @@ -1138,6 +1140,13 @@ export function ThemeEditorPanel({ isMinimized && "max-h-none", )} data-theme-editor-panel + onKeyDown={(event) => { + // Popups opened from the editor are portaled outside it and handle their own Esc. + if (event.key !== "Escape" || event.defaultPrevented) return; + if (!event.currentTarget.contains(event.target as Node)) return; + event.stopPropagation(); + onOpenChange(false); + }} ref={panelRef} role="dialog" style={{ From c0b140b5bcf7ee2151ad8bf06c062474960df17d Mon Sep 17 00:00:00 2001 From: MINIT Date: Sat, 14 Nov 2026 14:28:33 +0530 Subject: [PATCH 010/192] fix(server): file changes with an empty list keep their file name (#30290) --- apps/mobile/src/lib/threadActivity.ts | 4 +++- apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts | 4 ++++ apps/server/src/orchestration-v2/ContextHandoffService.ts | 2 +- apps/web/src/session-logic.ts | 4 +++- packages/provider-core/src/server/handoffBudget.ts | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/mobile/src/lib/threadActivity.ts b/apps/mobile/src/lib/threadActivity.ts index 02522d3ff81e5..ed85709f30059 100644 --- a/apps/mobile/src/lib/threadActivity.ts +++ b/apps/mobile/src/lib/threadActivity.ts @@ -761,7 +761,9 @@ function toWorkLogEntry( case "file_change": return { ...common, - changedFiles: item.changes?.map((change) => change.path) ?? [item.fileName], + changedFiles: item.changes?.length + ? item.changes.map((change) => change.path) + : [item.fileName], toolTitle: title ?? "File change", toolData: item, }; diff --git a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts index d263a146a50fc..227fc531602fc 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts @@ -151,6 +151,10 @@ describe("handoff budget", () => { })?.text, "File change: src/a.ts, src/b.ts, src/new.ts", ); + assert.equal( + historicalMessage({ ...base, type: "file_change", fileName: "src/a.ts", changes: [] })?.text, + "File change: src/a.ts", + ); assert.isNull( historicalMessage({ ...base, diff --git a/apps/server/src/orchestration-v2/ContextHandoffService.ts b/apps/server/src/orchestration-v2/ContextHandoffService.ts index c9022ee76a807..ca6b5ff0fcdf1 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffService.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffService.ts @@ -215,7 +215,7 @@ function summarizeDeltaItem(item: OrchestrationV2TurnItem): string | null { case "command_execution": return `- Command: ${compactText(item.input)}`; case "file_change": - return `- File change: ${compactText(item.changes?.map((change) => change.path).join(", ") ?? item.fileName)}`; + return `- File change: ${compactText(item.changes?.length ? item.changes.map((change) => change.path).join(", ") : item.fileName)}`; case "checkpoint": return `- Checkpoint: ${item.files.length} files`; case "handoff": diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index 6065dd7ab9043..e73b250d27217 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -394,7 +394,9 @@ function projectedWorkEntry(row: OrchestrationV2ProjectedTurnItem): WorkLogEntry (item.changes !== undefined && item.changes.length > 1 ? `Changed ${item.changes.length} files` : `Changed ${item.fileName}`), - changedFiles: item.changes?.map((change) => change.path) ?? [item.fileName], + changedFiles: item.changes?.length + ? item.changes.map((change) => change.path) + : [item.fileName], toolTitle: title ?? "File change", toolData: item, }; diff --git a/packages/provider-core/src/server/handoffBudget.ts b/packages/provider-core/src/server/handoffBudget.ts index ea2c90d8772d6..0b33c4c0e3f2f 100644 --- a/packages/provider-core/src/server/handoffBudget.ts +++ b/packages/provider-core/src/server/handoffBudget.ts @@ -152,7 +152,7 @@ export function historicalMessage( text = item.message; break; case "file_change": - text = `File change: ${item.changes?.map((change) => change.path).join(", ") ?? item.fileName}`; + text = `File change: ${item.changes?.length ? item.changes.map((change) => change.path).join(", ") : item.fileName}`; break; case "proposed_plan": text = item.markdown; From b4c1bd106d994c4c51cfa8b3a1999d3b11566c83 Mon Sep 17 00:00:00 2001 From: F4llen <76191805+f4llenz@users.noreply.github.com> Date: Sat, 14 Nov 2026 03:59:44 -0500 Subject: [PATCH 011/192] fix(web): re-picking the current model in Settings keeps its traits (#35612) Co-authored-by: Julius Marminge --- .../settings/ProjectDefaultsSettings.tsx | 12 +++++++--- .../components/settings/SettingsPanels.tsx | 8 +++++-- .../settings/SourceControlWritingSettings.tsx | 14 +++++++++--- .../settings/TextGeneratorsSettings.tsx | 4 ++-- .../settings/VoiceSettingsPanel.tsx | 4 ++-- packages/shared/src/model.test.ts | 22 +++++++++++++++++++ packages/shared/src/model.ts | 11 ++++++++++ 7 files changed, 63 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx index 182b8bc5b1fdf..97c9ca0483ab8 100644 --- a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx +++ b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx @@ -4,7 +4,7 @@ import { type WorktreeSubmodules, managedRuntimeModeDenial, } from "@t3tools/contracts"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; import { useNavigate } from "@tanstack/react-router"; @@ -172,7 +172,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting }); }} onInstanceModelChange={(instanceId, model) => - setModel(createModelSelection(instanceId, model)) + setModel(pickModelSelection(mixedModel ? null : selection, instanceId, model)) } /> {!mixedModel && !presentation.providerNote && !effortDisabledReason && activeEntry ? ( @@ -288,7 +288,13 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting }); return; } - updateSettings({ fallbackModelSelection: createModelSelection(instanceId, model) }); + updateSettings({ + fallbackModelSelection: pickModelSelection( + mixedFallback ? null : settings.fallbackModelSelection, + instanceId, + model, + ), + }); }} /> {settings.fallbackModelSelection !== null && fallbackEntry && !mixedFallback ? ( diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index ea874c2b18c30..633e18a567b2f 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -27,7 +27,7 @@ import { ToolCallVisibility, } from "@t3tools/contracts/settings"; import { desktopUpdateChannelOf } from "@t3tools/shared/cliRelease"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolvePastedPath } from "@t3tools/shared/path"; import * as Equal from "effect/Equal"; import * as Schema from "effect/Schema"; @@ -2046,7 +2046,11 @@ export function ThreadsSettingsPanel() { textGenerationModelSelection: resolveAppModelSelectionState( { ...settings, - textGenerationModelSelection: createModelSelection(instanceId, model), + textGenerationModelSelection: pickModelSelection( + textGenerationModelSelection, + instanceId, + model, + ), }, textGenerationProviders, ), diff --git a/apps/web/src/components/settings/SourceControlWritingSettings.tsx b/apps/web/src/components/settings/SourceControlWritingSettings.tsx index 26cf6eb3dcbb2..6270639f4c167 100644 --- a/apps/web/src/components/settings/SourceControlWritingSettings.tsx +++ b/apps/web/src/components/settings/SourceControlWritingSettings.tsx @@ -7,7 +7,7 @@ import { textGeneratorForSelection, } from "@t3tools/contracts"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolveReviewGuideModelSelection, resolveSourceControlWriterModelSelection, @@ -365,7 +365,11 @@ export function SourceControlWritingSettingsSection() { return; } updateSettings({ - reviewGuideModelSelection: createModelSelection(instanceId, model), + reviewGuideModelSelection: pickModelSelection( + mixedReviewGuideModel ? null : reviewGuideSelection, + instanceId, + model, + ), }); }} /> @@ -564,7 +568,11 @@ export function SourceControlWritingSettingsSection() { return; } updateSettings({ - sourceControlWriterModelSelection: createModelSelection(instanceId, model), + sourceControlWriterModelSelection: pickModelSelection( + mixedWriterModel ? null : activeSelection, + instanceId, + model, + ), }); }} /> diff --git a/apps/web/src/components/settings/TextGeneratorsSettings.tsx b/apps/web/src/components/settings/TextGeneratorsSettings.tsx index ce0e729309bf2..76bf4f6b41e58 100644 --- a/apps/web/src/components/settings/TextGeneratorsSettings.tsx +++ b/apps/web/src/components/settings/TextGeneratorsSettings.tsx @@ -7,7 +7,7 @@ import { TextGeneratorConfig, type TextGeneratorModel, } from "@t3tools/contracts"; -import { createModelSelection } from "@t3tools/shared/model"; +import { pickModelSelection } from "@t3tools/shared/model"; import { AsyncResult } from "effect/reactivity"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; @@ -571,7 +571,7 @@ function TextGenerationTaskPicker({ )} triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} onInstanceModelChange={(instanceId, model) => - updateSettings({ [settingKey]: createModelSelection(instanceId, model) }) + updateSettings({ [settingKey]: pickModelSelection(selection, instanceId, model) }) } /> diff --git a/apps/web/src/components/settings/VoiceSettingsPanel.tsx b/apps/web/src/components/settings/VoiceSettingsPanel.tsx index 22ae59e55d31e..66cacf6eb7ee9 100644 --- a/apps/web/src/components/settings/VoiceSettingsPanel.tsx +++ b/apps/web/src/components/settings/VoiceSettingsPanel.tsx @@ -28,7 +28,7 @@ import { type SpeechModelUnloadTimeout, type VoiceShortcutMode, } from "@t3tools/contracts"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolveSpeechPostProcessingModelSelection } from "@t3tools/shared/serverSettings"; import { cloudSpeechLanguages } from "@t3tools/shared/speech"; import { @@ -576,7 +576,7 @@ function CleanupModelRow() { triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} getModelDisabledReason={modelDisabledReason} onInstanceModelChange={(instanceId, model) => - setModel(createModelSelection(instanceId, model)) + setModel(pickModelSelection(selection, instanceId, model)) } /> { ).toBe(false); expect(modelSelectionsEqual(left, { ...reordered, model: "gpt-5.5" })).toBe(false); }); + + it("keeps the options when the current model is picked again", () => { + const codex = ProviderInstanceId.make("codex"); + const current = createModelSelection(codex, "gpt-5.4", [ + { id: "reasoningEffort", value: "high" }, + ]); + + expect(pickModelSelection(current, codex, "gpt-5.4")).toBe(current); + expect(pickModelSelection(current, codex, "gpt-5.5")).toEqual({ + instanceId: "codex", + model: "gpt-5.5", + }); + expect(pickModelSelection(current, ProviderInstanceId.make("codex_work"), "gpt-5.4")).toEqual({ + instanceId: "codex_work", + model: "gpt-5.4", + }); + expect(pickModelSelection(null, codex, "gpt-5.4")).toEqual({ + instanceId: "codex", + model: "gpt-5.4", + }); + }); }); describe("applyClaudePromptEffortPrefix", () => { diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index 6714bae9b8de1..babe1347cc056 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -538,6 +538,17 @@ export function createModelSelection( return selections.length > 0 ? { ...base, options: selections } : base; } +/** A model picker's pick: re-picking the current model keeps its options, another model starts without any. */ +export function pickModelSelection( + current: ModelSelection | null | undefined, + instanceId: ProviderInstanceId, + model: string, +): ModelSelection { + return current?.instanceId === instanceId && current.model === model + ? current + : createModelSelection(instanceId, model); +} + export function resolvePromptInjectedEffort( caps: ModelCapabilities, rawEffort: string | null | undefined, From 2b6145b4c81daec0080eb0e302073a07b237be4f Mon Sep 17 00:00:00 2001 From: Zach <113211893+Zacharon@users.noreply.github.com> Date: Sat, 14 Nov 2026 04:01:16 -0500 Subject: [PATCH 012/192] fix(web): browser tests no longer pick a port Windows has reserved (#24007) Co-authored-by: Julius Marminge --- apps/web/vite.config.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 5b98367b4ba19..83847db8c00a0 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -100,8 +100,9 @@ const unitTestProject = { }, } satisfies TestProjectInlineConfiguration; -// Hash checkout paths to ports 61000-64999; Vitest's fixed port collides under inherited strictPort. -const browserTestApiPort = 61_000 + ((Hash.string(import.meta.dirname) >>> 0) % 4_000); +// Hash checkout paths to ports 20000-23999; Vitest's fixed port collides under inherited strictPort. +// Stay below ephemeral ranges: Windows reserves Hyper-V/WSL blocks there and EACCES is not skipped. +const browserTestApiPort = 20_000 + ((Hash.string(import.meta.dirname) >>> 0) % 4_000); const browserTestProject = { extends: true, From e4688f7cb9f265249d077f8c68ab2bd7ee0b2a13 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:02:44 +0100 Subject: [PATCH 013/192] fix(server): find files in newline-named nested worktrees (#36588) Co-authored-by: Julius Marminge --- .../src/workspace/WorkspaceEntries.test.ts | 15 +++++++- apps/server/src/workspace/WorkspaceEntries.ts | 37 +++++++++++-------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/apps/server/src/workspace/WorkspaceEntries.test.ts b/apps/server/src/workspace/WorkspaceEntries.test.ts index 521e127779984..1c50b94a7cbcc 100644 --- a/apps/server/src/workspace/WorkspaceEntries.test.ts +++ b/apps/server/src/workspace/WorkspaceEntries.test.ts @@ -688,6 +688,13 @@ it.layer(layerTest, { excludeTestServices: true })("WorkspaceEntries", (it) => { yield* git(cwd, ["commit", "-m", "init"], env); yield* git(cwd, ["worktree", "add", "-b", "x", ".worktrees/x"]); yield* writeTextFile(cwd, ".worktrees/x/src/foo.py", "print(1)"); + const expected = [".worktrees/x/src/foo.py"]; + // Windows file names cannot contain a newline. + if ((yield* HostProcess.Platform) !== "win32") { + yield* git(cwd, ["worktree", "add", "--detach", ".worktrees/line\nname"]); + yield* writeTextFile(cwd, ".worktrees/line\nname/src/foo.py", "print(2)"); + expected.unshift(".worktrees/line\nname/src/foo.py"); + } const exact = yield* searchWorkspaceEntries({ cwd, @@ -695,10 +702,14 @@ it.layer(layerTest, { excludeTestServices: true })("WorkspaceEntries", (it) => { limit: 25, kind: "file", }); - expect(exact.entries).toEqual([{ path: ".worktrees/x/src/foo.py", kind: "file" }]); + expect(exact.entries.toSorted((a, b) => (a.path < b.path ? -1 : 1))).toEqual( + expected.map((path) => ({ path, kind: "file" })), + ); const partial = yield* searchWorkspaceEntries({ cwd, query: "src/fo", limit: 25 }); - expect(partial.entries.map((entry) => entry.path)).not.toContain(".worktrees/x/src/foo.py"); + for (const path of expected) { + expect(partial.entries.map((entry) => entry.path)).not.toContain(path); + } }), ); }); diff --git a/apps/server/src/workspace/WorkspaceEntries.ts b/apps/server/src/workspace/WorkspaceEntries.ts index 43c8fae4dfcaf..51b1720126536 100644 --- a/apps/server/src/workspace/WorkspaceEntries.ts +++ b/apps/server/src/workspace/WorkspaceEntries.ts @@ -307,23 +307,30 @@ export const make = Effect.gen(function* () { ) { return []; } - const listed = yield* vcsProcess - .run({ - operation: "WorkspaceEntries.listWorktrees", - command: "git", - args: ["worktree", "list", "--porcelain"], - cwd, - allowNonZeroExit: true, - timeoutMs: 5_000, - }) - .pipe(Effect.orElseSucceed(() => undefined)); - if (!listed || listed.exitCode !== 0) return []; + const listWorktrees = (args: ReadonlyArray) => + vcsProcess + .run({ + operation: "WorkspaceEntries.listWorktrees", + command: "git", + args: ["worktree", "list", "--porcelain", ...args], + cwd, + allowNonZeroExit: true, + timeoutMs: 5_000, + }) + .pipe( + Effect.map((result) => (result.exitCode === 0 ? result.stdout : undefined)), + Effect.orElseSucceed(() => undefined), + ); + // Only NUL framing keeps a newline in a worktree path. Pre-2.36 Git rejects `-z`. + const listed = + (yield* listWorktrees(["-z"])) ?? (yield* listWorktrees([]))?.replaceAll("\n", "\0"); + if (listed === undefined) return []; const realCwd = yield* Effect.promise(() => NodeFSP.realpath(cwd).catch(() => cwd)); - const worktreeRoots = listed.stdout.split(/\n\s*\n/).flatMap((record) => { - const lines = record.split("\n"); - const worktree = lines.find((line) => line.startsWith("worktree ")); + const worktreeRoots = listed.split("\0\0").flatMap((record) => { + const fields = record.split("\0"); + const worktree = fields.find((field) => field.startsWith("worktree ")); // A `prunable` record's directory is gone; another checkout may now sit there. - if (worktree === undefined || lines.some((line) => line.startsWith("prunable"))) return []; + if (worktree === undefined || fields.some((field) => field.startsWith("prunable"))) return []; const root = worktree.slice("worktree ".length); const relativeRoot = path.relative(realCwd, root); return relativeRoot === "" || From 28f178682949e0eaf732a6a8314cb7c2ba94dcc9 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:05:53 +0100 Subject: [PATCH 014/192] fix(web): recreated folders no longer show deleted files (#36590) Co-authored-by: Julius Marminge --- .../useDirectoryEntries.browser.test.tsx | 91 ++++++++++++++++--- .../components/files/useDirectoryEntries.ts | 64 ++++++++++--- 2 files changed, 125 insertions(+), 30 deletions(-) diff --git a/apps/web/src/components/files/useDirectoryEntries.browser.test.tsx b/apps/web/src/components/files/useDirectoryEntries.browser.test.tsx index 046563bcb22f4..694004d0a044d 100644 --- a/apps/web/src/components/files/useDirectoryEntries.browser.test.tsx +++ b/apps/web/src/components/files/useDirectoryEntries.browser.test.tsx @@ -12,6 +12,7 @@ const { listEntries, mocks } = vi.hoisted(() => ({ listEntries: vi.fn(), mocks: { reconnect: false, + generation: 0, reachable: null as unknown as Atom.Writable, connectedSession: null as unknown as Atom.Writable, }, @@ -21,26 +22,39 @@ vi.mock("~/connection/catalog", () => ({ sessionGenerationAtom: () => Atom.make((get) => get(mocks.connectedSession)), }, })); -vi.mock("~/state/projects", () => ({ - projectEnvironment: { - listEntries: (request: unknown) => - mocks.reconnect - ? Atom.make((get) => - get(mocks.reachable) - ? AsyncResult.success({ - entries: [{ path: "README.md", kind: "file" }], - truncated: false, - }) - : AsyncResult.failure(Cause.fail(new Error("Environment is reconnecting."))), - ) - : Atom.make(Effect.promise(() => listEntries(request))), - }, -})); +vi.mock("~/state/projects", () => { + const queries = new Map>(); + const makeQuery = (request: unknown) => Atom.make(Effect.promise(() => listEntries(request))); + return { + projectEnvironment: { + listEntries: (request: unknown) => + mocks.reconnect + ? Atom.make((get) => + get(mocks.reachable) + ? AsyncResult.success({ + entries: [{ path: "README.md", kind: "file" }], + truncated: false, + }) + : AsyncResult.failure(Cause.fail(new Error("Environment is reconnecting."))), + ) + : (() => { + const key = JSON.stringify([mocks.generation, request]); + let query = queries.get(key); + if (!query) { + query = makeQuery(request); + queries.set(key, query); + } + return query; + })(), + }, + }; +}); import { useDirectoryEntries } from "./useDirectoryEntries"; function setup() { mocks.reconnect = false; + mocks.generation++; mocks.reachable = Atom.make(false); mocks.connectedSession = Atom.make(1); let view!: ReturnType; @@ -125,6 +139,18 @@ it("rescans mutations during both the initial listing and the follow-up", async await state.settle(2, [entry]); expect(state.view.entries).toEqual([entry]); expect(state.view.isPending).toBe(false); + + const folder = { path: "src", kind: "directory" } as const; + const child = { path: "src/new.ts", kind: "file" } as const; + await act(async () => { + void state.view.load("src"); + }); + await state.settle(3, [child]); + await act(async () => { + void state.view.load("", true); + }); + await state.settle(4, [folder]); + expect(state.view.entries).toEqual([folder, child]); }); it("shares the follow-up promise and does not resolve callers before the final listing", async () => { @@ -214,3 +240,38 @@ it("reads a folder that failed while unreachable again once a new session is up" }); await expect.element(screen.getByText("README.md")).toBeInTheDocument(); }); + +it("reloads a removed folder when it reappears under the same name", async () => { + const state = setup(); + // The hook probe does not navigate. + await using _screen = await render(, { router: false }); + const folder = { path: "src", kind: "directory" } as const; + await state.settle(0, [folder]); + await act(async () => { + void state.view.load("src"); + }); + await state.settle(1, [{ path: "src/old.ts", kind: "file" }]); + + await act(async () => { + state.view.refresh(); + void state.view.load("src", true); + }); + await state.settle(2, []); + expect(state.view.entries).toEqual([]); + + await act(async () => state.view.refresh()); + await state.settle(4, [folder]); + await act(async () => { + void state.view.load("src"); + }); + const replacementRefresh = state.view.load("src", true); + await state.settle(3, [{ path: "src/old.ts", kind: "file" }]); + expect(state.view.load("src", true)).toBe(replacementRefresh); + expect(state.view.entries).toEqual([folder]); + expect(state.view.loadingDirectories.has("src")).toBe(true); + await state.settle(5, [{ path: "src/new.ts", kind: "file" }]); + await state.settle(6, [{ path: "src/new.ts", kind: "file" }]); + await replacementRefresh; + expect(state.view.entries).toEqual([folder, { path: "src/new.ts", kind: "file" }]); + expect(state.view.isPending).toBe(false); +}); diff --git a/apps/web/src/components/files/useDirectoryEntries.ts b/apps/web/src/components/files/useDirectoryEntries.ts index 32aa2fe466753..ec348a392cf58 100644 --- a/apps/web/src/components/files/useDirectoryEntries.ts +++ b/apps/web/src/components/files/useDirectoryEntries.ts @@ -15,7 +15,10 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) { const [loadingDirectories, setLoadingDirectories] = useState>( () => new Set([""]), ); + // Source of truth for `directories`, so a listing can purge folders it no longer holds. + const listings = useRef(new Map()); const requests = useRef(new Map>()); + const detachedRequests = useRef(new Map>()); const followUpRefreshes = useRef(new Map>()); const loaded = useRef(new Set()); const requested = useRef(new Set()); @@ -27,7 +30,10 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) { loaded.current.add(directoryPath); requested.current.add(directoryPath); const atom = projectEnvironment.listEntries({ environmentId, input: { cwd, directoryPath } }); + const detached = detachedRequests.current.get(directoryPath); const request = (async () => { + // A shared query still waiting would join the removed folder's read. + if (detached) await detached; if (running.current >= 4) await new Promise((resolve) => waiting.current.push(resolve)); else running.current++; const result = active.current @@ -43,21 +49,44 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) { return result; })() .then((result) => { - if (!active.current || !result) return; + // A request is dropped from `requests` when its folder disappears, so its read is stale. + if (!active.current || !result || requests.current.get(directoryPath) !== request) return; if (result._tag === "Success") { - setDirectories((previous) => - new Map(previous).set( - directoryPath, - result.value.entries.filter( - (entry) => - entry.path.slice(0, Math.max(0, entry.path.lastIndexOf("/"))) === directoryPath, - ), - ), + const children = result.value.entries.filter( + (entry) => + entry.path.slice(0, Math.max(0, entry.path.lastIndexOf("/"))) === directoryPath, + ); + const next = new Map(listings.current).set(directoryPath, children); + const childDirectories = new Set( + children.filter((entry) => entry.kind === "directory").map((entry) => entry.path), ); + const prefix = directoryPath === "" ? "" : `${directoryPath}/`; + const removed = [...requested.current].filter((path) => { + if (path === directoryPath || !path.startsWith(prefix)) return false; + const slash = path.indexOf("/", prefix.length); + return !childDirectories.has(slash === -1 ? path : path.slice(0, slash)); + }); + for (const path of removed) { + next.delete(path); + loaded.current.delete(path); + requested.current.delete(path); + const detachedRequest = requests.current.get(path); + if (detachedRequest) detachedRequests.current.set(path, detachedRequest); + requests.current.delete(path); + followUpRefreshes.current.delete(path); + } + listings.current = next; + setDirectories(next); + if (removed.length > 0) + setLoadingDirectories((previous) => { + const remaining = new Set(previous); + for (const path of removed) remaining.delete(path); + return remaining; + }); setErrors((previous) => { - const next = new Map(previous); - next.delete(directoryPath); - return next; + const remaining = new Map(previous); + for (const path of [directoryPath, ...removed]) remaining.delete(path); + return remaining; }); } else { loaded.current.delete(directoryPath); @@ -71,10 +100,13 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) { } }) .finally(() => { - requests.current.delete(directoryPath); + if (detachedRequests.current.get(directoryPath) === request) + detachedRequests.current.delete(directoryPath); + const current = requests.current.get(directoryPath) === request; + if (current) requests.current.delete(directoryPath); if (!active.current) return; setPending((count) => count - 1); - if (!refresh) + if (!refresh && current) setLoadingDirectories((previous) => { const next = new Set(previous); next.delete(directoryPath); @@ -92,8 +124,10 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) { let followUp = followUpRefreshes.current.get(directoryPath); if (!followUp) { followUp = existing.then(() => { + if (followUpRefreshes.current.get(directoryPath) !== followUp) return; followUpRefreshes.current.delete(directoryPath); - if (active.current) return loadDirectory(directoryPath, true); + if (active.current && requested.current.has(directoryPath)) + return loadDirectory(directoryPath, true); }); followUpRefreshes.current.set(directoryPath, followUp); } From 3b18dc2935af2f7cb9b7d5ed60594ffc42e0f605 Mon Sep 17 00:00:00 2001 From: Jui-Chen Chien Date: Sat, 14 Nov 2026 17:06:39 +0800 Subject: [PATCH 015/192] fix(web): manual compaction counts history beyond the loaded page (#25445) Co-authored-by: jason chien <3936511+jason5545@users.noreply.github.com> Co-authored-by: Julius Marminge --- .../web/src/components/ChatView.logic.test.ts | 81 +++++++++++++++++++ apps/web/src/components/ChatView.logic.ts | 19 +++++ apps/web/src/components/ChatView.tsx | 11 +-- 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/ChatView.logic.test.ts b/apps/web/src/components/ChatView.logic.test.ts index c241522084415..6cf3ce762d434 100644 --- a/apps/web/src/components/ChatView.logic.test.ts +++ b/apps/web/src/components/ChatView.logic.test.ts @@ -61,6 +61,7 @@ import { deriveCommittedServerUserMessageIds, deriveComposerSendState, deriveLockedProvider, + hasCompactableConversation, hasServerAcknowledgedLocalDispatch, waitForRevertedMessage, } from "./ChatView.logic"; @@ -434,6 +435,86 @@ describe("deriveCommittedServerUserMessageIds", () => { }); }); +describe("hasCompactableConversation", () => { + const at = DateTime.makeUnsafe("2026-10-26T08:00:00.000Z"); + const runId = RunId.make("run:thread:thread-1:ordinal:1"); + const itemBase = (id: string, ordinal: number) => ({ + id: TurnItemId.make(id), + threadId, + runId, + nodeId: null, + providerThreadId: null, + providerTurnId: null, + nativeItemRef: null, + parentItemId: null, + ordinal, + status: "completed" as const, + title: null, + startedAt: at, + completedAt: at, + updatedAt: at, + }); + const userMessage = (text: string): OrchestrationV2ProjectedTurnItem => ({ + position: 0, + visibility: "local", + sourceThreadId: threadId, + sourceItemId: TurnItemId.make("turn-item:user"), + item: { + ...itemBase("turn-item:user", 1), + createdBy: "user", + creationSource: "web", + type: "user_message", + messageId: MessageId.make("message-user"), + inputIntent: "turn_start", + text, + attachments: [], + }, + }); + const assistantMessage: OrchestrationV2ProjectedTurnItem = { + position: 1, + visibility: "local", + sourceThreadId: threadId, + sourceItemId: TurnItemId.make("turn-item:assistant"), + item: { + ...itemBase("turn-item:assistant", 2), + type: "assistant_message", + messageId: MessageId.make("message-assistant"), + text: "done", + streaming: false, + }, + }; + + it("counts a user message beyond the loaded page", () => { + expect( + hasCompactableConversation({ + visibleTurnItems: [assistantMessage], + hasMoreHistory: true, + latestUserMessageAt: "2026-10-26T08:00:00.000Z", + }), + ).toBe(true); + }); + + it("stays unavailable when the thread never had a user message", () => { + expect( + hasCompactableConversation({ + visibleTurnItems: [assistantMessage], + hasMoreHistory: true, + latestUserMessageAt: null, + }), + ).toBe(false); + }); + + it("does not count a lone /compact on a fully loaded thread", () => { + expect( + hasCompactableConversation({ + visibleTurnItems: [userMessage("/compact"), assistantMessage], + hasMoreHistory: false, + latestUserMessageAt: "2026-10-26T08:00:00.000Z", + }), + ).toBe(false); + }); +}); + describe("resolveThreadSwitchTimeline", () => { afterEach(() => { resetHeldThreadTimeline(); diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index 14590bb0ff386..927a253ead2b8 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -1014,6 +1014,25 @@ export function deriveCommittedServerUserMessageIds( ); } +/** + * The server checks the full history, but the first page of an + * agent-started thread or a long turn can hold no user message. + */ +export function hasCompactableConversation(input: { + readonly visibleTurnItems: ReadonlyArray; + readonly hasMoreHistory: boolean; + readonly latestUserMessageAt: string | null; +}): boolean { + return ( + input.visibleTurnItems.some( + ({ item }) => + item.type === "user_message" && + (item.text.trim().toLowerCase() !== "/compact" || item.attachments.length > 0), + ) || + (input.hasMoreHistory && input.latestUserMessageAt !== null) + ); +} + export function hasServerAcknowledgedLocalDispatch(input: { localDispatch: LocalDispatchSnapshot | null; phase: SessionPhase; diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 764ea2c6bf756..1110c730a722a 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -361,6 +361,7 @@ import { startNewThreadForProject, codexArtifactTemplatePromptToAppend, shouldRefocusComposerOnWindowFocus, + hasCompactableConversation, } from "./ChatView.logic"; import { useLocalStorage } from "~/hooks/useLocalStorage"; import { useComposerHandleContext } from "../composerHandleContext"; @@ -2485,11 +2486,11 @@ export default function ChatView(props: ChatViewProps) { scrollToEnd, persistThreadSettingsForNextTurn, }); - const activeThreadHasCompactableConversation = serverVisibleTurnItems.some( - ({ item }) => - item.type === "user_message" && - (item.text.trim().toLowerCase() !== "/compact" || item.attachments.length > 0), - ); + const activeThreadHasCompactableConversation = hasCompactableConversation({ + visibleTurnItems: serverVisibleTurnItems, + hasMoreHistory: serverThreadHistory.hasMoreHistory, + latestUserMessageAt: serverThread?.latestUserMessageAt ?? null, + }); const pendingHandoffSelection = (composerModelSelectionApplies ? composerSelectedModelSelection : null) ?? activeThread?.modelSelection; From 2fa77fbf553209df8583e1075e6931b2a74e2765 Mon Sep 17 00:00:00 2001 From: Yukun Shan <92423096+nateEc@users.noreply.github.com> Date: Sat, 14 Nov 2026 17:08:11 +0800 Subject: [PATCH 016/192] fix(web): horizontal table scrolling preserves timeline follow (#24045) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../chat/timelineScrollTarget.test.ts | 20 +++++++++++++++++-- .../components/chat/timelineScrollTarget.ts | 3 +++ .../src/components/chat/timelineViewport.ts | 3 ++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/chat/timelineScrollTarget.test.ts b/apps/web/src/components/chat/timelineScrollTarget.test.ts index 909b3171dfb58..9d5af4b0b0751 100644 --- a/apps/web/src/components/chat/timelineScrollTarget.test.ts +++ b/apps/web/src/components/chat/timelineScrollTarget.test.ts @@ -20,8 +20,13 @@ class ScrollElement extends EventTarget { } } -function targetsTimeline(target: EventTarget | null, timeline: ScrollElement, deltaY: number) { - return isTimelineScrollTarget(target, timeline as unknown as HTMLElement, deltaY); +function targetsTimeline( + target: EventTarget | null, + timeline: ScrollElement, + deltaY: number, + wheel?: { deltaX: number; shiftKey: boolean }, +) { + return isTimelineScrollTarget(target, timeline as unknown as HTMLElement, deltaY, wheel); } function setup() { @@ -46,6 +51,17 @@ beforeEach(() => { afterEach(() => vi.unstubAllGlobals()); describe("timeline scroll targets", () => { + it.each([ + { deltaX: 5, deltaY: -30, shiftKey: false, targets: true }, + { deltaX: 80, deltaY: -30, shiftKey: false, targets: false }, + { deltaX: -80, deltaY: 30, shiftKey: false, targets: false }, + { deltaX: 0, deltaY: -30, shiftKey: true, targets: false }, + ])("leaves sideways wheel gestures out of the timeline: %j", ({ targets, ...wheel }) => { + const { timeline } = setup(); + const table = new ScrollElement(timeline); + expect(targetsTimeline(table, timeline, wheel.deltaY, wheel)).toBe(targets); + }); + it.each([-30, 30])("keeps a nested tool group's scroll out of the timeline: %i", (deltaY) => { const { timeline, group, content } = setup(); expect(targetsTimeline(content, timeline, deltaY)).toBe(false); diff --git a/apps/web/src/components/chat/timelineScrollTarget.ts b/apps/web/src/components/chat/timelineScrollTarget.ts index 49314f006f556..5a44125ab1deb 100644 --- a/apps/web/src/components/chat/timelineScrollTarget.ts +++ b/apps/web/src/components/chat/timelineScrollTarget.ts @@ -2,8 +2,11 @@ export function isTimelineScrollTarget( target: EventTarget | null, timeline: HTMLElement, deltaY: number, + wheel?: Pick, ): boolean { if (!(target instanceof Element) || !timeline.contains(target) || deltaY === 0) return false; + // Shift-wheel scrolls sideways; trackpads can add vertical noise to a horizontal swipe. + if (wheel?.shiftKey || Math.abs(wheel?.deltaX ?? 0) > Math.abs(deltaY)) return false; for ( let element: Element | null = target; diff --git a/apps/web/src/components/chat/timelineViewport.ts b/apps/web/src/components/chat/timelineViewport.ts index 94240b38ad164..65e0f50f5ae9e 100644 --- a/apps/web/src/components/chat/timelineViewport.ts +++ b/apps/web/src/components/chat/timelineViewport.ts @@ -84,7 +84,8 @@ export function attachTimelineViewport( for (const listener of listeners) listener(navigation); }; const handleWheel = (event: WheelEvent) => { - if (event.ctrlKey || !isTimelineScrollTarget(event.target, scrollNode, event.deltaY)) return; + if (event.ctrlKey || !isTimelineScrollTarget(event.target, scrollNode, event.deltaY, event)) + return; emit({ source: "wheel", direction: event.deltaY < 0 ? "up" : "down" }); }; const handleTouchMove = () => emit({ source: "touch" }); From 83a276dfdd6981e31bfb5ce97b647e65d05e493c Mon Sep 17 00:00:00 2001 From: maria Date: Sat, 14 Nov 2026 09:09:30 +0000 Subject: [PATCH 017/192] fix(server): setup scripts complete with shell line editors (#25472) Co-authored-by: Abid A Gafoor <40262245+sgaabdu4@users.noreply.github.com> Co-authored-by: Julius Marminge --- .../project/ProjectSetupScriptRunner.test.ts | 119 +++++++++++++++++- .../src/project/ProjectSetupScriptRunner.ts | 54 +++++++- 2 files changed, 167 insertions(+), 6 deletions(-) diff --git a/apps/server/src/project/ProjectSetupScriptRunner.test.ts b/apps/server/src/project/ProjectSetupScriptRunner.test.ts index 034b168e52c79..2dcf239cb04b5 100644 --- a/apps/server/src/project/ProjectSetupScriptRunner.test.ts +++ b/apps/server/src/project/ProjectSetupScriptRunner.test.ts @@ -1,16 +1,20 @@ import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; -import { describe, expect, it } from "@effect/vitest"; +import * as NodeServices from "@effect/platform-node/NodeServices"; +import { assert, describe, expect, it } from "@effect/vitest"; import { ProjectId, TerminalNotRunningError, type TerminalEvent, type TerminalSessionSnapshot, } from "@t3tools/contracts"; +import * as HostProcess from "@t3tools/shared/HostProcess"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Queue from "effect/Queue"; +import * as Stream from "effect/Stream"; +import { ChildProcess, ChildProcessSpawner } from "effect/process"; import { TestClock } from "effect/testing"; import * as ServerSettings from "../serverSettings.ts"; @@ -164,7 +168,7 @@ describe("ProjectSetupScriptRunner", () => { yield* emit({ ...event, type: "activity", hasRunningSubprocess: false, label: "zsh" }); yield* emit({ ...event, type: "output", data: "\r\n% \x1b[?2004h" }); - expect(yield* Queue.take(writes)).toMatch(/^ \( vp i\r\);/); + expect(yield* Queue.take(writes)).toContain("vp i"); }), ); @@ -172,7 +176,7 @@ describe("ProjectSetupScriptRunner", () => { Effect.gen(function* () { const { writes } = yield* setup(); yield* TestClock.adjust("3 seconds"); - expect(yield* Queue.take(writes)).toMatch(/^ \( vp i\r\);/); + expect(yield* Queue.take(writes)).toContain("vp i"); }), ); @@ -194,7 +198,7 @@ describe("ProjectSetupScriptRunner", () => { yield* emit({ ...event, type: "restarted", snapshot: openSnapshot("") }); // The restart lands inside a quiet window, so the next full window decides. yield* TestClock.adjust("6 seconds"); - expect(yield* Queue.take(writes)).toMatch(/^ \( vp i\r\);/); + expect(yield* Queue.take(writes)).toContain("vp i"); }), ); @@ -227,3 +231,110 @@ describe("ProjectSetupScriptRunner", () => { }), ); }); + +const runObservedSetup = Effect.fnUntraced(function* (command: string) { + const { writes, outputLines, emit, completion } = yield* setup({ command }); + yield* emit({ ...event, type: "output", data: "\x1b[?2004h" }); + const submitted = yield* Queue.take(writes); + const cwd = yield* HostProcess.WorkingDirectory; + const env = yield* HostProcess.Environment; + + // Enter becomes a newline at the terminal boundary; transport assertions below + // cover line-editor submissions and canonical TTY limits that bash -c bypasses. + const child = yield* Effect.gen(function* () { + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const handle = yield* spawner.spawn( + ChildProcess.make( + "/bin/bash", + ["--noprofile", "--norc", "-c", submitted.replaceAll("\r", "\n")], + { + cwd, + env: { PATH: env.PATH }, + }, + ), + ); + return yield* Effect.all( + { + stdout: handle.stdout.pipe(Stream.decodeText(), Stream.mkString), + stderr: handle.stderr.pipe(Stream.decodeText(), Stream.mkString), + exitCode: handle.exitCode, + }, + { concurrency: "unbounded" }, + ); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)); + yield* emit({ ...event, type: "output", data: submitted + "\n" }); + yield* emit({ ...event, type: "output", data: child.stdout + child.stderr }); + yield* emit({ ...event, type: "closed" }); + return { + submitted, + lines: yield* Queue.clear(outputLines), + exitCode: (yield* completion).exitCode, + }; +}); + +describe.skipIf(HostProcess.Platform.defaultValue() === "win32")("POSIX setup completion", () => { + it.live("submits one line while preserving multiline shell syntax and literal text", () => + Effect.gen(function* () { + const result = yield* runObservedSetup( + [ + "printf '%s\\n' \"it's ready\" 'literal \\n $(printf wrong) `printf wrong` café'", + "cat <<'END'", + "$literal \\path 雪", + "END", + "printf '%s\\n' done # trailing comment", + ].join("\r\n"), + ); + assert.equal(result.submitted.at(-1), "\r"); + assert.notMatch(result.submitted.slice(0, -1), /[\r\n]/); + assert.deepEqual(result.lines, [ + "it's ready", + "literal \\n $(printf wrong) `printf wrong` café", + "$literal \\path 雪", + "done", + ]); + assert.equal(result.exitCode, 0); + }), + ); + + it.live("completes long scripts without exceeding canonical terminal line limits", () => + Effect.gen(function* () { + const expected = Array.from( + { length: 120 }, + (_, index) => `line-${index}: café 雪 'quoted' \\path`, + ); + const command = expected + .map((line) => `printf '%s\\n' "${line.replaceAll("\\", "\\\\")}"`) + .join("\n"); + const utf8 = new TextEncoder(); + assert.isAbove(utf8.encode(command).byteLength, 4_096); + + const result = yield* runObservedSetup(command); + assert.deepEqual(result.lines, expected); + assert.equal(result.exitCode, 0); + assert.equal(result.submitted.at(-1), "\r"); + assert.notInclude(result.submitted, "\n"); + // The runner can write before the shell switches the terminal out of canonical mode. + for (const line of result.submitted.slice(0, -1).split("\r")) { + assert.isBelow(utf8.encode(line).byteLength, 1_024); + } + }), + ); + + it.live("preserves a trailing backslash-newline even when losing it would still exit zero", () => + Effect.gen(function* () { + const result = yield* runObservedSetup("printf '%s\\n' direct\\\n"); + assert.deepEqual(result.lines, ["direct"]); + assert.equal(result.exitCode, 0); + }), + ); + + it.live.each([ + { name: "an explicit exit", command: "exit 7", exitCode: 7 }, + { name: "a syntax error", command: "if then", exitCode: 2 }, + ])("reports a nonzero completion for $name", ({ command, exitCode }) => + Effect.gen(function* () { + const result = yield* runObservedSetup(command); + assert.equal(result.exitCode, exitCode); + }), + ); +}); diff --git a/apps/server/src/project/ProjectSetupScriptRunner.ts b/apps/server/src/project/ProjectSetupScriptRunner.ts index a049bc89b01a1..7a5940818e0f0 100644 --- a/apps/server/src/project/ProjectSetupScriptRunner.ts +++ b/apps/server/src/project/ProjectSetupScriptRunner.ts @@ -177,7 +177,57 @@ function resolveCompletionShell(shell: string): CompletionShell | null { return "posix"; } -// A block with its own closing line protects the sentinel from trailing comments, heredocs and scripts reading stdin. +const utf8 = new TextEncoder(); +// Canonical TTY input (before a line editor starts reading) caps a line below 1 KiB on Linux. +const POSIX_MAX_LINE_BYTES = 1_023; +const POSIX_APPEND = '__t3_command="$__t3_command"'; + +function encodePosixScript(text: string): string { + let encoded = ""; + for (const character of text) { + const code = character.charCodeAt(0); + if (character === "'") { + encoded += "'\\''"; + } else if (character === "\\" || code <= 0x1f || code === 0x7f) { + encoded += `\\0${code.toString(8).padStart(3, "0")}`; + } else { + encoded += character; + } + } + return encoded; +} + +// Line editors run each submitted line on its own, so every line must be a complete command. +function wrapPosixCommand(command: string, sentinel: string): string { + const normalized = command.replace(/\r\n/g, "\n"); + // The `_` suffix keeps trailing newlines; the subshell contains exits and syntax errors. + const evaluate = (payload: string) => + `( __t3_command=$(printf '%b_' ${payload}); eval "\${__t3_command%_}" ); printf '\\n${sentinel}%s\\n' "$?"`; + const inline = evaluate(`'${encodePosixScript(normalized)}'`); + if (utf8.encode(` ${inline}\r`).byteLength <= POSIX_MAX_LINE_BYTES) return inline; + + const maxChunkBytes = POSIX_MAX_LINE_BYTES - utf8.encode(` ${POSIX_APPEND}''\r`).byteLength; + const lines: string[] = []; + let prefix = "__t3_command="; + let chunk = ""; + let chunkBytes = 0; + for (const character of normalized) { + const encoded = encodePosixScript(character); + const bytes = utf8.encode(encoded).byteLength; + if (chunkBytes + bytes > maxChunkBytes) { + lines.push(`${prefix}'${chunk}'`); + prefix = POSIX_APPEND; + chunk = ""; + chunkBytes = 0; + } + chunk += encoded; + chunkBytes += bytes; + } + lines.push(`${prefix}'${chunk}'`, `${evaluate('"$__t3_command"')}; unset __t3_command`); + // The leading space gives every line the history suppression a short command gets. + return lines.join("\r "); +} + function wrapCommandForCompletion( command: string, shell: CompletionShell, @@ -204,7 +254,7 @@ function wrapCommandForCompletion( ].join("; "); } case "posix": - return `( ${body}\r); printf '\\n${sentinel}%s\\n' "$?"`; + return wrapPosixCommand(command, sentinel); } } From a61291e3e2c401852454b701e82278847c9c4ab1 Mon Sep 17 00:00:00 2001 From: Zach <113211893+Zacharon@users.noreply.github.com> Date: Sat, 14 Nov 2026 04:09:43 -0500 Subject: [PATCH 018/192] fix(shared): one-letter link schemes no longer become file links on Windows (#24006) Co-authored-by: Julius Marminge --- packages/shared/src/fileLinks.test.ts | 11 +++++++++++ packages/shared/src/fileLinks.ts | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/packages/shared/src/fileLinks.test.ts b/packages/shared/src/fileLinks.test.ts index 541ac6506f8b4..923a7ce694f06 100644 --- a/packages/shared/src/fileLinks.test.ts +++ b/packages/shared/src/fileLinks.test.ts @@ -103,6 +103,17 @@ describe("parseFileUrlHref", () => { it.each(["https://example.com/a.ts", "file://%", "/Users/julius/a.ts"])("rejects %s", (href) => { expect(parseFileUrlHref(href)).toBeNull(); }); + + it("rejects one-letter schemes that Chromium on Windows reads as drive letters", () => { + const NativeURL = URL; + using _url = vi.spyOn(globalThis, "URL").mockImplementation(function (input: string | URL) { + const drive = /^([a-z]):/i.exec(String(input)); + return drive + ? new NativeURL(`file:///${drive[1]!.toUpperCase()}:/${String(input).slice(2)}`) + : new NativeURL(input); + }); + expect(parseFileUrlHref("d:x")).toBeNull(); + }); }); describe("splitFilePathPosition", () => { diff --git a/packages/shared/src/fileLinks.ts b/packages/shared/src/fileLinks.ts index 1ea5bad895c86..a60c22b4b1710 100644 --- a/packages/shared/src/fileLinks.ts +++ b/packages/shared/src/fileLinks.ts @@ -13,6 +13,10 @@ const POSITION_HASH_PATTERN = /^#L(\d+)(?:C(\d+)|-L?(\d+))?$/i; export function parseFileUrlHref( href: string, ): { readonly path: string; readonly hash: string } | null { + // Chromium on Windows parses any one-letter scheme as a drive letter + // (`new URL("d:x")` is `file:///D:/x`), so the protocol check alone would turn + // such links into file links only on Windows. + if (!href.trim().toLowerCase().startsWith("file:")) return null; try { const parsed = new URL(href); if (parsed.protocol.toLowerCase() !== "file:") return null; From 4192234c34e9f690df3b99715cfecba48b49e4de Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:16:39 +0100 Subject: [PATCH 019/192] fix(usage): distinct email-less hub accounts no longer collapse (#36561) Co-authored-by: Julius Marminge --- .../src/usage/UsageLimitSources.test.ts | 3 +- apps/server/src/usage/UsageLimitSources.ts | 7 +- packages/contracts/src/providerUsageLimits.ts | 2 + packages/shared/src/usageLimits.test.ts | 160 +++++++++++++++--- packages/shared/src/usageLimits.ts | 24 ++- 5 files changed, 172 insertions(+), 24 deletions(-) diff --git a/apps/server/src/usage/UsageLimitSources.test.ts b/apps/server/src/usage/UsageLimitSources.test.ts index d7e73a1da96c9..fc8a93f9e1bc6 100644 --- a/apps/server/src/usage/UsageLimitSources.test.ts +++ b/apps/server/src/usage/UsageLimitSources.test.ts @@ -44,6 +44,7 @@ it.effect("keeps malformed source URLs encodable for config subscribers", () => "https://hub.test:8317", undefined, ]); + expect(snapshots[3]?.endpoint).toBe("https://hub.test:8317/team-a"); expect(() => encodeConfigEvent({ version: 1, @@ -84,7 +85,7 @@ it.effect("keeps malformed source URLs encodable for config subscribers", () => }, [UsageLimitSourceId.make("valid-hub")]: { kind: "cliproxy", - url: "https://hub.test:8317", + url: "https://hub.test:8317/team-a/management.html", managementKey: "", enabled: true, }, diff --git a/apps/server/src/usage/UsageLimitSources.ts b/apps/server/src/usage/UsageLimitSources.ts index c591931843411..d6f867d621d23 100644 --- a/apps/server/src/usage/UsageLimitSources.ts +++ b/apps/server/src/usage/UsageLimitSources.ts @@ -86,7 +86,12 @@ export const make = Effect.gen(function* () { id, kind: config.kind, label: sourceLabel(id, config), - ...(url && /^https?:$/.test(url.protocol) ? { origin: url.origin } : {}), + ...(url && /^https?:$/.test(url.protocol) + ? { + origin: url.origin, + endpoint: `${url.origin}${url.pathname.replace(/(\/management\.html)?\/*$/i, "")}`, + } + : {}), checkedAt, } as const; const managementKey = yield* readManagementKey(config); diff --git a/packages/contracts/src/providerUsageLimits.ts b/packages/contracts/src/providerUsageLimits.ts index 7a356ba4e2d11..446d4f1f31849 100644 --- a/packages/contracts/src/providerUsageLimits.ts +++ b/packages/contracts/src/providerUsageLimits.ts @@ -88,6 +88,8 @@ export const UsageLimitSourceSnapshot = Schema.Struct({ label: TrimmedNonEmptyString, /** Origin of the hub, which also serves the agents sent through it. */ origin: Schema.optional(TrimmedNonEmptyString), + /** Origin and base path of the hub, which tell apart hubs served from one origin. */ + endpoint: Schema.optional(TrimmedNonEmptyString), checkedAt: IsoDateTime, accounts: ForwardCompatibleArray(UsageLimitSourceAccount), error: Schema.optional(TrimmedNonEmptyString), diff --git a/packages/shared/src/usageLimits.test.ts b/packages/shared/src/usageLimits.test.ts index 9d2bf395a9d72..b66001ca19244 100644 --- a/packages/shared/src/usageLimits.test.ts +++ b/packages/shared/src/usageLimits.test.ts @@ -663,7 +663,7 @@ describe("pools", () => { expect(accounts.map((account) => account.key)).toEqual([ "env-a:business", "env-a:personal", - "hub:x.json", + "env-a:hub:x.json", ]); expect(accounts[0]?.limits.windows[0]?.usedPercent).toBe(8); const withoutEmail = (id: string, workspaceId: string) => { @@ -693,8 +693,8 @@ describe("pools", () => { ], ]); expect(collectLimitAccounts(sameFile).map((account) => account.key)).toEqual([ - "hub:a.json:ws-1", - "hub:a.json:ws-2", + "env-a:hub:a.json:ws-1", + "env-b:hub:a.json:ws-2", ]); expect(accounts[1]?.environments.map((environment) => environment.label)).toEqual([ "Laptop", @@ -739,33 +739,147 @@ describe("pools", () => { }); expect(collectLimitAccounts(withOther(unreadable)).map((account) => account.key)).toEqual([ "env-a:codex", - "hub:claude.json", + "env-a:hub:claude.json", ]); }); - it("keys a hub account without an email by hub, so two environments on one hub share it", () => { - const seat = { - id: "claude-team-seat.json", - driver: claude, - usageLimits: { checkedAt, windows: [window] }, - }; - const hub = { ...source, accounts: [seat] }; + it.each([ + [ + "one remote hub under environment-local ids", + "http://hub:8317", + "laptop-hub", + "http://hub:8317", + 1, + ], + ["two remote hubs under one id", "http://hub:8317", "hub", "http://hub-8317", 2], + [ + "two remote hubs under paths of one origin", + "https://proxy.example/team-a", + "hub-b", + "https://proxy.example/team-b", + 2, + ], + [ + "a shared IPv6 tailnet hub", + "http://[fd7a:115c:a1e0::1]:8317", + "hub", + "http://[fd7a:115c:a1e0::1]:8317", + 1, + ], + [ + "each machine's own localhost hub", + "http://localhost:8317", + "hub", + "http://localhost:8317", + 2, + ], + ["each machine's own IPv6 loopback hub", "http://[::1]:8317", "hub", "http://[::1]:8317", 2], + [ + "each machine's own localhost subdomain hub", + "http://hub.localhost:8317", + "hub", + "http://hub.localhost:8317", + 2, + ], + [ + "each machine's own trailing-dot localhost subdomain hub", + "http://hub.localhost.:8317", + "hub", + "http://hub.localhost.:8317", + 2, + ], + [ + "each machine's own mapped IPv4 loopback hub", + "http://[::ffff:7f00:1]:8317", + "hub", + "http://[::ffff:7f00:1]:8317", + 2, + ], + ["each machine's own wildcard hub", "http://0.0.0.0:8317", "hub", "http://0.0.0.0:8317", 2], + ["hubs that report no endpoint", undefined, "hub", undefined, 2], + ] as const)( + "keeps email-less hub identity across environments: %s", + (_, firstEndpoint, secondId, secondEndpoint, count) => { + const seat = { + id: "claude-team-seat.json", + driver: claude, + usageLimits: { checkedAt, windows: [window] }, + }; + const hubAt = (id: string, endpoint: string | undefined) => ({ + ...source, + id: UsageLimitSourceId.make(id), + ...(endpoint ? { origin: new URL(endpoint).origin, endpoint } : {}), + accounts: [seat], + }); + const input = new Map([ + [ + EnvironmentId.make("env-a"), + { + ...laptop, + serverConfig: { providers: [], usageLimitSources: [hubAt("hub", firstEndpoint)] }, + }, + ], + [ + EnvironmentId.make("env-b"), + { + entry: { target: { label: "Desktop" } }, + serverConfig: { providers: [], usageLimitSources: [hubAt(secondId, secondEndpoint)] }, + }, + ], + ]); + const accounts = collectLimitAccounts(input); + expect(accounts).toHaveLength(count); + expect(accounts[0]?.displayName).toBe("claude-team-seat"); + }, + ); + + it("keeps public keys apart for different logins in one hub file", () => { + const hubAt = (id: string, email: string, usedPercent: number) => ({ + ...source, + id: UsageLimitSourceId.make(id), + origin: "https://hub.test", + endpoint: "https://hub.test", + accounts: [ + { + id: "seat.json", + driver: claude, + email, + usageLimits: { checkedAt, windows: [{ ...window, usedPercent }] }, + }, + ], + }); const input = new Map([ [ EnvironmentId.make("env-a"), - { ...laptop, serverConfig: { providers: [], usageLimitSources: [hub] } }, + { + ...laptop, + serverConfig: { + providers: [], + usageLimitSources: [hubAt("first", "old@example.com", 10)], + }, + }, ], [ EnvironmentId.make("env-b"), { entry: { target: { label: "Desktop" } }, - serverConfig: { providers: [], usageLimitSources: [hub] }, + serverConfig: { + providers: [], + usageLimitSources: [hubAt("second", "new@example.com", 80)], + }, }, ], ]); const accounts = collectLimitAccounts(input); - expect(accounts.map((account) => account.key)).toEqual(["hub:claude-team-seat.json"]); - expect(accounts[0]?.displayName).toBe("claude-team-seat"); + expect(accounts.map((account) => account.key)).toEqual([ + "env-a:first:seat.json", + "env-b:second:seat.json", + ]); + const columns = collectLimitPools(accounts, Date.parse(checkedAt))[0]?.windows[0]?.columns; + expect(columns?.map((column) => [column.account.email, column.window?.usedPercent])).toEqual([ + ["new@example.com", 80], + ["old@example.com", 10], + ]); }); it("labels a default-named instance merged with a hub account by e-mail, keeping a chosen name", () => { @@ -964,7 +1078,7 @@ describe("pools", () => { const [session, week] = pools[0]!.windows; const untimed = collectLimitPools( collectLimitAccounts(input).map((account) => - account.key === "hub:b" + account.key === "env-a:hub:b" ? { ...account, limits: { @@ -986,8 +1100,8 @@ describe("pools", () => { expect( session?.resets.map((reset) => [reset.member.account.key, reset.restoresPercent]), ).toEqual([ - ["hub:a", 40], - ["hub:b", 20], + ["env-a:hub:a", 40], + ["env-a:hub:b", 20], ]); expect(week).toMatchObject({ id: "seven_day", remainingPercent: 80, members: [{}] }); // Codex reports `primary` for both its five-hour and (on Go) monthly window. @@ -1026,8 +1140,14 @@ describe("pools", () => { ["weekly", 1], ["monthly", 1], ]); - expect(session?.members.map((member) => member.account.key)).toEqual(["hub:a", "hub:b"]); - expect(pools[0]?.accounts.map((account) => account.key)).toEqual(["hub:a", "hub:b"]); + expect(session?.members.map((member) => member.account.key)).toEqual([ + "env-a:hub:a", + "env-a:hub:b", + ]); + expect(pools[0]?.accounts.map((account) => account.key)).toEqual([ + "env-a:hub:a", + "env-a:hub:b", + ]); }); }); diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index b7902f11d6733..6fe6c70a89a4f 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -14,11 +14,14 @@ import { type ServerProviderResetCredits, type ServerProviderUsageLimits, type ServerProviderUsageWindow, + type UsageLimitSourceSnapshot, type UsageLimitSourceSnapshots, } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; +import { isLocalLoopbackHost, isWildcardHost } from "./hostClassification.ts"; + const MINUTE = 60_000; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; @@ -252,6 +255,20 @@ function accountKeyResolver(reports: Iterable) { const hubAccountName = (account: { readonly id: string }) => account.id.replace(/\.json$/i, ""); +// Source ids are environment-local, so only a remote endpoint names the same hub across environments. +function hubIdentity(environmentId: EnvironmentId, source: UsageLimitSourceSnapshot): string { + // The server supplies canonical endpoints; native URL polyfills can misparse IPv6 hosts. + const endpoint = source.endpoint; + const host = endpoint?.match(/^https?:\/\/(\[[\da-f:.]+\]|[^:/?#@]+)(?::\d+)?(?:\/|$)/)?.[1]; + const local = + !host || + isLocalLoopbackHost(host) || + isWildcardHost(host) || + // Each machine resolves these to itself too; shared host classification leaves them out. + /\.localhost\.*$|^\[::ffff:(?:127\.|7f[\da-f]{2}:)/i.test(host); + return endpoint && !local ? endpoint : `${environmentId}:${source.id}`; +} + export interface LimitAccount { readonly key: string; readonly driver: ServerProvider["driver"]; @@ -378,8 +395,11 @@ export function collectLimitAccounts(presentations: LimitPresentations): readonl : source.label; for (const account of source.accounts) { if (limitsNotice(account.usageLimits) !== null) continue; - const key = `${source.id}:${account.id}${account.workspaceId ? `:${account.workspaceId}` : ""}`; - merge(resolveKey(account) ?? key, { + const resolved = resolveKey(account); + // A resolved account already has an identity; its public key must stay distinct from others'. + const hub = resolved ? `${environmentId}:${source.id}` : hubIdentity(environmentId, source); + const key = `${hub}:${account.id}${account.workspaceId ? `:${account.workspaceId}` : ""}`; + merge(resolved ?? key, { key, driver: account.driver, displayName: account.email ? null : hubAccountName(account), From 75c544a9788651e885ff82f5c0c50bf4180b7fdb Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 01:22:01 -0800 Subject: [PATCH 020/192] fix(pi): async pi-subagents workflow, chain and parallel members show as subagents (#36599) --- .../provider-pi/src/server/adapter.test.ts | 112 ++++++++ packages/provider-pi/src/server/adapter.ts | 270 ++++++++++++++---- 2 files changed, 319 insertions(+), 63 deletions(-) diff --git a/packages/provider-pi/src/server/adapter.test.ts b/packages/provider-pi/src/server/adapter.test.ts index 7d167faee746a..ed9ce8c48c132 100644 --- a/packages/provider-pi/src/server/adapter.test.ts +++ b/packages/provider-pi/src/server/adapter.test.ts @@ -3232,6 +3232,118 @@ describe("PiAdapterV2", () => { }).pipe(Effect.scoped, Effect.provide(layerTest)), ); + it.effect("shows the members of an async pi-subagents workflow as running subagents", () => + Effect.gen(function* () { + const fake = yield* makeFakePi; + const { runtime, takeEvent } = yield* openRuntime(fake); + const providerThread = yield* runtime.ensureThread({ + threadId: THREAD_ID, + modelSelection: modelSelection("default"), + runtimePolicy, + }); + yield* startTurn(runtime, providerThread); + yield* fake.takeRequest("prompt"); + yield* fake.emit({ type: "agent_start" }); + // Frames as pi-subagents 0.88.1 sends them for an async `runs.all([alpha, beta])` workflow. + const step = (key: string, state: string) => ({ + id: key, + kind: "step", + label: key, + state, + startedAt: 1_000, + updatedAt: 2_000, + activity: { currentTool: "bash", turnCount: 1, toolCount: 1 }, + agent: "scout", + model: "anthropic/claude-haiku-5-5", + }); + const emitSnapshot = (state: string, alpha: string, beta: string) => + fake.emit({ + type: "extension_ui_request", + id: `widget-${state}`, + method: "setWidget", + widgetKey: "subagent-async", + widgetLines: [ + `PI_SUBAGENT_ASYNC_JSON:${JSON.stringify({ + kind: "pi-subagents.async-status-snapshot", + version: 1, + generatedAt: 2_000, + runs: [ + { + id: "wf-1", + kind: "workflow", + label: "scout, scout", + state, + startedAt: 1_000, + updatedAt: 2_000, + children: [step("alpha", alpha), step("beta", beta)], + }, + ], + })}`, + ], + }); + const collect = (marker: string) => + Effect.gen(function* () { + yield* fake.emit({ + type: "tool_execution_end", + toolCallId: marker, + toolName: marker, + result: { content: [{ type: "text", text: "after" }] }, + }); + const statuses = new Map(); + const pending: Array = []; + while (true) { + const event = yield* takeEvent( + (event) => + event.type === "subagent.updated" || + event.type === "background_work.updated" || + (event.type === "turn_item.updated" && + event.turnItem.type === "dynamic_tool" && + event.turnItem.toolName === marker), + ); + if (event.type === "background_work.updated") pending.push(event.pending); + else if (event.type === "subagent.updated") + statuses.set(event.subagent.id, event.subagent.status); + else break; + } + return { statuses: [...statuses.values()].toSorted(), pending }; + }); + // The launch snapshot precedes the tool result that names the run. + yield* emitSnapshot("running", "running", "running"); + yield* fake.emit({ + type: "tool_execution_end", + toolCallId: "call_wf", + toolName: "subagent", + isError: false, + result: { + content: [{ type: "text", text: "Async workflow started: wf-1" }], + details: { + mode: "workflow", + runId: "wf-1", + asyncId: "wf-1", + results: [], + workflowChildren: { + version: 1, + parentToolCallId: "call_wf", + workflowRunId: "wf-1", + inventoryComplete: false, + workflowState: "running", + children: [], + }, + }, + }, + }); + assert.deepEqual(yield* collect("marker-1"), { + statuses: ["running", "running"], + pending: [true], + }); + yield* emitSnapshot("failed", "complete", "failed"); + assert.deepEqual(yield* collect("marker-2"), { + statuses: ["completed", "failed"], + pending: [false], + }); + }).pipe(Effect.scoped, Effect.provide(layerTest)), + ); + it.effect("persists edit patches and write content on file change items", () => Effect.gen(function* () { const fake = yield* makeFakePi; diff --git a/packages/provider-pi/src/server/adapter.ts b/packages/provider-pi/src/server/adapter.ts index 26f2611a8454a..b41e31c778199 100644 --- a/packages/provider-pi/src/server/adapter.ts +++ b/packages/provider-pi/src/server/adapter.ts @@ -17,6 +17,7 @@ import { type OrchestrationV2ProviderThread, type OrchestrationV2ProviderTurn, type OrchestrationV2RuntimeRequest, + type OrchestrationV2Subagent, type OrchestrationV2TurnItem, type OrchestrationV2UserInputQuestion, type ProviderApprovalDecision, @@ -85,6 +86,8 @@ const PI_INHERIT_MODEL_SLUG = "default"; const STREAM_FLUSH_MS = 50; const PI_REQUEST_TIMEOUT_MS = 15_000; +const PI_SUBAGENT_ASYNC_PREFIX = "PI_SUBAGENT_ASYNC_JSON:"; +const decodePiJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown)); const PI_SESSION_TIMEOUT_MS = 60_000; const MAX_EXTENSION_STATUSES = 32; const MAX_EXTENSION_STATUS_KEY_LENGTH = 256; @@ -545,6 +548,17 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( >(); const pendingPrompts = new Map(); const sessionApprovals = new Set(); + // Async pi-subagents runs launched in this session, by async id, until they settle. + const piAsyncRuns = new Map< + string, + { + readonly turn: ActivePiTurn; + readonly toolCallId: string; + readonly emitted: Map; + } + >(); + // The launch snapshot arrives before the tool result that names its run. + let latestAsyncSnapshotRuns: ReadonlyArray = []; // Pi can settle immediately after dialog response; serialize so terminal cannot overtake request resolution. const sessionEventPermit = yield* Semaphore.make(1); let threadState: PiThreadState | null = null; @@ -1333,24 +1347,15 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( resultRecord: unknown, completed: boolean, ) { - const results = recordField(recordField(resultRecord, "details"), "results"); + const details = recordField(resultRecord, "details"); + const results = recordField(details, "results"); if (!Array.isArray(results)) return; - const emittedAt = yield* DateTime.now; - const parentNodeId = idAllocator.derive.nodeFromProviderItem({ - driver: PI_PROVIDER, - nativeItemId: toolCallId, - }); + let emitted = false; for (const [index, result] of results.entries()) { const agent = recordString(result, "agent"); const task = recordString(result, "task"); if (agent === undefined || task === undefined) continue; - const nativeTaskId = `${toolCallId}:subagent:${recordNumber(result, "step") ?? index}`; - const subagentId = idAllocator.derive.nodeFromProviderItem({ - driver: PI_PROVIDER, - nativeItemId: nativeTaskId, - }); - const startedAt = turn.toolStartedAt.get(nativeTaskId) ?? emittedAt; - turn.toolStartedAt.set(nativeTaskId, startedAt); + emitted = true; const finished = completed || recordField(result, "finished") === true; const stopReason = recordString(result, "stopReason"); const interrupted = finished && stopReason === "aborted"; @@ -1358,63 +1363,175 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( finished && !interrupted && ((recordNumber(result, "exitCode") ?? 0) !== 0 || stopReason === "error"); - const status = interrupted - ? "interrupted" - : failed - ? "failed" - : finished - ? "completed" - : "running"; const outputText = piSubagentOutput(result); - const progress = - !finished && outputText.length > 0 ? { progress: outputText.slice(0, 200) } : {}; - const resultText = finished && outputText.length > 0 ? outputText.slice(0, 10_000) : null; + yield* emitSubagent(turn, toolCallId, `${recordNumber(result, "step") ?? index}`, { + agent, + task, + model: recordString(result, "model") ?? null, + status: interrupted + ? "interrupted" + : failed + ? "failed" + : finished + ? "completed" + : "running", + progress: !finished && outputText.length > 0 ? outputText.slice(0, 200) : undefined, + result: finished && outputText.length > 0 ? outputText.slice(0, 10_000) : null, + }); + } + // Async launches return no results; their members arrive in the subagent-async widget. + const asyncId = recordString(details, "asyncId"); + if (!emitted && asyncId !== undefined && !piAsyncRuns.has(asyncId)) { + const tracked = { turn, toolCallId, emitted: new Map() }; + piAsyncRuns.set(asyncId, tracked); + // Keeps the run ingesting after the coordinator's turn ends, until the run settles. yield* emit({ - type: "subagent.updated", + type: "background_work.updated", driver: PI_PROVIDER, - subagent: { - id: subagentId, - threadId: turn.turnInput.threadId, - runId: turn.turnInput.runId, - parentNodeId, - origin: "provider_native", - createdBy: "agent", - driver: PI_PROVIDER, - providerInstanceId: options.instanceId, - providerThreadId: turn.turnInput.providerThread.id, - childThreadId: null, - nativeTaskRef: providerRef(nativeTaskId), - prompt: task, - title: agent, - model: recordString(result, "model") ?? null, - status, - ...progress, - result: resultText, - startedAt, - completedAt: finished ? emittedAt : null, - updatedAt: emittedAt, - }, + providerTurnId: turn.providerTurn.id, + pending: true, }); - yield* emit({ - type: "turn_item.updated", + const run = latestAsyncSnapshotRuns.find((run) => recordString(run, "id") === asyncId); + if (run !== undefined) yield* updateAsyncRun(asyncId, tracked, run); + } + }); + + const emitSubagent = Effect.fnUntraced(function* ( + turn: ActivePiTurn, + toolCallId: string, + taskKey: string, + task: { + readonly agent: string; + readonly task: string; + readonly model: string | null; + readonly status: OrchestrationV2Subagent["status"]; + readonly progress?: string | undefined; + readonly result: string | null; + }, + ) { + const emittedAt = yield* DateTime.now; + const parentNodeId = idAllocator.derive.nodeFromProviderItem({ + driver: PI_PROVIDER, + nativeItemId: toolCallId, + }); + const nativeTaskId = `${toolCallId}:subagent:${taskKey}`; + const subagentId = idAllocator.derive.nodeFromProviderItem({ + driver: PI_PROVIDER, + nativeItemId: nativeTaskId, + }); + const startedAt = turn.toolStartedAt.get(nativeTaskId) ?? emittedAt; + turn.toolStartedAt.set(nativeTaskId, startedAt); + const finished = + task.status !== "running" && task.status !== "pending" && task.status !== "waiting"; + const progress = task.progress === undefined ? {} : { progress: task.progress }; + yield* emit({ + type: "subagent.updated", + driver: PI_PROVIDER, + subagent: { + id: subagentId, + threadId: turn.turnInput.threadId, + runId: turn.turnInput.runId, + parentNodeId, + origin: "provider_native", + createdBy: "agent", driver: PI_PROVIDER, - turnItem: { - ...baseItemFields(turn, nativeTaskId, startedAt, emittedAt), - status, - title: agent, - completedAt: finished ? emittedAt : null, - type: "subagent", - subagentId, - origin: "provider_native", - driver: PI_PROVIDER, - providerInstanceId: options.instanceId, - childThreadId: null, - prompt: task, - ...progress, - result: resultText, - }, + providerInstanceId: options.instanceId, + providerThreadId: turn.turnInput.providerThread.id, + childThreadId: null, + nativeTaskRef: providerRef(nativeTaskId), + prompt: task.task, + title: task.agent, + model: task.model, + status: task.status, + ...progress, + result: task.result, + startedAt, + completedAt: finished ? emittedAt : null, + updatedAt: emittedAt, + }, + }); + yield* emit({ + type: "turn_item.updated", + driver: PI_PROVIDER, + turnItem: { + ...baseItemFields(turn, nativeTaskId, startedAt, emittedAt), + status: task.status, + title: task.agent, + completedAt: finished ? emittedAt : null, + type: "subagent", + subagentId, + origin: "provider_native", + driver: PI_PROVIDER, + providerInstanceId: options.instanceId, + childThreadId: null, + prompt: task.task, + ...progress, + result: task.result, + }, + }); + }); + + const updateAsyncRun = Effect.fnUntraced(function* ( + runId: string, + tracked: { + readonly turn: ActivePiTurn; + readonly toolCallId: string; + readonly emitted: Map; + }, + run: unknown, + ) { + const children = recordField(run, "children"); + for (const child of Array.isArray(children) ? children : []) { + const id = recordString(child, "id"); + const agent = recordString(child, "agent"); + const status = piAsyncSubagentStatus(recordString(child, "state")); + if (recordString(child, "kind") !== "step" || id === undefined) continue; + if (agent === undefined || status === undefined) continue; + const model = recordString(child, "model") ?? null; + const key = `${status}:${model}`; + if (tracked.emitted.get(id) === key) continue; + tracked.emitted.set(id, key); + yield* emitSubagent(tracked.turn, tracked.toolCallId, id, { + agent, + task: "", + model, + status, + result: null, }); } + const runStatus = piAsyncSubagentStatus(recordString(run, "state")); + if (runStatus === undefined || runStatus === "running" || runStatus === "pending") return; + piAsyncRuns.delete(runId); + const providerTurnId = tracked.turn.providerTurn.id; + for (const other of piAsyncRuns.values()) { + if (other.turn.providerTurn.id === providerTurnId) return; + } + yield* emit({ + type: "background_work.updated", + driver: PI_PROVIDER, + providerTurnId, + pending: false, + }); + }); + + const updateAsyncSubagents = Effect.fnUntraced(function* (event: PiRpcRecord) { + const lines = recordField(event, "widgetLines"); + if (!Array.isArray(lines)) return; + for (const line of lines) { + if (typeof line !== "string" || !line.startsWith(PI_SUBAGENT_ASYNC_PREFIX)) continue; + const snapshot = Option.getOrUndefined( + decodePiJson(line.slice(PI_SUBAGENT_ASYNC_PREFIX.length)), + ); + const runs = recordField(snapshot, "runs"); + if (!Array.isArray(runs)) continue; + latestAsyncSnapshotRuns = runs; + for (const run of runs) { + const runId = recordString(run, "id"); + const tracked = runId === undefined ? undefined : piAsyncRuns.get(runId); + if (runId === undefined || tracked === undefined) continue; + yield* updateAsyncRun(runId, tracked, run); + } + } }); const cancelPrompt = Effect.fn("PiAdapter.cancelPrompt")(function* ( @@ -1540,6 +1657,10 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( ); return; } + if (method === "setWidget" && recordString(event, "widgetKey") === "subagent-async") { + yield* updateAsyncSubagents(event); + return; + } if ( method !== "select" && method !== "confirm" && @@ -3764,6 +3885,29 @@ function piUncapturedForkEntry( return earliest; } +function piAsyncSubagentStatus( + state: string | undefined, +): OrchestrationV2Subagent["status"] | undefined { + switch (state) { + case "queued": + return "pending"; + case "running": + return "running"; + case "paused": + return "waiting"; + case "complete": + case "partial": + return "completed"; + case "failed": + case "rejected": + return "failed"; + case "stopped": + return "interrupted"; + default: + return undefined; + } +} + function piSubagentOutput(result: unknown): string { const stopReason = recordString(result, "stopReason"); const failed = From 651accb10002a1c4fbee2d3fa552ef56ef41f523 Mon Sep 17 00:00:00 2001 From: Bilal Irfan Date: Sat, 14 Nov 2026 14:24:20 +0500 Subject: [PATCH 021/192] fix(web): new browser tabs open at your default zoom and appearance (#25874) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/preview/ServerBrowser.test.ts | 24 +++++++++++++++++++ apps/server/src/preview/ServerBrowser.ts | 1 + .../components/preview/openPreviewSession.ts | 10 +++++++- apps/web/src/reopenClosedView.test.ts | 9 ++++++- apps/web/src/reopenClosedView.ts | 4 ++++ 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/apps/server/src/preview/ServerBrowser.test.ts b/apps/server/src/preview/ServerBrowser.test.ts index 51d6bb43ee512..05696820799b3 100644 --- a/apps/server/src/preview/ServerBrowser.test.ts +++ b/apps/server/src/preview/ServerBrowser.test.ts @@ -1545,6 +1545,30 @@ it.effect("a first still that never returns does not stall the tab's later captu ).pipe(Effect.provide(layer)), ); +it.effect("keeps a tab's zoom when its viewer resizes", () => + Effect.scoped( + Effect.gen(function* () { + const browser = yield* ServerBrowser.ServerBrowser; + const manager = yield* Manager.PreviewManager; + const { tabId } = yield* manager.open({ + threadId: scope.thread.threadId, + runtime: "server", + zoomFactor: 1.5, + }); + const viewer = yield* browser.attachViewer(viewerInput(tabId, true)); + yield* viewer.input({ type: "takeControl" }); + const cdp = contexts[0]!.sessions[0]!; + cdp.send.mockClear(); + // Resizing the page resets Chromium's device metrics, so zoom must be applied again. + yield* viewer.input({ type: "resize", width: 390, height: 844 }); + expect(cdp.send).toHaveBeenCalledWith( + "Emulation.setDeviceMetricsOverride", + expect.objectContaining({ deviceScaleFactor: 3 }), + ); + }), + ).pipe(Effect.provide(layer)), +); + it.effect( "streams to a read-only viewer without allowing takeover, input, or viewport changes", () => diff --git a/apps/server/src/preview/ServerBrowser.ts b/apps/server/src/preview/ServerBrowser.ts index 326d5d94a5e70..5c96327c76a56 100644 --- a/apps/server/src/preview/ServerBrowser.ts +++ b/apps/server/src/preview/ServerBrowser.ts @@ -2948,6 +2948,7 @@ const make = Effect.gen(function* () { const current = tab.page.viewportSize(); if (current?.width === width && current.height === height) return; await tab.page.setViewportSize({ width, height }); + await applyZoom(tab); await broadcastViewport(tab); return; } diff --git a/apps/web/src/components/preview/openPreviewSession.ts b/apps/web/src/components/preview/openPreviewSession.ts index 6c372888de5a2..1e62676383496 100644 --- a/apps/web/src/components/preview/openPreviewSession.ts +++ b/apps/web/src/components/preview/openPreviewSession.ts @@ -1,9 +1,11 @@ import type { EnvironmentId, + PreviewAppearancePreference, PreviewOpenInput, PreviewRuntime, PreviewSessionSnapshot, PreviewViewportSetting, + PreviewZoomFactor, ScopedThreadRef, } from "@t3tools/contracts"; import type { AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; @@ -24,6 +26,8 @@ interface OpenPreviewSessionInput { url?: string; viewport?: PreviewViewportSetting; profileId?: string; + zoomFactor?: PreviewZoomFactor; + colorScheme?: PreviewAppearancePreference; runtime?: PreviewRuntime; } @@ -47,7 +51,11 @@ export async function openPreviewSession( profileId: input.profileId ?? defaults.profileId, ...(runtime === undefined || runtime === "desktop" ? {} - : { runtime, zoomFactor: defaults.zoomFactor, colorScheme: defaults.appearance }), + : { + runtime, + zoomFactor: input.zoomFactor ?? defaults.zoomFactor, + colorScheme: input.colorScheme ?? defaults.appearance, + }), }, }); if (result._tag === "Failure") { diff --git a/apps/web/src/reopenClosedView.test.ts b/apps/web/src/reopenClosedView.test.ts index 219a7792a1bad..528fdce3dd7b0 100644 --- a/apps/web/src/reopenClosedView.test.ts +++ b/apps/web/src/reopenClosedView.test.ts @@ -7,6 +7,7 @@ import * as Cause from "effect/Cause"; import { AsyncResult } from "effect/reactivity"; import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; +import * as previewRuntime from "./browser/previewRuntime"; import { type ClosedViewEntry, useClosedViewStore } from "./closedViewStore"; import { __setClientSettingsForTests } from "./hooks/useSettings"; import { readThreadPreviewState, resetPreviewStateForTests } from "./previewStateStore"; @@ -30,6 +31,8 @@ const snapshot: PreviewSessionSnapshot = { canGoForward: false, viewport: { _tag: "freeform", width: 1200, height: 800 }, profileId: "work", + zoomFactor: 1.5, + colorScheme: "dark", updatedAt: "2026-09-22T12:00:00.000Z", }; @@ -185,7 +188,8 @@ describe("reopenClosedView", () => { ).toEqual([]); }); - it("recreates a browser tab with saved URL, viewport and profile, then selects its new ID", async () => { + it("recreates a server browser tab with its saved URL, viewport, profile, zoom and appearance, then selects its new ID", async () => { + using _runtime = vi.spyOn(previewRuntime, "previewRuntimeFor").mockReturnValue("server"); const openPreview = vi.fn(async () => AsyncResult.success({ ...snapshot, tabId: "new-tab" })); const result = await reopenClosedView( { kind: "browser", threadRef, snapshot }, @@ -199,6 +203,9 @@ describe("reopenClosedView", () => { url: "https://example.com", viewport: snapshot.viewport, profileId: "work", + runtime: "server", + zoomFactor: 1.5, + colorScheme: "dark", }, }); expect(readThreadPreviewState(threadRef).snapshot?.tabId).toBe("new-tab"); diff --git a/apps/web/src/reopenClosedView.ts b/apps/web/src/reopenClosedView.ts index 899a1b223ec71..4a16c40ff8f93 100644 --- a/apps/web/src/reopenClosedView.ts +++ b/apps/web/src/reopenClosedView.ts @@ -109,6 +109,10 @@ export async function reopenClosedView( ...(url === undefined ? {} : { url }), ...(view.snapshot.viewport === undefined ? {} : { viewport: view.snapshot.viewport }), ...(view.snapshot.profileId === undefined ? {} : { profileId: view.snapshot.profileId }), + ...(view.snapshot.zoomFactor === undefined ? {} : { zoomFactor: view.snapshot.zoomFactor }), + ...(view.snapshot.colorScheme === undefined + ? {} + : { colorScheme: view.snapshot.colorScheme }), }); if (result._tag === "Failure") return false; panels.openBrowser(ref, result.value.tabId); From 9e19b7f7e1ab504e05ee73df51c7fc7700f246af Mon Sep 17 00:00:00 2001 From: Yash Singh Date: Sat, 14 Nov 2026 03:24:35 -0600 Subject: [PATCH 022/192] feat(server): resume stopped provider threads natively (#36526) --- .../src/features/threads/resume-thread.ts | 2 +- .../Adapters/ClaudeAdapterV2.test.ts | 129 ++++++++++++ .../Adapters/ClaudeAdapterV2.ts | 189 +++++++++++++---- .../Adapters/CodexAdapterV2.test.ts | 75 +++++++ .../Adapters/CodexAdapterV2.ts | 14 +- .../Adapters/OpenCode2AdapterV2.test.ts | 14 ++ .../src/orchestration-v2/Orchestrator.ts | 35 +++- .../src/orchestration-v2/ProjectionStore.ts | 13 +- .../ProviderTurnStartService.test.ts | 194 ++++++++++++++++-- .../ProviderTurnStartService.ts | 76 +++++-- .../orchestration-v2/RunExecutionService.ts | 41 ++-- .../orchestration-v2/TurnStartReads.test.ts | 31 +++ .../orchestration-v2/manualContinuation.ts | 25 +++ .../src/orchestration-v2/runtimeLayer.test.ts | 11 + .../src/components/chat/useComposerResume.ts | 4 +- docs/user/composer.md | 8 +- docs/user/thread-sidebar.md | 4 +- packages/contracts/src/orchestrationV2.ts | 1 + .../src/server/ProviderAdapter.ts | 32 +++ .../src/server/adapter.test.ts | 11 +- .../provider-opencode/src/server/adapter.ts | 2 + .../src/server/v2/adapter.ts | 3 +- 22 files changed, 806 insertions(+), 108 deletions(-) create mode 100644 apps/server/src/orchestration-v2/manualContinuation.ts diff --git a/apps/mobile/src/features/threads/resume-thread.ts b/apps/mobile/src/features/threads/resume-thread.ts index e112423868254..94bed891d7ae4 100644 --- a/apps/mobile/src/features/threads/resume-thread.ts +++ b/apps/mobile/src/features/threads/resume-thread.ts @@ -69,7 +69,7 @@ export async function resumeThread(input: { message: { messageId: input.messageId, role: "user", - text: "Continue where you left off.", + text: "", attachments: [], }, runtimeMode, diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index fc46e2b100232..f0c4d00c7bdf3 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -281,6 +281,69 @@ function makeClaudeTestTurnInput(input: { }; } +describe("ClaudeAdapterV2 native resume history", () => { + const entry = (uuid: string, type: string, content: unknown) => ({ + uuid, + type, + message: { content }, + }); + const prompt = entry("prompt", "user", "Run the task"); + const completed = entry("completed-tool", "user", [ + { type: "tool_result", tool_use_id: "done", content: "Saved", is_error: false }, + ]); + const call = entry("call", "assistant", [ + { type: "tool_use", id: "pending", name: "Bash", input: {} }, + ]); + const interrupted = entry("stopped-tool", "user", [ + { + type: "tool_result", + tool_use_id: "pending", + content: "The user doesn't want to proceed with this tool use. STOP", + is_error: true, + }, + ]); + it.each([ + { + name: "an interrupted reply", + messages: [ + prompt, + entry("reply", "assistant", "Partial response"), + entry("stop", "user", [{ type: "text", text: "[Request interrupted by user]" }]), + ], + head: "reply", + }, + { + name: "an interrupted tool after completed work", + messages: [ + prompt, + completed, + call, + interrupted, + entry("stop", "user", "[Request interrupted by user for tool use]"), + ], + head: "call", + }, + { + name: "an interrupted request before any reply", + messages: [prompt, entry("stop", "user", "[Request interrupted by user]")], + head: "prompt", + }, + { + name: "completed tool results", + messages: [prompt, completed, entry("stop", "user", "[Request interrupted by user]")], + head: "completed-tool", + }, + { name: "an explicit refusal", messages: [prompt, call, interrupted], head: undefined }, + { + name: "ordinary conversation text", + messages: [prompt, entry("reply", "assistant", "Continue where you left off.")], + head: undefined, + }, + ])("selects the native history head for $name", ({ messages, head }) => { + assert.equal(ClaudeAdapterV2.claudeResumeSessionAt(messages), head); + }); +}); + describe("ClaudeAdapterV2 runtime query policy", () => { it.each([ "-- --thinking-display omitted", @@ -3661,6 +3724,72 @@ describe("ClaudeAdapterV2 background wake turns", () => { }); const makeWakeHarness = makeWakeHarnessWithOptions(); + it.effect.each([false, true])( + "resumes a stopped query with empty SDK content and a fresh native process, buffered: %s", + (buffered) => + Effect.gen(function* () { + const harness = yield* makeWakeHarnessWithOptions({ + freshQueueOnReopen: true, + close: (queue) => Queue.shutdown(queue), + }); + const input = makeClaudeTestTurnInput({ + threadId: harness.threadId, + providerThread: harness.providerThread, + now: DateTime.makeUnsafe(0), + attemptId: RunAttemptId.make("native-resume"), + text: "Initial task", + attachments: [], + providerTurnOrdinal: 1, + }); + yield* harness.runtime.startTurn(input); + yield* harness.offerAndWait(turnOneResult); + yield* Queue.take(harness.terminalReceipts); + if (buffered) { + yield* harness.offerAndWait(wakeAssistant); + yield* harness.offerAndWait(wakeResult); + } + const { message, ...turn } = input; + yield* harness.runtime.resumeTurn!({ + ...turn, + providerTurnOrdinal: 2, + messageId: message.messageId, + }); + assert.equal(harness.processQueues.length, 2); + assert.equal( + harness.getOpenedOptions()?.resume, + harness.providerThread.nativeThreadRef?.nativeId, + ); + if (buffered) { + assert.equal(harness.offeredMessages.length, 1); + } else { + assert.deepEqual(harness.offeredMessages.at(-1)?.message.content, []); + yield* harness.offerAndWait(turnOneResult); + } + yield* Queue.take(harness.terminalReceipts); + assert.equal(harness.terminalEvents().length, 2); + if (buffered) { + assert.isTrue( + harness.events.some( + (event) => + event.type === "turn_item.updated" && + event.turnItem.type === "assistant_message" && + event.turnItem.text === WAKE_ASSISTANT_TEXT, + ), + ); + } + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll( + IdAllocator.layer, + McpProviderSessions.layer, + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ), + ); + it.effect("resumes the session a /clear selected after the Claude process is replaced", () => Effect.gen(function* () { const harness = yield* makeWakeHarnessWithOptions({ diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 17a9ca8486a38..74cdde264e7e1 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -647,6 +647,7 @@ export type ClaudeAgentSdkQueryOptions = Omit< } & ClaudeAgentSdkThreadIdentity; export interface ClaudeAgentSdkQueryOpenInput { + readonly resume?: true; readonly options: ClaudeAgentSdkQueryOptions; readonly threadId: ThreadId; readonly providerSessionId: OrchestrationV2ProviderSession["id"]; @@ -1220,6 +1221,67 @@ const decodeClaudeHistoryFork = Schema.decodeUnknownEffect( ), ); +const decodeClaudeHistoryMessages = Schema.decodeUnknownEffect( + Schema.fromJsonString( + Schema.Array( + Schema.Struct({ + type: Schema.String, + uuid: Schema.String, + message: Schema.Struct({ content: Schema.Unknown }), + }), + ), + ), +); + +// Claude's interactive reply-on-resume removes native abort rows before querying the saved history. +export function claudeResumeSessionAt( + messages: ReadonlyArray<{ + readonly type: string; + readonly uuid: string; + readonly message: { readonly content: unknown }; + }>, +): string | undefined { + let end = messages.length; + let toolInterrupted = false; + while (end > 0) { + const message = messages[end - 1]!; + const content = message.message.content; + const blocks = typeof content === "string" ? [{ type: "text", text: content }] : content; + if (message.type !== "user" || !Array.isArray(blocks) || blocks.length === 0) break; + const stop = blocks.every( + (block: unknown) => + Predicate.isObject(block) && + block.type === "text" && + (block.text === "[Request interrupted by user]" || + block.text === "[Request interrupted by user for tool use]"), + ); + if (stop) { + toolInterrupted ||= blocks.some( + (block: unknown) => + Predicate.isObject(block) && block.text === "[Request interrupted by user for tool use]", + ); + end--; + continue; + } + if ( + toolInterrupted && + blocks.every( + (block: unknown) => + Predicate.isObject(block) && + block.type === "tool_result" && + block.is_error === true && + typeof block.content === "string" && + block.content.startsWith("The user doesn't want to proceed with this tool use."), + ) + ) { + end--; + continue; + } + break; + } + return end === messages.length ? undefined : messages[end - 1]?.uuid; +} + const claudeConfigDirOf = ( path: Path.Path, environment: NodeJS.ProcessEnv, @@ -1229,7 +1291,7 @@ const claudeConfigDirOf = ( // The SDK reads CLAUDE_CONFIG_DIR from process.env. Isolate custom homes in a // worker so concurrent providers keep the server's environment. const runClaudeWorker = Effect.fn("runClaudeWorker")(function* (input: { - readonly method: "forkSession"; + readonly method: "forkSession" | "getSessionMessages"; readonly sessionId: string; readonly options?: ForkSessionOptions; readonly environment: NodeJS.ProcessEnv; @@ -1358,10 +1420,36 @@ export const layerQueryRunner: Layer.Layer< // Send MCP credentials over stdin to keep them out of SDK argv and inherited command environments. const { mcpServers, ...sdkOptions } = input.options; // Jobs the agent starts inherit the thread, so port discovery can attribute their servers. + let resumeSessionAt = sdkOptions.resumeSessionAt; + if (input.resume && sdkOptions.resume !== undefined) { + const messages = yield* runClaudeWorker({ + method: "getSessionMessages", + sessionId: sdkOptions.resume, + options: sdkOptions.cwd === undefined ? {} : { dir: sdkOptions.cwd }, + environment: sdkOptions.env ?? hostEnvironment, + }).pipe( + Effect.flatMap(decodeClaudeHistoryMessages), + Effect.mapError((cause) => queryRunnerError(cause, "getSessionMessages")), + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + Effect.provideService(Path.Path, path), + ); + const boundary = + resumeSessionAt === undefined + ? messages.length + : messages.findIndex((message) => message.uuid === resumeSessionAt) + 1; + resumeSessionAt = claudeResumeSessionAt(messages.slice(0, boundary)) ?? resumeSessionAt; + } const queryOptions = { ...sdkOptions, + ...(resumeSessionAt === undefined ? {} : { resumeSessionAt }), env: { ...(sdkOptions.env ?? hostEnvironment), + ...(input.resume + ? { + CLAUDE_CODE_RESUME_INTERRUPTED_TURN: "1", + CLAUDE_CODE_RESUME_REASON: "user_resume", + } + : {}), [PortScanner.AGENT_THREAD_ENV]: input.threadId, }, }; @@ -12160,6 +12248,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( ); let compiledSelection = compileClaudeModelSelection(turnInput.modelSelection, catalog); if ( + !turnInput.resume && existing !== null && existing.nativeThreadId === nativeThreadId && existing.queryPolicyKey === queryPolicyKey && @@ -12172,6 +12261,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( yield* applySelectionLive(existing, compiledSelection); } if ( + !turnInput.resume && existing !== null && existing.nativeThreadId === nativeThreadId && existing.queryPolicyKey === queryPolicyKey && @@ -12302,6 +12392,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( threadId: turnInput.threadId, providerSessionId: input.providerSessionId, options: queryOptions, + ...(turnInput.resume ? { resume: true } : {}), }); // The SDK spawns the CLI per turn, so only the open waits for a provider update. const querySession = yield* ( @@ -12548,27 +12639,34 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( const userMessage = promptUuid === null ? null - : yield* makeClaudeUserMessageWithAttachments({ - text: promptText, - // Mentions in inline handoff history are history, not this turn's skill. - ...(turnInput.inlineContextLength === undefined - ? {} - : { - skillMentionsFrom: - promptText.length - - turnInput.message.text.slice(turnInput.inlineContextLength).trimEnd() - .length, - }), - attachments: turnInput.message.attachments, - attachmentsDir, - fileSystem, - pdfReservation, - ...(skills === undefined ? {} : { skills }), - uuid: promptUuid, - }).pipe( - Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), - Effect.provideService(Path.Path, path), - ); + : turnInput.resume + ? { + type: "user" as const, + message: { role: "user" as const, content: [] }, + parent_tool_use_id: null, + uuid: promptUuid, + } + : yield* makeClaudeUserMessageWithAttachments({ + text: promptText, + // Mentions in inline handoff history are history, not this turn's skill. + ...(turnInput.inlineContextLength === undefined + ? {} + : { + skillMentionsFrom: + promptText.length - + turnInput.message.text.slice(turnInput.inlineContextLength).trimEnd() + .length, + }), + attachments: turnInput.message.attachments, + attachmentsDir, + fileSystem, + pdfReservation, + ...(skills === undefined ? {} : { skills }), + uuid: promptUuid, + }).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + Effect.provideService(Path.Path, path), + ); const querySession = yield* openQuery(turnInput, nativeThreadId, queryPolicy); const autoCompactWindow = yield* resolveAutoCompactWindow( querySession.settingsCwd, @@ -12624,7 +12722,10 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( subagentsByTaskId: new Map(), subagentsByToolUseId: new Map(), promptUuid, - promptEcho: isClaudeProviderContinuationTurn(turnInput) ? "confirmed" : "pending", + promptEcho: + isClaudeProviderContinuationTurn(turnInput) || turnInput.resume + ? "confirmed" + : "pending", promptStarted: false, nativeWakeStarted: false, gatedFramesBeforeEcho: 0, @@ -12705,22 +12806,23 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( }), }); }); - if (userMessage !== null) { + const offerUserMessage = + userMessage === null + ? Effect.void + : startUserTurnForWakeReports(nativeThreadId).pipe( + Effect.andThen( + Effect.uninterruptibleMask((restore) => + restore( + querySession.query.offer(userMessage, () => { + pdfReservation.commit(); + }), + ).pipe(Effect.tap(() => Effect.sync(() => pdfReservation.commit()))), + ), + ), + ); + if (userMessage !== null && !turnInput.resume) { yield* attachTurn; - yield* startUserTurnForWakeReports(nativeThreadId); - yield* Effect.uninterruptibleMask((restore) => - restore( - querySession.query.offer(userMessage, () => { - pdfReservation.commit(); - }), - ).pipe( - Effect.tap(() => - Effect.sync(() => { - pdfReservation.commit(); - }), - ), - ), - ); + yield* offerUserMessage; return; } // Transcript reads suspend replay; hold SDK frames until the buffer is applied to preserve order. @@ -12751,6 +12853,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( : (yield* Ref.get(absorbedWakes)).has(nativeThreadId); if (!leavesLaterWake) yield* forgetAbsorbedWake(nativeThreadId); if (drained.length === 0) { + if (turnInput.resume) return; const completedAt = yield* DateTime.now; yield* finalizeActiveTurn({ context, status: "completed", completedAt }); return; @@ -12817,7 +12920,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( const completedAt = yield* DateTime.now; yield* finalizeActiveTurn({ context, status: "completed", completedAt }); if (leavesLaterWake) yield* offerBufferedWake(nativeThreadId); - } else if (!context.nativeWakeStarted) { + } else if (!context.nativeWakeStarted && !turnInput.resume) { yield* Effect.sleep(CLAUDE_WAKE_TURN_START_DEADLINE).pipe( Effect.andThen( sdkMessageLock.withPermits(1)( @@ -12847,6 +12950,13 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( } }), ); + if (userMessage !== null) { + yield* sdkMessageLock.withPermits(1)( + Effect.gen(function* () { + if ((yield* Ref.get(activeTurn)) === context) yield* offerUserMessage; + }), + ); + } }, (effect, turnInput) => effect.pipe( @@ -13390,6 +13500,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( ), ), ), + resumeTurn: (input) => startTurn(ProviderAdapter.resumeTurnInput(input)), startTurn, compactThread: (turnInput) => startTurn({ diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index d4f7d48c2a5d2..884e953a495d6 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -1519,6 +1519,81 @@ describe("CodexAdapterV2 post-settle continuation", () => { }; }); + it.effect.each(["native", "ordinary"] as const)( + "resumes Codex without a prompt only for an existing conversation (%s)", + (mode) => + Effect.gen(function* () { + const nativeThreadId = `resume-${mode}`; + const nativeTurnId = `resume-turn-${mode}`; + const preamble = codexReplayPreamble({ + nativeThreadId, + nativeTurnId, + prompt: "Hello", + }); + const harness = yield* makeCodexReplayHarness( + makeCodexReplayTranscript({ + scenario: `manual-resume-${mode}`, + entries: [ + ...preamble.map((entry) => + mode === "native" && + entry.type === "expect_outbound" && + Predicate.isObject(entry.frame) && + Predicate.isObject(entry.frame.params) && + entry.frame.method === "turn/start" + ? { + ...entry, + frame: { + ...entry.frame, + params: { + ...entry.frame.params, + input: [], + }, + }, + } + : entry, + ), + { + type: "emit_inbound", + frame: { + method: "turn/completed", + params: { + threadId: nativeThreadId, + turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }), + }, + }, + }, + ], + }), + ); + const input = { + ...makeCodexTestTurnInput({ + threadId: harness.threadId, + providerThread: harness.providerThread, + now: yield* DateTime.now, + attemptId: RunAttemptId.make(`resume-${mode}`), + text: "Hello", + }), + nativeThreadHasTurns: true, + }; + const { message, ...resumeInput } = input; + if (mode === "native") + yield* harness.runtime.resumeTurn!({ ...resumeInput, messageId: message.messageId }); + else yield* harness.runtime.startTurn(input); + yield* harness.firstTerminal; + assert.equal(harness.terminalEvents()[0]?.status, "completed"); + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll( + IdAllocator.layer, + McpProviderSessions.layer, + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ), + ); + it.effect("keeps native image views available to the clients", () => Effect.gen(function* () { const nativeThreadId = "image-view-thread"; diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index 95acbf13a3a80..7de690c5e47d0 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -7844,16 +7844,16 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( }), ), ), + resumeTurn: (input) => runtime.startTurn(ProviderAdapter.resumeTurnInput(input)), startTurn: (turnInput) => { - const goalCommand = - turnInput.restartContinuationOfRunId === undefined - ? parseGoalCommand(turnInput.message) - : null; + const continuation = + turnInput.restartContinuationOfRunId !== undefined || turnInput.resume === true; + const goalCommand = !continuation ? parseGoalCommand(turnInput.message) : null; if (goalCommand !== null) return runGoalCommand(turnInput, goalCommand); const nativeThreadId = turnInput.providerThread.nativeThreadRef?.nativeId; return Effect.gen(function* () { if ( - turnInput.restartContinuationOfRunId === undefined && + !continuation && nativeThreadId != null && goalsPausedForWake.has(nativeThreadId) && goalsByNativeThread.get(nativeThreadId)?.status === "paused" @@ -7862,9 +7862,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( } yield* startNativeTurn( turnInput, - turnInput.restartContinuationOfRunId === undefined - ? yield* toCodexInput(turnInput) - : [], + !continuation ? yield* toCodexInput(turnInput) : [], ); }).pipe( Effect.mapError( diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts index e5fedad35270c..acf2f628f5126 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts @@ -523,6 +523,20 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { }), ); + it.effect("resumes through OpenCode with an empty native prompt", () => + Effect.gen(function* () { + const { runtime, thread } = yield* resumed([ + out("session.prompt", { sessionID: SESSION, text: "" }), + promptAccepted, + event("session.execution.succeeded", { sessionID: SESSION }), + ]); + const terminal = yield* terminalOf(runtime).pipe(Effect.forkScoped); + const { message, ...input } = turnInput(thread); + yield* runtime.resumeTurn!({ ...input, messageId: message.messageId }); + assert.equal((yield* Fiber.join(terminal))?.status, "completed"); + }).pipe(Effect.scoped), + ); + it.effect("renames the session before the first turn after the thread was renamed", () => Effect.gen(function* () { const { runtime, thread } = yield* resumed([ diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts index 332eec9869b8f..2870e22ea5000 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.ts @@ -7134,10 +7134,20 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio } } } + const notification = + command.manualContinuationOfRunId === undefined + ? command.notification + : { + source: { kind: "system" as const }, + outcome: "updated" as const, + summary: "Resumed thread", + }; const dispatchText = - delegatedCompletion === undefined - ? command.text - : delegatedCompletionWakeDetail(delegatedCompletion.taskIds, projection.subagents); + command.manualContinuationOfRunId !== undefined + ? "" + : delegatedCompletion === undefined + ? command.text + : delegatedCompletionWakeDetail(delegatedCompletion.taskIds, projection.subagents); const sourcePlanProjection = command.sourcePlanRef === undefined ? null @@ -7226,12 +7236,12 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio effects, projection, modelSelection: - delegatedCompletion === undefined && command.notification === undefined + delegatedCompletion === undefined && notification === undefined ? modelSelection : (projection.runs.find((run) => run.id === dispatchMode.targetRunId)?.modelSelection ?? modelSelection), delegatedCompletion, - ...(command.notification === undefined ? {} : { notification: command.notification }), + ...(notification === undefined ? {} : { notification }), targetRunId: dispatchMode.targetRunId, messageId: command.messageId, text: dispatchText, @@ -7492,6 +7502,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio ...(command.restartContinuationOfRunId === undefined ? {} : { restartContinuationOfRunId: command.restartContinuationOfRunId }), + ...(command.manualContinuationOfRunId === undefined + ? {} + : { manualContinuationOfRunId: command.manualContinuationOfRunId }), ...(command.usageLimitContinuationOfRunId === undefined ? {} : { usageLimitContinuationOfRunId: command.usageLimitContinuationOfRunId }), @@ -7552,7 +7565,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio createdAt: now, updatedAt: now, ...(delegatedCompletion === undefined ? {} : { delegatedCompletion }), - ...(command.notification === undefined ? {} : { notification: command.notification }), + ...(notification === undefined ? {} : { notification }), }; const emitEvent = emit(events, command); if (targetProviderThread === undefined) { @@ -7903,6 +7916,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio ...(command.restartContinuationOfRunId === undefined ? {} : { restartContinuationOfRunId: command.restartContinuationOfRunId }), + ...(command.manualContinuationOfRunId === undefined + ? {} + : { manualContinuationOfRunId: command.manualContinuationOfRunId }), ...(command.usageLimitContinuationOfRunId === undefined ? {} : { usageLimitContinuationOfRunId: command.usageLimitContinuationOfRunId }), @@ -7967,7 +7983,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio createdAt: now, updatedAt: now, ...(delegatedCompletion === undefined ? {} : { delegatedCompletion }), - ...(command.notification === undefined ? {} : { notification: command.notification }), + ...(notification === undefined ? {} : { notification }), }; const turnItem: OrchestrationV2TurnItem = { createdBy: command.createdBy, @@ -8698,6 +8714,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio ...(command.restartContinuationOfRunId === undefined ? {} : { restartContinuationOfRunId: command.restartContinuationOfRunId }), + ...(command.manualContinuationOfRunId === undefined + ? {} + : { manualContinuationOfRunId: command.manualContinuationOfRunId }), ...(command.usageLimitContinuationOfRunId === undefined ? {} : { usageLimitContinuationOfRunId: command.usageLimitContinuationOfRunId }), @@ -8755,7 +8774,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio createdAt: now, updatedAt: now, ...(delegatedCompletion === undefined ? {} : { delegatedCompletion }), - ...(command.notification === undefined ? {} : { notification: command.notification }), + ...(notification === undefined ? {} : { notification }), }; const turnItem: OrchestrationV2TurnItem = { createdBy: command.createdBy, diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index 3bdbbffc0373c..5043b274941c1 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -5,6 +5,7 @@ import { projectTurnItemForWire } from "./WireProjection.ts"; import * as Stream from "effect/Stream"; import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; import { makeThreadFind } from "./ThreadFind.ts"; +import { unsentManualContinuationSource } from "./manualContinuation.ts"; import type { OrchestrationV2ThreadLinkMetadata, OrchestrationV2SearchThreadInput, @@ -5128,11 +5129,19 @@ export const layer: Layer.Layer = WHERE bindings.thread_id = ${threadId} ORDER BY sessions.updated_at ASC, sessions.provider_session_id ASC `.pipe(Effect.flatMap(decodeRows(decodeProviderSessionPayload, threadId))); + const run = runs.find((source) => source.id === runId); + const unsentSource = unsentManualContinuationSource(run, { + runs, + attempts, + providerTurns, + }); + const messageIds = [run, unsentSource].flatMap((source) => + source === undefined ? [] : [source.userMessageId], + ); const messages = yield* sql` SELECT payload_json FROM orchestration_v2_projection_messages WHERE thread_id = ${threadId} AND ( - message_id IN (SELECT json_extract(payload_json, '$.userMessageId') FROM orchestration_v2_projection_runs - WHERE thread_id = ${threadId} AND run_id = ${runId}) + message_id IN ${sql.in(messageIds)} OR (role = 'user' AND lower(trim(json_extract(payload_json, '$.text'), ${javascriptTrimWhitespace})) = '/compact') -- Earlier /goal turns defer context too; parseGoalCommand narrows this prefix match. OR (role = 'user' AND trim(json_extract(payload_json, '$.text'), ${javascriptTrimWhitespace}) GLOB '/goal*') diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index dcc40b0b44ac9..88a0b64cd80d8 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -373,6 +373,9 @@ it.each([ function makeLocalCommandHarness(input: { readonly text: string; + readonly manualContinuation?: boolean; + readonly nativeResumeUnsupported?: boolean; + readonly steeringRestart?: boolean; readonly nativeGoals?: boolean; readonly goalDriver?: "opencode" | "antigravity"; readonly worktreePath?: string; @@ -407,6 +410,9 @@ function makeLocalCommandHarness(input: { */ readonly sideChat?: { readonly contextRun: "current" | "failed" | "cancelled" | "delivered"; + readonly unsentResume?: boolean; + readonly repeatedResume?: boolean; + readonly acceptedResume?: boolean; readonly nativeForkFrom?: "idle" | "busy"; readonly switchedProvider?: boolean; readonly threadRuntimeMode?: OrchestrationV2ThreadProjection["thread"]["runtimeMode"]; @@ -426,6 +432,7 @@ function makeLocalCommandHarness(input: { const checkpointScopeId = CheckpointScopeId.make("scope-native-account-command"); const messageId = MessageId.make("message-native-account-command"); const run: OrchestrationV2ThreadProjection["runs"][number] = { + ...(input.manualContinuation ? { manualContinuationOfRunId: RunId.make("stopped-run") } : {}), id: runId, threadId, ordinal: 2, @@ -508,7 +515,7 @@ function makeLocalCommandHarness(input: { providerInstanceId: newInstanceId, providerThreadId, providerTurnId: null, - reason: "initial", + reason: input.steeringRestart ? "steering_restart" : "initial", status: "pending", startedAt: null, completedAt: null, @@ -759,6 +766,71 @@ function makeLocalCommandHarness(input: { ], }; } + if (input.sideChat?.unsentResume) { + const originalMessageId = MessageId.make("original-unsent-message"); + projection = { + ...projection, + runs: [ + { + ...run, + id: RunId.make("stopped-run"), + manualContinuationOfRunId: undefined, + userMessageId: originalMessageId, + status: "interrupted", + ordinal: 1, + }, + ...projection.runs, + ], + messages: [ + { ...message, id: originalMessageId, text: "Original request" }, + ...projection.messages, + ], + }; + } + if (input.sideChat?.repeatedResume) { + const retry = RunId.make("stopped-resume"); + projection = { + ...projection, + runs: projection.runs + .map((r) => (r.id === runId ? { ...r, manualContinuationOfRunId: retry } : r)) + .concat({ + ...run, + id: retry, + manualContinuationOfRunId: RunId.make("stopped-run"), + status: "interrupted", + }), + }; + } + if (input.sideChat?.acceptedResume) { + const acceptedId = RunAttemptId.make("accepted-original"); + projection = { + ...projection, + attempts: [ + ...projection.attempts, + { + ...projection.attempts[0]!, + id: acceptedId, + runId: RunId.make("stopped-run"), + providerThreadId: oldProviderThreadId, + status: "interrupted", + }, + ], + providerTurns: [ + ...projection.providerTurns, + { + id: ProviderTurnId.make("accepted-original-turn"), + runAttemptId: acceptedId, + providerThreadId: oldProviderThreadId, + nodeId: rootNodeId, + ordinal: 1, + nativeTurnRef: null, + status: "interrupted", + startedAt: now, + completedAt: now, + }, + ], + }; + } if ("historyReadFailureAfterFallback" in input || "resumeFailure" in input) { const nativeThreadRef = { driver: providerThread.driver, @@ -917,6 +989,10 @@ function makeLocalCommandHarness(input: { resumeThread: () => Effect.succeed(sideChatThread), forkThread, startTurn, + resumeTurn: input.nativeResumeUnsupported + ? undefined + : (input: ProviderAdapter.ProviderAdapterV2ResumeTurnInput) => + startTurn(ProviderAdapter.resumeTurnInput(input)), }; const open = vi.fn(() => input.sideChat !== undefined @@ -954,7 +1030,13 @@ function makeLocalCommandHarness(input: { (input: RunExecutionService.RunExecutionServiceV2StartRootRunInput) => Effect.Effect >((runInput) => input.sideChat !== undefined - ? runInput.session.startTurn(runInput as never).pipe(Effect.orDie) + ? (runInput.resume + ? runInput.session.resumeTurn!({ + ...runInput, + messageId: runInput.message.messageId, + } as never) + : runInput.session.startTurn(runInput as never) + ).pipe(Effect.orDie) : input.failReadsAfterRunning === true || input.previousTurn !== undefined ? Effect.suspend(() => providerBusy ? Effect.die("The previous provider turn is still running.") : Effect.void, @@ -1434,6 +1516,47 @@ effectIt.effect("recovers an abandoned failed turn before continuing its native }), ); +effectIt.effect("sends new steering text when restarting a manual continuation", () => + Effect.gen(function* () { + const harness = makeLocalCommandHarness({ + text: "New steering instruction", + manualContinuation: true, + steeringRestart: true, + nativeResumeUnsupported: true, + sideChat: { contextRun: "delivered" }, + }); + yield* harness.start; + expect(harness.startTurn.mock.calls[0]?.[0].message.text).toBe("New steering instruction"); + expect(harness.startRootRun.mock.calls[0]?.[0].resume).toBe(false); + }), +); + +effectIt.effect("refuses unsupported resume instead of manufacturing a user prompt", () => + Effect.gen(function* () { + const harness = makeLocalCommandHarness({ + text: "", + manualContinuation: true, + nativeResumeUnsupported: true, + sideChat: { contextRun: "delivered" }, + }); + yield* harness.start; + expect(harness.startTurn).not.toHaveBeenCalled(); + expect(harness.startRootRun).not.toHaveBeenCalled(); + expect(harness.settledRuns).toEqual(["failed"]); + expect(harness.projection().turnItems).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "error", + failure: expect.objectContaining({ + message: + "This provider cannot resume without a new message. Send a message to continue.", + }), + }), + ]), + ); + }), +); + effectIt.effect.each([ { contextRun: "current", runtimeMode: "full-access", context: "SIDE_CHAT_CONTEXT" }, { contextRun: "failed", runtimeMode: "full-access", context: "SIDE_CHAT_CONTEXT" }, @@ -1445,6 +1568,21 @@ effectIt.effect.each([ context: "SIDE_CHAT_CONTEXT", }, { contextRun: "delivered", runtimeMode: "full-access", context: "" }, + { contextRun: "delivered", runtimeMode: "full-access", context: "", unsentResume: true }, + { + contextRun: "delivered", + runtimeMode: "full-access", + context: "", + unsentResume: true, + repeatedResume: true, + }, + { + contextRun: "delivered", + runtimeMode: "full-access", + context: "", + unsentResume: true, + acceptedResume: true, + }, { contextRun: "current", threadRuntimeMode: "approval-required", @@ -1456,7 +1594,8 @@ effectIt.effect.each([ ({ runtimeMode, context, ...sideChat }) => Effect.gen(function* () { const harness = makeLocalCommandHarness({ - text: "What is the main thread doing?", + text: "", + manualContinuation: true, sideChat, }); @@ -1466,8 +1605,14 @@ effectIt.effect.each([ expect.objectContaining({ runtimePolicy: expect.objectContaining({ runtimeMode }) }), ); expect(harness.startTurn.mock.calls.map(([turn]) => turn.message.text)).toEqual([ - withInlineContext(context, "What is the main thread doing?"), + withInlineContext( + context, + "unsentResume" in sideChat && !("acceptedResume" in sideChat) ? "Original request" : "", + ), ]); + expect(harness.startTurn.mock.calls[0]?.[0].resume === true).toBe( + context === "" && (!("unsentResume" in sideChat) || "acceptedResume" in sideChat), + ); expect(harness.projection().contextHandoffs).toMatchObject([ { id: "side-chat-context", @@ -1512,26 +1657,47 @@ effectIt.effect.each([ }), ); -effectIt.effect.each([ - { goalDriver: "opencode", nativeGoals: false }, - { goalDriver: "opencode", nativeGoals: true }, - { goalDriver: "antigravity", nativeGoals: false }, - { goalDriver: "antigravity", nativeGoals: true }, -] as const)( - "$goalDriver goal prompts defer switched-provider history only with native support: $nativeGoals", - ({ goalDriver, nativeGoals }) => +effectIt.effect.each( + ( + [ + { goalDriver: "opencode", nativeGoals: false }, + { goalDriver: "opencode", nativeGoals: true }, + { goalDriver: "antigravity", nativeGoals: false }, + { goalDriver: "antigravity", nativeGoals: true }, + ] as const + ).flatMap((choice) => [ + { ...choice, resuming: false }, + { ...choice, resuming: true }, + ]), +)( + "$goalDriver goal prompts defer switched-provider history only with native support: $nativeGoals, resuming: $resuming", + ({ goalDriver, nativeGoals, resuming }) => Effect.gen(function* () { const harness = makeLocalCommandHarness({ - text: "/goal all tests pass", + text: resuming ? "" : "/goal all tests pass", + manualContinuation: resuming, goalDriver, nativeGoals, - sideChat: { contextRun: "failed", switchedProvider: true }, + sideChat: { contextRun: "failed", switchedProvider: true, unsentResume: resuming }, }); + if (resuming) { + const projection = harness.projection(); + harness.setProjection({ + ...projection, + messages: projection.messages.map((message) => + message.id === "original-unsent-message" + ? { ...message, text: "/goal all tests pass" } + : message, + ), + }); + } harness.tryHandlePromptCommand.mockReturnValue(Effect.succeed(false)); yield* harness.start; expect(harness.startTurn.mock.calls[0]?.[0].message.text.includes("SIDE_CHAT_CONTEXT")).toBe( !nativeGoals, ); + if (nativeGoals) + expect(harness.startTurn.mock.calls[0]?.[0].message.text).toBe("/goal all tests pass"); }), ); diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 7fe02946f2c80..15792dacecfa2 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -54,6 +54,7 @@ import { deliverContextHandoffs, withInlineContext } from "./ContextHandoffDeliv import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as ProjectionStore from "./ProjectionStore.ts"; +import { unsentManualContinuationSource } from "./manualContinuation.ts"; import * as ProviderSessionManager from "./ProviderSessionManager.ts"; import { ContextHandoffBackgroundWorkError, @@ -266,6 +267,12 @@ export const layer: Layer.Layer< return { ...session, startTurn: (input: Parameters[0]) => start(input), + ...(session.resumeTurn === undefined + ? {} + : { + resumeTurn: (input: ProviderAdapter.ProviderAdapterV2ResumeTurnInput) => + start(ProviderAdapter.resumeTurnInput(input)), + }), ...(session.compactThread === undefined ? {} : { @@ -360,6 +367,16 @@ export const layer: Layer.Layer< cause: `Run ${runId} is missing its execution projection state.`, }); } + const manualResume = + run.manualContinuationOfRunId !== undefined && attempt.reason !== "steering_restart"; + const resumeSource = manualResume + ? unsentManualContinuationSource(run, projection) + : undefined; + const unsentResumeMessage = + resumeSource !== undefined + ? projection.messages.find((source) => source.id === resumeSource.userMessageId) + : undefined; + const providerMessage = unsentResumeMessage ?? message; const settleRunBeforeStart = Effect.fn("orchestrationV2.providerTurnStart.settleBeforeStart")( function* (input: { readonly signal: string; @@ -479,9 +496,12 @@ export const layer: Layer.Layer< }); }, ); - if (message.attachments.length === 0 && message.text.trimStart().startsWith("/")) { + if ( + providerMessage.attachments.length === 0 && + providerMessage.text.trimStart().startsWith("/") + ) { const isEmptyCompaction = - message.text.trim().toLowerCase() === "/compact" && !projection.hasConversation; + providerMessage.text.trim().toLowerCase() === "/compact" && !projection.hasConversation; // Preparation may select a new provider; account commands still target the last native session. const nativeThreads = new Map( projection.providerThreads @@ -512,8 +532,8 @@ export const layer: Layer.Layer< providerAuth.tryHandlePromptCommand({ instanceId: authInstanceId, text: projectComposerContextForProvider({ - text: message.text, - records: message.context?.records ?? [], + text: providerMessage.text, + records: providerMessage.context?.records ?? [], }), hasAttachments: false, }), @@ -551,7 +571,7 @@ export const layer: Layer.Layer< : { type: "command_execution", title: "Provider signed out", - input: message.text.trim(), + input: providerMessage.text.trim(), output: "Provider signed out", exitCode: 0, }, @@ -755,15 +775,15 @@ export const layer: Layer.Layer< let effectiveHandoffs = handoffs; let resumeFellBack = false; const userText = projectComposerContextForProvider({ - text: message.text, - records: message.context?.records ?? [], + text: providerMessage.text, + records: providerMessage.context?.records ?? [], }); // Native goal commands go verbatim; handoff history waits a turn. const goalDriver = session.driver === "codex" || session.driver === "claudeAgent" || session.providerSession.capabilities.turns.supportsNativeGoals === true; - const goalCommand = goalDriver && parseGoalCommand(message) !== null; + const goalCommand = goalDriver && parseGoalCommand(providerMessage) !== null; // The provider turn marks cancellation work delivered; restart continuations use their own text or resume natively. const noteContinuation = isRestartNoteContinuation( run, @@ -936,7 +956,7 @@ export const layer: Layer.Layer< tokenCap, modelContextWindow, userText: restartNote === "" ? userText : `${restartNote}\n\n${userText}`, - attachments: message.attachments, + attachments: providerMessage.attachments, providerThread: budgetProviderThread, nativeContextEstimate: budgetProviderThread.contextUsage?.usedTokens === undefined @@ -1265,7 +1285,7 @@ export const layer: Layer.Layer< handoffs: [...handoffs, ...resumedRetryHandoff], providerThread: { ...resumed.success, id: providerThread.id }, budget: context.budget, - deferInline: isCompactionCommandMessage(message) || goalCommand, + deferInline: isCompactionCommandMessage(providerMessage) || goalCommand, alreadyDeliveredItemIds: context.deliveredItemIds, persist: () => Effect.void, }).pipe( @@ -1448,6 +1468,17 @@ export const layer: Layer.Layer< legacyInputRunIds, budget, } = handoffContext; + if (manualResume && unsentResumeMessage === undefined && session.resumeTurn === undefined) { + yield* settleStartFailure({ + signal: "resume-unsupported", + title: "Native resume is unavailable", + error: new ProviderAdapter.ProviderResumeUnsupportedError({ + driver: session.driver, + providerThreadId: providerThread.id, + }), + }); + return; + } const retryHandoff = resumedRetryHandoff ?? (yield* prepareRetryHandoff(handoffContext)); const runningProviderThread: OrchestrationV2ProviderThread = { ...loadedProviderThread, @@ -1649,11 +1680,23 @@ export const layer: Layer.Layer< .join("\n\n"); const { restartContinuationOfRunId: _resumedRunId, ...promptedInput } = turnInput; const text = goalCommand ? userText : withInlineContext(context, userText); - yield* start({ - ...(noteContinuation ? promptedInput : turnInput), - message: { ...turnInput.message, text }, - ...(text === userText ? {} : { inlineContextLength: text.length - userText.length }), - }).pipe( + const startInput = noteContinuation ? promptedInput : turnInput; + const { resume: _resume, ...contextInput } = startInput; + const { message: resumeMessage, resume: _resumeIntent, ...resumeInput } = startInput; + const operation = + turnInput.resume && context === "" && session.resumeTurn !== undefined + ? session.resumeTurn({ ...resumeInput, messageId: resumeMessage.messageId }) + : start({ + ...(context === "" ? startInput : contextInput), + message: { + ...turnInput.message, + text, + }, + ...(text === userText + ? {} + : { inlineContextLength: text.length - userText.length }), + }); + yield* operation.pipe( // Clear pending handoff markers for refused prompts so the next turn retains the native session. Effect.tapError((error) => refusedBeforePrompt(error) @@ -1740,6 +1783,7 @@ export const layer: Layer.Layer< .filter((turn) => turn.providerThreadId === providerThread.id) .map((turn) => turn.ordinal), ) + 1, + resume: manualResume && unsentResumeMessage === undefined, nativeThreadHasTurns: nativeInputRunIds.size > 0 || (legacyInputRunIds.size > 0 && @@ -1753,7 +1797,7 @@ export const layer: Layer.Layer< message: { messageId: message.id, text: userText, - attachments: message.attachments, + attachments: providerMessage.attachments, createdBy: message.createdBy, creationSource: message.creationSource, ...(message.scheduledTaskId === undefined diff --git a/apps/server/src/orchestration-v2/RunExecutionService.ts b/apps/server/src/orchestration-v2/RunExecutionService.ts index 10f10a98de0a4..49cdaf04406b7 100644 --- a/apps/server/src/orchestration-v2/RunExecutionService.ts +++ b/apps/server/src/orchestration-v2/RunExecutionService.ts @@ -582,6 +582,7 @@ export interface RunExecutionServiceV2StartRootRunInput { readonly baseline?: Effect.Effect; readonly providerTurnOrdinal: number; readonly nativeThreadHasTurns?: boolean; + readonly resume?: boolean; readonly loadInheritedBackgroundTurnItems?: () => Effect.Effect< ReadonlyArray, unknown @@ -1962,21 +1963,37 @@ export const layer: Layer.Layer< const compact = input.message.attachments.length === 0 && input.message.text.trim().toLowerCase() === "/compact"; - const startTurn = compact - ? (input.session.compactThread?.(turnInput) ?? - Effect.fail( - new ProviderAdapter.ProviderAdapterTurnStartError({ - driver: input.session.driver, - threadId: input.run.threadId, - providerThreadId: input.providerThread.id, - runId: input.run.id, - cause: new ProviderAdapter.ProviderCompactionUnsupportedError({ + const { message: _message, ...resumeInput } = turnInput; + const startTurn = + input.resume === true + ? (input.session.resumeTurn?.({ ...resumeInput, messageId: input.message.messageId }) ?? + Effect.fail( + new ProviderAdapter.ProviderAdapterTurnStartError({ driver: input.session.driver, + threadId: input.run.threadId, providerThreadId: input.providerThread.id, + runId: input.run.id, + cause: new ProviderAdapter.ProviderResumeUnsupportedError({ + driver: input.session.driver, + providerThreadId: input.providerThread.id, + }), }), - }), - )) - : input.session.startTurn(turnInput); + )) + : compact + ? (input.session.compactThread?.(turnInput) ?? + Effect.fail( + new ProviderAdapter.ProviderAdapterTurnStartError({ + driver: input.session.driver, + threadId: input.run.threadId, + providerThreadId: input.providerThread.id, + runId: input.run.id, + cause: new ProviderAdapter.ProviderCompactionUnsupportedError({ + driver: input.session.driver, + providerThreadId: input.providerThread.id, + }), + }), + )) + : input.session.startTurn(turnInput); yield* Effect.andThen(shouldStart, startTurn).pipe( // Wait for acceptance so abandoned startup cannot leave a refresher // on the already-detached ingestion fiber. diff --git a/apps/server/src/orchestration-v2/TurnStartReads.test.ts b/apps/server/src/orchestration-v2/TurnStartReads.test.ts index cfc362c9db080..ed2484ae9b19e 100644 --- a/apps/server/src/orchestration-v2/TurnStartReads.test.ts +++ b/apps/server/src/orchestration-v2/TurnStartReads.test.ts @@ -352,6 +352,37 @@ it.effect.each(["sqlite"] as const)( (yield* store.getTurnStartContext(threadId, runId)).messages.map((m) => m.id), marker.id, ); + const originalId = MessageId.make("unsent-original"); + yield* store.apply({ + id: EventId.make("unsent-original-event"), + type: "message.updated", + threadId, + runId: oldRunId, + occurredAt: now, + payload: { ...message, id: originalId, runId: oldRunId, text: "Original request" }, + }); + yield* putRun({ + ...run, + id: oldRunId, + ordinal: 1, + status: "interrupted", + userMessageId: originalId, + }); + const retryId = RunId.make("unsent-retry"); + yield* putRun({ ...run, ordinal: 3, manualContinuationOfRunId: retryId }); + yield* putRun({ + ...run, + id: retryId, + ordinal: 2, + status: "interrupted", + manualContinuationOfRunId: oldRunId, + }); + assert.equal( + (yield* store.getTurnStartContext(threadId, runId)).messages.find( + (m) => m.id === originalId, + )?.text, + "Original request", + ); const other = ThreadId.make("other"); yield* putThread({ ...thread, id: other }); assert.deepEqual((yield* store.getTurnStartContext(other, runId)).messages, []); diff --git a/apps/server/src/orchestration-v2/manualContinuation.ts b/apps/server/src/orchestration-v2/manualContinuation.ts new file mode 100644 index 0000000000000..ee255cb154eaa --- /dev/null +++ b/apps/server/src/orchestration-v2/manualContinuation.ts @@ -0,0 +1,25 @@ +import type { OrchestrationV2Run, OrchestrationV2ThreadProjection } from "@t3tools/contracts"; + +export function unsentManualContinuationSource( + run: OrchestrationV2Run | undefined, + projection: Pick, +) { + let sourceId = run?.manualContinuationOfRunId; + if (sourceId === undefined) return undefined; + const runs = new Map(projection.runs.map((source) => [source.id, source])); + const acceptedAttempts = new Set(projection.providerTurns.map((turn) => turn.runAttemptId)); + const acceptedRuns = new Set( + projection.attempts + .filter((attempt) => acceptedAttempts.has(attempt.id)) + .map((attempt) => attempt.runId), + ); + const visited = new Set(); + while (sourceId !== undefined && !visited.has(sourceId)) { + visited.add(sourceId); + const source = runs.get(sourceId); + if (source === undefined || acceptedRuns.has(sourceId)) return undefined; + if (source.manualContinuationOfRunId === undefined) return source; + sourceId = source.manualContinuationOfRunId; + } + return undefined; +} diff --git a/apps/server/src/orchestration-v2/runtimeLayer.test.ts b/apps/server/src/orchestration-v2/runtimeLayer.test.ts index 09e8fcb4e5be9..9e054e3a0bf89 100644 --- a/apps/server/src/orchestration-v2/runtimeLayer.test.ts +++ b/apps/server/src/orchestration-v2/runtimeLayer.test.ts @@ -10832,6 +10832,17 @@ it.layer(layerTest)("usage-limit recovery", (it) => { assert.lengthOf(after.runs, 3); assert.equal(after.runs[1]?.status, "queued"); assert.equal(after.runs[2]?.status, "starting"); + assert.equal(after.runs[2]?.manualContinuationOfRunId, source.id); + const resumeMessage = after.messages.find( + (message) => message.id === resume("first").messageId, + ); + assert.equal(resumeMessage?.text, ""); + assert.equal(resumeMessage?.notification?.summary, "Resumed thread"); + assert.isFalse( + after.turnItems.some( + (item) => item.runId === after.runs[2]?.id && item.type === "user_message", + ), + ); assert.equal( (yield* orchestrator.dispatch(resume("second")).pipe(Effect.exit))._tag, "Failure", diff --git a/apps/web/src/components/chat/useComposerResume.ts b/apps/web/src/components/chat/useComposerResume.ts index d525811e2fac7..7dac1a5d8e105 100644 --- a/apps/web/src/components/chat/useComposerResume.ts +++ b/apps/web/src/components/chat/useComposerResume.ts @@ -49,7 +49,7 @@ interface ComposerResumeInput { } /** - * Resumes an interrupted run ("Continue where you left off.") and/or releases the turns held + * Resumes an interrupted run and/or releases the turns held * in its queue. Shares the per-route send lock with the composer's send paths. */ export function useComposerResume(input: ComposerResumeInput) { @@ -130,7 +130,7 @@ export function useComposerResume(input: ComposerResumeInput) { message: { messageId: newMessageId(), role: "user", - text: "Continue where you left off.", + text: "", attachments: [], }, runtimeMode, diff --git a/docs/user/composer.md b/docs/user/composer.md index df880e49c6ca5..89a96f120d4d0 100644 --- a/docs/user/composer.md +++ b/docs/user/composer.md @@ -258,8 +258,12 @@ images. **Send now** sends a message right away, into the running turn if there also edit or remove it. If you stop the agent or the server restarts, queued messages keep their order and are held. -Press **Resume** in an empty composer to continue. **Resume** also continues a stopped -run or one that hit a usage limit, even with nothing queued. Sending a new message also resumes the queue, unless it steers a turn that is already +Press **Resume** in an empty composer to continue. For stopped runs and usage limits, +this uses the provider's native continuation without sending a new prompt. If the provider +does not support it, send a message to continue instead. If the original request never reached +the provider, Resume sends that request again. + +Sending a new message also resumes the queue, unless it steers a turn that is already running. You can edit or remove held messages without starting them. The pencil on a queued message opens it in the composer for editing. The original message diff --git a/docs/user/thread-sidebar.md b/docs/user/thread-sidebar.md index e5fe950e140a2..4c02f1c79baee 100644 --- a/docs/user/thread-sidebar.md +++ b/docs/user/thread-sidebar.md @@ -250,8 +250,8 @@ thread** from its long-press menu on mobile. keeps the provider's explanation. Retry after the limit resets, or switch to another provider instance. Press **Resume** in an empty composer to continue a limited or interrupted turn -manually. Do this when the provider reports no reset time, because nothing -continues the turn on its own. +manually when the provider supports native continuation, or send a message to continue. +Do this when the provider reports no reset time, because nothing continues the turn on its own. Queued messages stay saved while the limit blocks the thread. They run after the continuation finishes. If the queue was held by a restart, resume it then. diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index b10f45ab6dec3..d1b0dca92206a 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -741,6 +741,7 @@ export const OrchestrationV2Run = Schema.Struct({ checkpointId: Schema.NullOr(CheckpointId), contextHandoffId: Schema.NullOr(ContextHandoffId), restartContinuationOfRunId: Schema.optional(RunId), + manualContinuationOfRunId: Schema.optional(RunId), usageLimitContinuationOfRunId: Schema.optional(RunId), delegatedTaskId: Schema.optional(NodeId), workStartedAt: Schema.optional(Schema.DateTimeUtc), diff --git a/packages/provider-core/src/server/ProviderAdapter.ts b/packages/provider-core/src/server/ProviderAdapter.ts index 77e13b2ce0216..58f1773974069 100644 --- a/packages/provider-core/src/server/ProviderAdapter.ts +++ b/packages/provider-core/src/server/ProviderAdapter.ts @@ -277,6 +277,15 @@ export class ProviderAdapterThreadInUseError extends Schema.TaggedError()( + "ProviderResumeUnsupportedError", + { driver: ProviderDriverKind, providerThreadId: ProviderThreadId }, +) { + override get message(): string { + return "This provider cannot resume without a new message. Send a message to continue."; + } +} + export class ProviderCompactionUnsupportedError extends Schema.TaggedError()( "ProviderCompactionUnsupportedError", { @@ -491,6 +500,7 @@ export const ProviderAdapterV2Error = Schema.Union([ ProviderAdapterRollbackThreadError, ProviderAdapterForkThreadError, ProviderAdapterTurnStartError, + ProviderResumeUnsupportedError, ProviderAdapterSteerRunUnsupportedError, ProviderAdapterSteerRunError, ProviderAdapterSteerDeclinedError, @@ -533,6 +543,7 @@ export interface ProviderAdapterV2TurnInput { readonly nativeThreadHasTurns?: boolean; readonly providerTurnOrdinal: number; readonly restartContinuationOfRunId?: RunId; + readonly resume?: true; readonly attemptId: RunAttemptId; readonly rootNodeId: NodeId; readonly providerThread: OrchestrationV2ProviderThread; @@ -544,6 +555,24 @@ export interface ProviderAdapterV2TurnInput { readonly appContext?: ReadonlyArray<{ readonly key: string; readonly text: string }>; } +export type ProviderAdapterV2ResumeTurnInput = Omit< + ProviderAdapterV2TurnInput, + "message" | "resume" +> & { + readonly messageId: MessageId; +}; + +export function resumeTurnInput( + input: ProviderAdapterV2ResumeTurnInput, +): ProviderAdapterV2TurnInput { + const { messageId, ...turn } = input; + return { + ...turn, + resume: true, + message: { messageId, text: "", attachments: [], createdBy: "user", creationSource: "server" }, + }; +} + export interface ProviderAdapterV2SteerInput { readonly threadId: ThreadId; readonly runId: RunId; @@ -707,6 +736,9 @@ export interface ProviderAdapterV2SessionRuntime { readonly startTurn: ( input: ProviderAdapterV2TurnInput, ) => Effect.Effect; + readonly resumeTurn?: ( + input: ProviderAdapterV2ResumeTurnInput, + ) => Effect.Effect; readonly compactThread?: ( input: ProviderAdapterV2TurnInput, ) => Effect.Effect; diff --git a/packages/provider-opencode/src/server/adapter.test.ts b/packages/provider-opencode/src/server/adapter.test.ts index 0fa02bcfa035e..61c6191dca274 100644 --- a/packages/provider-opencode/src/server/adapter.test.ts +++ b/packages/provider-opencode/src/server/adapter.test.ts @@ -202,6 +202,7 @@ const makeOpenCodeRuntimeHarness = Effect.fn("makeOpenCodeRuntimeHarness")(funct text = "hello", startProviderThread: OrchestrationV2ProviderThread = providerThread, turnPolicy: ProviderAdapter.ProviderAdapterV2RuntimePolicy = policy, + resume = false, ) => runtime.startTurn({ appThread: { @@ -234,6 +235,7 @@ const makeOpenCodeRuntimeHarness = Effect.fn("makeOpenCodeRuntimeHarness")(funct attemptId: RunAttemptId.make(`attempt-opencode-${suffix}`), rootNodeId: NodeId.make(`node-opencode-${suffix}`), providerThread: startProviderThread, + ...(resume ? { resume: true as const } : {}), message: { createdBy: "user", creationSource: "web", @@ -264,6 +266,7 @@ describe("OpenCodeAdapterV2", () => { ["unknown", "prompt"], ["failure", "prompt"], ["unset", "prompt"], + ["unset", "resume"], ] as const)("normalizes reasoning variants for %s inventory at %s dispatch", ([kind, request]) => Effect.gen(function* () { const suffix = `variant-${kind}-${request}`; @@ -329,10 +332,16 @@ describe("OpenCodeAdapterV2", () => { ], }, ); - yield* harness.startTurn(request === "command" ? "/review staged changes" : "hello"); + yield* harness.startTurn( + request === "command" ? "/review staged changes" : request === "resume" ? "" : "hello", + harness.providerThread, + harness.policy, + request === "resume", + ); assert.equal(inputs.length, 1); const payload = inputs[0]!; assert.equal(payload.agent, "plan"); + if (request === "resume") assert.deepEqual(payload.parts, []); if (kind === "supported" || kind === "unknown" || kind === "failure") { assert.equal(payload.variant, "medium"); } else { diff --git a/packages/provider-opencode/src/server/adapter.ts b/packages/provider-opencode/src/server/adapter.ts index d841e819c2102..9da0acc1e2018 100644 --- a/packages/provider-opencode/src/server/adapter.ts +++ b/packages/provider-opencode/src/server/adapter.ts @@ -3090,6 +3090,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function const resolvePromptParts = Effect.fnUntraced(function* ( turnInput: ProviderAdapter.ProviderAdapterV2TurnInput, ) { + if (turnInput.resume) return []; const text = (yield* providerMessageTextWithAttachmentPaths({ text: turnInput.message.text, attachments: turnInput.message.attachments, @@ -3394,6 +3395,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function ...turnInput, message: { ...turnInput.message, text: "/compact" }, }), + resumeTurn: (input) => runtimeSession.startTurn(ProviderAdapter.resumeTurnInput(input)), startTurn: Effect.fn("OpencodeAdapter.startTurn")( function* (turnInput) { if (nativeStreamFailure !== null) { diff --git a/packages/provider-opencode/src/server/v2/adapter.ts b/packages/provider-opencode/src/server/v2/adapter.ts index 15f30b7fa0497..95cee021b2774 100644 --- a/packages/provider-opencode/src/server/v2/adapter.ts +++ b/packages/provider-opencode/src/server/v2/adapter.ts @@ -4540,7 +4540,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr const mentions = skillMentions(text); const skills = mentions.length === 0 ? [] : yield* skillsMentioned(location, mentions); const files = yield* promptFiles(turnInput.message.attachments); - const messageText = yield* promptText(turnInput); + const messageText = turnInput.resume ? "" : yield* promptText(turnInput); if (!sending()) return; return yield* client.session .prompt({ @@ -4704,6 +4704,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr ), compactThread: (turnInput) => runtime.startTurn({ ...turnInput, message: { ...turnInput.message, text: "/compact" } }), + resumeTurn: (input) => runtime.startTurn(ProviderAdapter.resumeTurnInput(input)), startTurn: Effect.fn("OpenCode2Adapter.startTurn")( function* (turnInput) { // Waited out before the session's gate, so a rollback or fork is not From 4574c6013d52867878038967297a885f1692383c Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:27:24 +0100 Subject: [PATCH 023/192] fix(web): preserve saved notebook traceback frame boundaries (#36602) Co-authored-by: Julius Marminge --- .../components/files/FilePreviewPanel.test.ts | 58 +++++++++++++++++++ .../files/extendedDocumentPreview.ts | 12 +++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index dc68a540842cd..384a3ca80683c 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -296,6 +296,64 @@ Third}`; }, ], }); + // IPython traceback frames can omit trailing newlines or place ANSI resets after them. + const failed = openNotebook( + bytes( + JSON.stringify({ + nbformat: 4, + cells: [ + { + cell_type: "code", + source: "fail()", + outputs: [ + { + output_type: "error", + traceback: [ + "-----", + "ValueError Traceback (most recent call last)", + "Cell In[1], line 4\n----> 4 fail()\n", + "Cell In[1], line 2, in fail()\n----> 2 raise ValueError()\n", + "ValueError: preview proof", + ], + }, + { + output_type: "error", + traceback: [ + "Traceback (most recent call last):\n", + " Cell In[2], line 5\n fail()\n", + "ValueError: preview proof\n", + ], + }, + { + output_type: "error", + traceback: [ + "\u001b[31mTraceback (most recent call last):\n\u001b[0m", + "\u001b[32mCell In[3], line 1\n----> 1 fail()\n\u001b[0m\u001b[39m", + "\u001b[31mValueError: preview proof\u001b[0m", + "final entry", + ], + }, + ], + }, + ], + }), + ), + ); + if (failed.type !== "notebook") throw new Error("Expected notebook"); + expect(failed.cells[0]!.outputs).toEqual([ + { + kind: "text", + text: "-----\nValueError Traceback (most recent call last)\nCell In[1], line 4\n----> 4 fail()\nCell In[1], line 2, in fail()\n----> 2 raise ValueError()\nValueError: preview proof", + }, + { + kind: "text", + text: "Traceback (most recent call last):\n Cell In[2], line 5\n fail()\nValueError: preview proof\n", + }, + { + kind: "text", + text: "\u001b[31mTraceback (most recent call last):\n\u001b[0m\u001b[32mCell In[3], line 1\n----> 1 fail()\n\u001b[0m\u001b[39m\u001b[31mValueError: preview proof\u001b[0m\nfinal entry", + }, + ]); expect(() => openNotebook(bytes('{"nbformat":3,"cells":[]}'))).toThrow("version 4"); expect(() => openNotebook( diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index abd07afb23c68..5f30fe77c34b9 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -423,6 +423,14 @@ function notebookText(value: unknown, limit = 256_000): string { if (result.length > limit) throw new Error("A notebook cell is too large to preview."); return result; } +// IPython traceback frames may omit a final newline or follow it with ANSI SGR sequences. +function tracebackLines(value: unknown): unknown { + if (!Array.isArray(value)) return value; + const frames = value.filter((frame): frame is string => typeof frame === "string"); + return frames.map((frame, index) => + index && !/\n(?:\u001b\[[\d;:]*m)*$/.test(frames[index - 1]!) ? `\n${frame}` : frame, + ); +} export function openNotebook(bytes: ArrayBuffer): OfficeWorkerResult { if (bytes.byteLength > 8 * 1024 * 1024) throw new Error("Notebook preview limited to files under 8 MB."); @@ -454,7 +462,9 @@ export function openNotebook(bytes: ArrayBuffer): OfficeWorkerResult { if (html.trim() && htmlImagesFit(html)) return { kind: "html" as const, text: html }; return { kind: "text" as const, - text: notebookText(output.text ?? data["text/plain"] ?? output.traceback), + text: notebookText( + output.text ?? data["text/plain"] ?? tracebackLines(output.traceback), + ), }; }) .filter((output) => output.text.trim().length > 0), From d6244ac5a337b6c71154715d197b3d915b8584a8 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:33:33 +0100 Subject: [PATCH 024/192] fix(web): preserve literal entities in EPUB CDATA (#36595) Co-authored-by: Julius Marminge --- .../src/components/files/FilePreviewPanel.test.ts | 7 ++++--- .../src/components/files/extendedDocumentPreview.ts | 12 ++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index 384a3ca80683c..d9361ba21244a 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -221,12 +221,12 @@ Third}`; it("resolves scoped ODF namespaces and explicit spreadsheet whitespace", async () => { const archive = new JSZip().file( "content.xml", - '

AccountNameABC

', + '

AccountNameABC &

', ); const result = await openDocument(archive, "ods"); if (result.type !== "ods") throw new Error("Expected workbook"); expect(result.sheets[0]!.name).toBe("Accounts & names"); - expect(result.sheets[0]!.rows[0]).toEqual(["Account Name A\tB\nC"]); + expect(result.sheets[0]!.rows[0]).toEqual(["Account Name A\tB\nC & &"]); }); it("stops repeated slide images before encoding beyond the output budget", async () => { @@ -439,7 +439,7 @@ Third}`; ) .file( "Book/two&&&.xhtml", - 'Second

Second chapter

A B © — ≂̸ &lt;

© ≂̸', + 'Second

Second chapter

A B © — ≂̸ &lt;

© ≂̸', ) .file( "Book/cover.png", @@ -453,6 +453,7 @@ Third}`; if (result.type !== "document") throw new Error("Expected document"); expect(result.pages.map((page) => page.title)).toEqual(["Second", "First"]); expect(result.pages[0]!.html).toContain("A\u00a0B © — ≂̸ &lt;"); + expect(result.pages[0]!.html).toContain("
Use &amp; and &lt; literally
"); expect(result.pages[0]!.html).toContain('alt="© ≂̸"'); expect(result.pages[1]!.html).toContain('src="data:image/png;base64,'); expect(result.pages[1]!.html).not.toMatch(/https:|script|alert/); diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index 5f30fe77c34b9..6b840a00543a6 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -12,6 +12,7 @@ import { type OfficeWorkerResult, type NotebookCell } from "./officePreviewProto const ODS_PREVIEW_MAX_ROWS = 500; const ODS_PREVIEW_MAX_COLUMNS = 50; +// `text` and `attrs` hold decoded values: escaped text is decoded once, CDATA stays literal. type XmlNode = { name: string; attrs: Record; children: XmlNode[]; text: string }; type OrderedXml = { [key: string]: OrderedXml[] | Record | string }; const escape = (value: string) => @@ -47,6 +48,7 @@ function parseXml(source: string): XmlNode { processEntities: false, trimValues: false, parseTagValue: false, + cdataPropName: "#cdata", }); const ordered = parser.parse(source) as OrderedXml[]; let count = 0; @@ -60,6 +62,12 @@ function parseXml(source: string): XmlNode { if (++count > 100_000) throw new Error("This document has too much content to preview."); const name = Object.keys(item).find((key) => key !== ":@") ?? "#text"; const value = item[name]; + if (name === "#cdata") { + const literal = Array.isArray(value) + ? value.map((part) => part["#text"] ?? "").join("") + : ""; + return { name: "#text", attrs: {}, children: [], text: literal }; + } const attrs = Object.fromEntries( Object.entries((item[":@"] ?? {}) as Record).map(([key, value]) => [ key, @@ -91,7 +99,7 @@ function parseXml(source: string): XmlNode { Object.entries(attrs).map(([key, value]) => [expanded(key, true), value]), ), children: Array.isArray(value) ? convert(value, depth + 1, namespaces) : [], - text: typeof value === "string" ? value : "", + text: typeof value === "string" ? decodeEntities(value) : "", }; }); } @@ -116,7 +124,7 @@ function text(node: XmlNode): string { if (node.name === "text:s") return " ".repeat(repeat(node.attrs["text:c"], 100)); if (node.name === "text:tab") return "\t"; if (node.name === "text:line-break") return "\n"; - return node.name === "#text" ? decodeEntities(node.text) : node.children.map(text).join(""); + return node.name === "#text" ? node.text : node.children.map(text).join(""); } function archivePath(base: string, href: string): string | null { if (/^(?:[a-z][a-z\d+.-]*:|\/|\\)/i.test(href)) return null; From 953ce60d12348948ceb12da92cc9271a2cfa9975 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:35:07 +0100 Subject: [PATCH 025/192] fix(web): changed root files with a leading space open in the diff panel (#36606) Co-authored-by: Julius Marminge --- .../src/components/DiffPanel.browser.test.tsx | 34 +++++++++++++++++++ apps/web/src/components/DiffPanel.tsx | 4 ++- apps/web/src/diffPanelStore.ts | 2 +- .../contracts/src/orchestrationV2.test.ts | 11 ++++++ packages/contracts/src/orchestrationV2.ts | 3 +- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/DiffPanel.browser.test.tsx b/apps/web/src/components/DiffPanel.browser.test.tsx index 51ba65933dff7..503f9b60c1b5d 100644 --- a/apps/web/src/components/DiffPanel.browser.test.tsx +++ b/apps/web/src/components/DiffPanel.browser.test.tsx @@ -302,3 +302,37 @@ it("says a turn diff cut off at the server's size limit is incomplete", async () .element(screen.getByText("This preview exceeds the size limit. Changes shown are incomplete.")) .toBeVisible(); }); + +// Paths as git's numstat reports them; the parsed patch trims the trailing space from its name. +it.each([ + { path: " leading.ts", name: " leading.ts" }, + { path: "src/trailing.ts ", name: "src/trailing.ts" }, +])("reveals the turn file $path picked from the changed files", async ({ path, name }) => { + using context = setup("origin/orphan"); + state.diffFilesCollapsed = true; + state.checkpoints = [ + { + runId: "run", + checkpointTurnCount: 1, + checkpointRef: "ref", + status: "ready", + files: [], + assistantMessageId: null, + completedAt: "2026-01-01T00:00:00Z", + }, + ]; + state.checkpointDiff = { + diff: `diff --git a/${path} b/${path}\nnew file mode 100644\n--- /dev/null\n+++ b/${path}\t\n@@ -0,0 +1 @@\n+revealed\n`, + }; + useDiffPanelStore.getState().selectTurn(context.threadRef, RunId.make("run"), path); + await using screen = await render( + +
+ +
+
, + ); + await expect + .element(screen.getByRole("button", { name: `Collapse ${name}`, exact: true })) + .toHaveAttribute("aria-expanded", "true"); +}); diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 2b0a6a78d4ec2..9dad875a2a8a6 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -336,7 +336,9 @@ export default function DiffPanel({ diffSelection.kind === "branch" || diffSelection.kind === "commit" ? diffSelection.baseRef : null; - const selectedFilePath = diffSelection.kind === "turn" ? diffSelection.filePath : null; + // @pierre/diffs trims trailing whitespace from parsed file names; leading whitespace survives. + const selectedFilePath = + diffSelection.kind === "turn" ? diffSelection.filePath?.trimEnd() || null : null; const selectedFileRevealRequestId = diffSelection.kind === "turn" ? diffSelection.revealRequestId : 0; const selectedTurn = diff --git a/apps/web/src/diffPanelStore.ts b/apps/web/src/diffPanelStore.ts index 2f685005c1e74..ddf454395443d 100644 --- a/apps/web/src/diffPanelStore.ts +++ b/apps/web/src/diffPanelStore.ts @@ -105,7 +105,7 @@ export const useDiffPanelStore = create()( [threadKey]: { kind: "turn", turnId, - filePath: filePath?.trim() || null, + filePath: filePath ?? null, revealRequestId: previous?.kind === "turn" ? previous.revealRequestId + 1 : 1, }, }, diff --git a/packages/contracts/src/orchestrationV2.test.ts b/packages/contracts/src/orchestrationV2.test.ts index 3782307171e87..76f78f536d7c5 100644 --- a/packages/contracts/src/orchestrationV2.test.ts +++ b/packages/contracts/src/orchestrationV2.test.ts @@ -5,6 +5,7 @@ import * as Schema from "effect/Schema"; import { NodeId, RunAttemptId, TrimmedNonEmptyString } from "./index.ts"; import { latestProviderTurnForAttempt, + OrchestrationV2CheckpointFileSummary, OrchestrationV2ProviderCapabilities, OrchestrationV2ProviderThread, OrchestrationV2ProviderThreadJson, @@ -22,6 +23,7 @@ const now = DateTime.makeUnsafe("2026-04-20T00:00:00.000Z"); const decodeOrchestrationV2TurnItemJson = Schema.decodeUnknownSync(OrchestrationV2TurnItemJson); const encodeOrchestrationV2TurnItemJson = Schema.encodeSync(OrchestrationV2TurnItemJson); const decodeOrchestrationV2Subagent = Schema.decodeUnknownSync(OrchestrationV2Subagent); +const decodeCheckpointFileSummary = Schema.decodeSync(OrchestrationV2CheckpointFileSummary); const encodeOrchestrationV2SubagentJson = Schema.encodeSync(OrchestrationV2SubagentJson); const decodeOrchestrationV2ProviderThreadJson = Schema.decodeUnknownSync( OrchestrationV2ProviderThreadJson, @@ -397,6 +399,15 @@ describe("orchestration V2 contracts", () => { }, ); + it.each([" leading.ts", "src/trailing.ts "])( + "decodes the checkpoint file path %j exactly", + (path) => { + expect( + decodeCheckpointFileSummary({ path, kind: "added", additions: 1, deletions: 0 }).path, + ).toBe(path); + }, + ); + it("decodes historical provider-thread JSON without pendingBackgroundTasks as empty roster", () => { const providerThread = decodeOrchestrationV2ProviderThreadJson({ id: "provider-thread-1", diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index d1b0dca92206a..053fc644fe1c0 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -1455,7 +1455,8 @@ export const OrchestrationV2PlanArtifact = Schema.Union([ export type OrchestrationV2PlanArtifact = typeof OrchestrationV2PlanArtifact.Type; export const OrchestrationV2CheckpointFileSummary = Schema.Struct({ - path: TrimmedNonEmptyString, + // Exact, as git reports it: file names may start or end with whitespace. + path: Schema.NonEmptyString, kind: TrimmedNonEmptyString, additions: NonNegativeInt, deletions: NonNegativeInt, From 43ba62b9fcf9e7bb34e921e76e7b2f609bb5d40a Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:35:52 +0100 Subject: [PATCH 026/192] fix(server): thread reads wait for pending background work (#36610) Co-authored-by: Julius Marminge --- .../OrchestratorMcpService.activity.test.ts | 41 ++++++++++++++----- apps/server/src/mcp/OrchestratorMcpService.ts | 17 +++++--- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/apps/server/src/mcp/OrchestratorMcpService.activity.test.ts b/apps/server/src/mcp/OrchestratorMcpService.activity.test.ts index 2ff2125a51800..cd8c5064476b2 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.activity.test.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.activity.test.ts @@ -105,7 +105,7 @@ function baseThread(input: { function makeRun(input: { readonly id: RunId; readonly ordinal: number; - readonly status: "running" | "waiting" | "cancelled" | "queued" | "completed"; + readonly status: "running" | "waiting" | "cancelled" | "queued" | "completed" | "failed"; readonly instanceId?: ProviderInstanceId; }) { return { @@ -125,6 +125,15 @@ function makeRun(input: { it("readThread preserves caller access errors and prefers activity-run status over a newer cancelled queued run", async () => { let callerUnavailable = false; + let pendingBackgroundTasks: OrchestrationV2ThreadShell["pendingBackgroundTasks"] = [ + { + taskId: "task-subagent", + kind: "subagent", + parentTaskId: "task-parent", + providerInstanceId: parentInstanceId, + childThreadId, + }, + ]; const pullRequestLink = (number: number, state: "open" | "merged", watched: boolean) => ({ host: "github.com", repository: "acme/web", @@ -202,15 +211,7 @@ it("readThread preserves caller access errors and prefers activity-run status ov ? ({ ...projection.thread, blockedOnThreadWait: true, - pendingBackgroundTasks: [ - { - taskId: "task-subagent", - kind: "subagent", - parentTaskId: "task-parent", - providerInstanceId: parentInstanceId, - childThreadId, - }, - ], + pendingBackgroundTasks, } as unknown as OrchestrationV2ThreadShell) : null, ), @@ -263,6 +264,26 @@ it("readThread preserves caller access errors and prefers activity-run status ov expect(laterPage.thread.pullRequests).toBeUndefined(); expect(laterPage.thread.pullRequestCount).toBe(8); + Object.assign(projection, { + runs: [makeRun({ id: activeRunId, ordinal: 1, status: "completed" })], + }); + const awaiting = yield* service.readThread(makeScope(), { threadId: parentThreadId }); + expect(awaiting.thread.status).toBe("waiting"); + + Object.assign(projection, { + runs: [makeRun({ id: activeRunId, ordinal: 1, status: "failed" })], + }); + const failed = yield* service.readThread(makeScope(), { threadId: parentThreadId }); + expect(failed.thread.status).toBe("failed"); + + Object.assign(projection, { + runs: [makeRun({ id: activeRunId, ordinal: 1, status: "completed" })], + }); + // An unawaited command (a dev server) does not hold the thread once its turn ends. + pendingBackgroundTasks = [{ taskId: "dev-server", kind: "command" }]; + const released = yield* service.readThread(makeScope(), { threadId: parentThreadId }); + expect(released.thread.status).toBe("completed"); + callerUnavailable = true; const denied = yield* service .readThread({ ...makeScope(), capabilities: new Set() }, { threadId: parentThreadId }) diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index 82948f7c5b055..d7ef5f788f0ba 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -865,11 +865,18 @@ function threadPullRequests( } // Awaited background work keeps a thread live after its turn ends, as auto-settle and the sidebar treat it. +function threadStatus( + activeStatus: OrchestratorMcpThreadListItem["status"] | null | undefined, + settledStatus: OrchestratorMcpThreadListItem["status"], + pendingBackgroundTasks: OrchestrationV2ThreadShell["pendingBackgroundTasks"], +) { + if (activeStatus != null) return activeStatus; + if (settledStatus === "failed") return settledStatus; + return backgroundWorkHoldsCompletion(pendingBackgroundTasks ?? []) ? "waiting" : settledStatus; +} + function listedThreadStatus(shell: OrchestrationV2ThreadShell) { - if (shell.activityRunStatus != null) return shell.activityRunStatus; - return backgroundWorkHoldsCompletion(shell.pendingBackgroundTasks ?? []) - ? "waiting" - : shell.status; + return threadStatus(shell.activityRunStatus, shell.status, shell.pendingBackgroundTasks); } function listItemFromShell( @@ -920,7 +927,7 @@ function threadDetail( link: threadLinkFor(scope, projection.thread.id, projection.thread.title), createdBy: projection.thread.createdBy, creationSource: projection.thread.creationSource, - status: active?.status ?? latest?.status ?? "idle", + status: threadStatus(active?.status, latest?.status ?? "idle", shell.pendingBackgroundTasks), latestRunId: latest?.id ?? null, activeRunId: active?.id ?? null, providerInstanceId: projection.thread.modelSelection.instanceId, From 91fa913be9c280dcf9da6ad655aa6bac7c8ce74a Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:36:05 +0100 Subject: [PATCH 027/192] fix(server): partial import retries no longer certify missing messages (#36604) Co-authored-by: Julius Marminge --- .../src/project/AgentSessionImporter.test.ts | 33 +++++++++++++++++++ .../src/project/AgentSessionImporter.ts | 21 ++++++------ .../server/src/project/AgentSessionScanner.ts | 5 +-- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/apps/server/src/project/AgentSessionImporter.test.ts b/apps/server/src/project/AgentSessionImporter.test.ts index 4378a7fde8470..23264b2eeefe6 100644 --- a/apps/server/src/project/AgentSessionImporter.test.ts +++ b/apps/server/src/project/AgentSessionImporter.test.ts @@ -453,6 +453,39 @@ it.effect( }).pipe(Effect.scoped, Effect.provide(persistence)), ); +it.effect("does not certify new native history after a partial import retry", () => + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + const projections = yield* ProjectionStore.ProjectionStoreV2; + const runtimes = yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository; + const original = candidate(CODEX_MODEL_SELECTION, "partial-retry"); + const threadId = importId(original); + const importer = yield* makeImporter([original]); + yield* sql`CREATE TEMP TRIGGER fail_runtime BEFORE INSERT ON provider_session_runtime BEGIN SELECT RAISE(ABORT, 'test runtime failure'); END`; + expect(yield* importer.run).toEqual({ importedCount: 0, skippedCount: 1 }); + yield* sql`DROP TRIGGER fail_runtime`; + expect(yield* runtimes.list()).toEqual([]); + const continued = candidate(CODEX_MODEL_SELECTION, original.source.providerSessionId); + continued.source.size = 200; + continued.source.mtimeMs = 3; + continued.thread.messages.push( + { role: "user", text: "Continue outside T3", createdAt: timestamp }, + { role: "assistant", text: "Native continuation", createdAt: timestamp }, + ); + // The thread holds only the original messages, so the continued transcript stays unrecorded. + expect(yield* (yield* makeImporter([continued])).run).toEqual({ + importedCount: 0, + skippedCount: 1, + }); + expect(yield* runtimes.list()).toEqual([]); + expect( + (yield* projections.getThreadRecords(threadId, ["messages"])).messages.map( + (message) => message.text, + ), + ).toEqual(["Fix it", "Fixed"]); + }).pipe(Effect.scoped, Effect.provide(persistence)), +); + it.effect("does not record a changed transcript as imported without its new messages", () => Effect.gen(function* () { const projections = yield* ProjectionStore.ProjectionStoreV2; diff --git a/apps/server/src/project/AgentSessionImporter.ts b/apps/server/src/project/AgentSessionImporter.ts index 6200fc11188fb..6f88b929260a6 100644 --- a/apps/server/src/project/AgentSessionImporter.ts +++ b/apps/server/src/project/AgentSessionImporter.ts @@ -313,7 +313,9 @@ const make = Effect.gen(function* () { }, { onConflict: "ignore" }, ); - const existing = yield* Effect.option(orchestrator.getThreadRecords(threadId, [])); + const existing = yield* Effect.option( + orchestrator.getThreadRecords(threadId, ["messages"]), + ); if (Option.isSome(existing)) { if (existing.value.thread.projectId !== input.projectId) { return yield* new AgentSessionThreadProjectConflictError({ @@ -325,16 +327,15 @@ const make = Effect.gen(function* () { if (existing.value.thread.historyOrigin !== "v1_import") { return yield* new AgentSessionThreadModifiedError({ threadId }); } - // Other recorded transcripts mean this one changed after the import, and the - // thread does not hold its new messages, so it must stay unrecorded. - const recorded = Option.flatMap(yield* runtimes.getByThreadId({ threadId }), (row) => - AgentSessionScanner.decodeImportedTranscriptPayload(row.runtimePayload), - ).pipe(Option.flatMapNullishOr((payload) => payload.importedTranscripts)); + // A transcript can change after the import, so record it only when the + // thread already holds exactly its messages. + const stored = existing.value.messages; if ( - Option.isSome(recorded) && - recorded.value.length > 0 && - !recorded.value.some((imported) => - AgentSessionScanner.sameTranscriptIdentity(imported, source), + stored.length !== thread.messages.length || + stored.some( + (message, index) => + message.role !== thread.messages[index]?.role || + message.text !== thread.messages[index]?.text, ) ) { return false; diff --git a/apps/server/src/project/AgentSessionScanner.ts b/apps/server/src/project/AgentSessionScanner.ts index 0de2e84b1f6e3..92ccc4efcd746 100644 --- a/apps/server/src/project/AgentSessionScanner.ts +++ b/apps/server/src/project/AgentSessionScanner.ts @@ -802,10 +802,7 @@ type TranscriptIdentity = Pick< const fileIdKey = (inode: TranscriptIdentity["inode"]) => (inode === null ? null : String(inode)); -export function sameTranscriptIdentity( - left: TranscriptIdentity, - right: TranscriptIdentity, -): boolean { +function sameTranscriptIdentity(left: TranscriptIdentity, right: TranscriptIdentity): boolean { // Unsafe legacy numbers may already have lost file ID precision. if ( (typeof left.inode === "number" && !Number.isSafeInteger(left.inode)) || From 4b99420d8e13d9cbbdfd60374583202ecff2dc0f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 01:36:17 -0800 Subject: [PATCH 028/192] fix(server): background subagents stop running when their Claude session is released (#36569) --- apps/server/src/orchestration-v2/EventSink.ts | 38 +++++++ .../src/orchestration-v2/ProjectionStore.ts | 2 +- .../ProviderSessionManager.test.ts | 60 ++++++++++- .../ProviderSessionManager.ts | 102 +++++++++++++++++- 4 files changed, 197 insertions(+), 5 deletions(-) diff --git a/apps/server/src/orchestration-v2/EventSink.ts b/apps/server/src/orchestration-v2/EventSink.ts index edae3077912ec..6b27f90aa6d5c 100644 --- a/apps/server/src/orchestration-v2/EventSink.ts +++ b/apps/server/src/orchestration-v2/EventSink.ts @@ -3,6 +3,7 @@ import { type OrchestrationV2Run, OrchestrationV2DomainEvent, type OrchestrationV2ProviderThread, + type OrchestrationV2Subagent, type OrchestrationV2TurnItem, OrchestrationV2StoredEvent, ProviderThreadId, @@ -180,6 +181,17 @@ export interface EventSinkV2Shape { }, EventSinkV2Error >; + /** Commits only if the stored subagent still equals expected. */ + readonly writeIfSubagentUnchanged: (input: { + readonly expected: OrchestrationV2Subagent; + readonly events: ReadonlyArray; + }) => Effect.Effect< + { + readonly committed: boolean; + readonly storedEvents: ReadonlyArray; + }, + EventSinkV2Error + >; readonly commitCommand: (input: { readonly commandId: CommandId; readonly threadId: ThreadId; @@ -712,6 +724,26 @@ const layerBase: Layer.Layer< ); }); + const writeIfSubagentUnchangedEffect = Effect.fn( + "orchestrationV2.EventSink.writeIfSubagentUnchanged", + )(function* (input: Parameters[0]) { + yield* Effect.annotateCurrentSpan({ + "orchestration_v2.event_count": input.events.length, + "orchestration_v2.subagent_id": input.expected.id, + }); + const expectedJson = yield* ProjectionStore.encodeSubagentPayload(input.expected); + return yield* writeIfStoredPayloadMatches( + sql<{ readonly payload_json: string }>` + SELECT payload_json + FROM orchestration_v2_projection_subagents + WHERE subagent_id = ${input.expected.id} + LIMIT 1 + `, + expectedJson, + input.events, + ); + }); + const existingCommandResult = /* This helper runs for individual orchestration events; the enclosing operation owns the span. */ Effect.fnUntraced(function* (commandId: CommandId) { @@ -1118,6 +1150,12 @@ const layerBase: Layer.Layer< (cause) => new EventSinkWriteError({ eventCount: input.events.length, cause }), ), ), + writeIfSubagentUnchanged: (input) => + writeIfSubagentUnchangedEffect(input).pipe( + Effect.mapError( + (cause) => new EventSinkWriteError({ eventCount: input.events.length, cause }), + ), + ), commitCommand: (input) => commitCommandEffect(input).pipe( Effect.mapError( diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index 5043b274941c1..6d845450c79da 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -1205,7 +1205,7 @@ const encodeRunAttemptPayload = Schema.encodeEffect( const encodeNodePayload = Schema.encodeEffect( Schema.fromJsonString(OrchestrationV2ExecutionNodeJsonSchema), ); -const encodeSubagentPayload = Schema.encodeEffect( +export const encodeSubagentPayload = Schema.encodeEffect( Schema.fromJsonString(OrchestrationV2SubagentJsonSchema), ); const encodeProviderSessionPayload = Schema.encodeEffect( diff --git a/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts b/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts index 5740faf8f8757..09441e2659166 100644 --- a/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts +++ b/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts @@ -3309,7 +3309,53 @@ const openSessionWithBackgroundCommand = (fixtureName: string) => }); }); yield* Effect.forEach(Object.values(seededToolCalls), writeToolCall); + // A background native subagent that outlived its completed run; Claude records no provider thread on it. + const nativeSubagentRunId = idAllocator.derive.run({ threadId, ordinal: 1 }); + const nativeSubagentId = idAllocator.derive.nodeFromProviderItem({ + driver: CODEX_DRIVER, + nativeItemId: "background-agent", + }); + yield* eventSink.write({ + events: [ + { + id: yield* idAllocator.allocate.event({ threadId }), + type: "subagent.updated", + threadId, + occurredAt: now, + payload: { + id: nativeSubagentId, + threadId, + runId: nativeSubagentRunId, + parentNodeId: idAllocator.derive.rootNode({ runId: nativeSubagentRunId }), + origin: "provider_native", + createdBy: "agent", + driver: CODEX_DRIVER, + providerInstanceId: modelSelection.instanceId, + providerThreadId: null, + childThreadId: null, + nativeTaskRef: { + driver: CODEX_DRIVER, + nativeId: "background-agent", + strength: "strong", + }, + prompt: "sleep 150; echo done", + title: "Background agent", + model: null, + status: "running", + result: null, + startedAt: now, + completedAt: null, + updatedAt: now, + }, + }, + ], + }); yield* manager.open({ threadId, providerSessionId, modelSelection, runtimePolicy }); + const nativeSubagentStatus = Effect.gen(function* () { + const projectionStore = yield* ProjectionStore.ProjectionStoreV2; + const projection = yield* projectionStore.getThreadProjection(threadId); + return projection.subagents.find((subagent) => subagent.id === nativeSubagentId)?.status; + }); const toolCallStatus = (id: OrchestrationV2TurnItem["id"]) => Effect.gen(function* () { const projectionStore = yield* ProjectionStore.ProjectionStoreV2; @@ -3345,6 +3391,7 @@ const openSessionWithBackgroundCommand = (fixtureName: string) => writeToolCall, toolCalls, toolCallStatus, + nativeSubagentStatus, }; }); @@ -3356,8 +3403,15 @@ it.effect.each(["detach", "stream failure", "provider stop", "early close"] as c const state = yield* Ref.make(emptyState); const effect = Effect.gen(function* () { const manager = yield* ProviderSessionManager.ProviderSessionManagerV2; - const { threadId, providerSessionId, rosters, toolCalls, seededToolCalls, writeToolCall } = - yield* openSessionWithBackgroundCommand("provider-session-manager-detach-roster"); + const { + threadId, + providerSessionId, + rosters, + toolCalls, + seededToolCalls, + writeToolCall, + nativeSubagentStatus, + } = yield* openSessionWithBackgroundCommand("provider-session-manager-detach-roster"); const runtime = Option.getOrThrow(yield* manager.get(providerSessionId)); const subscription = yield* runtime.subscribeEvents!; const consuming = yield* Deferred.make(); @@ -3433,6 +3487,8 @@ it.effect.each(["detach", "stream failure", "provider stop", "early close"] as c otherSession: "running", replacement: "running", }); + // regression: #36548 a native subagent the dead process ran stayed running after its run completed + assert.notInclude(["pending", "running", "waiting"], yield* nativeSubagentStatus); }); yield* effect.pipe( diff --git a/apps/server/src/orchestration-v2/ProviderSessionManager.ts b/apps/server/src/orchestration-v2/ProviderSessionManager.ts index eb56df9d049ac..ca54c14b2cb95 100644 --- a/apps/server/src/orchestration-v2/ProviderSessionManager.ts +++ b/apps/server/src/orchestration-v2/ProviderSessionManager.ts @@ -59,6 +59,10 @@ import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as ProviderEventIngestor from "./ProviderEventIngestor.ts"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import { makeProviderFailure } from "@t3tools/provider-core/server/failure"; +import { + endOrphanedNativeSubagent, + endRunlessRootTurns, +} from "@t3tools/provider-core/server/subagentProjection"; import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; import * as ProjectionStore from "./ProjectionStore.ts"; @@ -995,7 +999,7 @@ export const layerWithOptions = ( // Tool calls the dead process started never report an end either, and run finalization // keeps them open for a late result. Items started after the release belong to a - // replacement session. Subagent rows are left out: run finalization ends run-owned ones. + // replacement session. const writeReleasedToolCallEvents = Effect.fn( "ProviderSessionManager.writeReleasedToolCallEvents", )(function* (released: ReleasedProviderThread, releasedAt: DateTime.Utc) { @@ -1047,6 +1051,96 @@ export const layerWithOptions = ( }); }); + // Background native subagents die with the process too, often after their run completed, + // so run finalization does not end them. One started or resumed after the release belongs + // to a replacement session. + const writeReleasedSubagentEvents = Effect.fn( + "ProviderSessionManager.writeReleasedSubagentEvents", + )(function* (released: ReleasedProviderThread, releasedAt: DateTime.Utc) { + const { threadId, providerSessionId, providerThreadId, driver } = released; + const readCutOff = Effect.gen(function* () { + const { subagents, turnItems } = yield* projectionStore.getThreadRecords( + threadId, + ["subagents", "turnItems"], + { + omitSettledSubagents: true, + turnItemTypes: ["subagent"], + turnItemStatuses: activeTurnItemStatuses, + }, + ); + const cutOff = subagents.filter((subagent) => { + if ( + subagent.origin !== "provider_native" || + subagent.driver !== driver || + !isOrchestrationV2WorkActive(subagent.status) || + DateTime.isGreaterThan(subagent.startedAt ?? subagent.updatedAt, releasedAt) + ) { + return false; + } + // Record and item are written together; a record without a live item lost its end. + const item = turnItems.find( + (candidate) => candidate.type === "subagent" && candidate.subagentId === subagent.id, + ); + return item === undefined || item.providerThreadId === providerThreadId; + }); + return { subagents: cutOff, turnItems }; + }); + let pending = yield* readCutOff; + for (let pass = 0; pass < 3 && pending.subagents.length > 0; pass++) { + const { nodes } = yield* projectionStore.getThreadRecords(threadId, ["nodes"], { + nodeIds: pending.subagents.map((subagent) => subagent.id), + }); + const projection = { ...pending, nodes }; + for (const subagent of pending.subagents) { + const now = yield* DateTime.now; + const allocateEventId = () => + idAllocator.allocate.event({ threadId, providerSessionId }); + const events = yield* endOrphanedNativeSubagent({ + projection, + subagentId: subagent.id, + status: "interrupted", + now, + emitted: [], + allocateEventId, + }); + if (subagent.childThreadId !== null) { + const child = yield* projectionStore + .getThreadRecords(subagent.childThreadId, ["nodes", "providerTurns", "turnItems"], { + turnItemRunIds: [null], + turnItemStatuses: activeTurnItemStatuses, + }) + .pipe( + Effect.catchTags({ + ProjectionStoreThreadNotFoundError: () => Effect.succeed(null), + }), + ); + if (child !== null) { + events.push( + ...(yield* endRunlessRootTurns({ + threadId: subagent.childThreadId, + providerInstanceId: subagent.providerInstanceId, + projection: child, + status: "interrupted", + now, + emitted: events, + allocateEventId, + })), + ); + } + } + // A late update from the dead session's queued events or a resume rejects only this write. + yield* eventSink.writeIfSubagentUnchanged({ expected: subagent, events }); + } + pending = yield* readCutOff; + } + if (pending.subagents.length === 0) return; + yield* Effect.logWarning("orchestration-v2.provider-session-release-subagent-conflict", { + providerSessionId, + providerThreadId, + openSubagents: pending.subagents.length, + }); + }); + const writeReleasedBackgroundWorkEvents = ( released: ReadonlyArray, releasedAt: DateTime.Utc, @@ -1056,6 +1150,7 @@ export const layerWithOptions = ( (providerThread) => writeReleasedRosterEvents(providerThread).pipe( Effect.andThen(writeReleasedToolCallEvents(providerThread, releasedAt)), + Effect.andThen(writeReleasedSubagentEvents(providerThread, releasedAt)), ), { discard: true }, ); @@ -1269,7 +1364,10 @@ export const layerWithOptions = ( // Queued events can persist tool calls after the release-time scan. const afterDrain = Effect.forEach( releasedProviderThreads, - (thread) => writeReleasedToolCallEvents(thread, releasedAt), + (thread) => + writeReleasedToolCallEvents(thread, releasedAt).pipe( + Effect.andThen(writeReleasedSubagentEvents(thread, releasedAt)), + ), { discard: true }, ).pipe(logReleaseFailure(input.providerSessionId)); if (input.gracefulSubscribers === true) { From b37c959669ca3e4757bdd84053cdb2a06c03f0ac Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:38:57 +0100 Subject: [PATCH 029/192] fix(server): open colon-number project folders correctly in VS Code (#36613) Co-authored-by: Julius Marminge --- apps/server/src/process/externalLauncher.test.ts | 4 ++++ apps/server/src/process/externalLauncher.ts | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/server/src/process/externalLauncher.test.ts b/apps/server/src/process/externalLauncher.test.ts index a610a3b1709a3..4f6cabb783be9 100644 --- a/apps/server/src/process/externalLauncher.test.ts +++ b/apps/server/src/process/externalLauncher.test.ts @@ -195,6 +195,8 @@ it.effect.skipIf(windowsHost)( const codePath = path.join(binDir, "code"); yield* fileSystem.writeFileString(codePath, "#!/bin/sh\n"); yield* fileSystem.chmod(codePath, 0o755); + const colonProject = path.join(project, "build:2026"); + yield* fileSystem.makeDirectory(colonProject); const file = `${path.join(project, "file.ts")}:3:7`; const spawned: ChildProcess.StandardCommand[] = []; @@ -202,6 +204,7 @@ it.effect.skipIf(windowsHost)( const launcher = yield* ExternalLauncher.ExternalLauncher; yield* launcher.launchEditor({ editor: "vscode", cwd: project }); yield* launcher.launchEditor({ editor: "vscode", cwd: file }); + yield* launcher.launchEditor({ editor: "vscode", cwd: colonProject }); }).pipe( Effect.provide( layerTest({ @@ -218,6 +221,7 @@ it.effect.skipIf(windowsHost)( [ ["--new-window", project], ["--goto", file], + ["--new-window", colonProject], ], ); }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), diff --git a/apps/server/src/process/externalLauncher.ts b/apps/server/src/process/externalLauncher.ts index 4eecaf2c5cb2c..bb38a9d56afd4 100644 --- a/apps/server/src/process/externalLauncher.ts +++ b/apps/server/src/process/externalLauncher.ts @@ -149,9 +149,10 @@ function resolveCommandEditorArgs( case "direct-path": return [target]; case "goto": - if (Option.isSome(parsedTarget)) return ["--goto", target]; + // An existing folder wins over a position: `build:2026` is a legal folder name. // Without a window flag, VS Code's window settings may replace the user's last active window. - return isDirectory ? ["--new-window", target] : [target]; + if (isDirectory) return ["--new-window", target]; + return Option.isSome(parsedTarget) ? ["--goto", target] : [target]; case "line-column": return Option.match(parsedTarget, { onNone: () => [target], From 4121b30f78cd68c8aa033c949075ffcd9832be97 Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sat, 14 Nov 2026 20:44:11 +1100 Subject: [PATCH 030/192] fix(mobile): the open button no longer covers inline page headings (#25858) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: maria-rcks Co-authored-by: SAPHID Co-authored-by: Julius Marminge --- .../features/threads/HtmlRenderWebView.tsx | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/apps/mobile/src/features/threads/HtmlRenderWebView.tsx b/apps/mobile/src/features/threads/HtmlRenderWebView.tsx index bf22cd01a1966..b13704196468b 100644 --- a/apps/mobile/src/features/threads/HtmlRenderWebView.tsx +++ b/apps/mobile/src/features/threads/HtmlRenderWebView.tsx @@ -22,10 +22,11 @@ import { useAppearancePreferences } from "../settings/appearance/AppearancePrefe import { webViewDecelerationRate } from "../../lib/webViewDecelerationRate"; const ROW_BOTTOM_MARGIN = 8; +const ACTION_ROW_HEIGHT = 32; const FULL_SCREEN_GUTTER = 16; export function htmlRenderRowHeight(frameHeight: number) { - return frameHeight + ROW_BOTTOM_MARGIN; + return ACTION_ROW_HEIGHT + frameHeight + ROW_BOTTOM_MARGIN; } function useHtmlRenderTheme() { @@ -213,6 +214,34 @@ export function ThreadHtmlRender(props: { return ( + + {uri !== null && !failed ? ( + + navigation.navigate("ThreadAttachment", { + environmentId: String(props.environmentId), + threadId: String(props.threadId), + attachmentId, + name: fileName, + mimeType: "text/html", + sizeBytes: "0", + htmlRender: "1", + }) + } + > + + + ) : null} + {uri !== null && !failed ? ( )} - {uri !== null && !failed ? ( - - navigation.navigate("ThreadAttachment", { - environmentId: String(props.environmentId), - threadId: String(props.threadId), - attachmentId, - name: fileName, - mimeType: "text/html", - sizeBytes: "0", - htmlRender: "1", - }) - } - > - - - ) : null} ); From 02e29ce8fa57eff37b85afef0e9077658118c276 Mon Sep 17 00:00:00 2001 From: Hieu Le <78847613+hieu-lee@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:48:10 +0100 Subject: [PATCH 031/192] perf(usage): show saved totals before scanning history (#25711) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/usage/UsageService.test.ts | 67 ++++++++++++++++++++++ apps/server/src/usage/UsageService.ts | 3 +- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/apps/server/src/usage/UsageService.test.ts b/apps/server/src/usage/UsageService.test.ts index 9c616eb02ac1b..ed71046036c6d 100644 --- a/apps/server/src/usage/UsageService.test.ts +++ b/apps/server/src/usage/UsageService.test.ts @@ -522,6 +522,73 @@ describe("Codex usage history", () => { }); describe("UsageService", () => { + it.effect("keeps pricing metadata paired with the rates used during a concurrent refresh", () => + Effect.gen(function* () { + const { home, settings } = yield* setup; + const sessions = NodePath.join(home, "codex", "sessions"); + yield* Effect.promise(async () => { + await NodeFSP.mkdir(sessions, { recursive: true }); + await NodeFSP.writeFile( + NodePath.join(sessions, "session.jsonl"), + [ + { type: "session_meta", payload: { id: "session", model_provider: "openai" } }, + { type: "turn_context", payload: { model: "gpt-6-astra", turn_id: "turn" } }, + { + type: "event_msg", + timestamp: "2026-08-01T10:00:00Z", + payload: { + type: "token_count", + info: { last_token_usage: { input_tokens: 0, output_tokens: 1 } }, + }, + }, + ] + .map((record) => encodeUnknownJsonString(record)) + .join("\n") + "\n", + ); + }); + let rate = 0; + yield* Effect.gen(function* () { + const resolving = yield* Deferred.make(); + const release = yield* Deferred.make(); + const service = yield* UsageService.make.pipe( + Effect.provideService(CodexUsageHistory.CodexUsageHistory, { + resolve: () => + Deferred.succeed(resolving, undefined).pipe( + Effect.andThen(Deferred.await(release)), + Effect.as(new Map()), + ), + }), + ); + const initialPricing = yield* service.refreshRates; + yield* TestClock.adjust(61_000); + const pending = yield* service.readSummary(WINDOW).pipe(Effect.forkChild); + yield* Deferred.await(resolving); + yield* service.refreshRates; + yield* Deferred.succeed(release, undefined); + const original = yield* Fiber.join(pending); + assert.strictEqual(original.buckets[0]?.costUsd, 1); + assert.strictEqual(original.pricing.fetchedAt, initialPricing.fetchedAt); + const updated = yield* service.readSummary(WINDOW); + assert.strictEqual(updated.buckets[0]?.costUsd, 2); + assert.notStrictEqual(updated.pricing.fetchedAt, initialPricing.fetchedAt); + }).pipe( + Effect.provide( + layerService({ + prefix: "concurrent-rates", + home, + settings, + ratesResponse: () => + Effect.succeed( + Response.json({ + "gpt-6-astra": { input_cost_per_token: 0, output_cost_per_token: ++rate }, + }), + ), + }), + ), + ); + }).pipe(Effect.scoped), + ); + it.effect("counts compressed Codex rollouts across refresh, rewrites, and cache restarts", () => Effect.gen(function* () { const { home, settings } = yield* setup; diff --git a/apps/server/src/usage/UsageService.ts b/apps/server/src/usage/UsageService.ts index fbbc1205868ee..638d2fe2d2d97 100644 --- a/apps/server/src/usage/UsageService.ts +++ b/apps/server/src/usage/UsageService.ts @@ -881,6 +881,7 @@ export const make = Effect.gen(function* () { { concurrency: 2 }, ); + const summaryPricing = pricing(); const aggregator = new UsageAggregator({ timeZone: input.timeZone, sinceDay: input.sinceDay, @@ -1116,7 +1117,7 @@ export const make = Effect.gen(function* () { untilDay: input.untilDay, buckets: aggregated.buckets, sources, - pricing: pricing(), + pricing: summaryPricing, scanDurationMs: Math.max(0, finishedAtMs - startedAtMs), ...(input.modelFilter === undefined ? {} : { modelFilter: input.modelFilter }), } satisfies UsageSummary; From 89f1acb0f0a9f5d6ce12b7c80c510da099366b26 Mon Sep 17 00:00:00 2001 From: MarcoDotIO Date: Sat, 14 Nov 2026 04:48:22 -0500 Subject: [PATCH 032/192] fix(pi): text generation uses the thinking level picked in Settings (#26069) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/provider/model-manifest.json | 4 +- docs/user/providers-pi.md | 2 +- .../src/server/modelCapabilities.ts | 4 + .../provider-pi/src/server/status.test.ts | 13 +- packages/provider-pi/src/server/status.ts | 24 ++-- .../src/server/textGeneration.test.ts | 118 +++++++++++++++++- .../provider-pi/src/server/textGeneration.ts | 30 +++++ 7 files changed, 160 insertions(+), 35 deletions(-) diff --git a/apps/server/src/provider/model-manifest.json b/apps/server/src/provider/model-manifest.json index 4906358bab70b..e13e640aa1616 100644 --- a/apps/server/src/provider/model-manifest.json +++ b/apps/server/src/provider/model-manifest.json @@ -95,8 +95,8 @@ "t3CodeRange": ">=0.0.46", "recommendedRange": ">=1.0.0", "ranges": [ - { "range": ">=0.80.5", "status": "supported" }, - { "range": "<0.80.5", "status": "unsupported" } + { "range": ">=0.84.3", "status": "supported" }, + { "range": "<0.84.3", "status": "unsupported" } ] }, { diff --git a/docs/user/providers-pi.md b/docs/user/providers-pi.md index e6a7b7d244023..b434250e9a12d 100644 --- a/docs/user/providers-pi.md +++ b/docs/user/providers-pi.md @@ -7,7 +7,7 @@ extensions, skills, context files, and native session history. 1. Install [Pi](https://pi.dev) on the machine running the T3 Code server. Pi requires Node.js 22.19 or newer; its platform installers can install Node if needed. T3 Code supports Pi - 0.80.5 or newer. + 0.84.3 or newer. 2. Run `pi` in a terminal, then `/login` to connect your model provider or configure its API key. 3. Open T3 Code Settings, enable Pi, and refresh the provider. diff --git a/packages/provider-pi/src/server/modelCapabilities.ts b/packages/provider-pi/src/server/modelCapabilities.ts index 2d379286c36f5..682ff2d3ed077 100644 --- a/packages/provider-pi/src/server/modelCapabilities.ts +++ b/packages/provider-pi/src/server/modelCapabilities.ts @@ -11,6 +11,10 @@ const PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", export type PiThinkingLevel = (typeof PI_THINKING_LEVELS)[number]; +export function isPiThinkingLevel(value: string): value is PiThinkingLevel { + return PI_THINKING_LEVELS.some((level) => level === value); +} + const PI_THINKING_LEVEL_LABELS: Record = { off: "Off", minimal: "Minimal", diff --git a/packages/provider-pi/src/server/status.test.ts b/packages/provider-pi/src/server/status.test.ts index 78d16ebd31c62..37fa28c00e42a 100644 --- a/packages/provider-pi/src/server/status.test.ts +++ b/packages/provider-pi/src/server/status.test.ts @@ -153,13 +153,13 @@ const settings = { } as const; describe("PiProvider", () => { - it.effect("requires the first published Pi version with entries and settlement hooks", () => + it.effect("requires session-scoped thinking changes", () => Effect.gen(function* () { const snapshot = yield* checkPiProviderStatus(settings).pipe( - Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, piProbeSpawner("0.80.3")), + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, piProbeSpawner("0.84.2")), ); assert.equal(snapshot.status, "error"); - assert.equal(snapshot.version, "0.80.3"); + assert.equal(snapshot.version, "0.84.2"); assert.include(snapshot.message ?? "", `Pi ${MINIMUM_PI_VERSION} or newer`); }).pipe(Effect.provide(NodeServices.layer)), ); @@ -259,13 +259,6 @@ describe("PiProvider", () => { authCheck: '{"status":"not_ready","provider":"openai","reason":"provider_not_found"}', message: "Pi default (openai/gpt-5) uses openai, which has no available models in Pi.", }, - { - label: "a pinned model on Pi without auth check", - version: MINIMUM_PI_VERSION, - activeModel: { provider: "openai", id: "gpt-5" }, - authCheck: undefined, - message: "Pi default (openai/gpt-5) uses openai, which has no available models in Pi.", - }, { label: "an uncatalogued model id of a provider with credentials", version: "1.4.0", diff --git a/packages/provider-pi/src/server/status.ts b/packages/provider-pi/src/server/status.ts index 9361bc204eba5..43fa8f60c08a5 100644 --- a/packages/provider-pi/src/server/status.ts +++ b/packages/provider-pi/src/server/status.ts @@ -60,13 +60,8 @@ const PI_PRESENTATION = { const VERSION_PROBE_TIMEOUT_MS = 10_000; const PI_RPC_DISCOVERY_TIMEOUT_MS = 30_000; const PI_TEXT_GENERATION_DISCOVERY_TIMEOUT_MS = 5_000; -/** - * get_entries arrived in 0.80.3 and agent_settled landed in source at 0.80.4. - * Version 0.80.5 was the first published package containing both hooks. - */ -export const MINIMUM_PI_VERSION = "0.80.5"; -/** `pi auth check --json --no-refresh` arrived in 0.84.1. */ -const PI_AUTH_CHECK_VERSION = "0.84.1"; +// Earlier releases persist background set_thinking_level requests as the user's global default. +export const MINIMUM_PI_VERSION = "0.84.3"; const decodePiAuthCheck = Schema.decodeUnknownOption( Schema.fromJsonString(Schema.Struct({ reason: Schema.optional(Schema.String) })), @@ -290,9 +285,10 @@ export const discoverPiCommandsForCwd = Effect.fn("discoverPiCommandsForCwd")( }, ); -const runPiVersionCommand = Effect.fn("PiProviderStatus.runPiVersionCommand")(function* ( +export const runPiVersionCommand = Effect.fn("PiProviderStatus.runPiVersionCommand")(function* ( piSettings: PiSettings, environment: NodeJS.ProcessEnv, + cwd?: string, ) { const command = piSettings.binaryPath || "pi"; const spawnCommand = yield* resolveSpawnCommand(command, ["--version"], { @@ -303,13 +299,14 @@ const runPiVersionCommand = Effect.fn("PiProviderStatus.runPiVersionCommand")(fu ChildProcess.make(spawnCommand.command, spawnCommand.args, { env: environment, shell: spawnCommand.shell, + ...(cwd === undefined ? {} : { cwd }), }), ); }); /** - * Explains why Pi's active model cannot start a turn. On Pi versions with `auth check`, asks it - * whether credentials are missing, without refreshing or printing them. That command cannot see + * Explains why Pi's active model cannot start a turn. Asks `pi auth check` whether credentials + * are missing, without refreshing or printing them. That command cannot see * extension providers or a runtime --api-key, so it only refines Pi's own unavailable verdict, * and is skipped when launch args supply the key. */ @@ -317,16 +314,12 @@ const describeUnavailablePiModel = Effect.fn("PiProviderStatus.describeUnavailab function* ( piSettings: PiSettings, environment: NodeJS.ProcessEnv, - version: string, launchArgs: ReadonlyArray, model: { readonly provider: string; readonly id: string }, ) { const label = `Pi default (${model.provider}/${model.id})`; const unavailable = `${label} uses ${model.provider}, which has no available models in Pi. Choose another model, or check that provider's setup and the Pi launch arguments.`; - if ( - compareSemverVersions(version, PI_AUTH_CHECK_VERSION) < 0 || - launchArgs.includes("--api-key") - ) { + if (launchArgs.includes("--api-key")) { return unavailable; } const command = piSettings.binaryPath || "pi"; @@ -583,7 +576,6 @@ export const checkPiProviderStatus = Effect.fn("checkPiProviderStatus")(function : yield* describeUnavailablePiModel( piSettings, probeEnvironment, - version, resolvedLaunchArgs.args, discovery.unavailableActiveModel, ); diff --git a/packages/provider-pi/src/server/textGeneration.test.ts b/packages/provider-pi/src/server/textGeneration.test.ts index eda86cd2e62a4..6c977eee3cfa7 100644 --- a/packages/provider-pi/src/server/textGeneration.test.ts +++ b/packages/provider-pi/src/server/textGeneration.test.ts @@ -20,6 +20,8 @@ const makeFakePi = ( setModelError?: string, turnEvents: ReadonlyArray> = [], models?: ReadonlyArray>, + version = "0.84.3", + thinkingError?: string, ) => Effect.gen(function* () { const stdout = yield* Queue.unbounded(); @@ -46,29 +48,35 @@ const makeFakePi = ( yield* emit({ ...response, success: true, data: { models } }); } else if (record["type"] === "set_model" && setModelError !== undefined) { yield* emit({ ...response, success: false, error: setModelError }); + } else if (record["type"] === "set_thinking_level" && thinkingError !== undefined) { + yield* emit({ ...response, success: false, error: thinkingError }); } else { yield* emit({ ...response, success: true }); } } }); - const spawner = ChildProcessSpawner.make(() => - Effect.succeed( + const spawner = ChildProcessSpawner.make((command) => { + const versionProbe = + ChildProcess.isStandardCommand(command) && command.args.includes("--version"); + return Effect.succeed( ChildProcessSpawner.makeHandle({ // Outside the valid pid range, so PiRpc's process-group kill never lands. pid: ChildProcessSpawner.ProcessId(999_999_999), - exitCode: Effect.never, + exitCode: versionProbe ? Effect.succeed(ChildProcessSpawner.ExitCode(0)) : Effect.never, isRunning: Effect.succeed(true), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.forEach(onStdin), - stdout: Stream.fromQueue(stdout), + stdout: versionProbe + ? Stream.make(new TextEncoder().encode(version)) + : Stream.fromQueue(stdout), stderr: Stream.empty, all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }), - ), - ); + ); + }); return { spawner, received }; }); @@ -223,3 +231,101 @@ it.effect("drops an earlier error once a later attempt succeeds", () => assert.equal(error.detail, "Pi returned empty output."); }), ); + +it.effect.each([ + { model: "demo/demo-model", thinking: "off", expected: "off", version: "0.84.3" }, + { model: "demo/demo-model", thinking: "max", expected: "max", version: "1.4.0" }, + { model: "demo/demo-model", thinking: "invalid", expected: undefined, version: "0.84.3" }, + { model: "demo/demo-model", thinking: undefined, expected: undefined, version: "0.84.3" }, + { model: "default", thinking: "off", expected: "off", version: "0.84.3" }, + { model: "demo/demo-model", thinking: "low", expected: undefined, version: "0.84.2" }, +])( + "handles thinking=$thinking for $model on Pi $version", + ({ model, thinking, expected, version }) => + Effect.gen(function* () { + const pi = yield* makeFakePi( + JSON.stringify({ subject: "Update README", body: "" }), + undefined, + [], + undefined, + version, + ); + const textGeneration = yield* makePiTextGeneration( + { enabled: true, binaryPath: "pi", launchArgs: "", customModels: [] }, + {}, + ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, pi.spawner)); + + const generation = textGeneration.generateCommitMessage({ + cwd: process.cwd(), + branch: "main", + stagedSummary: "M README.md", + stagedPatch: "diff --git a/README.md b/README.md", + modelSelection: createModelSelection( + ProviderInstanceId.make("pi"), + model, + thinking === undefined ? [] : [{ id: "thinking", value: thinking }], + ), + }); + if (version === "0.84.2") { + const error = yield* generation.pipe(Effect.flip); + assert.include(error.detail, "Pi 0.84.3 or newer"); + assert.deepEqual(pi.received, []); + return; + } + const result = yield* generation; + + assert.equal(result.subject, "Update README"); + assert.deepEqual( + pi.received.map((record) => record["type"]), + [ + ...(model === "default" ? [] : ["get_available_models", "set_model"]), + ...(expected === undefined ? [] : ["set_thinking_level"]), + "prompt", + "get_last_assistant_text", + ], + ); + assert.equal( + pi.received.find((record) => record["type"] === "set_thinking_level")?.["level"], + expected, + ); + }), +); + +it.effect("generates at Pi's default level when Pi rejects the saved thinking level", () => + Effect.gen(function* () { + const pi = yield* makeFakePi( + JSON.stringify({ subject: "Update README", body: "" }), + undefined, + [], + undefined, + "1.4.0", + 'Thinking level "max" is not supported by demo/demo-model', + ); + const textGeneration = yield* makePiTextGeneration( + { enabled: true, binaryPath: "pi", launchArgs: "", customModels: [] }, + {}, + ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, pi.spawner)); + + const result = yield* textGeneration.generateCommitMessage({ + cwd: process.cwd(), + branch: "main", + stagedSummary: "M README.md", + stagedPatch: "diff --git a/README.md b/README.md", + modelSelection: createModelSelection(ProviderInstanceId.make("pi"), "demo/demo-model", [ + { id: "thinking", value: "max" }, + ]), + }); + + assert.equal(result.subject, "Update README"); + assert.deepEqual( + pi.received.map((record) => record["type"]), + [ + "get_available_models", + "set_model", + "set_thinking_level", + "prompt", + "get_last_assistant_text", + ], + ); + }), +); diff --git a/packages/provider-pi/src/server/textGeneration.ts b/packages/provider-pi/src/server/textGeneration.ts index 8b75b3c3c0bad..dbc80cdfb4f6f 100644 --- a/packages/provider-pi/src/server/textGeneration.ts +++ b/packages/provider-pi/src/server/textGeneration.ts @@ -5,11 +5,16 @@ import * as Schema from "effect/Schema"; import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; import { TextGenerationError } from "@t3tools/contracts"; +import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; +import { compareSemverVersions } from "@t3tools/shared/semver"; +import { parseGenericCliVersion } from "@t3tools/provider-core/server/snapshotProbe"; import type { PiSettings } from "../settings.ts"; import { makePiRpcConnection, piRecordField, piRecordString, resolvePiModelSlug } from "./rpc.ts"; import { buildPiRpcLaunch, resolvePiLaunchArgs } from "./mcpInjection.ts"; import * as TextGenerationOperations from "@t3tools/provider-core/server/textGenerationOperations"; +import { isPiThinkingLevel } from "./modelCapabilities.ts"; +import { MINIMUM_PI_VERSION, runPiVersionCommand } from "./status.ts"; const PI_TIMEOUT_MS = 180_000; @@ -33,6 +38,23 @@ export const makePiTextGeneration = Effect.fn("makePiTextGeneration")(function* const runPiJson: TextGenerationOperations.Runner = Effect.fn("PiTextGeneration.runPiJson")( function* (request) { const { operation, cwd, prompt, modelSelection } = request; + const thinking = getModelSelectionStringOptionValue(modelSelection, "thinking"); + if (thinking !== undefined && isPiThinkingLevel(thinking)) { + const output = yield* runPiVersionCommand(piSettings, environment, cwd).pipe( + Effect.timeoutOption(4_000), + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + ); + const version = + Option.isSome(output) && output.value.code === 0 + ? parseGenericCliVersion(`${output.value.stdout}\n${output.value.stderr}`) + : null; + if (version === null || compareSemverVersions(version, MINIMUM_PI_VERSION) < 0) { + return yield* new TextGenerationError({ + operation, + detail: `Pi ${MINIMUM_PI_VERSION} or newer is required for text generation thinking options. Update Pi and try again.`, + }); + } + } const resolvedLaunchArgs = resolvePiLaunchArgs(piSettings.launchArgs); if (!resolvedLaunchArgs.ok) { @@ -91,6 +113,14 @@ export const makePiTextGeneration = Effect.fn("makePiTextGeneration")(function* ); } + if (thinking !== undefined && isPiThinkingLevel(thinking)) { + // Pi rejects xhigh/max for models without a thinkingLevelMap entry; a stale saved + // level falls back to Pi's default instead of failing the whole generation. + yield* connection + .request({ type: "set_thinking_level", level: thinking }) + .pipe(Effect.catchTags({ PiRpcError: () => Effect.void })); + } + yield* connection.request({ type: "prompt", message: prompt }); // Provider failures (bad key, rate limit, 5xx) arrive on the assistant // `message_end`. Keep only the latest, since Pi can retry and still answer. From 0fee26d5bf69084e17ee8869c48e428f84a1e378 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:49:15 +0100 Subject: [PATCH 033/192] fix(mobile): launch terminal commands after the measured resize (#36584) Co-authored-by: Julius Marminge --- .../client-runtime/src/state/terminal.test.ts | 141 ++++++++++++++++-- packages/client-runtime/src/state/terminal.ts | 49 ++++-- 2 files changed, 161 insertions(+), 29 deletions(-) diff --git a/packages/client-runtime/src/state/terminal.test.ts b/packages/client-runtime/src/state/terminal.test.ts index 6fb2d21b19ea1..f3b2a58365d94 100644 --- a/packages/client-runtime/src/state/terminal.test.ts +++ b/packages/client-runtime/src/state/terminal.test.ts @@ -1,4 +1,12 @@ -import { EnvironmentId, ThreadId } from "@t3tools/contracts"; +import { + EnvironmentId, + ThreadId, + type TerminalOpenInput, + type TerminalResizeInput, + type TerminalWriteInput, + type TerminalCloseInput, +} from "@t3tools/contracts"; +import * as Cause from "effect/Cause"; import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity"; import { expect, it } from "vite-plus/test"; @@ -9,6 +17,7 @@ import { type TerminalLifecycleEvent, type TerminalSessionMode, } from "./terminal.ts"; +import { runAtomCommand } from "./runtime.ts"; import { EMPTY_TERMINAL_BUFFER_STATE, type TerminalBufferState } from "./terminalSession.ts"; function run(events: ReadonlyArray) { @@ -59,7 +68,19 @@ it("leaves the history a viewer was reading when operate is granted", () => { const environmentId = EnvironmentId.make("environment"); const threadId = ThreadId.make("thread"); -function setup(options: { readonly initialInput?: string } = {}) { +type CommandInput = + | TerminalOpenInput + | TerminalResizeInput + | TerminalWriteInput + | TerminalCloseInput; + +function setup( + options: { + readonly initialInput?: string; + readonly beforeCommand?: (name: string, input: CommandInput) => Promise; + readonly resizeFailure?: () => boolean; + } = {}, +) { // Tests flush the registry's scheduled work to control when driver finalizers run. const scheduled: Array<() => void> = []; const registry = AtomRegistry.make({ @@ -78,14 +99,30 @@ function setup(options: { readonly initialInput?: string } = {}) { AsyncResult.initial(true), ); const access = { canOperate: true }; - const calls: Array<{ readonly command: string; readonly input: unknown }> = []; + const calls: Array<{ readonly command: string; readonly input: CommandInput }> = []; + const pending: Promise[] = []; const command = (name: string) => ({ label: name, - run: async (_registry: AtomRegistry.AtomRegistry, value: { readonly input: unknown }) => { - calls.push({ command: name, input: value.input }); - return AsyncResult.success(undefined); + run: (_registry: AtomRegistry.AtomRegistry, value: { readonly input: CommandInput }) => { + const result = (async () => { + calls.push({ command: name, input: value.input }); + if (options.beforeCommand) await options.beforeCommand(name, value.input); + return name === "resize" && options.resizeFailure?.() + ? AsyncResult.failure(Cause.fail("resize rejected")) + : AsyncResult.success(undefined); + })(); + pending.push(result); + return result; }, }); + const write = command("write"); + // Await command completion, then a normal input receipt through the same command runner. + const completeCommands = async (count = pending.length) => { + await Promise.all(pending.slice(0, count)); + await runAtomCommand(registry, write, { + input: { threadId, terminalId: "term-1", data: "typed" }, + }); + }; const exits: Array = []; const live: Array = []; const session = createTerminalSession( @@ -94,7 +131,7 @@ function setup(options: { readonly initialInput?: string } = {}) { observe: () => observation, open: command("open"), resize: command("resize"), - write: command("write"), + write, close: command("close"), }, { @@ -126,7 +163,21 @@ function setup(options: { readonly initialInput?: string } = {}) { registry.set(observation, snapshot(state)); const mount = (mode: TerminalSessionMode) => registry.mount(session.driver(mode)); const of = (name: string) => calls.filter((call) => call.command === name).map((c) => c.input); - return { session, attached, access, flush, detach, emit, observe, mount, calls, of, exits, live }; + return { + session, + attached, + access, + flush, + detach, + emit, + observe, + mount, + calls, + of, + exits, + live, + completeCommands, + }; } it("replays the measured grid on every new attachment generation", () => { @@ -154,17 +205,79 @@ it("replays the measured grid on every new attachment generation", () => { ]); }); -it("writes the launch's initial input once the first snapshot lands", () => { - const { attached, emit, mount, calls, of } = setup({ initialInput: "pnpm dev\r" }); +it("writes the launch's initial input once, after the measured resize lands", async () => { + const resize = Promise.withResolvers(); + const latestResize = Promise.withResolvers(); + const written = Promise.withResolvers(); + let shellCols = 80; + let resizes = 0; + const commandWidths: number[] = []; + const { session, attached, emit, mount, calls, of, completeCommands } = setup({ + initialInput: "stty size\r", + beforeCommand: async (name, input) => { + if (name === "resize") { + await (++resizes === 1 ? resize : latestResize).promise; + if ("cols" in input && input.cols !== undefined) shellCols = input.cols; + } + if (name === "write" && "data" in input && input.data === "stty size\r") { + commandWidths.push(shellCols); + written.resolve(); + } + }, + }); mount(attached); - expect(of("write")).toEqual([]); emit({ status: "running", version: 1 }); emit({ status: "running", version: 2 }); - expect(of("write")).toEqual([{ threadId, terminalId: "term-1", data: "pnpm dev\r" }]); - // The shell gets the measured grid before the launched command runs. - expect(calls.map((call) => call.command)).toEqual(["resize", "write"]); + session.reportGridSize({ cols: 100, rows: 30 }); + resize.resolve(); + await completeCommands(1); + expect(of("write")).toEqual([{ threadId, terminalId: "term-1", data: "typed" }]); + latestResize.resolve(); + await written.promise; + expect(commandWidths).toEqual([100]); + expect(of("write")).toEqual([ + { threadId, terminalId: "term-1", data: "typed" }, + { threadId, terminalId: "term-1", data: "stty size\r" }, + ]); + expect(calls.map((call) => call.command)).toEqual(["resize", "resize", "write", "write"]); }); +it.each(["failed resize", "starting snapshot", "unmeasured grid"] as const)( + "keeps launch input pending after a %s until a running shell is successfully resized", + async (scenario) => { + let failResize = scenario === "failed resize"; + const written = Promise.withResolvers(); + const { session, attached, emit, mount, of, completeCommands } = setup({ + initialInput: "pnpm dev\r", + resizeFailure: () => failResize, + beforeCommand: async (name, input) => { + if (name === "write" && "data" in input && input.data === "pnpm dev\r") written.resolve(); + }, + }); + // The seeded grid is 120x40; only "unmeasured grid" never hears from the surface. + if (scenario !== "unmeasured grid") session.reportGridSize({ cols: 120, rows: 40 }); + mount(attached); + emit({ status: scenario === "starting snapshot" ? "starting" : "running", version: 1 }); + await completeCommands(); + expect(of("write")).toEqual([{ threadId, terminalId: "term-1", data: "typed" }]); + expect(of("resize")).toHaveLength(scenario === "starting snapshot" ? 0 : 1); + + failResize = false; + session.reportGridSize( + scenario === "unmeasured grid" ? { cols: 120, rows: 40 } : { cols: 100, rows: 30 }, + ); + emit({ status: "running", version: 2 }); + await written.promise; + emit({ status: "running", version: 3 }); + await completeCommands(); + expect(of("write")).toEqual([ + { threadId, terminalId: "term-1", data: "typed" }, + { threadId, terminalId: "term-1", data: "pnpm dev\r" }, + { threadId, terminalId: "term-1", data: "typed" }, + ]); + }, +); + it("closes and reports a shell that exits while attached", () => { const { attached, emit, mount, of, exits, live } = setup(); mount(attached); diff --git a/packages/client-runtime/src/state/terminal.ts b/packages/client-runtime/src/state/terminal.ts index f62c2549daf1d..7e87ef41f5edc 100644 --- a/packages/client-runtime/src/state/terminal.ts +++ b/packages/client-runtime/src/state/terminal.ts @@ -226,7 +226,7 @@ export interface TerminalSessionOptions { readonly terminalId: string; }; readonly gridSize: TerminalGridSize; - /** Written once, as soon as the first attachment snapshot lands. */ + /** Written once, after the first attachment snapshot lands and the shell has the measured grid. */ readonly initialInput?: string | undefined; /** Read right before every command, so revoking operate stops them at once. */ readonly canOperate: () => boolean; @@ -252,7 +252,7 @@ export interface TerminalSession { * Owns one shown terminal's lifecycle: it reopens an ended shell on an explicit * visit, closes and reports a shell that exits while attached, replays the * measured grid whenever the server starts a new output generation, and writes - * the launch's initial input once. + * the launch's initial input once that grid has reached the shell. */ export function createTerminalSession( atoms: TerminalSessionAtoms, @@ -261,19 +261,43 @@ export function createTerminalSession( const { environmentId, threadId, terminalId } = options.target; let lifecycle = INITIAL_TERMINAL_LIFECYCLE; let gridSize = options.gridSize; + // The seeded grid is a cache or default guess; launch input waits for the surface's own measurement. + let gridMeasured = false; let generation: number | null = null; let liveLifecycleVersion: number | null = null; let initialInputPending = Boolean(options.initialInput); let lastStatus: TerminalBufferState["status"] | null = null; let loggedFirstOutput = false; - let resizeRegistry: AtomRegistry.AtomRegistry | null = null; + let commandRegistry: AtomRegistry.AtomRegistry | null = null; + // Only a successful resize of the current measured grid may release initial input. + let shellResized: ReturnType | null = null; const sendResize = () => { - if (generation === null || resizeRegistry === null || !options.canOperate()) return; - void runAtomCommand(resizeRegistry, atoms.resize, { + const registry = commandRegistry; + if (generation === null || registry === null || !options.canOperate()) return; + const resized = runAtomCommand(registry, atoms.resize, { environmentId, input: { threadId, terminalId, cols: gridSize.cols, rows: gridSize.rows }, }); + shellResized = resized; + void resized.then((result) => { + const data = options.initialInput; + if ( + result._tag !== "Success" || + resized !== shellResized || + !gridMeasured || + generation === null || + !initialInputPending || + !data || + !options.canOperate() + ) + return; + initialInputPending = false; + void runAtomCommand(registry, atoms.write, { + environmentId, + input: { threadId, terminalId, data }, + }); + }); }; const setLive = (next: number | null) => { @@ -326,7 +350,7 @@ export function createTerminalSession( input: TerminalAttachInput, result: AsyncResult.AsyncResult, ) => { - resizeRegistry = registry; + commandRegistry = registry; const buffer = AsyncResult.getOrElse(result, () => EMPTY_TERMINAL_BUFFER_STATE); const status = result._tag === "Failure" ? "error" : buffer.status; if (status !== lastStatus) { @@ -357,17 +381,10 @@ export function createTerminalSession( buffer.version > 0 && status === "running" ? buffer.output.generation : null; if (nextGeneration !== generation) { generation = nextGeneration; + shellResized = null; sendResize(); } - if (initialInputPending && buffer.version > 0 && options.initialInput && options.canOperate()) { - initialInputPending = false; - void runAtomCommand(registry, atoms.write, { - environmentId, - input: { threadId, terminalId, data: options.initialInput }, - }); - } - apply({ type: "status", status, version: buffer.version }, registry, input); }; @@ -391,6 +408,7 @@ export function createTerminalSession( const registry = get.registry; get.addFinalizer(() => { generation = null; + shellResized = null; lastStatus = null; setLive(null); lifecycle = transitionTerminalLifecycle(lifecycle, { @@ -414,7 +432,8 @@ export function createTerminalSession( return attached; }, reportGridSize: (size) => { - if (size.cols === gridSize.cols && size.rows === gridSize.rows) return; + if (gridMeasured && size.cols === gridSize.cols && size.rows === gridSize.rows) return; + gridMeasured = true; gridSize = size; sendResize(); }, From 13f89ae0a7fcecbdcd06ac1551d5ff549bb76926 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:55:06 +0100 Subject: [PATCH 034/192] fix(server): repeated imports reach older worktree history (#36608) Co-authored-by: Julius Marminge --- .../src/project/AgentSessionImporter.test.ts | 148 +++++++++++++++++- .../src/project/AgentSessionImporter.ts | 32 ++-- 2 files changed, 164 insertions(+), 16 deletions(-) diff --git a/apps/server/src/project/AgentSessionImporter.test.ts b/apps/server/src/project/AgentSessionImporter.test.ts index 23264b2eeefe6..4c6109412d961 100644 --- a/apps/server/src/project/AgentSessionImporter.test.ts +++ b/apps/server/src/project/AgentSessionImporter.test.ts @@ -1,3 +1,4 @@ +import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import { type AgentSessionSource, @@ -22,17 +23,21 @@ import * as Context from "effect/Context"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; +import * as Path from "effect/Path"; import * as Stream from "effect/Stream"; import * as SqlClient from "effect/sql/SqlClient"; import * as TestClock from "effect/testing/TestClock"; +import * as ServerConfig from "../config.ts"; import * as EventSink from "../orchestration-v2/EventSink.ts"; import * as EventStore from "../orchestration-v2/EventStore.ts"; import * as Orchestrator from "../orchestration-v2/Orchestrator.ts"; import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts"; +import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts"; import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts"; import { @@ -188,6 +193,10 @@ const makeImporter = Effect.fn(function* ( ServerSettings.ServerSettingsService, ServerSettingsError > = ServerSettings.layerTest(), + realScan?: { + readonly workspaceRoot: string; + readonly scanner: AgentSessionScanner.AgentSessionScanner["Service"]; + }, ) { const sql = yield* SqlClient.SqlClient; const events = yield* EventStore.EventStoreV2; @@ -229,15 +238,17 @@ const makeImporter = Effect.fn(function* ( ProviderSessionRuntime.ProviderSessionRuntimeRepository, yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository, ), - Layer.mock(AgentSessionScanner.AgentSessionScanner)({ - recentThreads: () => Stream.fromIterable(candidates), - }), + realScan === undefined + ? Layer.mock(AgentSessionScanner.AgentSessionScanner)({ + recentThreads: () => Stream.fromIterable(candidates), + }) + : Layer.succeed(AgentSessionScanner.AgentSessionScanner, realScan.scanner), Layer.mock(ProjectService.ProjectService)({ getById: () => Effect.succeedSome({ id: projectId, title: "Project", - workspaceRoot: "/workspace/project", + workspaceRoot: realScan?.workspaceRoot ?? "/workspace/project", defaultModelSelection: null, scripts: [], createdAt: timestamp, @@ -377,6 +388,43 @@ it.effect( }).pipe(Effect.scoped, Effect.provide(persistence)), ); +it.effect("tolerates a missing transcript owner but propagates failed ownership reads", () => + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + const runtimes = yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository; + const source = candidate(CODEX_MODEL_SELECTION, "ownership-read"); + const threadId = importId(source); + yield* runtimes.upsert({ + threadId: ThreadId.make("missing-owner"), + providerName: "codex", + providerInstanceId: source.source.providerInstanceId, + adapterKey: "codex", + runtimeMode: "full-access", + status: "stopped", + lastSeenAt: timestamp, + resumeCursor: null, + runtimePayload: { importedTranscripts: [source.source] }, + }); + yield* runtimes.recordImportedTranscript({ + threadId: ThreadId.make("missing-owner"), + source: source.source, + }); + const importer = yield* makeImporter([source]); + expect(yield* importer.run).toEqual({ importedCount: 1, skippedCount: 0 }); + + yield* sql`UPDATE orchestration_v2_projection_threads SET payload_json = '{}' WHERE thread_id = ${threadId}`; + expect(yield* Effect.flip(importer.run)).toMatchObject({ + _tag: "AgentSessionScanError", + operation: "read-projects", + cause: { + _tag: "OrchestratorProjectionError", + threadId, + cause: { _tag: "ProjectionStoreReadError" }, + }, + }); + }).pipe(Effect.scoped, Effect.provide(persistence)), +); + it.effect("rechecks ownership after the import reaches the commit transaction", () => Effect.gen(function* () { const enteringTransaction = yield* Deferred.make(); @@ -869,3 +917,95 @@ it.effect("imports under a policy ceiling in the project's default, kept under t ); }).pipe(Effect.scoped, Effect.provide(persistence)), ); + +it.effect("repeated imports reach linked-worktree sessions behind the scan budget", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-import-worktree-budget-" }); + const repo = path.join(root, "repo"); + const worktree = path.join(root, "feature"); + const worktreeGitDir = path.join(repo, ".git", "worktrees", "feature"); + yield* fs.makeDirectory(worktreeGitDir, { recursive: true }); + yield* fs.makeDirectory(worktree); + yield* fs.writeFileString(path.join(repo, ".git", "config"), "[core]\n"); + yield* fs.writeFileString(path.join(worktreeGitDir, "commondir"), "../..\n"); + yield* fs.writeFileString(path.join(worktreeGitDir, "HEAD"), "ref: refs/heads/feature\n"); + yield* fs.writeFileString(path.join(worktree, ".git"), `gitdir: ${worktreeGitDir}\n`); + + const claudeHome = path.join(root, "claude"); + const codexHome = path.join(root, "codex"); + const sessions = path.join(codexHome, "sessions", "2026", "09", "01"); + yield* fs.makeDirectory(sessions, { recursive: true }); + const nowMs = DateTime.toEpochMillis(now); + yield* TestClock.setTime(nowMs); + // One more session than the 100-transcript scan budget, all run in the linked worktree. + for (let index = 0; index < 101; index++) { + const file = path.join(sessions, `rollout-${String(index).padStart(3, "0")}.jsonl`); + const records = [ + { type: "session_meta", payload: { id: `session-${index}`, cwd: worktree } }, + { type: "event_msg", payload: { type: "user_message", message: `Request ${index}` } }, + { + type: "response_item", + payload: { + type: "message", + role: "assistant", + content: [{ type: "output_text", text: "Done" }], + }, + }, + ]; + yield* fs.writeFileString( + file, + records.map((record) => JSON.stringify(record) + "\n").join(""), + ); + const modifiedAtSeconds = nowMs / 1000 - (index + 1); + yield* fs.utimes(file, modifiedAtSeconds, modifiedAtSeconds); + } + + const scannerContext = yield* Layer.build( + AgentSessionScanner.layer.pipe( + Layer.provide( + Layer.mergeAll( + ServerConfig.layerTest(claudeHome, { prefix: "t3-import-budget-config-" }), + ServerSettings.layerTest({ + providerInstances: { + [ProviderInstanceId.make("claudeAgent")]: { + driver: ProviderDriverKind.make("claudeAgent"), + config: { homePath: claudeHome }, + }, + [ProviderInstanceId.make("codex")]: { + driver: ProviderDriverKind.make("codex"), + config: { homePath: codexHome }, + }, + // Keeps the default Pi instance off the developer's real ~/.pi. + [ProviderInstanceId.make("pi")]: { + driver: ProviderDriverKind.make("pi"), + enabled: false, + config: {}, + }, + }, + }), + Layer.mock(ProjectStore.ProjectStoreV2)({ listShells: () => Effect.succeed([]) }), + Layer.succeed( + ProviderSessionRuntime.ProviderSessionRuntimeRepository, + yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository, + ), + ), + ), + ), + ); + const importer = yield* makeImporter([], Effect.void, undefined, undefined, undefined, { + workspaceRoot: repo, + scanner: Context.get(scannerContext, AgentSessionScanner.AgentSessionScanner), + }); + + expect(yield* importer.run).toEqual({ importedCount: 100, skippedCount: 1 }); + expect(yield* importer.run).toEqual({ importedCount: 101, skippedCount: 0 }); + const oldest = yield* ProjectionStore.ProjectionStoreV2.pipe( + Effect.flatMap((projections) => + projections.getThread(ThreadId.make("import:codex:session-100")), + ), + ); + expect(oldest.worktreePath).toBe(worktree); + }).pipe(Effect.scoped, Effect.provide(Layer.merge(persistence, NodeServices.layer))), +); diff --git a/apps/server/src/project/AgentSessionImporter.ts b/apps/server/src/project/AgentSessionImporter.ts index 6f88b929260a6..0ebd6688a28be 100644 --- a/apps/server/src/project/AgentSessionImporter.ts +++ b/apps/server/src/project/AgentSessionImporter.ts @@ -35,6 +35,7 @@ import * as Stream from "effect/Stream"; import * as EventSink from "../orchestration-v2/EventSink.ts"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as Orchestrator from "../orchestration-v2/Orchestrator.ts"; +import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts"; import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts"; import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts"; import * as ProviderSessionRuntime from "../persistence/ProviderSessionRuntime.ts"; @@ -42,6 +43,10 @@ import * as ServerSettings from "../serverSettings.ts"; import * as AgentSessionScanner from "./AgentSessionScanner.ts"; import * as ProjectService from "./ProjectService.ts"; +const isProjectionStoreThreadNotFoundError = Schema.is( + ProjectionStore.ProjectionStoreThreadNotFoundError, +); + const IMPORT_EVENT_PREFIX = "agent-session-import:v2"; const CLAUDE_SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; @@ -242,18 +247,21 @@ const make = Effect.gen(function* () { (cause) => new AgentSessionScanError({ operation: "read-projects", cause }), ), ); - const completedSources = runtimeRows.flatMap((runtime) => { - const payload = AgentSessionScanner.decodeImportedTranscriptPayload(runtime.runtimePayload); - if ( - Option.isNone(payload) || - payload.value.cwd === undefined || - normalizeProjectPathForComparison(payload.value.cwd) !== - normalizeProjectPathForComparison(project.workspaceRoot) - ) { - return []; - } - return payload.value.importedTranscripts ?? []; - }); + // The thread's project owns its transcripts: a linked worktree import records the worktree as cwd. + const completedSources = (yield* Effect.forEach(runtimeRows, (runtime) => { + const imported = AgentSessionScanner.decodeImportedTranscriptPayload( + runtime.runtimePayload, + ).pipe(Option.flatMapNullishOr((payload) => payload.importedTranscripts)); + if (Option.isNone(imported) || imported.value.length === 0) return Effect.succeed([]); + return orchestrator.getThreadRecords(runtime.threadId, []).pipe( + Effect.map(({ thread }) => (thread.projectId === input.projectId ? imported.value : [])), + Effect.catch((cause) => + isProjectionStoreThreadNotFoundError(cause.cause) + ? Effect.succeed([]) + : Effect.fail(new AgentSessionScanError({ operation: "read-projects", cause })), + ), + ); + })).flat(); const outcomes = scanner.recentThreads(project.workspaceRoot, completedSources); const importedThreadIds = new Set(); let importedCount = 0; From 8a072ca51d21ac5ec4cd593ab6151ea2f2443deb Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 02:08:06 -0800 Subject: [PATCH 035/192] fix(ci): pin unenv so release smoke installs resolve its exact peer (#36638) --- pnpm-lock.yaml | 1 + pnpm-workspace.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1157878a144ad..99afb66977519 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -299,6 +299,7 @@ overrides: expo-modules-core: 58.0.30 '@exodus/bytes>@noble/hashes': 1.8.0 '@react-navigation/core': 7.24.0 + unenv: 2.0.0-rc.24 dbus-next>usocket: '-' '@anthropic-ai/claude-agent-sdk>@anthropic-ai/claude-agent-sdk-darwin-arm64': '-' '@anthropic-ai/claude-agent-sdk>@anthropic-ai/claude-agent-sdk-darwin-x64': '-' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c7982f437914e..4742a77f937aa 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -137,6 +137,9 @@ overrides: # Move this with @react-navigation/native: an older core than it requires crashes # every native stack at launch. "@react-navigation/core": 7.24.0 + # @cloudflare/unenv-preset peers on this exact version, but alchemy's runtime + # takes a caret range, so lockfile-free installs drift to a newer prerelease. + unenv: 2.0.0-rc.24 # The dbus-next patch drops usocket from the package, but pnpm resolves from the # registry manifest, so the edge has to be cut here as well. "dbus-next>usocket": "-" From 38b3feeb75d6c02a3988d4ea2983ceebc4a1b66e Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:11:06 +0100 Subject: [PATCH 036/192] fix(server): cached refs include a newly checked-out external branch (#36626) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 39 ++++++++++++-------- apps/server/src/vcs/GitVcsDriverCore.ts | 22 ++++++++++- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 802d0d463cfdf..ab0483124433e 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -722,24 +722,31 @@ it.effect("reads a branch whose name starts with a parenthesis as attached", () ).pipe(Effect.provide(layerTest)), ); -it.effect("follows a checkout made outside the driver after refs were cached", () => - Effect.scoped( - Effect.gen(function* () { - const cwd = yield* makeTmpDir(); - const { initialBranch } = yield* initRepoWithCommit(cwd); - yield* git(cwd, ["branch", "feature/outside"]); - const driver = yield* GitVcsDriver.GitVcsDriver; - yield* driver.listRefs({ cwd }); +it.effect.each(["an existing", "a new"] as const)( + "follows a checkout of %s branch made outside the driver after refs were cached", + (branch) => + Effect.scoped( + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const { initialBranch } = yield* initRepoWithCommit(cwd); + if (branch === "an existing") yield* git(cwd, ["branch", "feature/outside"]); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* driver.listRefs({ cwd }); - yield* git(cwd, ["switch", "feature/outside"]); + yield* git( + cwd, + branch === "an existing" + ? ["switch", "feature/outside"] + : ["switch", "-c", "feature/outside"], + ); - const refs = (yield* driver.listRefs({ cwd })).refs; - const previous = refs.find((ref) => ref.name === initialBranch); - assert.isTrue(refs.find((ref) => ref.name === "feature/outside")?.current); - assert.isFalse(previous?.current); - assert.isNull(previous?.worktreePath ?? null); - }), - ).pipe(Effect.provide(layerTest)), + const refs = (yield* driver.listRefs({ cwd })).refs; + const previous = refs.find((ref) => ref.name === initialBranch); + assert.isTrue(refs.find((ref) => ref.name === "feature/outside")?.current); + assert.isFalse(previous?.current); + assert.isNull(previous?.worktreePath ?? null); + }), + ).pipe(Effect.provide(layerTest)), ); it.effect.each(["on a branch", "detached", "unborn"] as const)( diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index b85416032e177..1dc33f9a2b141 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -194,6 +194,7 @@ interface GitRepositoryPaths { } interface GitRefsSnapshot { + readonly defaultBranch: string | null; readonly localBranches: ReadonlyArray; readonly remoteBranches: ReadonlyArray; readonly hasPrimaryRemote: boolean; @@ -4387,6 +4388,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* : left.ref.name.localeCompare(right.ref.name); return { + defaultBranch, localBranches: localBranches.toSorted(byRecencyThenName).map(({ ref }) => ref), remoteBranches: remoteBranches.toSorted(byRecencyThenName).map(({ ref }) => ref), hasPrimaryRemote: remoteNames.length > 0, @@ -4527,7 +4529,24 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* { concurrency: 2 }, ); const { worktreeRoot } = repositoryPaths; - const localBranches = snapshot.localBranches.map((ref) => { + // The snapshot misses a branch created outside T3 Code until it refreshes. An unborn HEAD + // names a branch with no ref yet, so the ref must exist before it is listed. + const currentBranchUnlisted = + currentBranch !== null && !snapshot.localBranches.some((ref) => ref.name === currentBranch); + const unlistedCurrentBranch: ReadonlyArray = + currentBranchUnlisted && + (yield* branchExists(input.cwd, currentBranch).pipe(Effect.orElseSucceed(() => false))) + ? [ + { + name: currentBranch, + current: true, + isRemote: false, + isDefault: currentBranch === snapshot.defaultBranch, + worktreePath: worktreeRoot, + }, + ] + : []; + const listedBranches = snapshot.localBranches.map((ref) => { const current = ref.name === currentBranch; return { ...ref, @@ -4542,6 +4561,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* current, }; }); + const localBranches = [...unlistedCurrentBranch, ...listedBranches]; const combinedBranches = input.includeMatchingRemoteRefs ? [...localBranches, ...snapshot.remoteBranches] : dedupeRemoteBranchesWithLocalMatches([...localBranches, ...snapshot.remoteBranches]); From 820f620e3d63cfb03f3587aa2c6844b9c57295bb Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:11:19 +0100 Subject: [PATCH 037/192] =?UTF-8?q?fix(web):=20model=20picks=20no=20longer?= =?UTF-8?q?=20inherit=20another=20model=E2=80=99s=20traits=20(#36621)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Julius Marminge --- apps/web/src/composerDraftStore.test.ts | 28 +++++++++++++- apps/web/src/composerDraftStore.ts | 50 +++++++++++++++++-------- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/apps/web/src/composerDraftStore.test.ts b/apps/web/src/composerDraftStore.test.ts index a6d4e1ecde013..43d61924a8c74 100644 --- a/apps/web/src/composerDraftStore.test.ts +++ b/apps/web/src/composerDraftStore.test.ts @@ -1877,14 +1877,14 @@ describe("composerDraftStore modelSelection", () => { const threadSelection = modelSelection(CODEX_DRIVER, "gpt-5.3-codex", { reasoningEffort: "low", }); - const effective = (threadStarted: boolean) => { + const effective = (threadStarted: boolean, baseSelection = threadSelection) => { const state = deriveEffectiveComposerModelState({ draft: useComposerDraftStore.getState().getComposerDraft(threadRef), threadStarted, providers, selectedProvider: CODEX_DRIVER, selectedInstanceId: CODEX_INSTANCE, - threadModelSelection: threadSelection, + threadModelSelection: baseSelection, projectModelSelection: null, settings: DEFAULT_UNIFIED_SETTINGS, }); @@ -1921,6 +1921,30 @@ describe("composerDraftStore modelSelection", () => { }); }); + it.each([false, true])( + "an explicit optionless pick overrides base traits when threadStarted=%s", + (threadStarted) => { + const store = useComposerDraftStore.getState(); + store.setModelSelection(threadRef, modelSelection(CODEX_DRIVER, "gpt-5.4"), { + explicit: true, + replaceOptions: true, + }); + expect(effective(threadStarted)).toEqual({ model: "gpt-5.4", options: [] }); + }, + ); + + it("restores saved traits when the base model uses an alias", () => { + useComposerDraftStore + .getState() + .setModelSelection(threadRef, modelSelection(CODEX_DRIVER, "gpt-5.4"), { + explicit: true, + replaceOptions: true, + }); + expect( + effective(true, modelSelection(CODEX_DRIVER, "5.4", { reasoningEffort: "low" })), + ).toEqual({ model: "gpt-5.4", options: toSelections({ reasoningEffort: "low" }) }); + }); + it("keeps the thread's model when traits change over a seed", () => { const store = useComposerDraftStore.getState(); store.setModelSelection(threadRef, modelSelection(CODEX_DRIVER, "gpt-5.4")); diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index ee337af7b07e6..0bf2b690b4696 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -1235,6 +1235,26 @@ function legacyToModelSelectionByProvider( return result; } +// Static aliases are shared across instances; exact custom slugs and catalog aliases are not. +function composerModelIdentity( + input: { + providers: ReadonlyArray; + settings: UnifiedSettings; + selectedProvider: ProviderDriverKind; + }, + instanceId: ProviderInstanceId, + model: string | undefined, +) { + const isCustom = + input.providers + .find((provider) => provider.instanceId === instanceId) + ?.models.some((entry) => entry.slug === model && entry.isCustom) || + readInstanceCustomModels(input.settings, instanceId, input.selectedProvider).some( + (entry) => entry.slug === model, + ); + return isCustom ? model : normalizeModelSlug(model, input.selectedProvider); +} + export function deriveEffectiveComposerModelState(input: { draft: | Pick< @@ -1254,8 +1274,8 @@ export function deriveEffectiveComposerModelState(input: { settings: UnifiedSettings; }): EffectiveComposerModelState { const draft = draftModelSelectionApplies(input.draft, input.threadStarted) ? input.draft : null; - const baseModelCandidate = - input.threadModelSelection?.model ?? input.projectModelSelection?.model ?? null; + const baseSelection = input.threadModelSelection ?? input.projectModelSelection; + const baseModelCandidate = baseSelection?.model ?? null; const preserveThreadModel = input.selectedInstanceId !== null && input.selectedInstanceId !== undefined && @@ -1309,12 +1329,21 @@ export function deriveEffectiveComposerModelState(input: { activeSelection.model, )) : baseModel; + // Base options belong to the base model, so a draft pick of another model gets none of them. + const draftPicksOtherModel = + !!activeSelection?.model && + composerModelIdentity(input, activeSelectionInstanceId, activeSelection.model) !== + composerModelIdentity( + input, + baseSelection?.instanceId ?? activeSelectionInstanceId, + baseSelection?.model, + ); // Draft options win; otherwise an existing thread's options, even none, outrank the project's. // Selections stay with them, so a fallback instance can check which model the options belong to. const optionSelections = [ draft?.modelSelectionByProvider, - selectionsByInstance(input.threadModelSelection ?? input.projectModelSelection), + draftPicksOtherModel ? null : selectionsByInstance(baseSelection), ].find((selections) => modelSelectionByProviderToOptions(selections) !== null) ?? null; const modelOptions = modelSelectionByProviderToOptions(optionSelections); @@ -1359,21 +1388,10 @@ function withFallbackInstanceOptions(input: { (provider) => provider.instanceId === requestedInstanceId, ); const requestedModel = input.optionSelections?.[requestedInstanceId]?.model; - // Static aliases are shared across instances; exact custom slugs and catalog aliases are not. - const modelIdentity = (instanceId: ProviderInstanceId, model: string | undefined) => { - const isCustom = - input.providers - .find((provider) => provider.instanceId === instanceId) - ?.models.some((entry) => entry.slug === model && entry.isCustom) || - readInstanceCustomModels(input.settings, instanceId, input.selectedProvider).some( - (entry) => entry.slug === model, - ); - return isCustom ? model : normalizeModelSlug(model, input.selectedProvider); - }; const sameModel = requestedModel === input.selectedModel || - modelIdentity(requestedInstanceId, requestedModel) === - modelIdentity(selectedInstanceId, input.selectedModel); + composerModelIdentity(input, requestedInstanceId, requestedModel) === + composerModelIdentity(input, selectedInstanceId, input.selectedModel); if (!requestedOptions || requestedProvider?.driver !== input.selectedProvider || !sameModel) { return modelOptions; } From 298c38d7f282c97f54cab590e6a87aebc7440716 Mon Sep 17 00:00:00 2001 From: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:16:11 +0300 Subject: [PATCH 038/192] fix(server): subagent hover cards show reasoning effort again (#35333) Co-authored-by: Claude Opus 5.5 Co-authored-by: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> --- .../Adapters/ClaudeAdapterV2.test.ts | 87 +++++++++++++++++-- .../Adapters/ClaudeAdapterV2.ts | 76 +++++++++++++++- .../Orchestrator.control-reads.test.ts | 1 + .../src/orchestration-v2/Orchestrator.ts | 2 + .../RemoteDelegatedTask.test.ts | 1 + 5 files changed, 158 insertions(+), 9 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index f0c4d00c7bdf3..21fc06ef28d77 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -17889,6 +17889,14 @@ describe("ClaudeAdapterV2 background wake turns", () => { yield* harness.awaitContinuation(1); yield* Queue.shutdown(harness.sdkMessages); + yield* harness.until(() => + harness.events.some( + (event) => + event.type === "subagent.updated" && + event.subagent.nativeTaskRef?.nativeId === OUTER_TASK_ID && + event.subagent.status === "completed", + ), + ); yield* yieldTicks(50); yield* harness.runtime.startTurn( makeClaudeTestTurnInput({ @@ -19043,12 +19051,36 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); - it.effect("records only explicit effort for nested subagents", () => - Effect.gen(function* () { + it.effect("records explicit effort, else the effort a subagent's transcript reports", () => { + const UNSPECIFIED_TOOL_USE_ID = "toolu-effort-nested-unspecified"; + const firstRead = Deferred.makeUnsafe(); + return Effect.gen(function* () { const OUTER_TOOL_USE_ID = "toolu-effort-outer"; - const UNSPECIFIED_TOOL_USE_ID = "toolu-effort-nested-unspecified"; const OVERRIDING_TOOL_USE_ID = "toolu-effort-nested-overriding"; - const harness = yield* makeWakeHarness; + const fileSystem = yield* FileSystem.FileSystem; + const configDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-claude-effort-", + }); + // Claude names the folder after the resolved cwd, here as if /workspace were a symlink. + const subagentsDir = NodePath.join( + configDir, + "projects", + "-private-workspace", + WAKE_NATIVE_SESSION, + "subagents", + ); + yield* fileSystem.makeDirectory(subagentsDir, { recursive: true }); + const transcriptPath = NodePath.join( + subagentsDir, + `agent-task-${UNSPECIFIED_TOOL_USE_ID}.jsonl`, + ); + yield* fileSystem.writeFileString( + transcriptPath, + `{"type":"user","uuid":"effort-line-0","message":{"role":"user","content":"Check one thing."}}\n`, + ); + const harness = yield* makeWakeHarnessWithOptions({ + environment: { CLAUDE_CONFIG_DIR: configDir }, + }); const now = yield* DateTime.now; const parentSelection = { instanceId: CLAUDE_TEST_MODEL_SELECTION.instanceId, @@ -19118,6 +19150,22 @@ describe("ClaudeAdapterV2 background wake turns", () => { }), taskStarted(UNSPECIFIED_TOOL_USE_ID, "00000000-0000-4000-8000-000000000224"), taskStarted(OVERRIDING_TOOL_USE_ID, "00000000-0000-4000-8000-000000000225"), + claudeSdkFrame({ + type: "system", + subtype: "task_notification", + task_id: `task-${UNSPECIFIED_TOOL_USE_ID}`, + tool_use_id: UNSPECIFIED_TOOL_USE_ID, + status: "completed", + output_file: "/tmp/claude-replay/tasks/unspecified.output", + summary: "OK", + uuid: "00000000-0000-4000-8000-000000000227", + session_id: WAKE_NATIVE_SESSION, + }), + makeSubagentToolResultFrame({ + parentToolUseId: OUTER_TOOL_USE_ID, + uuid: "00000000-0000-4000-8000-000000000228", + toolUseId: UNSPECIFIED_TOOL_USE_ID, + }), makeResultFrame({ uuid: "00000000-0000-4000-8000-000000000226", result: "Spawned the subagents.", @@ -19125,7 +19173,27 @@ describe("ClaudeAdapterV2 background wake turns", () => { ]) { yield* Queue.offer(harness.sdkMessages, frame); } + // The settled subagent's reply lands after the first lookup, beyond one read's cap. + yield* Deferred.await(firstRead); + NodeFS.appendFileSync( + transcriptPath, + `${"x".repeat(512 * 1024 + 1)}\n{"type":"assistant","effort":"low","uuid":"effort-line-1","message":{"role":"assistant","id":"msg_effort_child","content":[{"type":"text","text":"OK"}]}}\n`, + ); + yield* TestClock.adjust(500); yield* Queue.take(harness.terminalReceipts); + // A run can stop reading events once its subagents settle, so the settle carries the effort. + const settle = harness.events.find( + (event) => + event.type === "subagent.updated" && + event.subagent.status === "completed" && + event.subagent.nativeTaskRef?.nativeId === `task-${UNSPECIFIED_TOOL_USE_ID}`, + ); + assert.equal( + settle?.type === "subagent.updated" + ? getModelSelectionStringOptionValue(settle.subagent.modelSelection, "effort") + : null, + "low", + ); const efforts = new Map( harness.events.flatMap((event) => @@ -19141,7 +19209,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.deepEqual(Object.fromEntries(efforts), { [`task-${OUTER_TOOL_USE_ID}`]: "low", - [`task-${UNSPECIFIED_TOOL_USE_ID}`]: undefined, + [`task-${UNSPECIFIED_TOOL_USE_ID}`]: "low", [`task-${OVERRIDING_TOOL_USE_ID}`]: "max", }); const childEfforts = harness.events.flatMap((event) => @@ -19151,6 +19219,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.deepEqual(childEfforts, ["low", undefined, "max"]); }).pipe( + Effect.provideService(WorkflowAgentTranscriptReadObserver, (read) => + read.agentId === `task-${UNSPECIFIED_TOOL_USE_ID}` + ? Deferred.succeed(firstRead, undefined).pipe(Effect.asVoid) + : Effect.void, + ), Effect.provide( Layer.mergeAll( IdAllocator.layer, @@ -19159,8 +19232,8 @@ describe("ClaudeAdapterV2 background wake turns", () => { NodeCrypto.layer, ), ), - ), - ); + ); + }); it.effect.each([ { diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 74cdde264e7e1..15edf21b6d2a5 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -7503,6 +7503,63 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( : new Map(current).set(nativeThreadId, { ...entry, messages }); }); + // Claude Code records the effort a subagent ran at only in its transcript. + const readSubagentTranscriptEffort = Effect.fnUntraced(function* ( + context: ActiveClaudeTurnContext, + taskId: string, + completed: boolean, + ) { + const cwd = context.input.runtimePolicy.cwd; + if (cwd === null) return undefined; + const configured = adapterOptions.environment.CLAUDE_CONFIG_DIR?.trim(); + const configDir = configured ? path.resolve(cwd, configured) : undefined; + const projectsDir = path.join( + configDir ?? path.join(yield* HostProcess.HomeDirectory, ".claude"), + "projects", + ); + // Claude names the project folder after the resolved, possibly hashed, cwd; find the session instead. + let found: string | undefined; + for (const project of yield* fileSystem + .readDirectory(projectsDir) + .pipe(Effect.orElseSucceed((): ReadonlyArray => []))) { + const candidate = path.join(projectsDir, project, context.nativeThreadId, "subagents"); + if ( + yield* fileSystem + .exists(path.join(candidate, `agent-${taskId}.jsonl`)) + .pipe(Effect.orElseSucceed(() => false)) + ) { + found = candidate; + break; + } + } + if (found === undefined) return undefined; + const transcriptDir = found; + let metadata: WorkflowAgentMetadata = {}; + let cursor = initialWorkflowAgentTranscriptCursor; + const read = Effect.fnUntraced(function* () { + while (metadata.messageId === undefined) { + const next = yield* readWorkflowAgentTranscript({ + transcriptDir, + agentId: taskId, + cursor, + ...(configDir === undefined ? {} : { configDir }), + }).pipe(Effect.option); + if (Option.isNone(next)) return false; + metadata = mergeWorkflowAgentMetadata(next.value.lines, metadata); + const advanced = next.value.cursor.offset !== cursor.offset; + cursor = next.value.cursor; + if (!next.value.truncated || !advanced) break; + } + return true; + }); + // A completed subagent can settle before its first reply is flushed; wait out the flush once. + if ((yield* read()) && completed && metadata.messageId === undefined) { + yield* Effect.sleep(WORKFLOW_MEMBER_TRANSCRIPT_FLUSH_MS); + yield* read(); + } + return metadata.effort; + }); + const updateClaudeSubagentNode = Effect.fnUntraced(function* (input: { readonly context: ActiveClaudeTurnContext; readonly taskId: string; @@ -7606,8 +7663,23 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( workflow === undefined ? undefined : withWorkflowMemberThreadIds(workflow, childThreadId); - // Unstated effort stays unknown: a custom agent definition may override the owner's. - const effort = existingSubagent === undefined ? input.effort : existingSubagent.effort; + // Unstated effort stays unknown until the transcript reports it: a custom agent definition may override the owner's. + // Read it before the settle: a run stops consuming events once its subagents settle. + const effort = + existingSubagent === undefined + ? input.effort + : (existingSubagent.effort ?? + (lifecycleChanged && + input.status !== "running" && + existingSubagent.task.modelSelection !== undefined && + (input.nativeTaskType ?? existingSubagent.nativeTaskType) !== + CLAUDE_WORKFLOW_TASK_TYPE + ? yield* readSubagentTranscriptEffort( + input.context, + input.taskId, + input.status === "completed", + ) + : undefined)); const model = input.model ?? priorTask?.model ?? null; const task = { ...(priorTask ?? { diff --git a/apps/server/src/orchestration-v2/Orchestrator.control-reads.test.ts b/apps/server/src/orchestration-v2/Orchestrator.control-reads.test.ts index f36ce5bdc564b..2845860efac20 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.control-reads.test.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.control-reads.test.ts @@ -1254,6 +1254,7 @@ it.effect("keeps delegated child pull-request links independent of the parent", creationSource: "mcp", }); const updatedParent = yield* projections.getThreadProjection(parentThreadId); + assert.deepEqual(updatedParent.subagents[0]!.modelSelection, modelSelection); const childThreadId = updatedParent.subagents[0]!.childThreadId!; const child = yield* projections.getThreadProjection(childThreadId); assert.isNull(child.thread.linkedPullRequest); diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts index 2870e22ea5000..c7e3a670de316 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.ts @@ -9313,6 +9313,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio prompt: command.task, title: command.title ?? null, model: command.modelSelection.model, + modelSelection: command.modelSelection, ...(command.completionWake === undefined ? {} : { completionWake: command.completionWake }), status: "running", result: null, @@ -9775,6 +9776,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio prompt: command.task, title: command.title ?? null, model: command.modelSelection.model, + modelSelection: command.modelSelection, ...(command.completionWake === undefined ? {} : { completionWake: command.completionWake }), status: "running", result: null, diff --git a/apps/server/src/orchestration-v2/RemoteDelegatedTask.test.ts b/apps/server/src/orchestration-v2/RemoteDelegatedTask.test.ts index f075b43d9f882..292882d0f894b 100644 --- a/apps/server/src/orchestration-v2/RemoteDelegatedTask.test.ts +++ b/apps/server/src/orchestration-v2/RemoteDelegatedTask.test.ts @@ -217,6 +217,7 @@ it.layer(layerTest)("a delegated task in a linked environment", (it) => { const [task] = projection.subagents; assert.equal(task?.childThreadId, null); assert.deepEqual(task?.remoteChild, remoteChild); + assert.deepEqual(task?.modelSelection, modelSelection); assert.equal(task?.status, "running"); const item = projection.turnItems.find((candidate) => candidate.type === "subagent"); assert.deepEqual(item?.type === "subagent" ? item.remoteChild : undefined, remoteChild); From f3b43efd9a4eae018d966b24210d4b66450a4b5d Mon Sep 17 00:00:00 2001 From: Mahir Abbas <74065513+MahirAbbas@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:17:23 +0000 Subject: [PATCH 039/192] fix(server): a hung Codex app-server no longer blocks every later Codex run (#26555) Co-authored-by: Julius Marminge --- .../Adapters/CodexAdapterV2.test.ts | 84 +++++++++++++++++++ .../Adapters/CodexAdapterV2.ts | 33 +++++++- 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 884e953a495d6..76c10382b3cb8 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -1146,6 +1146,7 @@ describe("CodexAdapterV2 session initialize", () => { runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }), initializeRequests: () => initializeRequests, + events: runtime.events, }; }); @@ -1293,6 +1294,89 @@ describe("CodexAdapterV2 session initialize", () => { ), ); + it.effect("fails the open when initialize never answers during open", () => + Effect.gen(function* () { + const initializeAwaitingResponse = yield* Deferred.make(); + const opened = yield* openReplaySession( + makeCodexReplayTranscript({ + scenario: "initialize-hung-open", + entries: replayPreamble("initialize-hung-open").slice(0, 2), + }), + (entry) => + entry.label === "initialize" + ? Deferred.succeed(initializeAwaitingResponse, undefined).pipe( + Effect.andThen(Effect.never), + ) + : Effect.void, + () => Effect.die("initialize never answers"), + ).pipe(Effect.flip, Effect.forkChild({ startImmediately: true })); + yield* Deferred.await(initializeAwaitingResponse); + yield* TestClock.adjust("30 seconds"); + + // The session manager discards a session whose open fails. + assert.instanceOf(yield* Fiber.join(opened), ProviderAdapter.ProviderAdapterOpenSessionError); + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll( + IdAllocator.layer, + McpProviderSessions.layer, + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ), + ); + + it.effect("retires the shared session when initialize never answers", () => + Effect.gen(function* () { + const initializeAwaitingResponse = yield* Deferred.make(); + const session = yield* openReplaySession( + makeCodexReplayTranscript({ + scenario: "initialize-hung", + entries: replayPreamble("initialize-hung").slice(0, 2), + }), + (entry) => + entry.label === "initialize" + ? Deferred.succeed(initializeAwaitingResponse, undefined).pipe( + Effect.andThen(Effect.never), + ) + : Effect.void, + ); + const eventsExit = yield* Stream.runDrain(session.events).pipe( + Effect.exit, + Effect.forkChild({ startImmediately: true }), + ); + + const ensured = yield* session + .ensureThread("thread-initialize-hung") + .pipe(Effect.flip, Effect.forkChild({ startImmediately: true })); + yield* Deferred.await(initializeAwaitingResponse); + const waiting = yield* session + .ensureThread("thread-initialize-hung-waiting") + .pipe(Effect.flip, Effect.forkChild({ startImmediately: true })); + yield* TestClock.adjust("30 seconds"); + + assert.equal((yield* Fiber.join(ensured))._tag, "ProviderAdapterEnsureThreadError"); + // A waiter fails at once instead of re-sending initialize to the hung app-server. + assert.equal((yield* Fiber.join(waiting))._tag, "ProviderAdapterEnsureThreadError"); + assert.equal(session.initializeRequests(), 1); + // The session manager retires a session whose event stream fails. + const exit = yield* Fiber.join(eventsExit); + assert.isTrue(Exit.isFailure(exit)); + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll( + IdAllocator.layer, + McpProviderSessions.layer, + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ), + ); + it.effect("completes the handshake after a caller is interrupted mid-initialize", () => Effect.gen(function* () { const initializeAwaitingResponse = yield* Deferred.make(); diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index 7de690c5e47d0..f7d3b49a687e5 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -245,6 +245,8 @@ const decodeCodexBackgroundTerminalTerminateResponse = Schema.decodeUnknownEffec const decodeCodexBackgroundTerminalsListResponse = Schema.decodeUnknownEffect( CodexBackgroundTerminalsListResponse, ); +// Initialize normally answers in under a second; leave room for a slow first launch. +const CODEX_INITIALIZE_TIMEOUT = "30 seconds"; const CodexThreadSampleResponse = Schema.Struct({ text: Schema.NullOr(Schema.String) }); const decodeCodexThreadSampleResponse = Schema.decodeUnknownEffect(CodexThreadSampleResponse); export const CODEX_PROMPT_SUGGESTION_PROMPT = @@ -1929,7 +1931,15 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( }), ), ); + const events = yield* Queue.unbounded< + ProviderAdapter.ProviderAdapterV2Event, + ProviderAdapter.ProviderAdapterEventStreamError + >(); const initialized = yield* Ref.make(false); + // After a timeout the session is retiring; waiters must not re-send to the hung app-server. + const initializeTimeout = yield* Ref.make< + ProviderAdapter.ProviderAdapterEventStreamError | undefined + >(undefined); // Codex rejects a second initialize on the shared app-server. const initializePermit = yield* Semaphore.make(1); const ensureInitialized = initializePermit.withPermit( @@ -1938,6 +1948,8 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( if (alreadyInitialized) { return; } + const timedOut = yield* Ref.get(initializeTimeout); + if (timedOut !== undefined) return yield* timedOut; const userAgent = yield* client .request("initialize", { @@ -1953,6 +1965,23 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( ? Effect.succeed(null) : Effect.fail(error), }), + // A hung app-server never answers, and every thread on this shared + // session waits behind the permit. Failing the event stream retires + // the session, so the next turn spawns a fresh app-server. + Effect.timeoutOrElse({ + duration: CODEX_INITIALIZE_TIMEOUT, + orElse: () => { + const error = new ProviderAdapter.ProviderAdapterEventStreamError({ + driver: CODEX_PROVIDER, + providerSessionId: input.providerSessionId, + cause: `Codex app-server did not answer initialize within ${CODEX_INITIALIZE_TIMEOUT}.`, + }); + return Ref.set(initializeTimeout, error).pipe( + Effect.andThen(Queue.fail(events, error)), + Effect.andThen(Effect.fail(error)), + ); + }, + }), ); const version = userAgent && /\/(\S+)/u.exec(userAgent)?.[1]; const advisory = @@ -1982,10 +2011,6 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( model: input.modelSelection.model, now, }); - const events = yield* Queue.unbounded< - ProviderAdapter.ProviderAdapterV2Event, - ProviderAdapter.ProviderAdapterEventStreamError - >(); // Drain final notifications before the session manager retires the dead process. // Queue.fail lets readers take items already buffered before they see the failure. yield* client.awaitTermination.pipe( From df8c1a1fd48e6969ae85aaef6c9486e56f072066 Mon Sep 17 00:00:00 2001 From: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com> Date: Sat, 14 Nov 2026 05:20:21 -0500 Subject: [PATCH 040/192] fix(web): Narrator highlights the sidebar thread title where it is drawn (#25269) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/web/src/components/sidebar/SidebarDraftBlock.tsx | 5 +---- apps/web/src/components/sidebar/SidebarThreadRow.tsx | 4 ---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/web/src/components/sidebar/SidebarDraftBlock.tsx b/apps/web/src/components/sidebar/SidebarDraftBlock.tsx index c4868c4776ea7..7ac90c0d48219 100644 --- a/apps/web/src/components/sidebar/SidebarDraftBlock.tsx +++ b/apps/web/src/components/sidebar/SidebarDraftBlock.tsx @@ -99,7 +99,6 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: { onContextMenu={handleContextMenu} onKeyDown={handleKeyDown} > - {preview}
@@ -127,9 +126,7 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: {
-
- {preview} -
+
{preview}
diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx index 216f6635f7088..9098782006ef3 100644 --- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx @@ -1042,7 +1042,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP /> ) : ( ); - const accessibleTitle = isRenaming ? null : {thread.title}; const prBadgeShape = supportsMultiplePullRequests ? resolveThreadPullRequestBadge(thread.pullRequests) @@ -1262,7 +1260,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP > {/* `contents` keeps one-line rows a single flex row. */}
- {accessibleTitle} {inFolder ? null : ( } > - {accessibleTitle}
Date: Sat, 14 Nov 2026 11:22:00 +0100 Subject: [PATCH 041/192] fix(desktop): desktop tests no longer race to install Electron (#26654) Co-authored-by: Julius Marminge --- apps/desktop/scripts/ensure-electron-runtime.mjs | 4 ++++ apps/desktop/vite.config.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/apps/desktop/scripts/ensure-electron-runtime.mjs b/apps/desktop/scripts/ensure-electron-runtime.mjs index 933a763dfa354..741a2d9ee85d8 100644 --- a/apps/desktop/scripts/ensure-electron-runtime.mjs +++ b/apps/desktop/scripts/ensure-electron-runtime.mjs @@ -209,6 +209,10 @@ export function ensureElectronRuntime() { return electronPath; } +export function setup() { + ensureElectronRuntime(); +} + // `file://${argv[1]}` never matches on Windows (drive letters need `file:///C:/`). if (process.argv[1] && NodeURL.pathToFileURL(process.argv[1]).href === import.meta.url) { const electronPath = ensureElectronRuntime(); diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 455901299edc3..3e6fd9531106b 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -164,6 +164,9 @@ export default defineConfig({ // The Windows lane runs workspace suites concurrently; filesystem-heavy // desktop integration tests can exceed Vitest's 5 second default there. testTimeout: 15_000, + // `require("electron")` downloads a missing binary, so concurrent workers race to + // extract it into the same dist/. Install it once before any worker starts. + globalSetup: ["./scripts/ensure-electron-runtime.mjs"], projects: [ { extends: true, From 9ff435b060f3a73579ac599c80c00616ce760800 Mon Sep 17 00:00:00 2001 From: gyurix Date: Sat, 14 Nov 2026 11:24:18 +0100 Subject: [PATCH 042/192] fix(server): pause service restart when disk is full (#25924) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/serviceLauncher.test.ts | 206 ++++++++++++++++++++++++ apps/server/src/serviceLauncher.ts | 76 ++++++++- 2 files changed, 280 insertions(+), 2 deletions(-) diff --git a/apps/server/src/serviceLauncher.test.ts b/apps/server/src/serviceLauncher.test.ts index ac699b9c7d6cb..1e504154398f4 100644 --- a/apps/server/src/serviceLauncher.test.ts +++ b/apps/server/src/serviceLauncher.test.ts @@ -201,6 +201,212 @@ const fakeServiceUnit = ( }); it.layer(NodeServices.layer)("service state persistence", (it) => { + it.effect.each([ + "startup", + "log write fails", + "home probe error", + "database probe error", + "pending update", + "update handoff", + "trial exit", + "failed trial start", + "stop", + ] as const)("waits for disk recovery during %s without losing the update lifecycle", (scenario) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-service-disk-" }); + const statePath = path.join(root, "runtime", "service-state.json"); + const databasePath = path.join(root, "userdata", "statev2.sqlite"); + let backupPath = path.join(root, "runtime", "db-backup", "disk-update"); + const pending = { + id: "disk-update", + fromVersion: "1.0.0", + targetVersion: "1.1.0", + dbPath: databasePath, + status: "pending" as const, + }; + yield* fs.makeDirectory(path.dirname(databasePath), { recursive: true }); + yield* fs.writeFileString(databasePath, "before trial"); + const childSource = ` +const fs = require("node:fs"); +const context = JSON.parse(process.env.T3_SERVICE_LAUNCHER_CONTEXT); +if (context.update?.status === "pending") { + if (context.childVersion !== context.update.targetVersion) throw new Error("wrong trial version"); + if (fs.readFileSync(${JSON.stringify(path.dirname(backupPath))} + "/" + context.update.id + "/database", "utf8") !== "before trial") { + throw new Error("missing original backup"); + } + if (${JSON.stringify(scenario)} === "trial exit") { + fs.writeFileSync(context.update.dbPath, "migrated"); + process.on("message", () => process.exit(1)); + process.send("migrated"); + } else { + process.send({ type: "prepared", updateId: context.update.id }); + process.on("message", (message) => { + if (message.type === "committed") process.send("ready"); + }); + } +} else if (${JSON.stringify(scenario)} === "update handoff" && context.update === undefined) { + process.on("message", (message) => { + if (message.type === "update-accepted") process.send("exiting"); + if (message === "exit") process.exit(0); + }); + process.send({ type: "request-update", targetVersion: "1.1.0", dbPath: ${JSON.stringify(databasePath)} }); +} else { + process.send("ready"); +} +setInterval(() => {}, 1_000); +`; + for (const version of ["1.0.0", "1.1.0"]) { + yield* writeFakeRuntime( + fs, + path, + path.join(root, "runtime", "versions", version), + childSource, + ); + } + yield* Effect.promise(() => + writeServiceState(statePath, { + protocol: SERVICE_LAUNCHER_PROTOCOL, + activeVersion: "1.0.0", + ...([ + "startup", + "log write fails", + "home probe error", + "database probe error", + "update handoff", + "stop", + ].includes(scenario) + ? {} + : { update: pending }), + }), + ); + let low = [ + "startup", + "log write fails", + "home probe error", + "database probe error", + "pending update", + "stop", + ].includes(scenario); + let recovered = false; + const blocked = Promise.withResolvers(); + const ready = Promise.withResolvers(); + const probing = Promise.withResolvers(); + const capacityResult = Promise.withResolvers(); + const actualStatfs = NodeFSP.statfs; + const stats = yield* Effect.promise(() => actualStatfs(root)); + yield* Effect.acquireRelease( + Effect.sync(() => { + vi.useFakeTimers({ + toFake: ["setInterval", "clearInterval", "setTimeout", "clearTimeout"], + }); + const capacity = vi.spyOn(NodeFSP, "statfs").mockImplementation(async (directory) => { + if (scenario === "stop" && directory === root) { + probing.resolve(); + await capacityResult.promise; + } + if ( + (scenario === "home probe error" && directory === root) || + (scenario === "database probe error" && directory === path.dirname(databasePath)) + ) { + throw new Error("statfs unavailable"); + } + if (scenario === "failed trial start" && !recovered && NodeFS.existsSync(backupPath)) + low = true; + return { ...stats, bavail: low ? 0 : 1024 ** 4, bsize: 1 }; + }); + const log = vi.spyOn(process.stderr, "write").mockImplementation((chunk) => { + if (String(chunk).includes("Waiting for free space")) { + blocked.resolve(); + if (scenario === "log write fails") { + throw Object.assign(new Error("no space left"), { code: "ENOSPC" }); + } + } + return true; + }); + spawnHook.current = (child) => + child.on("message", (message) => { + if (message === "ready") ready.resolve(); + if (message === "migrated" || message === "exiting") { + low = true; + child.send("exit"); + } + }); + return { capacity, log }; + }), + ({ capacity, log }) => + Effect.sync(() => { + capacity.mockRestore(); + log.mockRestore(); + spawnHook.current = undefined; + vi.useRealTimers(); + }), + ); + const beforeSpawns = spawnedChildren.length; + const launcher = new Launcher(root, yield* Effect.promise(() => readServiceState(statePath))); + const running = launcher.run(); + yield* Effect.addFinalizer(() => + Effect.promise(async () => { + await launcher.stop("SIGTERM"); + await running; + }), + ); + if (scenario === "stop") { + yield* Effect.promise(() => probing.promise); + const stopping = launcher.stop("SIGTERM"); + capacityResult.resolve(); + yield* Effect.promise(() => stopping); + yield* Effect.promise(() => running); + assert.equal(spawnedChildren.length, beforeSpawns); + assert.equal(vi.getTimerCount(), 0); + return; + } + yield* Effect.promise(() => Promise.race([blocked.promise, ready.promise, running])); + assert.equal( + spawnedChildren.length - beforeSpawns, + ["trial exit", "update handoff"].includes(scenario) ? 1 : 0, + ); + if (scenario === "update handoff") { + const state = yield* Effect.promise(() => readServiceState(statePath)); + backupPath = path.join(root, "runtime", "db-backup", state.update!.id); + } + assert.equal( + yield* fs.exists(backupPath), + scenario === "trial exit" || scenario === "failed trial start", + ); + if (scenario === "trial exit") + assert.equal(yield* fs.readFileString(databasePath), "migrated"); + if (scenario === "failed trial start") { + yield* fs.remove(path.join(root, "runtime", "versions", "1.1.0"), { recursive: true }); + } + low = false; + recovered = true; + yield* Effect.promise(() => vi.advanceTimersByTimeAsync(30_000)); + yield* Effect.promise(() => Promise.race([ready.promise, running])); + const state = yield* Effect.promise(() => readServiceState(statePath)); + assert.equal( + state.activeVersion, + ["pending update", "update handoff"].includes(scenario) ? "1.1.0" : "1.0.0", + ); + assert.equal( + state.update?.status, + { + startup: undefined, + "log write fails": undefined, + "home probe error": undefined, + "database probe error": undefined, + "pending update": "committed", + "update handoff": "committed", + "trial exit": "rolled-back", + "failed trial start": "failed", + }[scenario], + ); + assert.equal(yield* fs.readFileString(databasePath), "before trial"); + assert.isFalse(yield* fs.exists(backupPath)); + }), + ); + it.effect( "loads optional service.env literally without changing inherited or managed variables", () => diff --git a/apps/server/src/serviceLauncher.ts b/apps/server/src/serviceLauncher.ts index d57a3abaccc99..a0f7752025027 100644 --- a/apps/server/src/serviceLauncher.ts +++ b/apps/server/src/serviceLauncher.ts @@ -50,6 +50,9 @@ const HANDOFF_DELAY_MS = 2_000; */ const PREPARED_TIMEOUT_MS = 30 * 60_000; const TERMINATE_GRACE_MS = 5_000; +// Keep SQLite startup and journaling from turning a full filesystem into a restart loop. +const MIN_SERVICE_START_FREE_BYTES = 64 * 1024 ** 2; +const DISK_SPACE_RETRY_MS = 30_000; // A replacement started by a handoff waits up to two minutes for the stopping launcher. const LOCK_RETRY_MS = 500; const LOCK_ATTEMPTS = 240; @@ -928,9 +931,12 @@ export class Launcher { return terminateChild(child, signal, this.#stopChildOverIpc); } + // Diagnostics are best-effort: a full log filesystem must not stop the launcher waiting for space. #log(line: string): void { - if (this.#output === undefined) process.stderr.write(line); - else NodeFS.writeSync(this.#output, line); + try { + if (this.#output === undefined) process.stderr.write(line); + else NodeFS.writeSync(this.#output, line); + } catch {} } #enqueue(transition: () => Promise): void { @@ -983,6 +989,53 @@ export class Launcher { this.#timer = undefined; } + async #serviceStartDiskSpaceBlocker(): Promise { + const databasePath = + this.#state.update?.status === "pending" + ? this.#state.update.dbPath + : NodePath.join(this.#baseDir, "userdata", "statev2.sqlite"); + const resolvedDatabasePath = await restoreTarget(databasePath).catch(() => databasePath); + const freeBytes = async (directory: string): Promise => { + try { + return await filesystemBackupSpaceProbe.readFreeBytes(directory); + } catch (cause) { + const parent = NodePath.dirname(directory); + if ((cause as NodeJS.ErrnoException).code === "ENOENT" && parent !== directory) { + return freeBytes(parent); + } + return Number.POSITIVE_INFINITY; + } + }; + const available = await Promise.all([ + freeBytes(this.#baseDir), + freeBytes(NodePath.dirname(resolvedDatabasePath)), + ]); + const free = Math.min(...available); + return free < MIN_SERVICE_START_FREE_BYTES + ? `T3 Code needs at least ${MIN_SERVICE_START_FREE_BYTES} bytes free before starting the server; ${free} bytes available.` + : undefined; + } + + async #deferForDiskSpace(retry: () => Promise): Promise { + const reason = await this.#serviceStartDiskSpaceBlocker(); + if (reason === undefined) return false; + // A stop during the probe must not arm a retry that outlives it. + if (this.#stopRequested) return true; + this.#clearTimer(); + this.#log(`[service-launcher] ${reason} Waiting for free space before retrying.\n`); + this.#timer = setInterval( + () => + this.#enqueue(async () => { + if (this.#child !== null || this.#stopping) return; + if ((await this.#serviceStartDiskSpaceBlocker()) !== undefined) return; + this.#clearTimer(); + await retry(); + }), + DISK_SPACE_RETRY_MS, + ); + return true; + } + async #selectState(next: ServiceState): Promise { const release = await acquireServiceSelectionLock(this.#statePath); try { @@ -1057,6 +1110,7 @@ export class Launcher { } async #startTrial(pending: PendingServiceUpdate): Promise { + if (await this.#deferForDiskSpace(() => this.#startTrial(pending))) return; try { await backupDatabaseOnce(this.#baseDir, pending, this.#stopSignal.signal); } catch { @@ -1075,6 +1129,14 @@ export class Launcher { async #startChild(version: string, role: ChildRole, update?: ServiceUpdateRecord): Promise { if (this.#stopping) return; + if ( + await this.#deferForDiskSpace(() => + role === "trial" && update?.status === "pending" + ? this.#startTrial(update) + : this.#startChild(version, role, update), + ) + ) + return; if (!(await runtimeExists(this.#baseDir, version))) { throw new Error(`Selected t3@${version} runtime is missing or incomplete.`); } @@ -1333,6 +1395,12 @@ export class Launcher { await this.#startTrial(pending); return; } + if ( + await this.#deferForDiskSpace(() => + this.#startChild(child.version, "active", this.#state.update), + ) + ) + return; throw new Error(`Active child exited unexpectedly (${String(code ?? signal ?? "unknown")}).`); } @@ -1398,6 +1466,8 @@ export class Launcher { this.#child = null; await this.#terminate(child.process); } + if (await this.#deferForDiskSpace(() => this.#returnToPrevious(pending, status, reason))) + return; await restoreDatabaseBackup(this.#baseDir, pending); const outcome = terminalUpdate({ pending, status, reason }); const next: ServiceState = { @@ -1432,6 +1502,8 @@ export async function main(options: LauncherOptions = {}): Promise { // oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher has no Effect runtime. const windows = process.platform === "win32"; let logTimer: NodeJS.Timeout | undefined; + // A failed stderr write (ENOSPC on a redirected log) is emitted asynchronously; without a listener it is fatal. + process.stderr.on("error", () => undefined); try { // What the service manager started the launcher with. It locates the unit; service.env // changes only what the server gets. From 4c2f04d35d371c2b2ecb04b283e282325026be8a Mon Sep 17 00:00:00 2001 From: Batuhan1999 Date: Sat, 14 Nov 2026 11:24:51 +0100 Subject: [PATCH 043/192] fix(web): queued runs, relationships and changed files no longer re-render on every streamed token (#24055) Co-authored-by: Julius Marminge --- .../chat/QueuedRunsControl.browser.test.tsx | 97 +++++++++++++++++-- .../src/components/chat/QueuedRunsControl.tsx | 4 +- ...hreadRelationshipsControl.browser.test.tsx | 2 +- .../chat/ThreadRelationshipsControl.tsx | 6 +- .../components/chat/TimelineAssistantRows.tsx | 4 +- apps/web/src/state/entities.ts | 17 ++-- .../src/state/threadDetail.test.ts | 30 +++++- .../client-runtime/src/state/threadDetail.ts | 15 +++ .../src/state/threadRelationships.ts | 7 +- 9 files changed, 155 insertions(+), 27 deletions(-) diff --git a/apps/web/src/components/chat/QueuedRunsControl.browser.test.tsx b/apps/web/src/components/chat/QueuedRunsControl.browser.test.tsx index c226a26160773..af20840837d47 100644 --- a/apps/web/src/components/chat/QueuedRunsControl.browser.test.tsx +++ b/apps/web/src/components/chat/QueuedRunsControl.browser.test.tsx @@ -1,3 +1,5 @@ +import { RegistryContext } from "@effect/atom-react"; +import type { EnvironmentThreadState } from "@t3tools/client-runtime/state/threads"; import { EnvironmentId, MessageId, ThreadId } from "@t3tools/contracts"; import { serializeAssistantCitation } from "@t3tools/shared/assistantCitations"; import { @@ -5,12 +7,14 @@ import { toComposerContextId, } from "@t3tools/shared/composerContextReferences"; import * as DateTime from "effect/DateTime"; -import type { ComponentProps } from "react"; +import * as Option from "effect/Option"; +import { AsyncResult, type Atom, AtomRegistry } from "effect/reactivity"; +import { Profiler, type ComponentProps } from "react"; import { expect, it, vi } from "vite-plus/test"; import { page } from "vite-plus/test/browser"; const state = vi.hoisted(() => ({ - projection: null as unknown, + threadState: null as unknown as Atom.Writable>, workflow: null as unknown, })); @@ -21,7 +25,6 @@ vi.mock(import("@t3tools/client-runtime/state/thread-workflows"), async (importO vi.mock(import("../../state/entities"), async (importOriginal) => ({ ...(await importOriginal()), - useThreadProjection: (() => state.projection) as never, // The linked environment is connected, but its thread is not in the loaded shell. useQuietThreadShell: ((target: { readonly environmentId: string } | null) => target === null || target.environmentId === "environment:linked" @@ -42,6 +45,25 @@ vi.mock(import("../../state/query"), async (importOriginal) => ({ })) as never, })); +// The thread detail atoms are real, fed by one state atom the test writes. +vi.mock(import("../../state/threads"), async (importOriginal) => { + const { createEnvironmentThreadDetailAtoms, EMPTY_ENVIRONMENT_THREAD_STATE } = + await import("@t3tools/client-runtime/state/threads"); + const { AsyncResult, Atom } = await import("effect/reactivity"); + const Option = await import("effect/Option"); + const { makeThreadProjectionFixture } = await import("../../test-fixtures"); + state.threadState = Atom.make>( + AsyncResult.success({ + ...EMPTY_ENVIRONMENT_THREAD_STATE, + data: Option.some(makeThreadProjectionFixture()), + }), + ).pipe(Atom.keepAlive); + return { + ...(await importOriginal()), + environmentThreadDetails: createEnvironmentThreadDetailAtoms(() => state.threadState), + }; +}); + vi.mock(import("../../state/use-atom-command"), async (importOriginal) => ({ ...(await importOriginal()), useAtomCommand: (() => async () => undefined) as never, @@ -54,11 +76,13 @@ vi.mock(import("../../assets/assetUrls"), async (importOriginal) => ({ })); import "~/index.css"; -import { render as renderUi } from "../../../test/render"; +import { EMPTY_ENVIRONMENT_THREAD_STATE } from "@t3tools/client-runtime/state/threads"; + +import { act, render as renderUi } from "../../../test/render"; +import { makeStreamingTimelineFixture, makeThreadProjectionFixture } from "../../test-fixtures"; import { QueuedRunsControl } from "./QueuedRunsControl"; function setup() { - state.projection = null; state.workflow = null; } @@ -98,7 +122,6 @@ it("previews assistant quotes as readable text and preserves the citation when e contextId: toComposerContextId("header"), label: "header.png", }); - state.projection = { projection: { messages: [] } }; state.workflow = { activeRun: { id: "run:active" }, canPromoteToSteer: true, @@ -147,7 +170,6 @@ it("shows who sent a queued message and folds automation and agent prompts", asy createdAt: DateTime.makeUnsafe("2026-10-27T12:00:00.000Z"), ...fields, }); - state.projection = { projection: { messages: [] } }; state.workflow = { activeRun: { id: "run:active" }, canPromoteToSteer: true, @@ -223,7 +245,6 @@ it("shows who sent a queued message and folds automation and agent prompts", asy it("stays inside the timeline scroller instead of growing the chat column", async () => { setup(); - state.projection = { projection: { messages: [] } }; state.workflow = { activeRun: { id: "run:active" }, canPromoteToSteer: true, @@ -250,3 +271,63 @@ it("stays inside the timeline scroller instead of growing the chat column", asyn const column = screen.getByTestId("column").element(); expect(column.scrollHeight).toBe(column.clientHeight); }); + +it("does not re-render while assistant text streams into the thread", async () => { + setup(); + state.workflow = { + activeRun: { id: "run:active" }, + canPromoteToSteer: true, + canReorder: true, + queuedRuns: [ + { + run: { id: "run:queued", userMessageId: "message:queued" }, + text: "Run the tests next", + attachments: [], + }, + ], + }; + const base = makeThreadProjectionFixture(); + // Each delta replaces only the streaming reply; every other item keeps its identity. + const timeline = makeStreamingTimelineFixture().visibleTurnItems; + const thread = (text: string, done = false) => { + const items = timeline.map((entry) => + entry.item.type === "assistant_message" && entry.item.streaming + ? { + ...entry, + item: done + ? { ...entry.item, text, streaming: false, status: "completed" as const } + : { ...entry.item, text }, + } + : entry, + ); + return AsyncResult.success({ + ...EMPTY_ENVIRONMENT_THREAD_STATE, + data: Option.some({ + ...base, + turnItems: items.map(({ item }) => item), + visibleTurnItems: items, + }), + }); + }; + const registry = AtomRegistry.make(); + await using stack = new AsyncDisposableStack(); + stack.defer(() => registry.dispose()); + registry.set(state.threadState, thread("Hel")); + let renders = 0; + await using _screen = await renderUi( + + (renders += 1)}> + + + , + ); + await expect.element(page.getByText("Run the tests next", { exact: true })).toBeVisible(); + const settled = renders; + + await act(() => registry.set(state.threadState, thread("Hello"))); + await act(() => registry.set(state.threadState, thread("Hello there"))); + expect(renders).toBe(settled); + + await act(() => registry.set(state.threadState, thread("Hello there", true))); + expect(renders).toBeGreaterThan(settled); +}); diff --git a/apps/web/src/components/chat/QueuedRunsControl.tsx b/apps/web/src/components/chat/QueuedRunsControl.tsx index 29a99d385573d..68fcd504159c3 100644 --- a/apps/web/src/components/chat/QueuedRunsControl.tsx +++ b/apps/web/src/components/chat/QueuedRunsControl.tsx @@ -28,7 +28,7 @@ import { useId, useImperativeHandle, useRef, useState, type ReactNode, type Ref import { useAssetUrls } from "../../assets/assetUrls"; import { threadEnvironment } from "../../state/threads"; -import { useQuietThreadShell, useThreadProjection } from "../../state/entities"; +import { useQuietThreadProjection, useQuietThreadShell } from "../../state/entities"; import { useEnvironment } from "../../state/environments"; import { useAtomCommand } from "../../state/use-atom-command"; import { isImageAttachment, type ChatMessage } from "../../types"; @@ -94,7 +94,7 @@ export function QueuedRunsControl({ readonly timestampFormat: TimestampFormat; readonly onOpenThread: (threadId: ThreadId, environmentId?: EnvironmentId) => void; }) { - const projection = useThreadProjection( + const projection = useQuietThreadProjection( scopeThreadRef(props.environmentId, props.threadId), )?.projection; const promote = useAtomCommand(threadEnvironment.promoteQueuedRun); diff --git a/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx b/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx index 9679f42569c6b..cafc2ac171cee 100644 --- a/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx +++ b/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx @@ -30,7 +30,7 @@ vi.mock(import("@effect/atom-react"), async (original) => ({ vi.mock(import("../../state/entities"), () => ({ useQuietThreadShell: (() => null) as never, - useThreadProjection: ((ref: unknown) => + useQuietThreadProjection: ((ref: unknown) => ref === null ? null : { projection: state.projection }) as never, useThreadRelationshipShells: (() => state.projection === null diff --git a/apps/web/src/components/chat/ThreadRelationshipsControl.tsx b/apps/web/src/components/chat/ThreadRelationshipsControl.tsx index 49c69ff4fadee..48213f9618c6d 100644 --- a/apps/web/src/components/chat/ThreadRelationshipsControl.tsx +++ b/apps/web/src/components/chat/ThreadRelationshipsControl.tsx @@ -62,8 +62,8 @@ import { useArchivedThreadSnapshots } from "../../lib/archivedThreadsState"; import { buildThreadRouteParams } from "../../threadRoutes"; import { useProjects, + useQuietThreadProjection, useQuietThreadShell, - useThreadProjection, useThreadRelationshipShells, useHiddenThreadKeys, useAnyEnvironmentHiding, @@ -428,7 +428,7 @@ export function ThreadRelationshipsPanel(props: { readonly showSideChats: boolean; }) { const ref = scopeThreadRef(props.environmentId, props.threadId); - const projection = useThreadProjection(ref)?.projection ?? null; + const projection = useQuietThreadProjection(ref)?.projection ?? null; const providersByEnvironment = useEnvironmentProviders(); const settings = useAtomValue(serverEnvironment.settingsValueAtom(props.environmentId)); const relationshipShells = useThreadRelationshipShells(ref); @@ -455,7 +455,7 @@ export function ThreadRelationshipsPanel(props: { parentShell && isProviderNativeSubagentThread(parentShell) ? scopeThreadRef(props.environmentId, parentShell.id) : null; - const parentProjection = useThreadProjection(parentRef)?.projection; + const parentProjection = useQuietThreadProjection(parentRef)?.projection; const navigate = useNavigate(); const mergeBack = useAtomCommand(threadEnvironment.mergeBack); const stopSession = useAtomCommand(threadEnvironment.stopSession); diff --git a/apps/web/src/components/chat/TimelineAssistantRows.tsx b/apps/web/src/components/chat/TimelineAssistantRows.tsx index 6af0004ba24d4..d508e0071dadf 100644 --- a/apps/web/src/components/chat/TimelineAssistantRows.tsx +++ b/apps/web/src/components/chat/TimelineAssistantRows.tsx @@ -19,7 +19,7 @@ import { readResponseSubagents, useAssistantMessageText, useProject, - useThreadShell, + useQuietThreadShell, } from "../../state/entities"; import { MessageCopyButton } from "./MessageCopyButton"; import { AssistantCitationSource } from "./AssistantCitationSource"; @@ -444,7 +444,7 @@ function AssistantChangedFilesSectionInner({ const setExpanded = useUiStateStore((store) => store.setThreadChangedFilesExpanded); const allDirectoriesExpanded = persistedExpanded ?? false; - const thread = useThreadShell(ctx.threadRef); + const thread = useQuietThreadShell(ctx.threadRef); const activeProject = useProject( thread && thread.projectId ? { environmentId: thread.environmentId, projectId: thread.projectId } diff --git a/apps/web/src/state/entities.ts b/apps/web/src/state/entities.ts index f0f7e33be50c6..0cb7bbc600937 100644 --- a/apps/web/src/state/entities.ts +++ b/apps/web/src/state/entities.ts @@ -5,6 +5,7 @@ import type { EnvironmentThread, EnvironmentThreadShell, EnvironmentThreadStructure, + ThreadStructure, ThreadStructureVisibleTurnItem, } from "@t3tools/client-runtime/state/shell"; import { @@ -22,7 +23,6 @@ import type { EnvironmentId, OrchestrationV2ProjectedTurnItem, OrchestrationV2RemoteTaskChild, - OrchestrationV2ThreadProjection, OrchestrationV2ThreadShell, NodeId, ThreadId, @@ -396,10 +396,7 @@ function shellParentThreadId( /** Retains unchanged reachable shells so unrelated streams do not re-render the panel. */ export function selectThreadRelationshipShells( threadId: ThreadId, - projection: Pick< - OrchestrationV2ThreadProjection, - "thread" | "subagents" | "contextTransfers" - > | null, + projection: Pick | null, threads: ReadonlyArray, ): ReadonlyArray { const named = new Set([threadId]); @@ -431,8 +428,16 @@ const relationshipShellsAtomFamily = Atom.family((key: string) => { return Atom.make((get) => { const next = selectThreadRelationshipShells( ref.threadId, - get(environmentThreadDetails.threadAtom(ref))?.projection ?? null, + get(environmentThreadDetails.quietThreadAtom(ref))?.projection ?? null, get(environmentThreadShells.environmentThreadsAtom(ref.environmentId)), + ).map( + (thread) => + get( + environmentThreadShells.quietThreadShellAtom({ + environmentId: ref.environmentId, + threadId: thread.id, + }), + )?.source ?? thread, ); if (arrayElementsEqual(previous, next)) return previous; previous = next; diff --git a/packages/client-runtime/src/state/threadDetail.test.ts b/packages/client-runtime/src/state/threadDetail.test.ts index 31fb095043ee8..e32d70215e202 100644 --- a/packages/client-runtime/src/state/threadDetail.test.ts +++ b/packages/client-runtime/src/state/threadDetail.test.ts @@ -53,9 +53,29 @@ const assistant: OrchestrationV2TurnItem = { streaming: true, }; -function withItem(item: OrchestrationV2TurnItem, updatedAt = v2Now) { +const assistantMessage: OrchestrationV2ConversationMessage = { + createdBy: "agent", + creationSource: "provider", + id: MessageId.make("message-assistant"), + threadId: v2ThreadId, + runId: RunId.make("run-1"), + nodeId: null, + role: "assistant", + text: "Hel", + attachments: [], + streaming: true, + createdAt: v2Now, + updatedAt: v2Now, +}; + +function withItem( + item: OrchestrationV2TurnItem, + updatedAt = v2Now, + message: OrchestrationV2ConversationMessage = assistantMessage, +) { return { ...v2Projection, + messages: [message], turnItems: [item], visibleTurnItems: [ { @@ -74,13 +94,19 @@ describe("sameThreadStructure", () => { const before = withItem(assistant); it("holds through streamed text", () => { - const next = withItem({ ...assistant, text: "Hello", updatedAt: later }, later); + const next = withItem({ ...assistant, text: "Hello", updatedAt: later }, later, { + ...assistantMessage, + text: "Hello", + updatedAt: later, + }); expect(sameThreadStructure(before, next)).toBe(true); }); it("sees the item finish streaming", () => { const next = withItem({ ...assistant, text: "Hello", streaming: false, status: "completed" }); expect(sameThreadStructure(before, next)).toBe(false); + const message = { ...assistantMessage, text: "Hello", streaming: false }; + expect(sameThreadStructure(before, withItem(assistant, v2Now, message))).toBe(false); }); it("sees a new item", () => { diff --git a/packages/client-runtime/src/state/threadDetail.ts b/packages/client-runtime/src/state/threadDetail.ts index 74012f436318b..10ba574e0bb08 100644 --- a/packages/client-runtime/src/state/threadDetail.ts +++ b/packages/client-runtime/src/state/threadDetail.ts @@ -1,5 +1,6 @@ import { OrchestrationV2AppThread, + OrchestrationV2ConversationMessage, OrchestrationV2TurnItem, type OrchestrationV2ThreadProjection, type ScopedThreadRef, @@ -159,6 +160,7 @@ function toThreadStructure(projection: OrchestrationV2ThreadProjection): ThreadS const sameTurnItem = Schema.toEquivalence(OrchestrationV2TurnItem); const sameAppThread = Schema.toEquivalence(OrchestrationV2AppThread); +const sameMessage = Schema.toEquivalence(OrchestrationV2ConversationMessage); // Overlays previous's structure on next, so the fields the strip functions // remove are exactly the ones ignored here. @@ -183,6 +185,17 @@ function sameTurnItemStructure(previous: TurnItem, next: TurnItem): boolean { return sameApartFromStripped(sameTurnItem, previous, structure, next); } +// A streaming reply's text also lands on its message; readers of that text read the live thread. +function sameMessageStructure( + previous: OrchestrationV2ConversationMessage, + next: OrchestrationV2ConversationMessage, +): boolean { + if (previous === next) return true; + if (!previous.streaming || !next.streaming || previous.id !== next.id) return false; + const { text: _text, updatedAt: _updatedAt, ...structure } = previous; + return sameApartFromStripped(sameMessage, previous, structure, next); +} + function sameProjectionArray( previous: ReadonlyArray, next: ReadonlyArray, @@ -240,6 +253,7 @@ export function sameThreadStructure( for (const key of Object.keys(next) as Array) { if ( key === "thread" || + key === "messages" || key === "turnItems" || key === "visibleTurnItems" || key === "updatedAt" @@ -260,6 +274,7 @@ export function sameThreadStructure( return false; } return ( + sameProjectionArray(previous.messages, next.messages, sameMessageStructure) && sameProjectionArray(previous.turnItems, next.turnItems, sameTurnItemStructure) && sameProjectionArray( previous.visibleTurnItems, diff --git a/packages/client-runtime/src/state/threadRelationships.ts b/packages/client-runtime/src/state/threadRelationships.ts index 2aecc93470c2f..26ff658a5191b 100644 --- a/packages/client-runtime/src/state/threadRelationships.ts +++ b/packages/client-runtime/src/state/threadRelationships.ts @@ -1,11 +1,12 @@ import { isOrchestrationV2WorkActive, - type OrchestrationV2ThreadProjection, type OrchestrationV2ThreadShell, type ThreadId, } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; +import type { ThreadStructure } from "./models.ts"; + export type ThreadRelationshipKind = "parent" | "fork" | "subagent" | "transfer"; export interface ThreadRelationshipNode { @@ -34,7 +35,7 @@ export interface ThreadRelationshipWalkRow { } export function resolveMergeBackTargetThreadId( - projection: Pick | null, + projection: Pick | null, ): ThreadId | null { if (projection?.thread.lineage.relationshipToParent !== "fork") return null; return projection.thread.forkedFrom?.type === "run" @@ -48,7 +49,7 @@ function edgeKey(edge: ThreadRelationshipEdge): string { export function deriveThreadRelationshipGraph(input: { readonly threads: ReadonlyArray; - readonly projection: OrchestrationV2ThreadProjection | null; + readonly projection: ThreadStructure | null; }): ThreadRelationshipGraph { const threadsById = new Map(); for (const thread of input.threads) { From 487733ba0a06dcd2362f1a3efc2add8caac06c19 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 02:27:14 -0800 Subject: [PATCH 044/192] fix(server): recover Claude cooldown reset times from proxy errors (#36642) Co-authored-by: greyxp1 Co-authored-by: Grey <120752906+greyxp1@users.noreply.github.com> Co-authored-by: maria-rcks --- .../Adapters/ClaudeAdapterV2.test.ts | 52 +++++++++++++++++-- .../Adapters/ClaudeAdapterV2.ts | 37 ++++++++----- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index 21fc06ef28d77..92f0e4205b639 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -8918,6 +8918,25 @@ describe("ClaudeAdapterV2 background wake turns", () => { { status: 503, cooldown: "999999999999999999999999h", seconds: 0 }, { status: 503, cooldown: "4m 6s", seconds: 0, errorType: "overloaded_error" }, { status: 401, cooldown: "4m 6s", seconds: 0 }, + { status: 429, cooldown: "1h2m3s", seconds: 3723, proxy: true, sdkResetSeconds: 60 }, + { status: 429, cooldown: "1h2m3s", seconds: 7200, proxy: true, sdkResetSeconds: 7200 }, + { status: 429, cooldown: "1h2m3s", seconds: 0, proxy: true, sdkResetSeconds: null }, + { + status: 429, + cooldown: "nested 1h", + seconds: 0, + proxy: true, + result: + "API Error: Request rejected (429) · All credentials for model claude-opus-5-5 are cooling down (last error: rate_limit_error (reset after 1h))", + }, + { + status: 429, + cooldown: "outer cooldown", + seconds: 60, + proxy: true, + result: + "API Error: Request rejected (429) · All credentials for model claude-opus-5-5 are cooling down (reset after 1m) (last error: All credentials for model upstream are cooling down (reset after 1h))", + }, ])("recovers wrapped gateway limits with status $status and cooldown $cooldown", (input) => Effect.gen(function* () { const harness = yield* makeWakeHarness; @@ -8933,13 +8952,35 @@ describe("ClaudeAdapterV2 background wake turns", () => { }), ); yield* Queue.offerAll(harness.sdkMessages, [ + ...(input.sdkResetSeconds !== undefined + ? [ + claudeSdkFrame({ + type: "rate_limit_event", + rate_limit_info: { + status: "rejected", + rateLimitType: "five_hour", + ...(input.sdkResetSeconds === null + ? {} + : { + resetsAt: DateTime.toEpochMillis(now) / 1000 + input.sdkResetSeconds, + }), + }, + uuid: "00000000-0000-4000-8000-000000000651", + session_id: WAKE_NATIVE_SESSION, + }), + ] + : []), makeAssistantErrorFrame({ uuid: "00000000-0000-4000-8000-000000000652", - error: "server_error", + error: input.proxy ? "rate_limit" : "server_error", }), makeResultFrame({ uuid: "00000000-0000-4000-8000-000000000653", - result: `API Error: ${input.status} [claude/claude-opus-5-5] [429]: {"type":"error","error":{"type":"${input.errorType ?? "rate_limit_error"}","message":"Please try again later."}} (reset after ${input.cooldown}).`, + result: + input.result ?? + (input.proxy + ? `API Error: Request rejected (429) · All credentials for model claude-opus-5-5 are cooling down (reset after ${input.cooldown}) (last error: rate_limit_error: Please try again later.)` + : `API Error: ${input.status} [claude/claude-opus-5-5] [429]: {"type":"error","error":{"type":"${input.errorType ?? "rate_limit_error"}","message":"Please try again later."}} (reset after ${input.cooldown}).`), terminalReason: "api_error", isError: true, apiErrorStatus: input.status, @@ -8948,7 +8989,10 @@ describe("ClaudeAdapterV2 background wake turns", () => { const terminal = yield* Queue.take(harness.terminalReceipts); assert.equal(terminal.status, "failed"); if (terminal.status !== "failed") return; - assert.equal(terminal.failure.class, input.seconds > 0 ? "usage_limit" : "provider_error"); + assert.equal( + terminal.failure.class, + input.seconds > 0 || input.proxy ? "usage_limit" : "provider_error", + ); if (input.seconds > 0) { assert.equal(terminal.failure.retryable, true); assert.equal( @@ -8958,7 +9002,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); } else { - assert.isUndefined(terminal.failure.resetAt); + assert.equal(terminal.failure.resetAt, input.proxy ? null : undefined); } }).pipe( Effect.provide( diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 15edf21b6d2a5..4ea7b6f8008e4 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -4637,7 +4637,13 @@ function providerFailureFromResult( const resetAt = apiErrorStatus === 429 && message.result.startsWith("API Error:") ? gatewayCooldownResetAt( - /\(reset after (?:(\d+)h ?)?(?:(\d+)m ?)?(?:(\d+)s)?\)/.exec(message.result), + // CLIProxyAPI nests upstream errors in its message; only its own cooldown counts. + (message.result.startsWith( + "API Error: Request rejected (429) · All credentials for model ", + ) + ? /^API Error: Request rejected \(429\) · All credentials for model [^\s()]+ are cooling down \(reset after (?:(\d+)h ?)?(?:(\d+)m ?)?(?:(\d+)s)?\)/ + : /\(reset after (?:(\d+)h ?)?(?:(\d+)m ?)?(?:(\d+)s)?\)/ + ).exec(message.result), receivedAt, ) : undefined; @@ -11469,18 +11475,6 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( usageLimited && message.subtype === "success" && message.local_command === "compact" ? context.compactionFailure : null; - const resetTimes = Array.from(context.rateLimitResetTimes.values()); - const assistantLimit = context.latestAssistantRateLimit; - if ( - assistantLimit?.type != null && - !context.rateLimitResetTimes.has(assistantLimit.type) - ) { - resetTimes.push(assistantLimit.resetAt); - } - const resetAt = - resetTimes.length > 0 && resetTimes.every((time) => time !== null) - ? resetTimes.reduce((latest, time) => (time! > latest ? time! : latest), "") - : null; const resultFailure = interrupted ? null : limitedCompaction !== null @@ -11495,9 +11489,24 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( failureHint, usageLimited, ); + const resetTimes = Array.from(context.rateLimitResetTimes.values()); + const assistantLimit = context.latestAssistantRateLimit; + if ( + assistantLimit?.type != null && + !context.rateLimitResetTimes.has(assistantLimit.type) + ) { + resetTimes.push(assistantLimit.resetAt); + } + if (resultFailure?.class === "usage_limit" && resultFailure.resetAt != null) { + resetTimes.push(resultFailure.resetAt); + } + const resetAt = + resetTimes.length > 0 && resetTimes.every((time) => time !== null) + ? resetTimes.reduce((latest, time) => (time! > latest ? time! : latest), "") + : null; const terminalFailure = resultFailure?.class === "usage_limit" - ? { ...resultFailure, resetAt: resetAt ?? resultFailure.resetAt ?? null } + ? { ...resultFailure, resetAt } : resultFailure; yield* finalizeActiveTurn({ context, From 5958d00ede91d3bd6f1f85c26bd162100e51ac91 Mon Sep 17 00:00:00 2001 From: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:27:57 +0300 Subject: [PATCH 045/192] perf(web): Update everything downloads a server's update while providers install (#35346) Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> --- apps/server/src/cloud/selfUpdate.test.ts | 13 ++++++ apps/server/src/cloud/selfUpdate.ts | 4 ++ .../src/environment/ServerEnvironment.ts | 1 + .../sidebar/updateEverything.logic.test.ts | 44 +++++++++++++++++++ .../sidebar/updateEverything.logic.ts | 15 +++++-- .../components/sidebar/useUpdateEverything.ts | 29 +++++++++++- docs/internals/server-updates.md | 5 +++ packages/client-runtime/src/state/server.ts | 4 ++ packages/contracts/src/environment.ts | 1 + packages/contracts/src/server.ts | 1 + 10 files changed, 111 insertions(+), 6 deletions(-) diff --git a/apps/server/src/cloud/selfUpdate.test.ts b/apps/server/src/cloud/selfUpdate.test.ts index 4cae117b2bcc6..8bd1d8168680b 100644 --- a/apps/server/src/cloud/selfUpdate.test.ts +++ b/apps/server/src/cloud/selfUpdate.test.ts @@ -346,6 +346,19 @@ it.layer(NodeServices.layer)("server self update", (it) => { }), ); + it.effect("stages the runtime without asking the launcher to restart", () => + Effect.gen(function* () { + const { selfUpdate, order } = yield* makeHarness(); + expect(yield* selfUpdate.update({ targetVersion: "1.1.0", stageOnly: true })).toEqual({ + targetVersion: "1.1.0", + method: "boot-service", + }); + expect(order).toEqual(["download", "extract", "preflight"]); + expect((yield* selfUpdate.update({ targetVersion: "1.1.0" })).updateId).toBe("launcher-id"); + expect(order).toEqual(["download", "extract", "preflight", "preflight", "accept"]); + }), + ); + // regression: #28198 a slow staged preflight on a loaded host no longer fails the update it.effect("waits for a staged preflight that takes a minute on a loaded host", () => Effect.gen(function* () { diff --git a/apps/server/src/cloud/selfUpdate.ts b/apps/server/src/cloud/selfUpdate.ts index 88c9d2d46bd3a..5ae8e76a1058c 100644 --- a/apps/server/src/cloud/selfUpdate.ts +++ b/apps/server/src/cloud/selfUpdate.ts @@ -323,6 +323,10 @@ export const make = Effect.fn("cloud.server_self_update.make")(function* () { }), ); + if (input.stageOnly === true) { + yield* Ref.set(inFlight, false); + return { targetVersion, method: "boot-service" as const }; + } yield* reportProgress("installing"); const updateId = yield* Effect.uninterruptible( launcher.requestUpdate({ targetVersion, dbPath: serverConfig.dbPath }).pipe( diff --git a/apps/server/src/environment/ServerEnvironment.ts b/apps/server/src/environment/ServerEnvironment.ts index 2c79189878e8a..813efbe53ea30 100644 --- a/apps/server/src/environment/ServerEnvironment.ts +++ b/apps/server/src/environment/ServerEnvironment.ts @@ -287,6 +287,7 @@ export const make = Effect.gen(function* () { ...(serverSelfUpdate === "boot-service" || desktopAppUpdate ? { serverSelfUpdateProgress: true } : {}), + ...(serverSelfUpdate === "boot-service" ? { serverUpdateStaging: true } : {}), ...(desktopAppUpdate ? { desktopAppUpdate: true, desktopAppChannelSwitch: true } : {}), serverBrowser: true, peerLinks: true, diff --git a/apps/web/src/components/sidebar/updateEverything.logic.test.ts b/apps/web/src/components/sidebar/updateEverything.logic.test.ts index 58328dbc1eeeb..12ee88b03bdc1 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -331,6 +331,50 @@ describe("runUpdateEverything", () => { expect(calls).toEqual(["providers", "servers"]); expect(result.localReady).toBe(false); }); + + it("stages servers while providers install and updates each once its own stage settles", async () => { + const calls: string[] = []; + const [slow, fast] = [EnvironmentId.make("slow"), EnvironmentId.make("fast")]; + let finishProviders = () => {}; + let failSlowStage = () => {}; + const providers = new Promise((resolve) => (finishProviders = resolve)); + const run = runUpdateEverything({ + updateProviders: () => { + calls.push("providers"); + return providers.then(() => { + calls.push("providers done"); + }); + }, + stageServers: () => { + calls.push("stage"); + return new Map([ + [slow, new Promise((_, reject) => (failSlowStage = () => reject(new Error("failed"))))], + [fast, Promise.resolve()], + ]); + }, + updateServers: (staged) => { + calls.push("servers"); + return Promise.all( + [slow, fast].map(async (id) => { + await staged.get(id); + calls.push(`server ${id}`); + if (id === fast) failSlowStage(); + }), + ); + }, + }); + finishProviders(); + await run; + + expect(calls).toEqual([ + "stage", + "providers", + "providers done", + "servers", + "server fast", + "server slow", + ]); + }); }); describe("describeUpdateEverything", () => { diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index cbbd3baa7c776..bc26ca9b46083 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -348,11 +348,15 @@ function trackSwitchMove( export interface UpdateEverythingSteps { readonly downloadLocal?: () => Promise; readonly updateProviders: () => Promise; - readonly updateServers: () => Promise; + readonly stageServers?: () => ReadonlyMap>; + readonly updateServers: ( + staged: ReadonlyMap>, + ) => Promise; } -/** Finish provider installs before server restarts. Install this app last when localReady; failed - * downloads skip only that install. */ +/** Finish provider installs before server restarts; servers stage meanwhile and each server's + * update waits only on its own stage. Install this app last when localReady; failed downloads skip + * only that install. */ export async function runUpdateEverything( steps: UpdateEverythingSteps, ): Promise<{ @@ -360,11 +364,14 @@ export async function runUpdateEverything( readonly providers: Providers; readonly servers: Servers; }> { + const staged = new Map( + Array.from(steps.stageServers?.() ?? [], ([id, stage]) => [id, stage.catch(() => undefined)]), + ); const [localReady, remote] = await Promise.all([ steps.downloadLocal?.() ?? Promise.resolve(true), steps .updateProviders() - .then(async (providers) => ({ providers, servers: await steps.updateServers() })), + .then(async (providers) => ({ providers, servers: await steps.updateServers(staged) })), ]); return { localReady, ...remote }; } diff --git a/apps/web/src/components/sidebar/useUpdateEverything.ts b/apps/web/src/components/sidebar/useUpdateEverything.ts index 05127d9885768..ed02493197cea 100644 --- a/apps/web/src/components/sidebar/useUpdateEverything.ts +++ b/apps/web/src/components/sidebar/useUpdateEverything.ts @@ -6,8 +6,9 @@ import { isDesktopLocalConnectionTarget } from "~/connection/desktopLocal"; import { isElectron } from "~/env"; import { useEnvironments } from "~/state/environments"; import { environmentIdsKey } from "~/state/environmentAtomList"; -import { environmentUpdateStatesAtom } from "~/state/server"; +import { environmentUpdateStatesAtom, serverEnvironment } from "~/state/server"; import { environmentSessionStatesAtom } from "~/state/session"; +import { useAtomCommand } from "~/state/use-atom-command"; import { getProviderUpdateRunOutcome } from "../ProviderUpdateLaunchNotification.logic"; import { useProviderUpdates } from "../ProviderUpdatesAction"; import { canUpdateServer, type ServerUpdateOutcome, useServerUpdate } from "../ServerUpdateAction"; @@ -76,6 +77,9 @@ export function useUpdateEverything( // A track switch moves app and servers only; provider updates stay a separate choice. const providers = channelSwitch ? { machines: [], updateAll: async () => [] } : providerUpdates; const updateServer = useServerUpdate(); + const stageServerUpdate = useAtomCommand(serverEnvironment.stageServerUpdate, { + reportFailure: false, + }); const view = describeUpdateEverything({ localVersion: localUpdateVersion, @@ -181,8 +185,29 @@ export function useUpdateEverything( ); return outcomes; }, - updateServers: async () => { + ...(providers.machines.length > 0 + ? { + stageServers: () => + new Map( + servers + .filter( + (server) => + machines.find((machine) => machine.environmentId === server.environmentId) + ?.serverConfig?.environment.capabilities.serverUpdateStaging === true, + ) + .map((server) => [ + server.environmentId, + stageServerUpdate({ + environmentId: server.environmentId, + input: { targetVersion: server.targetVersion, stageOnly: true }, + }), + ]), + ), + } + : {}), + updateServers: async (staged) => { const runs = servers.map(async (server) => { + await staged.get(server.environmentId); const serverOutcome = await updateServer(server, { quiet: true }); settleUpdateRunMachine( server.environmentId, diff --git a/docs/internals/server-updates.md b/docs/internals/server-updates.md index 10b196a72adf5..10df2ef464f8b 100644 --- a/docs/internals/server-updates.md +++ b/docs/internals/server-updates.md @@ -27,6 +27,11 @@ Protocol 4 changed the launcher alone, so its runtime still runs under protocol The server names its launcher's protocol to preflight, not its own, since the two differ after the first such update. +A client can stage the target early with `stageOnly`, which installs and preflights the +runtime and restarts nothing. "Update everything" does this while provider installs run, +because a restart would kill them. A server without the `serverUpdateStaging` capability +ignores the flag and runs the whole update, so send it only to a server that advertises it. + After a commit or rollback the launcher points the unit at the selected runtime and reloads the manager, so the next service start runs that version's launcher; the protocol check is what makes that safe. Each start after a finished update retries this, because diff --git a/packages/client-runtime/src/state/server.ts b/packages/client-runtime/src/state/server.ts index f16861f5333fd..a8c22d24d755e 100644 --- a/packages/client-runtime/src/state/server.ts +++ b/packages/client-runtime/src/state/server.ts @@ -1649,6 +1649,10 @@ export function createServerEnvironmentAtoms( tag: WS_METHODS.serverUpdateProvider, }), updateServer, + stageServerUpdate: createEnvironmentRpcCommand(runtime, { + label: "environment-data:server:stage-update", + tag: WS_METHODS.serverUpdateServer, + }), upsertKeybinding: createEnvironmentRpcCommand(runtime, { label: "environment-data:server:upsert-keybinding", tag: WS_METHODS.serverUpsertKeybinding, diff --git a/packages/contracts/src/environment.ts b/packages/contracts/src/environment.ts index e305efb51f838..ad1cfe30b0214 100644 --- a/packages/contracts/src/environment.ts +++ b/packages/contracts/src/environment.ts @@ -152,6 +152,7 @@ export const ExecutionEnvironmentCapabilities = Schema.Struct({ /** Manual commands must update this install, not the host's default global prefix. */ serverInstallation: ForwardCompatibleOptional(ServerInstallation), serverSelfUpdateProgress: Schema.optionalKey(Schema.Boolean), + serverUpdateStaging: Schema.optionalKey(Schema.Boolean), serverUpdateThreadContinuation: Schema.optionalKey(Schema.Boolean), /** Only explicit false skips Live Activity seeding; older servers may still publish. */ agentActivityPublishing: Schema.optionalKey(Schema.Boolean), diff --git a/packages/contracts/src/server.ts b/packages/contracts/src/server.ts index 88fa0220fe920..d859ecb91ffa4 100644 --- a/packages/contracts/src/server.ts +++ b/packages/contracts/src/server.ts @@ -1013,6 +1013,7 @@ export const ServerSelfUpdateInput = Schema.Struct({ channel: Schema.optionalKey(DesktopUpdateChannelSchema), /** The target release's highest migration; the host refuses to leave Nightly without it. */ targetDatabaseMigration: Schema.optionalKey(Schema.Int), + stageOnly: Schema.optionalKey(Schema.Boolean), }); export type ServerSelfUpdateInput = typeof ServerSelfUpdateInput.Type; From 6be1bd64722f048ec79c2c7d033fc1359381f6aa Mon Sep 17 00:00:00 2001 From: Hieu Le <78847613+hieu-lee@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:31:09 +0100 Subject: [PATCH 046/192] perf(cursor): long command output streams without copying its tail per chunk (#26934) Co-authored-by: Julius Marminge --- .../src/server/adapter.test.ts | 151 ++++++++++++++++++ .../provider-cursor/src/server/adapter.ts | 14 +- 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/packages/provider-cursor/src/server/adapter.test.ts b/packages/provider-cursor/src/server/adapter.test.ts index 9a535eb9b52bb..d627328e7e427 100644 --- a/packages/provider-cursor/src/server/adapter.test.ts +++ b/packages/provider-cursor/src/server/adapter.test.ts @@ -1726,6 +1726,157 @@ describe("CursorAdapterV2", () => { ), ); + it.effect("keeps the last 256 KiB of streamed shell output when the result carries none", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const workspace = yield* fileSystem.makeTempDirectoryScoped({ prefix: "cursor-v2-shell-" }); + const instanceId = ProviderInstanceId.make("cursor"); + const threadId = ThreadId.make("cursor-shell-thread"); + const modelSelection = { instanceId, model: "composer-2.5" }; + const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ + runtimeMode: "full-access", + interactionMode: "default", + cwd: workspace, + }); + const chunks = Array.from({ length: 3000 }, (_, index) => `${index}`.padEnd(255, ".") + "\n"); + const shell = { + type: "shell" as const, + args: { command: "yes", workingDirectory: workspace }, + }; + const adapter = yield* makeCursorAdapterV2({ + instanceId, + environment: { HOME: workspace }, + }).pipe( + Effect.provideService(CursorAgentSdk.CursorAgentSdkRunner, { + assertComplete: Effect.void, + open: () => + Effect.succeed({ + agentId: "native-cursor-shell", + listMessages: Effect.succeed([]), + close: Effect.void, + send: (input) => + Effect.gen(function* () { + yield* input.onDelta!({ + type: "tool-call-started", + modelCallId: "model-call", + callId: "shell-call", + toolCall: shell, + }); + for (const chunk of chunks) { + yield* input.onDelta!({ + type: "shell-output-delta", + event: { case: "stdout", value: { data: chunk }, toolCallId: "shell-call" }, + }); + } + yield* input.onDelta!({ + type: "tool-call-completed", + modelCallId: "model-call", + callId: "shell-call", + toolCall: { + ...shell, + result: { + status: "success", + value: { + exitCode: 0, + signal: "", + stdout: "", + stderr: "", + executionTime: 0, + }, + }, + }, + }); + return { + agentId: "native-cursor-shell", + runId: "native-cursor-run", + wait: Effect.succeed({ + id: "native-cursor-run", + requestId: "native-request", + status: "finished" as const, + durationMs: 1, + }), + cancel: Effect.void, + }; + }).pipe(Effect.orDie), + }), + }), + Effect.provide(TestProviderHost.layer({ cwd: workspace })), + ); + const runtime = yield* adapter.openSession({ + threadId, + providerSessionId: ProviderSessionId.make("cursor-shell-session"), + modelSelection, + runtimePolicy, + }); + const providerThread = yield* runtime.ensureThread({ + threadId, + modelSelection, + runtimePolicy, + }); + const now = yield* DateTime.now; + yield* runtime.startTurn({ + threadId, + providerThread, + modelSelection, + runtimePolicy, + runId: RunId.make("cursor-shell-run"), + runOrdinal: 1, + providerTurnOrdinal: 1, + attemptId: RunAttemptId.make("cursor-shell-attempt"), + rootNodeId: NodeId.make("cursor-shell-root"), + appThread: { + id: threadId, + projectId: ProjectId.make("cursor-shell-project"), + createdBy: "user", + creationSource: "web", + title: "Cursor shell", + providerInstanceId: instanceId, + modelSelection, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + activeProviderThreadId: providerThread.id, + lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId }, + forkedFrom: null, + createdAt: now, + updatedAt: now, + archivedAt: null, + settledOverride: null, + settledAt: null, + lastVisitedAt: null, + deletedAt: null, + }, + message: { + messageId: MessageId.make("cursor-shell-message"), + createdBy: "user", + creationSource: "web", + text: "hi", + attachments: [], + }, + }); + const events = yield* runtime.events.pipe( + Stream.takeUntil((event) => event.type === "turn.terminal"), + Stream.runCollect, + ); + const output = events + .flatMap((event) => + event.type === "turn_item.updated" && + event.turnItem.type === "command_execution" && + event.turnItem.status === "completed" + ? [event.turnItem.output] + : [], + ) + .at(-1); + assert.equal(output, chunks.join("").slice(-256 * 1024)); + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer), + ), + ), + ); + it.effect("names a custom model Cursor's account catalog rejects", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; diff --git a/packages/provider-cursor/src/server/adapter.ts b/packages/provider-cursor/src/server/adapter.ts index 02ec1ce09d8fc..ad0525a5f19e5 100644 --- a/packages/provider-cursor/src/server/adapter.ts +++ b/packages/provider-cursor/src/server/adapter.ts @@ -1364,7 +1364,9 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* ( | "completedAt" | "updatedAt" >; - const outputText = cursorToolOutputText(toolCall) || active.streamedOutput; + const outputText = + cursorToolOutputText(toolCall) || + active.streamedOutput.slice(-MAX_CURSOR_STREAMED_OUTPUT_CHARS); let turnItem: OrchestrationV2TurnItem; switch (toolCall.type) { case "shell": @@ -2112,9 +2114,13 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* ( const chunk = shellOutputText(update.event); if (chunk.length === 0) return; // Persist output once; live hub deltas avoid quadratic event-store growth. - shell.streamedOutput = (shell.streamedOutput + chunk).slice( - -MAX_CURSOR_STREAMED_OUTPUT_CHARS, - ); + shell.streamedOutput += chunk; + // Trimming on every chunk would flatten and copy the whole tail each time. + if (shell.streamedOutput.length > 2 * MAX_CURSOR_STREAMED_OUTPUT_CHARS) { + shell.streamedOutput = shell.streamedOutput.slice( + -MAX_CURSOR_STREAMED_OUTPUT_CHARS, + ); + } yield* emitProviderEvent({ type: "command_output.delta", driver: CursorAgentSdk.CURSOR_PROVIDER, From c5f842d4c7d70f483a99de77301e25566eb6bad8 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:36:36 +0100 Subject: [PATCH 047/192] fix(server): regenerate titles on fresh run forks (#36637) Co-authored-by: Julius Marminge --- .../src/orchestration-v2/ProjectionStore.ts | 2 +- .../ThreadTitleRegenerationService.test.ts | 329 +++++++++++++++--- .../ThreadTitleRegenerationService.ts | 50 ++- ...-regenerates-from-inherited-history.e2e.ts | 26 ++ 4 files changed, 334 insertions(+), 73 deletions(-) create mode 100644 scripts/scenarios/fork-title-regenerates-from-inherited-history.e2e.ts diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index 6d845450c79da..ecac162d868b9 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -1531,7 +1531,7 @@ function isRolledBackBeforeFork( return rolledBackAt !== null && DateTime.isLessThan(rolledBackAt, forkCreatedAt); } -function forkInheritedRunOrdinals( +export function forkInheritedRunOrdinals( runs: ReadonlyArray, forkCreatedAt: DateTime.Utc, ): Map { diff --git a/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts b/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts index d56256372f73e..325d4e4b7aab2 100644 --- a/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts +++ b/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts @@ -19,6 +19,7 @@ import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; +import * as TestClock from "effect/testing/TestClock"; import { ATTACHMENT_ONLY_BOOTSTRAP_PROMPT } from "@t3tools/shared/composerContextReferences"; import * as SqlitePersistence from "../persistence/Sqlite.ts"; @@ -271,6 +272,53 @@ function armRegeneration(input: { readonly command: string; readonly threadId: T }); } +/** Forks a source thread at its middle run, between a rolled-back run and later work. */ +function forkSeededSource(prefix: string, sideChat = false, rollbackPriorRun = true) { + return Effect.gen(function* () { + const threads = yield* ThreadManagement.ThreadManagementService; + const sourceThreadId = yield* createThread({ + command: `${prefix}:create`, + thread: `${prefix}:source`, + }); + yield* recordRun({ + threadId: sourceThreadId, + runId: RunId.make(`${prefix}:run:0`), + ordinal: 1, + user: "Rolled back attempt", + assistant: "Discarded.", + status: rollbackPriorRun ? "rolled_back" : "completed", + }); + yield* TestClock.adjust(1); + const forkRunId = RunId.make(`${prefix}:run:1`); + yield* recordRun({ + threadId: sourceThreadId, + runId: forkRunId, + ordinal: 2, + user: "Move session storage from Redis to Postgres", + assistant: "Sessions now persist in the Postgres sessions table.", + }); + yield* recordRun({ + threadId: sourceThreadId, + runId: RunId.make(`${prefix}:run:2`), + ordinal: 3, + user: "Later work the fork never saw", + assistant: "Done.", + }); + const forkThreadId = ThreadId.make(`${prefix}:target`); + yield* threads.dispatch({ + type: "thread.fork", + commandId: CommandId.make(`${prefix}:fork`), + sourceThreadId, + targetThreadId: forkThreadId, + sourcePoint: { type: "run", runId: forkRunId }, + sideChat, + createdBy: "user", + creationSource: "web", + }); + return { sourceThreadId, forkRunId, forkThreadId }; + }); +} + describe("formatThreadTitleContext", () => { it("pins the first user message ahead of the retained tail once content stops fitting", () => { const context = ThreadTitleRegeneration.formatThreadTitleContext([ @@ -443,80 +491,251 @@ describe("ThreadTitleRegenerationService", () => { }).pipe(Effect.provide(NodeServices.layer)), ); - it.effect("titles a fork's first message with the source history up to the fork point", () => + it.effect.each([ + { name: "its first message", followUp: true }, + { name: "no message of its own yet", followUp: false }, + ])("titles a fork with $name from the source history up to the fork point", ({ followUp }) => Effect.gen(function* () { const harness = makeHarness(); yield* Effect.gen(function* () { const threads = yield* ThreadManagement.ThreadManagementService; const titleRegeneration = yield* ThreadTitleRegeneration.ThreadTitleRegenerationService; - const sourceThreadId = yield* createThread({ - command: "command:title:fork:create", - thread: "thread:title:fork:source", + const { forkThreadId } = yield* forkSeededSource("thread:title:fork"); + const requestId = CommandId.make("command:title:fork:request"); + const messageId = MessageId.make("command:title:fork:request:message"); + if (followUp) { + yield* threads.dispatch({ + type: "message.dispatch", + commandId: requestId, + threadId: forkThreadId, + messageId, + text: "Try SQLite instead", + titleSeed: "Try SQLite instead", + attachments: [], + modelSelection, + dispatchMode: { type: "defer_start" }, + createdBy: "user", + creationSource: "web", + }); + } else { + yield* armRegeneration({ command: requestId, threadId: forkThreadId }); + } + + yield* titleRegeneration.execute({ + threadId: forkThreadId, + requestId, + kind: followUp ? { type: "initial", messageId } : { type: "regenerate" }, }); - yield* recordRun({ - threadId: sourceThreadId, - runId: RunId.make("run:title:fork:0"), - ordinal: 1, - user: "Rolled back attempt", - assistant: "Discarded.", - status: "rolled_back", + + const call = harness.generateThreadTitle.mock.calls[0]?.[0]; + assert.equal(call?.message, followUp ? "Try SQLite instead" : ""); + assert.equal(call?.forkSource?.title, "Seed title"); + assert.include(call?.forkSource?.history, "Move session storage from Redis to Postgres"); + assert.include(call?.forkSource?.history, "Sessions now persist in the Postgres"); + assert.notInclude(call?.forkSource?.history, "Later work the fork never saw"); + assert.notInclude(call?.forkSource?.history, "Rolled back attempt"); + const fork = yield* threads.getThreadRecords(forkThreadId, []); + assert.equal(fork.thread.title, "Generated title"); + }).pipe(Effect.provide(harness.layer)); + }), + ); + + it.effect.each(["plain", "deleted source"] as const)( + "refuses to title an empty thread with %s history", + (history) => + Effect.gen(function* () { + const harness = makeHarness(); + yield* Effect.gen(function* () { + const threads = yield* ThreadManagement.ThreadManagementService; + const titleRegeneration = yield* ThreadTitleRegeneration.ThreadTitleRegenerationService; + const { forkThreadId } = yield* forkSeededSource("thread:title:empty"); + const { thread } = yield* threads.getThreadProjection(forkThreadId); + const eventSink = yield* EventSink.EventSinkV2; + yield* eventSink.write({ + events: [ + { + id: EventId.make("empty:lineage"), + type: "thread.metadata-updated", + threadId: forkThreadId, + occurredAt: yield* DateTime.now, + payload: { + ...thread, + forkedFrom: + history === "plain" + ? null + : { + type: "run", + threadId: ThreadId.make("absent"), + runId: RunId.make("absent"), + }, + }, + }, + ], + }); + const requestId = yield* armRegeneration({ + command: "empty:request", + threadId: forkThreadId, + }); + yield* titleRegeneration.execute({ + threadId: forkThreadId, + requestId, + kind: { type: "regenerate" }, + }); + assert.equal(harness.generateThreadTitle.mock.calls.length, 0); + const fork = yield* threads.getThreadRecords(forkThreadId, []); + assert.deepInclude(fork.thread.lastTitleRegeneration, { + requestId, + status: "failed", + message: "This thread has no messages to title yet.", + }); + }).pipe(Effect.provide(harness.layer)); + }), + ); + + it.effect("keeps the fork point when the source rolls it back after forking", () => + Effect.gen(function* () { + const harness = makeHarness(); + yield* Effect.gen(function* () { + const threads = yield* ThreadManagement.ThreadManagementService; + const titleRegeneration = yield* ThreadTitleRegeneration.ThreadTitleRegenerationService; + const { sourceThreadId, forkRunId, forkThreadId } = yield* forkSeededSource( + "thread:title:rollback", + false, + false, + ); + yield* TestClock.adjust(1); + const source = yield* threads.getThreadRecords(sourceThreadId, ["runs"]); + const run = source.runs.find((run) => run.id === forkRunId); + assert.isDefined(run); + const now = yield* DateTime.now; + const eventSink = yield* EventSink.EventSinkV2; + yield* eventSink.write({ + events: [ + { + id: EventId.make("source:rollback"), + type: "run.updated", + threadId: sourceThreadId, + runId: run.id, + occurredAt: now, + payload: { ...run, status: "rolled_back", completedAt: now }, + }, + ], }); - const forkRunId = RunId.make("run:title:fork:1"); - yield* recordRun({ - threadId: sourceThreadId, - runId: forkRunId, - ordinal: 2, - user: "Move session storage from Redis to Postgres", - assistant: "Sessions now persist in the Postgres sessions table.", + const requestId = yield* armRegeneration({ + command: "rollback:request", + threadId: forkThreadId, }); - yield* recordRun({ + yield* titleRegeneration.execute({ + threadId: forkThreadId, + requestId, + kind: { type: "regenerate" }, + }); + const call = harness.generateThreadTitle.mock.calls[0]?.[0]; + assert.include(call?.forkSource?.history, "Move session storage from Redis to Postgres"); + assert.include(call?.forkSource?.history, "Rolled back attempt"); + assert.notInclude(call?.forkSource?.history, "Later work the fork never saw"); + const fork = yield* threads.getThreadRecords(forkThreadId, []); + assert.equal(fork.thread.title, "Generated title"); + }).pipe(Effect.provide(harness.layer)); + }), + ); + + it.effect("does not inherit imported history when the fork point is missing", () => + Effect.gen(function* () { + const harness = makeHarness(); + yield* Effect.gen(function* () { + const threads = yield* ThreadManagement.ThreadManagementService; + const titleRegeneration = yield* ThreadTitleRegeneration.ThreadTitleRegenerationService; + const { sourceThreadId, forkThreadId } = yield* forkSeededSource("thread:title:invalid"); + yield* dispatchUserMessage({ + command: "imported:message", threadId: sourceThreadId, - runId: RunId.make("run:title:fork:2"), - ordinal: 3, - user: "Later work the fork never saw", - assistant: "Done.", + text: "Imported history", }); - const forkThreadId = ThreadId.make("thread:title:fork:target"); - yield* threads.dispatch({ - type: "thread.fork", - commandId: CommandId.make("command:title:fork:fork"), - sourceThreadId, - targetThreadId: forkThreadId, - sourcePoint: { type: "run", runId: forkRunId }, - createdBy: "user", - creationSource: "web", + const source = yield* threads.getThreadRecords(sourceThreadId, []); + const fork = yield* threads.getThreadRecords(forkThreadId, []); + const eventSink = yield* EventSink.EventSinkV2; + yield* eventSink.write({ + events: [ + { + id: EventId.make("source:imported"), + type: "thread.metadata-updated", + threadId: sourceThreadId, + occurredAt: yield* DateTime.now, + payload: { ...source.thread, historyOrigin: "v1_import" }, + }, + { + id: EventId.make("fork:missing-run"), + type: "thread.metadata-updated", + threadId: forkThreadId, + occurredAt: yield* DateTime.now, + payload: { + ...fork.thread, + forkedFrom: { + type: "run", + threadId: sourceThreadId, + runId: RunId.make("absent:run"), + }, + }, + }, + ], }); - const requestId = CommandId.make("command:title:fork:message"); - const messageId = MessageId.make("command:title:fork:message:message"); - yield* threads.dispatch({ - type: "message.dispatch", - commandId: requestId, + const requestId = yield* armRegeneration({ + command: "invalid:request", threadId: forkThreadId, - messageId, - text: "Try SQLite instead", - titleSeed: "Try SQLite instead", - attachments: [], - modelSelection, - dispatchMode: { type: "defer_start" }, - createdBy: "user", - creationSource: "web", }); - yield* titleRegeneration.execute({ threadId: forkThreadId, requestId, - kind: { type: "initial", messageId }, + kind: { type: "regenerate" }, + }); + assert.equal(harness.generateThreadTitle.mock.calls.length, 0); + const updated = yield* threads.getThreadRecords(forkThreadId, []); + assert.deepInclude(updated.thread.lastTitleRegeneration, { + requestId, + status: "failed", + message: "This thread has no messages to title yet.", }); + }).pipe(Effect.provide(harness.layer)); + }), + ); + it.effect("inherits a side chat's own exchange without its parent's history", () => + Effect.gen(function* () { + const harness = makeHarness(); + yield* Effect.gen(function* () { + const threads = yield* ThreadManagement.ThreadManagementService; + const titleRegeneration = yield* ThreadTitleRegeneration.ThreadTitleRegenerationService; + const { forkThreadId: sideChatId } = yield* forkSeededSource( + "thread:title:side-ancestor", + true, + ); + const runId = RunId.make("side:run"); + yield* recordRun({ + threadId: sideChatId, + runId, + ordinal: 1, + user: "Side topic", + assistant: "Side answer", + }); + const threadId = ThreadId.make("side:fork"); + yield* threads.dispatch({ + type: "thread.fork", + commandId: CommandId.make("side:fork"), + sourceThreadId: sideChatId, + targetThreadId: threadId, + sourcePoint: { type: "run", runId }, + title: "Side fork", + createdBy: "user", + creationSource: "web", + }); + const requestId = yield* armRegeneration({ command: "side:request", threadId }); + yield* titleRegeneration.execute({ threadId, requestId, kind: { type: "regenerate" } }); const call = harness.generateThreadTitle.mock.calls[0]?.[0]; - assert.equal(call?.message, "Try SQLite instead"); - assert.equal(call?.forkSource?.title, "Seed title"); - assert.include(call?.forkSource?.history, "Move session storage from Redis to Postgres"); - assert.include(call?.forkSource?.history, "Sessions now persist in the Postgres"); - assert.notInclude(call?.forkSource?.history, "Later work the fork never saw"); - assert.notInclude(call?.forkSource?.history, "Rolled back attempt"); - const fork = yield* threads.getThreadProjection(forkThreadId); - assert.equal(fork.thread.title, "Generated title"); + assert.equal(call?.forkSource?.title, "Seed title fork"); + assert.include(call?.forkSource?.history, "Side topic"); + assert.include(call?.forkSource?.history, "Side answer"); + assert.notInclude(call?.forkSource?.history, "Move session storage"); }).pipe(Effect.provide(harness.layer)); }), ); diff --git a/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts b/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts index d3216b1cbd938..725a15aa136e0 100644 --- a/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts +++ b/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts @@ -14,6 +14,7 @@ import { import * as Cause from "effect/Cause"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; +import type * as DateTime from "effect/DateTime"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; @@ -24,6 +25,7 @@ import * as ServerSettings from "../serverSettings.ts"; import * as TextGeneration from "../textGeneration/TextGeneration.ts"; import type { OrchestratorV2Error } from "./Orchestrator.ts"; import * as ProjectStore from "./ProjectStore.ts"; +import { forkInheritedRunOrdinals, ProjectionStoreThreadNotFoundError } from "./ProjectionStore.ts"; import * as ThreadManagementService from "./ThreadManagementService.ts"; import { formatThreadTitleContext } from "@t3tools/provider-core/server/threadTitleContext"; @@ -55,6 +57,7 @@ type Outcome = const MAX_FAILURE_MESSAGE_LENGTH = 300; const isTextGenerationError = Schema.is(TextGenerationError); +const isProjectionStoreThreadNotFoundError = Schema.is(ProjectionStoreThreadNotFoundError); function failureMessage(cause: Cause.Cause): string { const error = Cause.squash(cause); @@ -103,16 +106,13 @@ const make = Effect.gen(function* () { .pipe(Effect.asVoid); const messagesThroughRun = Effect.fn("ThreadTitleRegenerationService.messagesThroughRun")( - function* (threadId: ThreadId, runId: RunId) { + function* (threadId: ThreadId, runId: RunId, forkCreatedAt: DateTime.Utc) { const source = yield* threads.getThreadRecords(threadId, ["messages", "runs"], { messageRoles: ["user", "assistant"], }); - const ordinals = new Map( - source.runs - .filter((run) => run.status !== "rolled_back") - .map((run) => [run.id, run.ordinal]), - ); - const forkOrdinal = ordinals.get(runId) ?? -1; + const ordinals = forkInheritedRunOrdinals(source.runs, forkCreatedAt); + const forkOrdinal = ordinals.get(runId); + if (forkOrdinal === undefined) return undefined; const messages = source.messages.filter((message) => { if (message.streaming) return false; if (message.runId === null) return source.thread.historyOrigin === "v1_import"; @@ -128,12 +128,20 @@ const make = Effect.gen(function* () { ) { let title: string | undefined; const history: Array> = []; - let forkedFrom = thread.forkedFrom; - while (forkedFrom?.type === "run") { - const source = yield* messagesThroughRun(forkedFrom.threadId, forkedFrom.runId); + const seen = new Set([thread.id]); + let current = thread; + while ( + current.forkedFrom?.type === "run" && + current.lineage.sideChat !== true && + !seen.has(current.forkedFrom.threadId) + ) { + const { threadId, runId } = current.forkedFrom; + seen.add(threadId); + const source = yield* messagesThroughRun(threadId, runId, current.createdAt); + if (source === undefined) break; history.push(source.messages); title ??= source.thread.title; - forkedFrom = source.thread.forkedFrom; + current = source.thread; } const context = formatThreadTitleContext(history.reverse().flat()); return title === undefined || context.message.length === 0 @@ -182,17 +190,25 @@ const make = Effect.gen(function* () { projection.messages.filter((message) => !message.streaming), ); } - if (context.message.length === 0 && context.attachments.length === 0) { + // A deleted source should not cost the fork its title. + const inherited = yield* forkSource(projection.thread).pipe( + Effect.catchTags({ + OrchestratorProjectionError: (error) => + isProjectionStoreThreadNotFoundError(error.cause) + ? Effect.undefined + : Effect.fail(error), + }), + ); + if ( + context.message.length === 0 && + context.attachments.length === 0 && + inherited === undefined + ) { return { status: "failed" as const, message: "This thread has no messages to title yet.", }; } - - // A deleted source should not cost the fork its title. - const inherited = yield* forkSource(projection.thread).pipe( - Effect.orElseSucceed(() => undefined), - ); const settings = resolveProjectSettings( yield* serverSettings.getSettings, projection.thread.projectId, diff --git a/scripts/scenarios/fork-title-regenerates-from-inherited-history.e2e.ts b/scripts/scenarios/fork-title-regenerates-from-inherited-history.e2e.ts new file mode 100644 index 0000000000000..535ffde207198 --- /dev/null +++ b/scripts/scenarios/fork-title-regenerates-from-inherited-history.e2e.ts @@ -0,0 +1,26 @@ +import { expect } from "e2e"; + +import { test, WEB } from "./t3.ts"; + +// Scenarios title with a Codex command that always fails, so reaching it is the fixed behavior; +// the bug refused first with "This thread has no messages to title yet." +test( + "Regenerate title on a fresh fork reaches the title model with the inherited history", + WEB, + async ({ t3, app, screen }) => { + const project = await t3.project("demo"); + const thread = await t3.thread({ project, title: "Earlier work", messages: ["first"] }); + await t3.open(thread); + + await expect(screen.getByText("first fixture turn complete", { exact: false })).toBeVisible(); + await screen.getByRole("button", "Fork from this response").first().tap(); + const forkActions = screen.getByRole("button", "Thread actions for Earlier work fork"); + await expect(forkActions).toBeVisible(); + + await forkActions.tap(); + await screen.getByRole("menuitem", "Regenerate title").tap(); + await expect(screen.getByText("Failed to regenerate thread title")).toBeVisible(); + await app.screenshot("regenerate-result"); + await expect(screen.getByText("Codex CLI command", { exact: false })).toBeVisible(); + }, +); From 6aa14d3cd5300e9fbc59b0c5be816a33d00ca4fd Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:37:46 +0100 Subject: [PATCH 048/192] fix(server): short search prefixes find accented messages (#36632) Co-authored-by: Julius Marminge --- .../src/orchestration-v2/ThreadSearch.test.ts | 28 ++++++--- .../src/orchestration-v2/ThreadSearch.ts | 60 ++++++++++++++++--- ...hread-search-finds-accented-message.e2e.ts | 25 ++++++++ 3 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 scripts/scenarios/thread-search-finds-accented-message.e2e.ts diff --git a/apps/server/src/orchestration-v2/ThreadSearch.test.ts b/apps/server/src/orchestration-v2/ThreadSearch.test.ts index 421e79f5a0904..1dee6c9cfc0f7 100644 --- a/apps/server/src/orchestration-v2/ThreadSearch.test.ts +++ b/apps/server/src/orchestration-v2/ThreadSearch.test.ts @@ -840,7 +840,7 @@ it.layer(layerTest)("ThreadSearch filters before it ranks", (it) => { }); it.effect.each([true, false])( - "ThreadSearch narrows short words outside ASCII before the hit limit (indexed: %s)", + "ThreadSearch narrows Cyrillic and accent-folded Latin short words before the hit limit (indexed: %s)", (indexed) => Effect.gen(function* () { const projections = yield* ProjectionStore.ProjectionStoreV2; @@ -848,12 +848,15 @@ it.effect.each([true, false])( const sql = yield* SqlClient.SqlClient; const project = ProjectId.make("project:short-limit"); const older = ThreadId.make("thread:short-older"); + const olderFrench = ThreadId.make("thread:short-older-french"); const newer = ThreadId.make("thread:short-newer"); yield* createProject(project); yield* Effect.forEach( [ thread(older, project), message(older, "older", "user", "В логах ошибка"), + thread(olderFrench, project), + message(olderFrench, "older-french", "user", "École fermée à Lyon"), thread(newer, project), ], projections.apply, @@ -870,15 +873,14 @@ it.effect.each([true, false])( WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n WHERE i < 2000) INSERT INTO orchestration_v2_projection_messages (message_id, thread_id, role, streaming, created_at, updated_at, payload_json) - SELECT 'short-newer-' || i, ${newer}, 'assistant', 0, ${DateTime.formatIso(at(5))}, - ${DateTime.formatIso(at(5))}, json_object('text', 'ошибка здесь') - FROM n + SELECT 'short-newer-' || i || '-' || word, ${newer}, 'assistant', 0, + ${DateTime.formatIso(at(5))}, ${DateTime.formatIso(at(5))}, json_object('text', text) + FROM n, (SELECT 'ru' AS word, 'ошибка здесь' AS text UNION ALL SELECT 'fr', 'fermée ici') `; - const { matches } = yield* search.search({ query: "ошибка в" }); - assert.deepEqual( - matches.map((match) => match.threadId), - [older], - ); + const threadIds = (query: string) => + search.search({ query }).pipe(Effect.map(({ matches }) => matches.map((m) => m.threadId))); + assert.deepEqual(yield* threadIds("ошибка в"), [older]); + assert.deepEqual(yield* threadIds("fermée ec"), [olderFrench]); }).pipe(Effect.provide(layerTest)), ); @@ -898,6 +900,9 @@ it.layer(layerTest)("ThreadSearch short words outside ASCII", (it) => { message(russian, "russian", "user", "В логах ошибка"), thread(french, project), message(french, "french", "user", "École fermée à Lyon"), + message(french, "french-shout", "user", "Météo école"), + message(french, "romanian", "user", "Școala închisă"), + message(french, "vietnamese", "user", "Hồ Chí Minh"), ], projections.apply, { discard: true }, @@ -908,8 +913,13 @@ it.layer(layerTest)("ThreadSearch short words outside ASCII", (it) => { const assertMatches = Effect.gen(function* () { assert.deepEqual(yield* threadIds("ошибка в"), [russian]); assert.deepEqual(yield* threadIds("fermée éc"), [french]); + assert.deepEqual(yield* threadIds("ecole fermée"), [french]); + assert.deepEqual(yield* threadIds("école me"), [french]); + assert.deepEqual(yield* threadIds("închisă sc"), [french]); + assert.deepEqual(yield* threadIds("chí ho"), [french]); // They still narrow: a word the message lacks rules it out. assert.deepEqual(yield* threadIds("ошибка я"), []); + assert.deepEqual(yield* threadIds("fermée ce"), []); }); yield* assertMatches; yield* sql`DROP TABLE orchestration_v2_message_search`; diff --git a/apps/server/src/orchestration-v2/ThreadSearch.ts b/apps/server/src/orchestration-v2/ThreadSearch.ts index d3c96a40ce2b8..3bcdf9e84d4a2 100644 --- a/apps/server/src/orchestration-v2/ThreadSearch.ts +++ b/apps/server/src/orchestration-v2/ThreadSearch.ts @@ -69,8 +69,10 @@ const TokenList = Schema.fromJsonString(Schema.Array(Schema.String)); const SearchRequest = Schema.Struct({ /** The FTS5 query, or null to scan messages instead. */ match: Schema.NullOr(Schema.String), - /** Tokens every hit must contain, checked on the text. */ - substrings: TokenList, + /** Tokens every hit must contain, as typed or as their GLOB folding case and Latin accents. */ + substrings: Schema.fromJsonString( + Schema.Array(Schema.Struct({ token: Schema.String, accentInsensitive: Schema.String })), + ), /** For each short word outside ASCII, the spellings a hit must contain one of. */ shortSpellings: Schema.fromJsonString(Schema.Array(Schema.Array(Schema.String))), projectId: Schema.NullOr(ProjectId), @@ -120,6 +122,37 @@ function shortTokenSpellings(token: string): ReadonlyArray { return [...new Set(spellings)]; } +// Latin-1 Supplement and Extended-A/B/Additional letters that fold to ASCII, +// including Romanian and Vietnamese letters SQLite's lower() leaves alone. +const accentedForms = Map.groupBy( + [ + ...Array.from({ length: 0x250 - 0xc0 }, (_, offset) => String.fromCharCode(0xc0 + offset)), + ...Array.from({ length: 0x1f00 - 0x1e00 }, (_, offset) => String.fromCharCode(0x1e00 + offset)), + ].filter((char) => /^[a-z]$/.test(foldSearchQuery(char))), + (char) => foldSearchQuery(char), +); +// Only text that may hold one of those letters (by UTF-8 lead byte) needs the GLOB: +// lower() already folds ASCII case. +const MESSAGE_HOLDS_ACCENTED_LATIN = [ + ...new Set( + [...accentedForms.values()].flat().map((char) => Buffer.from(char).toString("hex", 0, 1)), + ), +] + .map((lead) => `instr(CAST(message.text AS BLOB), x'${lead}') > 0`) + .join(" OR "); + +/** A GLOB pattern for `token` anywhere, in any case and with or without Latin accents. */ +function accentInsensitiveGlob(token: string): string { + const pattern = Array.from(token, (char) => { + const letter = foldSearchQuery(char); + if (/^[a-z]$/.test(letter)) { + return `[${letter}${letter.toUpperCase()}${accentedForms.get(letter)?.join("") ?? ""}]`; + } + return /[*?[]/.test(char) ? `[${char}]` : char; + }); + return `*${pattern.join("")}*`; +} + /** * The SQL inputs for a message search, or null when the query has no token * long enough to search. Tokens of three or more characters go to the @@ -137,7 +170,10 @@ function buildMessageSearch( const narrowing = short.filter((token) => !NON_ASCII.test(token)); return { match: indexed ? long.map((token) => `"${token.replaceAll('"', '""')}"`).join(" ") : null, - substrings: indexed ? narrowing : [...long, ...narrowing], + substrings: (indexed ? narrowing : [...long, ...narrowing]).map((token) => ({ + token, + accentInsensitive: accentInsensitiveGlob(token), + })), shortSpellings: short.filter((token) => NON_ASCII.test(token)).map(shortTokenSpellings), }; } @@ -176,10 +212,10 @@ export function buildSearchSnippet(text: string, matchStart: number): string { return `${start > 0 ? "…" : ""}${shown.slice(0, end)}${cut ? "…" : ""}`; } -// Filter hidden subagents and side chats before the hit limit so visible matches aren't lost. -// Word starts and ranking follow the title rules in JS. -// Without FTS5 trigrams, or for two-character CJK words, scan the same eligible messages. -// The scan folds ASCII case only, so accent variants and non-ASCII case variants may not match. +// Filter hidden subagents and side chats before the hit limit; JS checks word starts and ranking. +// Both paths narrow ASCII tokens with Latin-1 and Extended-A/B/Additional accent variants. +// Without FTS5 trigrams, or for two-character CJK words, scan the same eligible messages; +// accented queries and non-Latin case can still miss: "fermée"/"fermee", "ОШИБКА"/"ошибка". export const messageHitsQuery = ( sql: SqlClient.SqlClient, { match, substrings, shortSpellings, projectId, excludedThreadIds }: typeof SearchRequest.Encoded, @@ -244,8 +280,14 @@ export const messageHitsQuery = ( -- neither find nor a jump can reach them. AND messages.message_id NOT LIKE 'async-answer:%' AND NOT EXISTS ( - SELECT 1 FROM json_each(${substrings}) AS token - WHERE instr(lower(${messageSearchText(sql)}), token.value) = 0 + -- The text read once, not once per check. + SELECT 1 FROM (SELECT ${messageSearchText(sql)} AS text) AS message, + json_each(${substrings}) AS substring + WHERE instr(lower(message.text), json_extract(substring.value, '$.token')) = 0 + AND NOT ( + (${sql.literal(MESSAGE_HOLDS_ACCENTED_LATIN)}) + AND message.text GLOB json_extract(substring.value, '$.accentInsensitive') + ) ) AND NOT EXISTS ( SELECT 1 FROM json_each(${shortSpellings}) AS token diff --git a/scripts/scenarios/thread-search-finds-accented-message.e2e.ts b/scripts/scenarios/thread-search-finds-accented-message.e2e.ts new file mode 100644 index 0000000000000..1e4e2a8f4ac91 --- /dev/null +++ b/scripts/scenarios/thread-search-finds-accented-message.e2e.ts @@ -0,0 +1,25 @@ +import { expect } from "e2e"; + +import { test, WEB } from "./t3.ts"; + +test( + "Thread search finds an accented message by a short unaccented word", + WEB, + async ({ t3, app, screen }) => { + const project = await t3.project("demo"); + const thread = await t3.thread({ project, title: "Weekly notes", messages: ["Start"] }); + await t3.open(thread); + + // Search reads what the user wrote; messages the CLI sends count as an agent's. + const composer = screen.getByRole("textbox"); + await composer.pressSequentially("École fermée à Lyon"); + await composer.press("Enter"); + await expect(screen.getByText("second fixture turn complete", { exact: false })).toBeVisible(); + + await screen.getByRole("combobox", "Search threads").fill("fermée ec"); + await expect(screen.getByRole("listbox", "Thread search results")).toContainText( + "École fermée", + ); + await app.screenshot("search"); + }, +); From 060ac23d6f827f101d5c906f2ba2df51f14d66ce Mon Sep 17 00:00:00 2001 From: Kristof Katzenberger <110766429+kristofkatzenberger@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:40:15 +0100 Subject: [PATCH 049/192] fix(devices): explain missing npm during tool updates (#27084) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/device/DeviceService.test.ts | 133 +++++++++++-------- apps/server/src/device/DeviceService.ts | 5 +- packages/contracts/src/device.ts | 3 + 3 files changed, 81 insertions(+), 60 deletions(-) diff --git a/apps/server/src/device/DeviceService.test.ts b/apps/server/src/device/DeviceService.test.ts index 604b92c34d091..71c7b6994ca86 100644 --- a/apps/server/src/device/DeviceService.test.ts +++ b/apps/server/src/device/DeviceService.test.ts @@ -1782,67 +1782,84 @@ it.effect("reconciles SSH host settings without publishing no-op changes", () => ), ); -it.effect("a failed tool update reports npm's exit code but not its output", () => - Effect.gen(function* () { - const host: DeviceHost.DeviceHost["Service"] = { - id: LOCAL_DEVICE_HOST_ID, - label: "This machine", - summary: Effect.succeed({ +it.effect.each(["exit", "missing"] as const)( + "a failed tool update reports a safe %s failure", + (failure) => + Effect.gen(function* () { + const host: DeviceHost.DeviceHost["Service"] = { id: LOCAL_DEVICE_HOST_ID, - kind: "local", - label: "Test server", - platforms: [], - hubInstalled: false, - agentDeviceInstalled: false, - }), - platformAvailability: (platform) => Effect.succeed({ platform, available: false }), - ensureReady: () => Effect.die("Updating a tool does not start the hub"), - ensureAgentReady: () => Effect.die("Updating a tool does not start the agent"), - current: Effect.succeed(null), - stopAgent: Effect.void, - stop: Effect.void, - }; - const npmFailure = { - code: ChildProcessSpawner.ExitCode(243), - stdout: "", - stderr: "npm error EACCES https://private:credential@registry.example.test/expo-device-hub", - timedOut: false, - stdoutTruncated: false, - stderrTruncated: false, - stdoutInvalidUtf8: false, - stderrInvalidUtf8: false, - }; - const service = yield* DeviceService.make.pipe( - Effect.provideService(DeviceHost.DeviceHost, host), - Effect.provideService(ProcessRunner.ProcessRunner, { - run: () => Effect.succeed(npmFailure), - }), - Effect.provideService( - HttpClient.HttpClient, - HttpClient.make(() => Effect.die("Updating a tool makes no hub requests")), - ), - ); + label: "This machine", + summary: Effect.succeed({ + id: LOCAL_DEVICE_HOST_ID, + kind: "local", + label: "Test server", + platforms: [], + hubInstalled: false, + agentDeviceInstalled: false, + }), + platformAvailability: (platform) => Effect.succeed({ platform, available: false }), + ensureReady: () => Effect.die("Updating a tool does not start the hub"), + ensureAgentReady: () => Effect.die("Updating a tool does not start the agent"), + current: Effect.succeed(null), + stopAgent: Effect.void, + stop: Effect.void, + }; + const npmFailure = { + code: ChildProcessSpawner.ExitCode(243), + stdout: "", + stderr: "npm error EACCES https://private:credential@registry.example.test/expo-device-hub", + timedOut: false, + stdoutTruncated: false, + stderrTruncated: false, + stdoutInvalidUtf8: false, + stderrInvalidUtf8: false, + }; + const service = yield* DeviceService.make.pipe( + Effect.provideService(DeviceHost.DeviceHost, host), + Effect.provideService(ProcessRunner.ProcessRunner, { + run: ({ command, args }) => + failure === "exit" + ? Effect.succeed(npmFailure) + : Effect.fail( + new ProcessRunner.ProcessSpawnError({ + command, + argumentCount: args.length, + cause: PlatformError.systemError({ + _tag: "NotFound", + module: "ChildProcess", + method: "spawn", + description: "private diagnostic text", + }), + }), + ), + }), + Effect.provideService( + HttpClient.HttpClient, + HttpClient.make(() => Effect.die("Updating a tool makes no hub requests")), + ), + ); - const error = yield* service.updateTool("hub").pipe(Effect.flip); - expect(error).toMatchObject({ - _tag: "DeviceOperationError", - reason: "command_failed", - exitCode: 243, - }); - expect(error.message).toBe( - "Device update device tool failed: The device command failed (exit code 243).", - ); - }).pipe( - Effect.provide( - Layer.mergeAll( - ServerSettings.layerTest({ enableDeviceSupport: true }), - layerSecretResolver, - ServerConfig.layerTest(process.cwd(), { prefix: "t3-device-update-" }), - NodeCrypto.layer, - NetService.layer, - ).pipe(Layer.provideMerge(NodeServices.layer)), + const error = yield* service.updateTool("hub").pipe(Effect.flip); + expect(error).toMatchObject({ + _tag: "DeviceOperationError", + reason: failure === "exit" ? "command_failed" : "package_manager_missing", + }); + expect(error.message).toBe( + failure === "exit" + ? "Device update device tool failed: The device command failed (exit code 243)." + : "Device update device tool failed: Device support needs npm, which is not on the T3 server's PATH. Install npm (it comes with Node.js from nodejs.org), restart T3 Code, then retry.", + ); + }).pipe( + Effect.provide( + Layer.mergeAll( + ServerSettings.layerTest({ enableDeviceSupport: true }), + layerSecretResolver, + ServerConfig.layerTest(process.cwd(), { prefix: "t3-device-update-" }), + NodeCrypto.layer, + NetService.layer, + ).pipe(Layer.provideMerge(NodeServices.layer)), + ), ), - ), ); describe("agent session release on close", () => { diff --git a/apps/server/src/device/DeviceService.ts b/apps/server/src/device/DeviceService.ts index 15e159062a32d..78b31b87aefd3 100644 --- a/apps/server/src/device/DeviceService.ts +++ b/apps/server/src/device/DeviceService.ts @@ -1430,8 +1430,9 @@ export const make = Effect.gen(function* () { (cause) => new DeviceOperationError({ operation: "update device tool", - reason: "command_failed", - // Only the exit code goes into the shown message; npm's stderr can carry registry credentials. + reason: + cause.reason === "npm_not_found" ? "package_manager_missing" : "command_failed", + // npm's stderr can carry registry credentials; expose only the reason and exit code. ...(cause.exitCode !== undefined ? { exitCode: cause.exitCode } : {}), cause, }), diff --git a/packages/contracts/src/device.ts b/packages/contracts/src/device.ts index ad52b213f10fe..dee7daa445242 100644 --- a/packages/contracts/src/device.ts +++ b/packages/contracts/src/device.ts @@ -588,6 +588,7 @@ export class DeviceOperationError extends Schema.TaggedError Date: Sat, 14 Nov 2026 02:42:45 -0800 Subject: [PATCH 050/192] fix(server): a locked database no longer leaves a thread stuck in "starting" (#36635) Co-authored-by: Rene Franke <132335976+ReneFr81@users.noreply.github.com> --- .../src/orchestration-v2/EffectWorker.test.ts | 136 ++++++++++++++++++ .../src/orchestration-v2/EffectWorker.ts | 14 ++ packages/shared/src/nodeSqliteClient.test.ts | 19 +++ packages/shared/src/nodeSqliteClient.ts | 8 +- 4 files changed, 176 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/EffectWorker.test.ts b/apps/server/src/orchestration-v2/EffectWorker.test.ts index cc43601543eec..c753e07d4689a 100644 --- a/apps/server/src/orchestration-v2/EffectWorker.test.ts +++ b/apps/server/src/orchestration-v2/EffectWorker.test.ts @@ -16,12 +16,16 @@ import { import * as Cause from "effect/Cause"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; +import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; +import * as SqlError from "effect/sql/SqlError"; import * as Stream from "effect/Stream"; +import * as TestClock from "effect/testing/TestClock"; import * as CheckpointRollbackService from "./CheckpointRollbackService.ts"; import * as CheckpointService from "./CheckpointService.ts"; @@ -396,6 +400,138 @@ it.effect("requeues a claim when a pre-execution worker check fails", () => }), ); +it.effect("keeps requeueing a failed claim while the database is locked", () => + Effect.gen(function* () { + const now = DateTime.formatIso(yield* DateTime.now); + const effectId = "effect:worker-requeue-lock-timeout"; + const workerId = "worker-requeue-lock-timeout"; + const claimedEffect: EffectOutbox.OrchestrationEffectV2 = { + id: effectId, + commandId: CommandId.make("command:worker-requeue-lock-timeout"), + threadId, + request: { type: "provider-runtime.continue", sourceRunId: runId }, + status: "running", + attemptCount: 1, + availableAt: now, + leaseOwner: workerId, + createdAt: now, + updatedAt: now, + completedAt: null, + lastError: null, + }; + const lockedWrites = yield* Ref.make(3); + const requeued = yield* Ref.make(false); + const firstLockTimeout = yield* Deferred.make(); + const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ + claimNext: () => Effect.succeedSome(claimedEffect), + get: () => Effect.succeedSome(claimedEffect), + awaitCancellation: () => Effect.never, + clearCancellation: () => Effect.void, + retry: () => + Effect.gen(function* () { + if ((yield* Ref.getAndUpdate(lockedWrites, (count) => count - 1)) > 0) { + yield* Deferred.succeed(firstLockTimeout, undefined); + return yield* new EffectOutbox.EffectOutboxError({ + operation: "retry", + effectId, + cause: new SqlError.SqlError({ + reason: new SqlError.LockTimeoutError({ cause: null }), + }), + }); + } + yield* Ref.set(requeued, true); + return true; + }), + }); + const layerExecutor = Layer.succeed( + EffectWorker.OrchestrationEffectExecutorV2, + EffectWorker.OrchestrationEffectExecutorV2.of({ + execute: () => + Effect.fail( + new EffectWorker.OrchestrationEffectExecutionError({ + effectId, + effectType: "provider-runtime.continue", + cause: "simulated database is locked", + }), + ), + }), + ); + const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe( + Layer.provide(Layer.merge(layerOutbox, layerExecutor)), + ); + + const workerFiber = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( + Effect.flatMap((worker) => worker.runOnce()), + Effect.provide(layerWorker), + Effect.forkChild, + ); + yield* Deferred.await(firstLockTimeout); + yield* TestClock.adjust(Duration.millis(99)); + assert.isFalse(yield* Ref.get(requeued)); + yield* TestClock.adjust(Duration.seconds(1)); + yield* Effect.exit(Fiber.join(workerFiber)); + + assert.isTrue(yield* Ref.get(requeued)); + }), +); + +it.effect.each([ + { name: "requeue", request: { type: "provider-runtime.continue", sourceRunId: runId } }, + { name: "terminalize", request: { type: "provider-turn.start", runId } }, +] as const)("interrupts $name settlement retries when the worker shuts down", ({ request }) => + Effect.gen(function* () { + const claimedEffect: EffectOutbox.OrchestrationEffectV2 = { + ...restartEffect(yield* DateTime.now, { type: "detach" }), + request, + }; + const settlementStarted = yield* Deferred.make(); + const releaseLock = yield* Ref.make(false); + const lockedWrite = Effect.gen(function* () { + yield* Deferred.succeed(settlementStarted, undefined); + // An in-flight database write can finish its own wait before noticing shutdown. + yield* Effect.sleep(Duration.millis(1)); + if (yield* Ref.get(releaseLock)) return true; + return yield* new EffectOutbox.EffectOutboxError({ + operation: "settlement", + effectId: claimedEffect.id, + cause: new SqlError.SqlError({ reason: new SqlError.LockTimeoutError({ cause: null }) }), + }); + }); + const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ + claimNext: () => Effect.succeedSome(claimedEffect), + get: () => Effect.succeedSome(claimedEffect), + awaitCancellation: () => Effect.never, + clearCancellation: () => Effect.void, + succeed: () => Effect.fail(new EffectOutbox.EffectOutboxError({ operation: "succeed" })), + retry: () => lockedWrite, + fail: () => lockedWrite, + }); + const layerExecutor = Layer.succeed( + EffectWorker.OrchestrationEffectExecutorV2, + EffectWorker.OrchestrationEffectExecutorV2.of({ execute: () => Effect.void }), + ); + const layerWorker = EffectWorker.layerWithOptions({ workerId: "test-worker" }).pipe( + Layer.provide(Layer.merge(layerOutbox, layerExecutor)), + ); + + const daemon = yield* EffectWorker.runDaemonWithOptions({ concurrency: 1 }).pipe( + Effect.provide(layerWorker), + Effect.forkChild, + ); + yield* Deferred.await(settlementStarted); + const shutdown = yield* Fiber.interrupt(daemon).pipe( + Effect.timeoutOption(Duration.millis(100)), + Effect.forkChild, + ); + yield* TestClock.adjust(Duration.millis(100)); + const stopped = yield* Fiber.join(shutdown); + yield* Ref.set(releaseLock, true); + yield* TestClock.adjust(Duration.seconds(1)); + yield* Fiber.await(daemon); + assert.isTrue(Option.isSome(stopped)); + }), +); + it.effect("arms cancellation before the durable pre-execution check", () => Effect.gen(function* () { const now = DateTime.formatIso(yield* DateTime.now); diff --git a/apps/server/src/orchestration-v2/EffectWorker.ts b/apps/server/src/orchestration-v2/EffectWorker.ts index 9dd5079805745..bbb214542540d 100644 --- a/apps/server/src/orchestration-v2/EffectWorker.ts +++ b/apps/server/src/orchestration-v2/EffectWorker.ts @@ -9,7 +9,9 @@ import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Metric from "effect/Metric"; import * as Option from "effect/Option"; +import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; +import * as SqlError from "effect/sql/SqlError"; import { increment, @@ -667,6 +669,16 @@ export class OrchestrationEffectWorkerError extends Schema.TaggedError + SqlError.isSqlError(error.cause) && error.cause.reason._tag === "LockTimeoutError"; +const lockRetrySchedule = Schedule.spaced(Duration.millis(100)); +const retryWhileLocked = (settlement: Effect.Effect) => + Effect.interruptible( + Effect.retry(settlement, { schedule: lockRetrySchedule, while: isLockTimeout }), + ); + type ClaimFilter = Omit[0], "workerId">; export interface OrchestrationEffectWorkerV2Shape { @@ -728,6 +740,7 @@ export const layerWithOptions = ( delayMs: 0, }) .pipe( + retryWhileLocked, Effect.flatMap((requeued) => requeued ? Effect.logWarning("Requeued effect after unexpected worker failure", { @@ -759,6 +772,7 @@ export const layerWithOptions = ( error: `Worker failed to settle a process-bound effect after execution started: ${Cause.pretty(cause)}`, }) .pipe( + retryWhileLocked, Effect.flatMap((failed) => failed ? Effect.logError("Terminalized process-bound effect after settlement failure", { diff --git a/packages/shared/src/nodeSqliteClient.test.ts b/packages/shared/src/nodeSqliteClient.test.ts index ac65e7f12daa7..b7b9b7a0866a2 100644 --- a/packages/shared/src/nodeSqliteClient.test.ts +++ b/packages/shared/src/nodeSqliteClient.test.ts @@ -240,6 +240,25 @@ it.effect("reuses a worker query after an iterator constraint failure", () => }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), ); +it.effect("commits on the worker writer after a returning write timed out on a lock", () => + Effect.gen(function* () { + const { filename, other } = yield* makeTempDatabase; + yield* Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + yield* sql`PRAGMA busy_timeout = 0`; + yield* Effect.sync(() => other.exec("BEGIN IMMEDIATE")); + const error = yield* sql`UPDATE counters SET value = 1 WHERE id = 1 RETURNING value`.pipe( + Effect.flip, + ); + assert.equal(error.reason._tag, "LockTimeoutError"); + yield* Effect.sync(() => other.exec("COMMIT")); + + yield* sql.withTransaction(sql`UPDATE counters SET value = 2 WHERE id = 1`); + assert.deepEqual(yield* sql`SELECT value FROM counters WHERE id = 1`.values, [[2]]); + }).pipe(Effect.provide(SqliteClient.layer({ filename, writerWorker: true }))); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), +); + it.effect("keeps another connection from committing between a transaction's read and write", () => Effect.gen(function* () { const { filename, other } = yield* makeTempDatabase; diff --git a/packages/shared/src/nodeSqliteClient.ts b/packages/shared/src/nodeSqliteClient.ts index fda878a114d9a..ab14675c7bfc0 100644 --- a/packages/shared/src/nodeSqliteClient.ts +++ b/packages/shared/src/nodeSqliteClient.ts @@ -244,6 +244,7 @@ parentPort.on("message", (request) => { } const { id, sql, params, cached, values, raw, safeIntegers, received } = request; let operation = "open"; + let iterator; try { if (db === undefined) throw new Error("database connection is unavailable"); operation = "prepare"; @@ -255,7 +256,8 @@ parentPort.on("message", (request) => { let rows = []; let bytes = 0; let pendingBatch = false; - for (const row of statement.iterate(...params)) { + iterator = statement.iterate(...params); + for (const row of iterator) { let rowBytes = 0; for (const key in row) { const value = row[key]; @@ -281,6 +283,10 @@ parentPort.on("message", (request) => { parentPort.postMessage({ id, rows: raw ? result : [] }); } } catch (error) { + // A step that throws leaves its statement active; an active write blocks every later COMMIT. + try { + iterator?.return(); + } catch {} // A failed iterator can report its previous SQLite error when the statement is reused. statements.delete(sql); parentPort.postMessage({ id, error: { operation, ...describe(error) } }); From 0a8aaa3ccb944dadb70054ae4ef2e29897b0db03 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 02:46:36 -0800 Subject: [PATCH 051/192] fix(server): retained forks keep inherited shell item counts after source deletion (#36657) --- .../orchestration-v2/ProjectionStore.test.ts | 20 +++++++++++++++++++ .../src/orchestration-v2/ProjectionStore.ts | 15 ++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProjectionStore.test.ts b/apps/server/src/orchestration-v2/ProjectionStore.test.ts index 8b967f648165f..50f2a0a12d1ae 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.test.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.test.ts @@ -10562,6 +10562,26 @@ it.layer(layerTest)("ProjectionStoreV2", (it) => { ["local", targetThreadId, "command_execution"], ], ); + + const sourceThread = (yield* projectionStore.getThreadProjection(sourceThreadId)).thread; + yield* projectionStore.apply({ + id: EventId.make("event:projection-fork-source-rollback:source-deleted"), + type: "thread.deleted", + threadId: sourceThreadId, + occurredAt: now, + payload: { ...sourceThread, deletedAt: now }, + }); + const afterSourceDeleted = yield* projectionStore.getShellSnapshot(); + assert.isUndefined(afterSourceDeleted.threads.find((thread) => thread.id === sourceThreadId)); + for (const forkId of [targetThreadId, nestedThreadId]) { + const expected = (yield* projectionStore.getThreadProjection(forkId)).visibleTurnItems + .length; + assert.equal((yield* projectionStore.getThreadShell(forkId))?.visibleItemCount, expected); + assert.equal( + afterSourceDeleted.threads.find((thread) => thread.id === forkId)?.visibleItemCount, + expected, + ); + } }), ); diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index ecac162d868b9..3c1d353f061b4 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -7660,7 +7660,12 @@ export const layer: Layer.Layer = worktreePath, threadIds, listedOnly = false, - }: ShellSnapshotOptions & { readonly listedOnly?: boolean } = {}, + includeDeleted = false, + }: ShellSnapshotOptions & { + readonly listedOnly?: boolean; + // A retained fork still shows the history of a deleted source run. + readonly includeDeleted?: boolean; + } = {}, ) => sql` SELECT @@ -7839,7 +7844,7 @@ export const layer: Layer.Layer = candidate.ordinal DESC, candidate.run_id DESC LIMIT 1 ) AND blocked.status = 'failed' - WHERE t.deleted_at IS NULL${ + WHERE ${includeDeleted ? sql`1 = 1` : sql`t.deleted_at IS NULL`}${ listedOnly ? sql` AND json_extract(t.payload_json, '$.lineage.relationshipToParent') IS NOT 'subagent' AND json_extract(t.payload_json, '$.lineage.sideChat') IS NOT 1` @@ -8496,7 +8501,7 @@ export const layer: Layer.Layer = while (pendingSourceIds.length > 0) { const sourceId = pendingSourceIds.pop(); if (sourceId === undefined || rowsByThreadId.has(sourceId)) continue; - const source = (yield* selectShellThreadRows(sourceId))[0]; + const source = (yield* selectShellThreadRows(sourceId, { includeDeleted: true }))[0]; if (source === undefined) continue; rowsByThreadId.set(sourceId, source); if (source.forked_from_run_source_thread_id !== null) { @@ -8976,7 +8981,9 @@ export const layer: Layer.Layer = if (nextId === undefined || rowsByThreadId.has(nextId)) { continue; } - const rows = yield* selectShellThreadRows(nextId); + const rows = yield* selectShellThreadRows(nextId, { + includeDeleted: nextId !== threadId, + }); const row = rows[0]; if (row === undefined) { continue; From bc1dc2a81ee51bf0d2883bc55470e4c70ca1990e Mon Sep 17 00:00:00 2001 From: Joseph Chekanoff Date: Sat, 14 Nov 2026 05:54:47 -0500 Subject: [PATCH 052/192] fix(mobile): keep the thread column out of the iPhone Duo fold (#26923) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../createWorkspaceStackNavigator.ios.tsx | 5 +- .../features/threads/ThreadDetailScreen.tsx | 8 +- .../src/features/threads/ThreadFeed.tsx | 16 ++-- apps/mobile/src/lib/reserved-regions.test.ts | 78 ++++++++++++++++--- apps/mobile/src/lib/reserved-regions.ts | 32 ++++++-- 5 files changed, 112 insertions(+), 27 deletions(-) diff --git a/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx b/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx index bac954d99f118..71a452f7d342a 100644 --- a/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx +++ b/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx @@ -331,6 +331,7 @@ function WorkspaceColumns( const primary = props.descriptors[props.primary.key]; if (!primary) return null; + const primaryColumnWidth = (layout.listPaneWidth ?? 320) + (layout.listPaneGap ?? 0) / 2; return ( + + + ); } diff --git a/apps/mobile/src/lib/reserved-regions.test.ts b/apps/mobile/src/lib/reserved-regions.test.ts index c5faebc31653c..60dd501ae37a4 100644 --- a/apps/mobile/src/lib/reserved-regions.test.ts +++ b/apps/mobile/src/lib/reserved-regions.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vite-plus/test"; -import { deriveBottomControlInsets, type NativeLayoutMetrics } from "./reserved-regions"; +import { + deriveBottomControlInsets, + deriveContentSideInsets, + type NativeLayoutMetrics, +} from "./reserved-regions"; import { deriveLayout, deriveWorkspacePaneLayout } from "./layout"; const innerDisplay: NativeLayoutMetrics = { @@ -21,26 +25,27 @@ describe("bottom controls around reserved regions", () => { expect(innerDisplay.width - insets.left - insets.right).toBeGreaterThan(320); }); - it("does not reserve a hinge twice when a native column starts at the division", () => { + it("starts the editor after a fold that covers a native column's leading edge", () => { expect( deriveBottomControlInsets({ ...innerDisplay, - width: 412, + width: 411.33, + height: 669, safeArea: { top: 82, bottom: 34, left: 0, right: 0 }, - reservedRegions: [{ kind: "division", x: 0, y: 0, width: 40, height: 560 }], + reservedRegions: [{ kind: "division", x: 0, y: 0, width: 39.83, height: 669 }], }), - ).toEqual({ left: 0, right: 0, bottom: 0 }); + ).toEqual({ left: 39.83, right: 0, bottom: 0 }); }); - it("keeps a native column's trailing division at its existing edge", () => { + it("does not reserve a hinge twice when the safe area already covers it", () => { expect( deriveBottomControlInsets({ ...innerDisplay, - width: 456, - safeArea: { top: 82, bottom: 34, left: 0, right: 0 }, - reservedRegions: [{ kind: "division", x: 455.75, y: 0, width: 0.25, height: 560 }], + width: 412, + safeArea: { top: 82, bottom: 34, left: 40, right: 0 }, + reservedRegions: [{ kind: "division", x: 0, y: 0, width: 40, height: 560 }], }), - ).toEqual({ left: 0, right: 0, bottom: 0 }); + ).toEqual({ left: 40, right: 0, bottom: 0 }); }); it("keeps controls below a horizontal fold in a hands-free pose", () => { @@ -65,6 +70,59 @@ describe("bottom controls around reserved regions", () => { }); }); +describe("content side insets around reserved regions", () => { + it("keeps transcript text on the wider side of a book fold", () => { + expect( + deriveContentSideInsets({ + ...innerDisplay, + reservedRegions: [{ kind: "division", x: 456, y: 0, width: 48, height: 560 }], + }), + ).toEqual({ left: 8, right: 504 }); + expect( + deriveContentSideInsets({ + ...innerDisplay, + width: 800, + reservedRegions: [{ kind: "division", x: 240, y: 0, width: 40, height: 560 }], + }), + ).toEqual({ left: 280, right: 72 }); + }); + + it("starts transcript text after a fold that covers a native column's leading edge", () => { + expect( + deriveContentSideInsets({ + ...innerDisplay, + width: 411.33, + height: 669, + safeArea: { top: 82, bottom: 34, left: 0, right: 0 }, + reservedRegions: [{ kind: "division", x: 0, y: 0, width: 39.83, height: 669 }], + }), + ).toEqual({ left: 39.83, right: 0 }); + }); + + it("ends transcript text before a fold at a native column's trailing edge", () => { + expect( + deriveContentSideInsets({ + ...innerDisplay, + width: 456, + safeArea: { top: 82, bottom: 34, left: 0, right: 0 }, + reservedRegions: [{ kind: "division", x: 416, y: 0, width: 40, height: 560 }], + }), + ).toEqual({ left: 0, right: 40 }); + }); + + it("ignores a horizontal fold and bottom cameras", () => { + expect( + deriveContentSideInsets({ + ...innerDisplay, + reservedRegions: [ + { kind: "division", x: 0, y: 240, width: 960, height: 40 }, + { kind: "occlusion", x: 8, y: 510, width: 60, height: 50 }, + ], + }), + ).toEqual({ left: 8, right: 72 }); + }); +}); + describe("Duo workspace sizing", () => { it("shows both columns on the unfolded inner display in portrait", () => { // Reported by the iOS 27.1 Duo simulator: no side bar or fold, only the hinge. diff --git a/apps/mobile/src/lib/reserved-regions.ts b/apps/mobile/src/lib/reserved-regions.ts index bb8ff981a6de9..a3d8b6c140657 100644 --- a/apps/mobile/src/lib/reserved-regions.ts +++ b/apps/mobile/src/lib/reserved-regions.ts @@ -23,6 +23,30 @@ export interface NativeLayoutMetrics { readonly bottomBarInset?: number; } +function isFullHeightDivision(region: ReservedRegion, metrics: NativeLayoutMetrics) { + return region.kind === "division" && region.height >= metrics.height / 2; +} + +function sideOfDivision(left: number, right: number, x: number, endX: number) { + if (x <= left) return { left: Math.min(right, endX), right }; + if (endX >= right) return { left, right: Math.max(left, x) }; + return right - endX >= x - left ? { left: endX, right } : { left, right: x }; +} + +export function deriveContentSideInsets(metrics: NativeLayoutMetrics | null) { + if (!metrics) return { left: 0, right: 0 }; + let left = metrics.safeArea.left; + let right = metrics.width - metrics.safeArea.right; + for (const region of metrics.reservedRegions) { + if (!isFullHeightDivision(region, metrics)) continue; + const x = Math.max(left, region.x); + const endX = Math.min(right, region.x + region.width); + if (endX <= x) continue; + ({ left, right } = sideOfDivision(left, right, x, endX)); + } + return { left, right: metrics.width - right }; +} + export function deriveBottomControlInsets(metrics: NativeLayoutMetrics | null) { if (!metrics) return { left: 0, right: 0, bottom: 0 }; let left = metrics.safeArea.left; @@ -34,12 +58,8 @@ export function deriveBottomControlInsets(metrics: NativeLayoutMetrics | null) { const y = Math.max(0, region.y); const endY = Math.min(bottom, region.y + region.height); if (endX <= x || endY <= y) continue; - if (region.kind === "division" && region.height >= metrics.height / 2) { - // A native column ending at a division is already split there. Only an - // interior division needs to move these controls into one side of the view. - if (x <= left || endX >= right) continue; - if (right - endX >= x - left) left = endX; - else right = x; + if (isFullHeightDivision(region, metrics)) { + ({ left, right } = sideOfDivision(left, right, x, endX)); } else if (endY >= bottom - metrics.safeArea.bottom) { if (region.width >= right - left) bottom = y; else if (right - endX >= x - left) left = endX; From 77ad2ebec500d331422e3c5093ad62fddeeb19e4 Mon Sep 17 00:00:00 2001 From: Stevan Borus Date: Sat, 14 Nov 2026 11:54:58 +0100 Subject: [PATCH 053/192] fix(server): end obsolete link cards after a matching retry succeeds (#26944) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/orchestration-v2/Orchestrator.ts | 46 ++ apps/server/src/peer/PeerLinkRequests.test.ts | 486 +++++++++++++++--- apps/server/src/peer/PeerLinkRequests.ts | 96 ++-- packages/contracts/src/orchestrationV2.ts | 5 + 4 files changed, 531 insertions(+), 102 deletions(-) diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts index c7e3a670de316..c6d1ab4654110 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.ts @@ -763,6 +763,18 @@ function cancelledAppOwnedTask( }; } +function askedForLinkedMachine( + card: Extract, + linked: Extract, +): boolean { + if (card.requestedAccess !== linked.requestedAccess) return false; + if (card.environmentId !== undefined) return card.environmentId === linked.linkedEnvironmentId; + if (card.url !== undefined) return linked.url !== undefined && card.url === linked.url; + const normalize = (text: string | undefined) => text?.trim().toLowerCase() ?? ""; + const hint = normalize(card.hint); + return hint !== "" && hint === normalize(linked.hint) && hint === normalize(linked.linkedLabel); +} + // A workspace change detaches the session, which can settle the calling run at once. Queuing its // follow-up in a later command would let a delegated task report its result before that exists. function metadataUpdateContinuation( @@ -10448,6 +10460,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio type: _type, commandId: _commandId, turnItemId: _turnItemId, + answeringTurnItemIds, wake, ...fields } = command; @@ -10507,6 +10520,39 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio occurredAt: now, payload: turnItem, }); + // The link answers the older cards that asked for the same machine and + // access. They end without waking anyone: the agent hears this link. + // A card still being answered stays open for its own answer to end. + if (!unchanged && command.linkStatus === "linked") { + for (const item of projection.turnItems) { + if ( + item.type !== "link_request" || + item.linkStatus !== "pending" || + item.ordinal >= turnItem.ordinal || + answeringTurnItemIds?.includes(item.id) === true || + !askedForLinkedMachine(item, command) + ) { + continue; + } + yield* emit( + events, + command, + )({ + type: "turn-item.updated", + threadId: command.threadId, + ...(item.runId === null ? {} : { runId: item.runId }), + ...(item.nodeId === null ? {} : { nodeId: item.nodeId }), + occurredAt: now, + payload: { + ...item, + status: "cancelled", + linkStatus: "cancelled", + completedAt: now, + updatedAt: now, + }, + }); + } + } // The agent asked without waiting, so the answer reaches it as a // notification: it starts a turn, or follows the one running. Only the // answer that closes the card wakes it, and never a closed thread. diff --git a/apps/server/src/peer/PeerLinkRequests.test.ts b/apps/server/src/peer/PeerLinkRequests.test.ts index ab16197ba05b7..2190e9a50e6a9 100644 --- a/apps/server/src/peer/PeerLinkRequests.test.ts +++ b/apps/server/src/peer/PeerLinkRequests.test.ts @@ -7,6 +7,7 @@ import { MessageId, NodeId, OrchestratorMcpFailure, + type PeerLinkCreateInput, ProjectId, ProviderDriverKind, ProviderInstanceId, @@ -125,74 +126,86 @@ const scope: McpInvocationContext.McpInvocationScope = { issuedAt: 1, }; -/** The laptop: a real orchestrator, its orchestrator toolkit, and its links. */ -const makeLaptop = Effect.gen(function* () { - const linking = yield* layerLinkingEnvironment(laptop).pipe(Layer.build); - // The orchestrator's own database, apart from the one links are kept in. - const database = yield* SqlitePersistence.layerMemory.pipe(Layer.build); - const layerDatabase = Layer.succeedContext(database); - const layerOrchestrator = Layer.mergeAll( - layerDatabase, - ProjectionStore.layer.pipe(Layer.provide(layerDatabase)), - ProviderReplayHarness.layerWithRegistry( - { name: "peer-link-requests" }, - ProviderAdapterRegistry.layerFromAdapters([adapter]), - { databaseLayer: layerDatabase, runEffectWorker: false }, - ), - ).pipe( - Layer.provide( - Layer.succeed(ProviderContinuationRequests.ProviderContinuationRequests, { - offer: () => Effect.void, - take: Effect.never, - }), - ), - ); - const layerThreads = ThreadManagement.layer.pipe( - Layer.provide(CommandReceiptStore.layer.pipe(Layer.provide(layerDatabase))), - Layer.provideMerge(Layer.merge(layerOrchestrator, ServerSettings.layerTest())), - ); - const layerHere = McpHttpServer.layerOrchestratorToolkit.pipe( - Layer.provideMerge(McpHttpServer.layerInProcessMcpServer), - Layer.provideMerge(PeerLinkRequests.layer), - Layer.provide(Layer.mock(PeerForwarding.PeerForwarding)({})), - Layer.provide(Layer.mock(RemoteDelegation.RemoteDelegation)({})), - Layer.provide(Layer.succeedContext(linking)), - Layer.provide(NodeCrypto.layer), - Layer.provideMerge(layerThreads), - Layer.provide(Layer.mock(ProviderRegistry.ProviderRegistry)({})), - Layer.provide(Layer.mock(ProviderAdapterRegistry.ProviderAdapterRegistryV2)({})), - Layer.provide(Layer.mock(ScheduledTaskService.ScheduledTaskService)({})), - Layer.provide(Layer.mock(ProjectService.ProjectService)({})), - Layer.provide(Layer.mock(SecretRequests.SecretRequests)({})), - Layer.provide(Layer.mock(ThreadLaunch.ThreadLaunchService)({})), - Layer.provide(Layer.mock(GitVcsDriver.GitVcsDriver)({})), - Layer.provide( - ServerConfig.layerTest(process.cwd(), { prefix: "t3-peer-link-requests-" }).pipe( - Layer.provide(NodeServices.layer), +/** A real orchestrator and links; the wrapper can hold a link in progress. */ +const makeLaptopWith = ( + wrapLinks?: (links: PeerLinks.PeerLinks["Service"]) => PeerLinks.PeerLinks["Service"], +) => + Effect.gen(function* () { + const linking = yield* layerLinkingEnvironment(laptop).pipe(Layer.build); + const answering = + wrapLinks === undefined + ? linking + : Context.add( + linking, + PeerLinks.PeerLinks, + wrapLinks(Context.get(linking, PeerLinks.PeerLinks)), + ); + // The orchestrator's own database, apart from the one links are kept in. + const database = yield* SqlitePersistence.layerMemory.pipe(Layer.build); + const layerDatabase = Layer.succeedContext(database); + const layerOrchestrator = Layer.mergeAll( + layerDatabase, + ProjectionStore.layer.pipe(Layer.provide(layerDatabase)), + ProviderReplayHarness.layerWithRegistry( + { name: "peer-link-requests" }, + ProviderAdapterRegistry.layerFromAdapters([adapter]), + { databaseLayer: layerDatabase, runEffectWorker: false }, ), - ), - Layer.provide(NodeServices.layer), - Layer.fresh, - ); - const here = yield* Layer.build(layerHere); - const server = Context.get(here, McpServer.McpServer); - return { - links: Context.get(linking, PeerLinks.PeerLinks), - linksSql: Context.get(linking, SqlClient.SqlClient), - requests: Context.get(here, PeerLinkRequests.PeerLinkRequests), - orchestrator: Context.get(here, Orchestrator.OrchestratorV2), - sink: Context.get(here, EventSink.EventSinkV2), - projections: Context.get(here, ProjectionStore.ProjectionStoreV2), - sql: Context.get(database, SqlClient.SqlClient), - call: (args: Record, callScope = scope) => - server - .callTool({ name: "t3_environment_link", arguments: args }) - .pipe( - Effect.provideService(McpInvocationContext.McpInvocationContext, callScope), - Effect.provideService(McpSchema.McpServerClient, mcpClient), + ).pipe( + Layer.provide( + Layer.succeed(ProviderContinuationRequests.ProviderContinuationRequests, { + offer: () => Effect.void, + take: Effect.never, + }), + ), + ); + const layerThreads = ThreadManagement.layer.pipe( + Layer.provide(CommandReceiptStore.layer.pipe(Layer.provide(layerDatabase))), + Layer.provideMerge(Layer.merge(layerOrchestrator, ServerSettings.layerTest())), + ); + const layerHere = McpHttpServer.layerOrchestratorToolkit.pipe( + Layer.provideMerge(McpHttpServer.layerInProcessMcpServer), + Layer.provideMerge(PeerLinkRequests.layer), + Layer.provide(Layer.mock(PeerForwarding.PeerForwarding)({})), + Layer.provide(Layer.mock(RemoteDelegation.RemoteDelegation)({})), + Layer.provide(Layer.succeedContext(answering)), + Layer.provide(NodeCrypto.layer), + Layer.provideMerge(layerThreads), + Layer.provide(Layer.mock(ProviderRegistry.ProviderRegistry)({})), + Layer.provide(Layer.mock(ProviderAdapterRegistry.ProviderAdapterRegistryV2)({})), + Layer.provide(Layer.mock(ScheduledTaskService.ScheduledTaskService)({})), + Layer.provide(Layer.mock(ProjectService.ProjectService)({})), + Layer.provide(Layer.mock(SecretRequests.SecretRequests)({})), + Layer.provide(Layer.mock(ThreadLaunch.ThreadLaunchService)({})), + Layer.provide(Layer.mock(GitVcsDriver.GitVcsDriver)({})), + Layer.provide( + ServerConfig.layerTest(process.cwd(), { prefix: "t3-peer-link-requests-" }).pipe( + Layer.provide(NodeServices.layer), ), - }; -}); + ), + Layer.provide(NodeServices.layer), + Layer.fresh, + ); + const here = yield* Layer.build(layerHere); + const server = Context.get(here, McpServer.McpServer); + return { + links: Context.get(linking, PeerLinks.PeerLinks), + linksSql: Context.get(linking, SqlClient.SqlClient), + requests: Context.get(here, PeerLinkRequests.PeerLinkRequests), + orchestrator: Context.get(here, Orchestrator.OrchestratorV2), + sink: Context.get(here, EventSink.EventSinkV2), + projections: Context.get(here, ProjectionStore.ProjectionStoreV2), + sql: Context.get(database, SqlClient.SqlClient), + call: (args: Record, callScope = scope) => + server + .callTool({ name: "t3_environment_link", arguments: args }) + .pipe( + Effect.provideService(McpInvocationContext.McpInvocationContext, callScope), + Effect.provideService(McpSchema.McpServerClient, mcpClient), + ), + }; + }); +const makeLaptop = makeLaptopWith(); type Laptop = Effect.Success; /** The laptop's thread, mid-turn when its agent asks, in `runtimeMode`. */ @@ -1118,3 +1131,344 @@ it.live( }), ), ); + +const linkCards = (a: Laptop) => + a.orchestrator + .getThreadProjection(threadId) + .pipe( + Effect.map((projection) => + projection.turnItems.flatMap((item) => (item.type === "link_request" ? [item] : [])), + ), + ); + +const ask = (a: Laptop, args: Record) => + Effect.gen(function* () { + const asked = yield* a.call(args); + const { turnItemId } = asked.structuredContent as { readonly turnItemId: TurnItemId }; + return (yield* linkCards(a)).find((card) => card.id === turnItemId)!; + }); + +const linkBox = (a: Laptop, b: ServedPeer, cardId: TurnItemId) => + Effect.gen(function* () { + const pairing = yield* b.auth.issuePairingCredential(); + yield* a.requests.answer({ + threadId, + turnItemId: cardId, + answer: { + type: "link", + urls: [b.url], + access: "full-access", + pairingCode: pairing.credential, + }, + }); + return pairing.credential; + }); + +const recordCard = ( + a: Laptop, + key: string, + fields: { + readonly url?: string; + readonly environmentId?: typeof box.environmentId; + readonly hint?: string; + readonly requestedAccess?: "read-only" | "full-access"; + }, +) => + Effect.gen(function* () { + const id = TurnItemId.make(`turn-item:link-request:${key}`); + yield* a.orchestrator.dispatch({ + type: "link_request.record", + commandId: CommandId.make(`command:pending:${key}`), + threadId, + runId, + nodeId: rootNode, + turnItemId: id, + reason: "", + linkStatus: "pending", + ...fields, + }); + return id; + }); + +it.live("a link ends older cards that asked for the same machine by hint, id or address", () => + withBoth((a, b) => + Effect.gen(function* () { + yield* seedThread(a); + // The agent first asks by the user's word, then again by the box's address. + const older = yield* ask(a, { + reason: "Delegate to the box.", + hint: "Box", + requestedAccess: "full-access", + clientRequestId: "by-hint", + }); + const byId = yield* ask(a, { + environmentId: box.environmentId, + requestedAccess: "full-access", + clientRequestId: "by-id", + }); + const byAddress = yield* recordCard(a, "by-address", { + url: b.url, + requestedAccess: "full-access", + }); + const newer = yield* ask(a, { + url: b.url, + reason: "Delegate to the box.", + hint: "Box", + requestedAccess: "full-access", + clientRequestId: "by-url", + }); + const credential = yield* linkBox(a, b, newer.id); + + const cards = yield* linkCards(a); + expect(cards.find((card) => card.id === newer.id)).toMatchObject({ + status: "completed", + linkStatus: "linked", + linkedEnvironmentId: box.environmentId, + }); + // The older card keeps what it asked and ends; it no longer asks the user. + expect(cards.find((card) => card.id === older.id)).toMatchObject({ + status: "cancelled", + linkStatus: "cancelled", + hint: "Box", + reason: "Delegate to the box.", + requestedAccess: "full-access", + }); + for (const id of [byId.id, byAddress]) { + expect(cards.find((card) => card.id === id)).toMatchObject({ linkStatus: "cancelled" }); + } + expect((yield* a.orchestrator.getThreadShell(threadId))?.pendingRuntimeRequest).toBeNull(); + expect( + ProjectionStore.threadShellFromProjection( + yield* a.orchestrator.getThreadProjection(threadId), + ).pendingRuntimeRequest, + ).toBeNull(); + // The agent hears of the one link it made, and nothing about the card that link ended. + expect((yield* outcomeMessages(a)).messages).toHaveLength(1); + expect(yield* storedText(a)).not.toContain(credential); + }), + ), +); + +it.live("a link leaves open the cards that asked for something else, or asked later", () => + withBoth((a, b) => + Effect.gen(function* () { + yield* seedThread(a); + const other = descriptorOf("environment-other", "Other"); + const left = [ + // Another machine, and a word that names neither. + yield* recordCard(a, "other-hint", { hint: "vps", requestedAccess: "full-access" }), + yield* recordCard(a, "no-hint", { requestedAccess: "full-access" }), + // The same word, but for less access than the link was asked with. + yield* recordCard(a, "less-access", { hint: "Box", requestedAccess: "read-only" }), + // The same word, on a card that named another machine. + yield* recordCard(a, "other-id", { + environmentId: other.environmentId, + hint: "Box", + requestedAccess: "full-access", + }), + yield* recordCard(a, "other-address", { + url: "http://127.0.0.1:9", + hint: "Box", + requestedAccess: "full-access", + }), + // A word the label only contains. + yield* recordCard(a, "partial-hint", { hint: "Bo", requestedAccess: "full-access" }), + ]; + const linked = yield* ask(a, { url: b.url, hint: "Box", requestedAccess: "full-access" }); + const later = yield* recordCard(a, "later", { hint: "Box", requestedAccess: "full-access" }); + yield* linkBox(a, b, linked.id); + + const cards = yield* linkCards(a); + expect(cards.find((card) => card.id === linked.id)).toMatchObject({ linkStatus: "linked" }); + for (const id of [...left, later]) { + expect(cards.find((card) => card.id === id)).toMatchObject({ + status: "waiting", + linkStatus: "pending", + }); + } + }), + ), +); + +it.live("a later card's failed link leaves the older card open", () => + withBoth((a, b) => + Effect.gen(function* () { + yield* seedThread(a); + const older = yield* ask(a, { hint: "Box", requestedAccess: "full-access" }); + const byAddress = yield* recordCard(a, "by-address", { + url: b.url, + requestedAccess: "full-access", + }); + const newer = yield* ask(a, { url: b.url, hint: "Box", requestedAccess: "full-access" }); + yield* a.requests.answer({ + threadId, + turnItemId: newer.id, + answer: { type: "link", urls: [b.url], access: "full-access", pairingCode: "not-a-code" }, + }); + + const cards = yield* linkCards(a); + expect(cards.find((card) => card.id === newer.id)).toMatchObject({ linkStatus: "failed" }); + for (const id of [older.id, byAddress]) { + expect(cards.find((card) => card.id === id)).toMatchObject({ + status: "waiting", + linkStatus: "pending", + }); + } + expect((yield* a.orchestrator.getThreadShell(threadId))?.pendingRuntimeRequest).toMatchObject( + { kind: "user_input" }, + ); + + // The older card still takes its answer, and the failed one stays as it ended. + yield* linkBox(a, b, older.id); + const after = yield* linkCards(a); + expect(after.find((card) => card.id === older.id)).toMatchObject({ linkStatus: "linked" }); + expect(after.find((card) => card.id === newer.id)).toMatchObject({ linkStatus: "failed" }); + }), + ), +); + +const withHeldLinks = ( + hold: (access: PeerLinkCreateInput["access"]) => Effect.Effect | undefined, + body: (a: Laptop, b: ServedPeer) => Effect.Effect, +) => + Effect.scoped( + Effect.gen(function* () { + const b = yield* serveBox; + const a = yield* makeLaptopWith((links) => ({ + ...links, + link: (input) => + (hold(input.access) ?? Effect.void).pipe(Effect.andThen(links.link(input))), + })); + return yield* body(a, b); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)); + +it.live("an older card answered while a newer one links the same machine keeps its own link", () => + Effect.gen(function* () { + const reached = yield* Deferred.make(); + const release = yield* Deferred.make(); + const fullAccessHeld = Deferred.succeed(reached, undefined).pipe( + Effect.andThen(Deferred.await(release)), + ); + yield* withHeldLinks( + (access) => (access === "full-access" ? fullAccessHeld : undefined), + (a, b) => + Effect.gen(function* () { + yield* seedThread(a); + // Both cards suggest the same machine and access. + const older = yield* ask(a, { + url: b.url, + requestedAccess: "full-access", + clientRequestId: "older", + }); + const newer = yield* ask(a, { + url: b.url, + requestedAccess: "full-access", + clientRequestId: "newer", + }); + const olderCode = yield* b.auth.issuePairingCredential(); + const newerCode = yield* b.auth.issuePairingCredential(); + + // The user grants full access on the older card, which stalls while linking. + const olderAnswer = yield* a.requests + .answer({ + threadId, + turnItemId: older.id, + answer: { + type: "link", + urls: [b.url], + access: "full-access", + pairingCode: olderCode.credential, + }, + }) + .pipe(Effect.forkChild); + yield* Deferred.await(reached); + + // Meanwhile the newer card links with read-only access and finishes. + yield* a.requests.answer({ + threadId, + turnItemId: newer.id, + answer: { + type: "link", + urls: [b.url], + access: "read-only", + pairingCode: newerCode.credential, + }, + }); + // The older card is still being answered, so the newer link leaves it open. + expect((yield* linkCards(a)).find((card) => card.id === older.id)).toMatchObject({ + status: "waiting", + linkStatus: "pending", + }); + + yield* Deferred.succeed(release, undefined); + yield* Fiber.join(olderAnswer); + + const cards = yield* linkCards(a); + const stored = Option.getOrThrow(yield* a.links.get(box.environmentId)); + expect(cards.find((card) => card.id === newer.id)).toMatchObject({ + status: "completed", + linkStatus: "linked", + linkedAccess: "read-only", + }); + // The older card says what the link the box now has grants, and the agent hears it. + expect(stored.access).toBe("full-access"); + expect(cards.find((card) => card.id === older.id)).toMatchObject({ + status: "completed", + linkStatus: "linked", + linkedEnvironmentId: stored.environmentId, + linkedAccess: stored.access, + }); + const { messages } = yield* outcomeMessages(a); + expect(messages.map((message) => message.notification?.outcome)).toEqual([ + "completed", + "completed", + ]); + }), + ); + }), +); + +it.live("a card a newer link ended links nothing when answered after", () => + Effect.gen(function* () { + let links = 0; + yield* withHeldLinks( + () => Effect.sync(() => void links++), + (a, b) => + Effect.gen(function* () { + yield* seedThread(a); + const older = yield* ask(a, { + url: b.url, + requestedAccess: "full-access", + clientRequestId: "older", + }); + const newer = yield* ask(a, { + url: b.url, + requestedAccess: "full-access", + clientRequestId: "newer", + }); + yield* linkBox(a, b, newer.id); + expect(links).toBe(1); + + const pairing = yield* b.auth.issuePairingCredential(); + const late = yield* a.requests + .answer({ + threadId, + turnItemId: older.id, + answer: { + type: "link", + urls: [b.url], + access: "read-only", + pairingCode: pairing.credential, + }, + }) + .pipe(Effect.flip); + expect(late.reason).toBe("already_answered"); + expect(links).toBe(1); + expect(Option.getOrThrow(yield* a.links.get(box.environmentId)).access).toBe( + "full-access", + ); + }), + ); + }), +); diff --git a/apps/server/src/peer/PeerLinkRequests.ts b/apps/server/src/peer/PeerLinkRequests.ts index 3d1129a599f84..a33bb47d7ec17 100644 --- a/apps/server/src/peer/PeerLinkRequests.ts +++ b/apps/server/src/peer/PeerLinkRequests.ts @@ -118,6 +118,7 @@ const make = Effect.gen(function* () { outcome: LinkRequestOutcome, commandId: CommandId, wake?: LinkRequestWake, + answeringTurnItemIds?: ReadonlyArray, ) => threadManagement.dispatch({ type: "link_request.record", @@ -133,6 +134,7 @@ const make = Effect.gen(function* () { ...(card.requestedAccess === undefined ? {} : { requestedAccess: card.requestedAccess }), ...outcome, ...(wake === undefined ? {} : { wake }), + ...(answeringTurnItemIds === undefined ? {} : { answeringTurnItemIds }), }); const linkedOutcome = (linked: PeerLink): LinkRequestOutcome => ({ @@ -145,6 +147,11 @@ const make = Effect.gen(function* () { // One answer per card at a time: two answers racing on one card must not // both link. Other cards go on while a slow peer holds one. const answerLocks = yield* KeyedLock.make(); + // Per thread, held only to read a card before answering and to record the + // answer, never while linking. A link ends older cards for the same + // machine, but not one already being answered (its key is in answerLocks); + // an answer that starts after that reads the ended card and links nothing. + const lifecycleLocks = yield* KeyedLock.make(); /** * Closes a card still waiting when its agent retries naming a machine linked @@ -153,22 +160,27 @@ const make = Effect.gen(function* () { const closeAsLinked = (threadId: ThreadId, turnItemId: TurnItemId, linked: PeerLink) => answerLocks.withLock( turnItemId, - Effect.gen(function* () { - const card = yield* readCard(threadId, turnItemId); - if ( - card === undefined || - card.linkStatus !== "pending" || - card.runId === null || - card.nodeId === null - ) { - return; - } - yield* record( - { ...card, runId: card.runId, nodeId: card.nodeId }, - linkedOutcome(linked), - CommandId.make(`link-request:${card.id}:linked-on-retry`), - ); - }), + lifecycleLocks.withLock( + threadId, + Effect.gen(function* () { + const card = yield* readCard(threadId, turnItemId); + if ( + card === undefined || + card.linkStatus !== "pending" || + card.runId === null || + card.nodeId === null + ) { + return; + } + yield* record( + { ...card, runId: card.runId, nodeId: card.nodeId }, + linkedOutcome(linked), + CommandId.make(`link-request:${card.id}:linked-on-retry`), + undefined, + yield* answerLocks.activeKeys, + ); + }), + ), ); const request: PeerLinkRequests["Service"]["request"] = Effect.fn("PeerLinkRequests.request")( @@ -340,17 +352,23 @@ const make = Effect.gen(function* () { "orchestration_v2.thread_id": input.threadId, "link_request.answer": input.answer.type, }); - const card = yield* readCard(input.threadId, input.turnItemId).pipe( - Effect.mapError((cause) => new PeerLinkRequestError({ reason: "load_failed", cause })), + const card = yield* lifecycleLocks.withLock( + input.threadId, + Effect.gen(function* () { + const card = yield* readCard(input.threadId, input.turnItemId).pipe( + Effect.mapError((cause) => new PeerLinkRequestError({ reason: "load_failed", cause })), + ); + if (card === undefined || card.runId === null || card.nodeId === null) { + return yield* new PeerLinkRequestError({ reason: "not_found" }); + } + // Any time while the card is open: the agent hears the answer as a + // notification, whether or not its run is still going. + if (card.linkStatus !== "pending") { + return yield* new PeerLinkRequestError({ reason: "already_answered" }); + } + return { ...card, runId: card.runId, nodeId: card.nodeId }; + }), ); - if (card === undefined || card.runId === null || card.nodeId === null) { - return yield* new PeerLinkRequestError({ reason: "not_found" }); - } - // Any time while the card is open: the agent hears the answer as a - // notification, whether or not its run is still going. - if (card.linkStatus !== "pending") { - return yield* new PeerLinkRequestError({ reason: "already_answered" }); - } // An id beside an address is what answered there, recorded by an older // server. Linking that machine could send its pairing code to the address. const outcome: LinkRequestOutcome = @@ -365,16 +383,22 @@ const make = Effect.gen(function* () { : yield* answerOutcome(input.answer); // If Stop closed the card while linking, the card keeps that; the link // itself stands and Settings lists it. - yield* record( - { ...card, runId: card.runId, nodeId: card.nodeId }, - outcome, - CommandId.make(`link-request:${card.id}:${outcome.linkStatus}`), - { - messageId: MessageId.make(`message:link-request:${card.id}`), - ...outcomeNotice(card, outcome), - }, - ).pipe( - Effect.mapError((cause) => new PeerLinkRequestError({ reason: "record_failed", cause })), + yield* lifecycleLocks.withLock( + input.threadId, + Effect.flatMap(answerLocks.activeKeys, (answering) => + record( + card, + outcome, + CommandId.make(`link-request:${card.id}:${outcome.linkStatus}`), + { + messageId: MessageId.make(`message:link-request:${card.id}`), + ...outcomeNotice(card, outcome), + }, + answering, + ), + ).pipe( + Effect.mapError((cause) => new PeerLinkRequestError({ reason: "record_failed", cause })), + ), ); }, (effect, input) => diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index 053fc644fe1c0..e4a57262d12dc 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -3762,6 +3762,11 @@ const OrchestrationV2InternalCommand = Schema.Union([ linkedLabel: Schema.optional(Schema.String), linkedAccess: Schema.optional(AuthMcpClientAccess), failure: Schema.optional(Schema.String), + /** + * Cards still being answered when this one linked. Their own answers end + * them, so the link leaves them open; never stored on a card. + */ + answeringTurnItemIds: Schema.optional(Schema.Array(TurnItemId)), /** Tells the agent how the card ended, in the same commit that closes it. */ wake: Schema.optional( Schema.Struct({ From 071ae4e9514c46b2607d40053b31007077fcd900 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:56:22 +0100 Subject: [PATCH 054/192] fix(server): deleted projects keep their worktree cleanup policy (#36649) Co-authored-by: Julius Marminge --- .../project/ProjectService.deletion.test.ts | 48 ++++++++++++++++--- apps/server/src/project/ProjectService.ts | 10 +++- .../provider/ProjectSettingsOverrideSweep.ts | 39 +++++++++------ 3 files changed, 75 insertions(+), 22 deletions(-) diff --git a/apps/server/src/project/ProjectService.deletion.test.ts b/apps/server/src/project/ProjectService.deletion.test.ts index 28b1c40541ddf..25e5ee54a2737 100644 --- a/apps/server/src/project/ProjectService.deletion.test.ts +++ b/apps/server/src/project/ProjectService.deletion.test.ts @@ -28,6 +28,7 @@ import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts"; import { planThreadDeletion } from "../orchestration-v2/ThreadDeletion.ts"; import * as SqlitePersistence from "../persistence/Sqlite.ts"; +import * as ProjectSettingsOverrideSweep from "../provider/ProjectSettingsOverrideSweep.ts"; import * as ServerSettings from "../serverSettings.ts"; import * as WorkspacePaths from "../workspace/WorkspacePaths.ts"; import * as ProjectEnrichmentService from "./ProjectEnrichmentService.ts"; @@ -501,9 +502,13 @@ it.effect("deletes a project without force once its imported threads were delete }).pipe(Effect.provide(layerDatabase)), ); -it.effect( - "clears a deleted project's settings overrides so a machine-disabled instance cannot stay alive", - () => +it.effect.each([ + { policy: "no", worktreeCleanup: undefined }, + { policy: "its off", worktreeCleanup: { mode: "off" } as const }, + { policy: "its legacy", worktreeCleanup: null }, +])( + "clears a deleted project's settings overrides, keeping $policy worktree cleanup policy, so a machine-disabled instance cannot stay alive", + ({ worktreeCleanup }) => Effect.gen(function* () { const projectId = ProjectId.make("project:overrides-cleanup"); const instanceId = ProviderInstanceId.make("codex_work"); @@ -514,7 +519,11 @@ it.effect( const serverSettings = yield* ServerSettings.ServerSettingsService; yield* serverSettings.updateSettings({ projectSettingsOverrides: { - [projectId]: { providerInstances: { [instanceId]: { enabled: true } } }, + [projectId]: { + providerInstances: { [instanceId]: { enabled: true } }, + defaultAutoPull: true, + ...(worktreeCleanup === undefined ? {} : { worktreeCleanup }), + }, }, }); @@ -523,8 +532,35 @@ it.effect( projectId, }); - const settingsAfterDelete = yield* serverSettings.getSettings; - assert.isUndefined(settingsAfterDelete.projectSettingsOverrides[projectId]); + const entry = (yield* serverSettings.getSettings).projectSettingsOverrides[projectId]; + if (worktreeCleanup === undefined) { + assert.isUndefined(entry); + } else { + assert.deepStrictEqual(entry, { worktreeCleanup, providerInstances: {} }); + } + + const liveProjectId = ProjectId.make("project:still-live"); + const missingProjectId = ProjectId.make("project:missing"); + yield* seedProject(liveProjectId); + // Older deletions or interrupted best-effort cleanup can leave the original overrides on restart. + yield* serverSettings.updateSettings({ + projectSettingsOverrides: { + [projectId]: { + providerInstances: { [instanceId]: { enabled: true } }, + defaultAutoPull: true, + ...(worktreeCleanup === undefined ? {} : { worktreeCleanup }), + }, + [liveProjectId]: { defaultAutoPull: true }, + [missingProjectId]: { worktreeCleanup: { mode: "off" } }, + }, + }); + yield* ProjectSettingsOverrideSweep.sweep; + assert.deepStrictEqual((yield* serverSettings.getSettings).projectSettingsOverrides, { + [liveProjectId]: { defaultAutoPull: true }, + ...(worktreeCleanup === undefined + ? {} + : { [projectId]: { worktreeCleanup, providerInstances: {} } }), + }); }).pipe(Effect.provide(layerServices)); }).pipe(Effect.provide(layerDatabase)), ); diff --git a/apps/server/src/project/ProjectService.ts b/apps/server/src/project/ProjectService.ts index d6bc3e8eedf4d..4bdc8386438ca 100644 --- a/apps/server/src/project/ProjectService.ts +++ b/apps/server/src/project/ProjectService.ts @@ -33,6 +33,7 @@ import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts"; import { planThreadDeletion } from "../orchestration-v2/ThreadDeletion.ts"; import * as ServerSettings from "../serverSettings.ts"; +import { deletedProjectOverridePatch } from "../provider/ProjectSettingsOverrideSweep.ts"; import { makeIsManagedWorktreePath } from "../worktreesDirectory.ts"; import * as ProcessRunner from "../processRunner.ts"; import * as ProjectEnrichmentService from "./ProjectEnrichmentService.ts"; @@ -550,7 +551,14 @@ export const make = Effect.gen(function* () { // Best-effort cleanup releases disabled instances kept alive by overrides; crashes have no replay to revisit deletion. const clearProjectSettingsOverrides = (projectId: ProjectId) => - serverSettings.updateSettings({ projectSettingsOverrides: { [projectId]: null } }).pipe( + serverSettings.getSettings.pipe( + Effect.flatMap(({ projectSettingsOverrides }) => + serverSettings.updateSettings({ + projectSettingsOverrides: { + [projectId]: deletedProjectOverridePatch(projectSettingsOverrides[projectId]), + }, + }), + ), Effect.asVoid, Effect.catchCause((cause) => Effect.logWarning( diff --git a/apps/server/src/provider/ProjectSettingsOverrideSweep.ts b/apps/server/src/provider/ProjectSettingsOverrideSweep.ts index aa7d205271494..06f61f9ad8bfe 100644 --- a/apps/server/src/provider/ProjectSettingsOverrideSweep.ts +++ b/apps/server/src/provider/ProjectSettingsOverrideSweep.ts @@ -1,32 +1,41 @@ -import { type ProjectId, type ServerSettings } from "@t3tools/contracts"; +import * as NodeUtil from "node:util"; + +import { type ProjectSettingsOverrides } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; import * as Settings from "../serverSettings.ts"; -const findOrphanedProjectIds = ( - settings: Pick, - liveProjectIds: ReadonlySet, -): ReadonlyArray => - Object.keys(settings.projectSettingsOverrides) - .map((projectId) => projectId as ProjectId) - .filter((projectId) => !liveProjectIds.has(projectId)); +export function deletedProjectOverridePatch(entry: ProjectSettingsOverrides | undefined) { + // Keep cleanup policy for deleted threads; omitting providerInstances would preserve obsolete references in the merge. + return entry?.worktreeCleanup !== undefined + ? { worktreeCleanup: entry.worktreeCleanup, providerInstances: {} } + : null; +} export const sweep = Effect.gen(function* () { const serverSettings = yield* Settings.ServerSettingsService; const projectStore = yield* ProjectStore.ProjectStoreV2; const settings = yield* serverSettings.getSettings; - const liveShells = yield* projectStore.listShells(); - const liveProjectIds = new Set(liveShells.map((shell) => shell.id)); + const projects = yield* projectStore.list({ includeDeleted: true }); + const projectsById = new Map( + projects.map((project) => [project.projectId, project]), + ); - const orphanedProjectIds = findOrphanedProjectIds(settings, liveProjectIds); - if (orphanedProjectIds.length === 0) return; + const overrides = Object.entries(settings.projectSettingsOverrides).flatMap( + ([projectId, entry]) => { + const project = projectsById.get(projectId); + if (project?.deletedAt === null) return []; + const patch = project ? deletedProjectOverridePatch(entry) : null; + if (NodeUtil.isDeepStrictEqual(entry, patch)) return []; + return [[projectId, patch] as const]; + }, + ); + if (overrides.length === 0) return; yield* serverSettings.updateSettings({ - projectSettingsOverrides: Object.fromEntries( - orphanedProjectIds.map((projectId) => [projectId, null]), - ), + projectSettingsOverrides: Object.fromEntries(overrides), }); }).pipe( Effect.asVoid, From 179c032b3f1577049736d9df591b4fd8f62b1902 Mon Sep 17 00:00:00 2001 From: Grey <120752906+greyxp1@users.noreply.github.com> Date: Sat, 14 Nov 2026 05:58:01 -0500 Subject: [PATCH 055/192] fix(server): include managed Claude skills in the skill menu (#26732) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/provider/Drivers/ClaudeSkills.test.ts | 69 ++++++++++++++++++- .../src/provider/Drivers/ClaudeSkills.ts | 31 ++++++--- 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/apps/server/src/provider/Drivers/ClaudeSkills.test.ts b/apps/server/src/provider/Drivers/ClaudeSkills.test.ts index 8b9661a779ae1..947d5f1a67eac 100644 --- a/apps/server/src/provider/Drivers/ClaudeSkills.test.ts +++ b/apps/server/src/provider/Drivers/ClaudeSkills.test.ts @@ -3,10 +3,19 @@ import { assert, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; +import * as HostProcess from "@t3tools/shared/HostProcess"; -import { discoverClaudeSkills } from "./ClaudeSkills.ts"; +import { discoverClaudeSkills as discoverSkills } from "./ClaudeSkills.ts"; import { planClaudeSkillDispatch } from "./ClaudeSkillDispatch.ts"; +// Windows makes the managed directory fixture-controlled without reading host policy or skills. +const discoverClaudeSkills = ( + ...[config, cwd, environment = {}]: Parameters +) => + discoverSkills(config, cwd, environment).pipe( + Effect.provideService(HostProcess.Platform, "win32"), + ); + const writeSkill = Effect.fn(function* ( skillsDir: string, directoryName: string, @@ -688,7 +697,7 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => { }), ); - it.effect("prefers user skills on name collisions even with a stray .agents copy", () => + it.effect("prefers managed then user skills on collisions, even with a stray .agents copy", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; @@ -723,6 +732,62 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => { description: "User deploy.", }, ]); + + const programFiles = path.join(tempDir, "Program Files"); + const programData = path.join(tempDir, "ProgramData"); + yield* writeSkill( + path.join(programData, "ClaudeCode", ".claude", "skills"), + "legacy-only", + "# Legacy skills must not be discovered", + ); + yield* fs.writeFileString( + path.join(programData, "ClaudeCode", "managed-settings.json"), + '{"skillOverrides":{"deploy":"off"}}', + ); + for (const ProgramFiles of [undefined, "", programFiles]) { + const legacySkills = yield* discoverClaudeSkills({ homePath: configDir }, workspace, { + PROGRAMDATA: programData, + ...(ProgramFiles !== undefined ? { ProgramFiles } : {}), + }); + assert.deepEqual( + legacySkills, + skills.map((skill) => ({ ...skill, enabled: false })), + ); + } + yield* writeSkill( + path.join(programFiles, "ClaudeCode", ".claude", "skills"), + "deploy", + ["---", "name: deploy", "description: Managed deploy.", "---"].join("\n"), + ); + yield* fs.writeFileString( + path.join(configDir, "settings.json"), + '{"skillOverrides":{"deploy":"off"}}', + ); + yield* fs.writeFileString( + path.join(programFiles, "ClaudeCode", "managed-settings.json"), + '{"skillOverrides":{"deploy":"on"}}', + ); + const managedSkills = yield* discoverClaudeSkills({ homePath: configDir }, workspace, { + ProgramFiles: programFiles, + PROGRAMDATA: programData, + }); + + assert.deepEqual(managedSkills, [ + { + name: "deploy", + path: path.join(programFiles, "ClaudeCode", ".claude", "skills", "deploy", "SKILL.md"), + enabled: true, + scope: "user", + description: "Managed deploy.", + }, + ]); + assert.deepEqual( + yield* discoverClaudeSkills({ homePath: configDir }, workspace, { + PROGRAMFILES: ` ${programFiles} `, + PROGRAMDATA: programData, + }), + managedSkills, + ); }), ); diff --git a/apps/server/src/provider/Drivers/ClaudeSkills.ts b/apps/server/src/provider/Drivers/ClaudeSkills.ts index c24c4afca9e7d..a5df2671540f8 100644 --- a/apps/server/src/provider/Drivers/ClaudeSkills.ts +++ b/apps/server/src/provider/Drivers/ClaudeSkills.ts @@ -106,19 +106,27 @@ function parseSkillFrontmatter(contents: string): SkillFrontmatter { }; } -function claudeManagedSettingsPath( +// A caller-supplied environment is a plain object, without Windows' case-insensitive keys. +function windowsEnvironmentValue(environment: NodeJS.ProcessEnv, name: string) { + const key = name.toLowerCase(); + return Object.entries(environment) + .find(([candidate]) => candidate.toLowerCase() === key)?.[1] + ?.trim(); +} + +function claudeManagedDirectory( path: Path.Path, platform: NodeJS.Platform, environment: NodeJS.ProcessEnv, ): string | undefined { if (platform === "darwin") { - return "/Library/Application Support/ClaudeCode/managed-settings.json"; + return "/Library/Application Support/ClaudeCode"; } if (platform === "win32") { - const programData = environment.PROGRAMDATA?.trim(); - return programData ? path.join(programData, "ClaudeCode", "managed-settings.json") : undefined; + const programFiles = windowsEnvironmentValue(environment, "ProgramFiles"); + return programFiles ? path.join(programFiles, "ClaudeCode") : undefined; } - return "/etc/claude-code/managed-settings.json"; + return "/etc/claude-code"; } /** Claude Code settings files, lowest precedence first. */ @@ -130,7 +138,9 @@ function claudeSettingsPaths( environment: NodeJS.ProcessEnv, repositoryRoot?: string, ): ReadonlyArray { - const managedPath = claudeManagedSettingsPath(path, platform, environment); + const managedDirectory = claudeManagedDirectory(path, platform, environment); + const programData = + platform === "win32" ? windowsEnvironmentValue(environment, "ProgramData") : undefined; const root = repositoryRoot !== undefined && repositoryRoot !== cwd ? repositoryRoot : undefined; return [ path.join(configDirPath, "settings.json"), @@ -141,7 +151,8 @@ function claudeSettingsPaths( ] : []), ...(root ? [path.join(root, ".claude", "settings.local.json")] : []), - ...(managedPath ? [managedPath] : []), + ...(programData ? [path.join(programData, "ClaudeCode", "managed-settings.json")] : []), + ...(managedDirectory ? [path.join(managedDirectory, "managed-settings.json")] : []), ]; } @@ -627,7 +638,7 @@ export function claudeLaunchArgDirectories(launchArgs?: string): ReadonlyArray & Partial>, cwd?: string, @@ -642,6 +653,7 @@ export const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* cwd, env, ); + const managedDirectory = claudeManagedDirectory(path, yield* HostProcess.Platform, env); // Most installs have no synced folder; skip reading `.claude.json` for them. const hasSyncedPlugins = !syncedPluginsHidden && @@ -653,6 +665,9 @@ export const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* : undefined; const roots: ReadonlyArray = [ + ...(managedDirectory + ? [{ directory: path.join(managedDirectory, ".claude", "skills"), scope: "user" as const }] + : []), { directory: path.join(configDirPath, "skills"), scope: "user" }, ...(cwd ? [{ directory: path.join(cwd, ".claude", "skills"), scope: "project" as const }] : []), ...claudeLaunchArgDirectories(config.launchArgs) From 3d3275b067c3ee7d4676e470fe26d14e7d978751 Mon Sep 17 00:00:00 2001 From: Alex Martani Date: Sat, 14 Nov 2026 03:07:59 -0800 Subject: [PATCH 056/192] fix(server): PR checkout respects restricted fetch mappings (#27853) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 106 +++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 50 +++++++-- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index ab0483124433e..b748d4f364c84 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -6385,6 +6385,112 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }); describe("remote operations", () => { + it.effect.each([ + { + configuration: "ordinary", + fetchRefspecs: ["+refs/heads/*:refs/remotes/origin/*"], + addsMapping: false, + }, + { + configuration: "restricted", + fetchRefspecs: [ + "+refs/heads/main:refs/remotes/origin/main", + "+refs/heads/team/*:refs/remotes/origin/team/*", + "+refs/heads/release/release-*:refs/remotes/origin/release/release-*", + ], + addsMapping: true, + }, + ])( + "sets a fetched branch's upstream with $configuration fetch configuration", + ({ fetchRefspecs, addsMapping }) => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const remote = yield* makeTmpDir("git-remote-"); + yield* git(remote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "origin", remote]); + yield* git(cwd, ["config", "--unset-all", "remote.origin.fetch"]); + for (const refspec of fetchRefspecs) { + yield* git(cwd, ["config", "--add", "remote.origin.fetch", refspec]); + } + const remoteBranch = "feature/example-change"; + const branch = "review/example-change"; + const mapping = `+refs/heads/${remoteBranch}*:refs/remotes/origin/${remoteBranch}*`; + yield* git(cwd, [ + "push", + remote, + "HEAD:refs/heads/main", + `HEAD:refs/heads/${remoteBranch}`, + ]); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* driver.fetchRemoteTrackingBranch({ cwd, remoteName: "origin", remoteBranch }); + yield* git(cwd, ["checkout", "--no-track", "-b", branch, `origin/${remoteBranch}`]); + + const input = { cwd, branch, remoteName: "origin", remoteBranch }; + yield* driver.setBranchUpstream(input); + yield* driver.setBranchUpstream(input); + + assert.equal( + yield* git(cwd, ["rev-parse", "--abbrev-ref", `${branch}@{upstream}`]), + `origin/${remoteBranch}`, + ); + assert.equal(yield* git(cwd, ["config", `branch.${branch}.remote`]), "origin"); + assert.equal( + yield* git(cwd, ["config", `branch.${branch}.merge`]), + `refs/heads/${remoteBranch}`, + ); + assert.deepEqual( + (yield* git(cwd, ["config", "--get-all", "remote.origin.fetch"])).split("\n"), + addsMapping ? [...fetchRefspecs, mapping] : fetchRefspecs, + ); + // A merged PR's head is deleted; later fetches of the remote must keep working. + yield* git(remote, ["--git-dir", remote, "branch", "-D", remoteBranch]); + yield* git(cwd, ["fetch", "origin"]); + }), + ); + + it.effect.each(["ordinary", "restricted"] as const)( + "retries upstream configuration with a config lock and %s fetch mappings", + (configuration) => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const { initialBranch } = yield* initRepoWithCommit(cwd); + yield* git(cwd, ["remote", "add", "origin", "https://example.invalid/repo.git"]); + if (configuration === "restricted") { + yield* git(cwd, [ + "config", + "remote.origin.fetch", + `+refs/heads/${initialBranch}:refs/remotes/origin/${initialBranch}`, + ]); + } + yield* git(cwd, ["branch", "review/pr-head"]); + yield* git(cwd, ["update-ref", "refs/remotes/origin/pr-head", "HEAD"]); + const lockPath = (yield* Path.Path).join(cwd, ".git", "config.lock"); + yield* (yield* FileSystem.FileSystem).writeFileString(lockPath, ""); + const { driver, failures } = yield* makeDriverReleasingLock( + configuration === "restricted" + ? (args) => args[0] === "config" && args[1] === "--add" + : (args) => args.includes("--set-upstream-to"), + lockPath, + ); + + yield* TestClock.withLive( + driver.setBranchUpstream({ + cwd, + branch: "review/pr-head", + remoteName: "origin", + remoteBranch: "pr-head", + }), + ); + + assert.equal(yield* Ref.get(failures), 1); + assert.equal( + yield* git(cwd, ["rev-parse", "--abbrev-ref", "review/pr-head@{upstream}"]), + "origin/pr-head", + ); + }), + ); + it.effect("ensureRemote reuses a partial clone remote and avoids its name", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 1dc33f9a2b141..212ccb3a0b449 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -5201,19 +5201,51 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ]); }); - const setBranchUpstream: GitVcsDriver.GitVcsDriver["Service"]["setBranchUpstream"] = (input) => - executeGitWithStableDiagnostics( - "GitVcsDriver.setBranchUpstream", - input.cwd, - [ + const setBranchUpstream: GitVcsDriver.GitVcsDriver["Service"]["setBranchUpstream"] = Effect.fn( + "setBranchUpstream", + )( + function* (input) { + const args = [ "branch", "--set-upstream-to", - `${input.remoteName}/${input.remoteBranch}`, + `refs/remotes/${input.remoteName}/${input.remoteBranch}`, "--", input.branch, - ], - { writesSharedConfig: true }, - ).pipe(retryWhileConfigLocked, Effect.asVoid); + ]; + const result = yield* executeGitWithStableDiagnostics( + "GitVcsDriver.setBranchUpstream", + input.cwd, + args, + { allowNonZeroExit: true }, + ); + if (result.exitCode === 0) return; + // Git requires a configured fetch mapping even when the tracking ref already exists. The + // mapping is a glob because an exact source fails every later fetch once the branch is deleted. + if ( + /^fatal: cannot set up tracking information; starting point '.+' is not a branch$/m.test( + result.stderr, + ) + ) { + yield* executeGitWithStableDiagnostics( + "GitVcsDriver.setBranchUpstream.addFetchMapping", + input.cwd, + [ + "config", + "--add", + `remote.${input.remoteName}.fetch`, + `+refs/heads/${input.remoteBranch}*:refs/remotes/${input.remoteName}/${input.remoteBranch}*`, + ], + ).pipe(retryWhileConfigLocked); + } + yield* executeGitWithStableDiagnostics( + "GitVcsDriver.setBranchUpstream", + input.cwd, + args, + ).pipe(retryWhileConfigLocked); + }, + // Keep mapping detection and both writes under the same lock across sibling worktrees. + (effect, input) => withSharedConfigLock(input.cwd, effect), + ); /** * Unlinks links under `target` without following them and removes the From 121f0cf014dcd6e94327588bf3f1f200a5f07ad7 Mon Sep 17 00:00:00 2001 From: MarcoDotIO Date: Sat, 14 Nov 2026 06:09:38 -0500 Subject: [PATCH 057/192] fix(server): t3_thread_list clamps an oversized limit instead of failing (#27814) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/mcp/McpHttpServer.test.ts | 15 ++++++++++ .../src/provider/agentContextBudget.json | 2 +- packages/contracts/src/orchestratorMcp.ts | 30 +++++++++---------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/apps/server/src/mcp/McpHttpServer.test.ts b/apps/server/src/mcp/McpHttpServer.test.ts index bd32b3b81c82b..3802a5c2e4023 100644 --- a/apps/server/src/mcp/McpHttpServer.test.ts +++ b/apps/server/src/mcp/McpHttpServer.test.ts @@ -276,6 +276,7 @@ it.effect.each([ const layerRecordingTools = (calls: Array) => { const RecordingToolkit = Toolkit.make( OrchestratorToolkit.tools.delegate_task, + OrchestratorToolkit.tools.t3_thread_list, OrchestratorToolkit.tools.t3_thread_read, OrchestratorToolkit.tools.t3_thread_wait, ProjectToolkit.tools.t3_project_update, @@ -292,6 +293,7 @@ const layerRecordingTools = (calls: Array) => { RecordingToolkit, McpToolAccess.toLayer(RecordingToolkit, { delegate_task: record, + t3_thread_list: record, t3_thread_read: record, t3_thread_wait: record, t3_project_update: record, @@ -727,6 +729,19 @@ it.effect.each(["limit", "runLimit", "maxCharsPerItem"])( }, ); +it.effect("clamps a t3_thread_list limit above its maximum instead of rejecting the call", () => { + const calls: Array = []; + return Effect.gen(function* () { + yield* callTool("t3_thread_list", { allProjects: true, limit: 200 }); + yield* callTool("t3_thread_list", { allProjects: true, limit: 7 }); + + expect(calls).toEqual([ + { allProjects: true, limit: 100 }, + { allProjects: true, limit: 7 }, + ]); + }).pipe(Effect.provide(layerRecordingTools(calls))); +}); + it("keeps cross-tool routing out of the delegate_task description", () => { const description = OrchestratorToolkit.tools.delegate_task.description ?? ""; expect(description).toContain("Start one child agent with only `task`"); diff --git a/apps/server/src/provider/agentContextBudget.json b/apps/server/src/provider/agentContextBudget.json index 0a354c1e7d560..43b48e60b6314 100644 --- a/apps/server/src/provider/agentContextBudget.json +++ b/apps/server/src/provider/agentContextBudget.json @@ -1,5 +1,5 @@ { "claudeSystemAppendBytes": 5930, "codexAdditionalContextBytes": 7324, - "mcpToolListJsonBytes": 49806 + "mcpToolListJsonBytes": 49792 } diff --git a/packages/contracts/src/orchestratorMcp.ts b/packages/contracts/src/orchestratorMcp.ts index 914bb30e535c9..816a202711c71 100644 --- a/packages/contracts/src/orchestratorMcp.ts +++ b/packages/contracts/src/orchestratorMcp.ts @@ -339,6 +339,20 @@ export const OrchestratorMcpEnvironmentTarget = Schema.optional( }), ); +// Codex hides `minimum` and `maximum` from the model, so a value above the maximum clamps on +// decode instead of failing the call; pagination returns the rest. Values below 1 still fail: +// 0 asks for nothing, and answering it with one item would be a different request. +const clampedPositiveInt = (maximum: number) => + PositiveInt.pipe( + Schema.decodeTo( + PositiveInt.check(Schema.isLessThanOrEqualTo(maximum)), + SchemaTransformation.transform({ + decode: (value) => Math.min(value, maximum), + encode: (value) => value, + }), + ), + ); + export const OrchestratorMcpThreadListInput = Schema.Struct({ environmentId: OrchestratorMcpEnvironmentTarget, projectId: OrchestratorMcpProjectTarget, @@ -351,7 +365,7 @@ export const OrchestratorMcpThreadListInput = Schema.Struct({ snoozed: Schema.optional(Schema.Boolean), includeSubagents: Schema.optional(Schema.Boolean), cursor: Schema.optional(NonNegativeInt), - limit: Schema.optional(PositiveInt.check(Schema.isLessThanOrEqualTo(100))), + limit: Schema.optional(clampedPositiveInt(100)), }); export type OrchestratorMcpThreadListInput = typeof OrchestratorMcpThreadListInput.Type; @@ -421,20 +435,6 @@ export const OrchestratorMcpThreadListResult = Schema.Struct({ }); export type OrchestratorMcpThreadListResult = typeof OrchestratorMcpThreadListResult.Type; -// Codex hides `minimum` and `maximum` from the model, so a value above the maximum clamps on -// decode instead of failing the call; pagination returns the rest. Values below 1 still fail: -// 0 asks for nothing, and answering it with one item would be a different request. -const clampedPositiveInt = (maximum: number) => - PositiveInt.pipe( - Schema.decodeTo( - PositiveInt.check(Schema.isLessThanOrEqualTo(maximum)), - SchemaTransformation.transform({ - decode: (value) => Math.min(value, maximum), - encode: (value) => value, - }), - ), - ); - export const OrchestratorMcpThreadReadInput = Schema.Struct({ environmentId: OrchestratorMcpEnvironmentTarget, threadId: ThreadId, From 0c306f45203eb14b71902de9ccfbbfa2afe716af Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 03:30:25 -0800 Subject: [PATCH 058/192] fix(ssh): wait for a boot service's server instead of starting a second one (#36672) --- apps/server/src/cli/sshHelper.ts | 16 ++++- apps/server/src/cloud/selfUpdate.test.ts | 13 +++- apps/server/src/cloud/selfUpdate.ts | 9 +++ apps/server/src/cloud/serviceProtocol.ts | 3 + .../src/desktopUpdate/DesktopUpdateMarker.ts | 18 +++++ apps/server/src/serviceLauncher.test.ts | 4 ++ apps/server/src/serviceLauncher.ts | 31 ++++++++- packages/ssh/src/launcherTestHarness.ts | 20 +++++- packages/ssh/src/tunnel.test.ts | 27 ++++++++ packages/ssh/src/tunnel.ts | 69 +++++++++++++++++-- 10 files changed, 201 insertions(+), 9 deletions(-) diff --git a/apps/server/src/cli/sshHelper.ts b/apps/server/src/cli/sshHelper.ts index 09a729ef44891..e98e5e0537af1 100644 --- a/apps/server/src/cli/sshHelper.ts +++ b/apps/server/src/cli/sshHelper.ts @@ -10,6 +10,7 @@ import * as Option from "effect/Option"; import { Argument, Command } from "effect/cli"; import * as DesktopUpdateMarker from "../desktopUpdate/DesktopUpdateMarker.ts"; +import { serviceLauncherIsServing, stateDirOwned } from "../serviceLauncher.ts"; const canBind = (port: number) => new Promise((resolve) => { @@ -142,7 +143,20 @@ const updateRemaining = Command.make("update-remaining", { Command.provide(DesktopUpdateMarker.layer), ); +/** Exits 0 while a server holds this home or a boot service is about to start one. */ +const homeOwned = Command.make("home-owned", { + baseDir: Argument.String("base-dir"), +}).pipe( + Command.withHandler(({ baseDir }) => + Effect.promise(async () => { + if (!((await serviceLauncherIsServing(baseDir)) || (await stateDirOwned(baseDir)))) { + process.exitCode = 1; + } + }), + ), +); + export const sshHelperCommand = Command.make("__ssh-helper").pipe( Command.unlisted, - Command.withSubcommands([pickPort, waitReady, runtimePort, updateRemaining]), + Command.withSubcommands([pickPort, waitReady, runtimePort, updateRemaining, homeOwned]), ); diff --git a/apps/server/src/cloud/selfUpdate.test.ts b/apps/server/src/cloud/selfUpdate.test.ts index 8bd1d8168680b..73a7fea4aea72 100644 --- a/apps/server/src/cloud/selfUpdate.test.ts +++ b/apps/server/src/cloud/selfUpdate.test.ts @@ -223,9 +223,20 @@ it.layer(NodeServices.layer)("server self update", (it) => { // regression: #34502 a remote update never replaces the launcher, so it can be older than the server it.effect("names its launcher's protocol to the staged preflight, not its own", () => Effect.gen(function* () { - const { selfUpdate, preflightProtocols } = yield* makeHarness({ launcherProtocol: 3 }); + const { selfUpdate, preflightProtocols, baseDir } = yield* makeHarness({ + launcherProtocol: 3, + }); + yield* TestClock.setTime(5_000); expect((yield* selfUpdate.update({ targetVersion: "1.1.0" })).updateId).toBe("launcher-id"); expect(preflightProtocols).toEqual(["3"]); + // That launcher holds no lock between its servers; SSH launches wait on this marker instead. + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + expect( + JSON.parse( + yield* fs.readFileString(path.join(baseDir, "runtime", "desktop-update-in-progress")), + ), + ).toEqual({ startedAt: 5_000, expiresAt: 65_000 }); }), ); diff --git a/apps/server/src/cloud/selfUpdate.ts b/apps/server/src/cloud/selfUpdate.ts index 5ae8e76a1058c..2c6d04f8259fa 100644 --- a/apps/server/src/cloud/selfUpdate.ts +++ b/apps/server/src/cloud/selfUpdate.ts @@ -1,4 +1,5 @@ import { + DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE, ServerSelfUpdateError, type ServerSelfUpdateCapability, type ServerSelfUpdateInput, @@ -29,6 +30,7 @@ import { import packageJson from "../../package.json" with { type: "json" }; import * as ServerConfig from "../config.ts"; import * as DesktopAppUpdate from "../desktopUpdate/DesktopAppUpdate.ts"; +import * as DesktopUpdateMarker from "../desktopUpdate/DesktopUpdateMarker.ts"; import { databaseMigrationHead } from "../persistence/databaseMigrationHead.ts"; import * as ManagedPolicy from "../policy/ManagedPolicy.ts"; import { managedUpdateMirror, managedUpdateRefusal } from "../policy/managedUpdates.ts"; @@ -338,6 +340,13 @@ export const make = Effect.fn("cloud.server_self_update.make")(function* () { error, ), ), + // The launcher stops this server next; one from before its serving lock holds nothing + // until the new server starts, so this marker is what an SSH launch waits on. + Effect.tap(() => + DesktopUpdateMarker.publish( + path.join(serverConfig.baseDir, "runtime", DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE), + ).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.ignore({ log: true })), + ), Effect.tap(() => onHandoffAccepted()), ), ); diff --git a/apps/server/src/cloud/serviceProtocol.ts b/apps/server/src/cloud/serviceProtocol.ts index c0a43bfe23e16..3800d58bdb1a8 100644 --- a/apps/server/src/cloud/serviceProtocol.ts +++ b/apps/server/src/cloud/serviceProtocol.ts @@ -23,6 +23,9 @@ export const SERVICE_STOP_MARKER_FILE = ".service-stopping"; export const SERVICE_RESTART_PENDING_FILE = ".restart-pending"; /** Held by a Windows launcher while it runs: Task Scheduler tracks only the task's console host. */ export const SERVICE_LAUNCHER_LOCK_FILE = "service-launcher.lock"; +/** Held by a launcher while it has, or is about to start, a server: an SSH launch waits for that + server instead of starting its own. Released while the launcher idles. */ +export const SERVICE_SERVING_LOCK_FILE = "service-serving.lock"; const unquoteSystemdValue = (raw: string) => (raw.startsWith('"') && raw.endsWith('"') diff --git a/apps/server/src/desktopUpdate/DesktopUpdateMarker.ts b/apps/server/src/desktopUpdate/DesktopUpdateMarker.ts index 1f175bbe7db14..9befa63b4becb 100644 --- a/apps/server/src/desktopUpdate/DesktopUpdateMarker.ts +++ b/apps/server/src/desktopUpdate/DesktopUpdateMarker.ts @@ -10,6 +10,24 @@ import * as Schema from "effect/Schema"; const UpdateMarker = Schema.fromJsonString( Schema.Struct({ startedAt: Schema.Number, expiresAt: Schema.Number }), ); +const encodeUpdateMarker = Schema.encodeEffect(UpdateMarker); + +/** + * Makes SSH launches wait for the server that replaces this one instead of starting their own. + * The server writes it, so it also covers a boot service whose launcher predates its own lock. + */ +export const publish = Effect.fn("DesktopUpdateMarker.publish")(function* (markerFile: string) { + const fileSystem = yield* FileSystem.FileSystem; + const startedAt = yield* Clock.currentTimeMillis; + const contents = yield* encodeUpdateMarker({ + startedAt, + expiresAt: startedAt + DESKTOP_UPDATE_IN_PROGRESS_TTL_MS, + }); + // Renamed into place: a launch must never read a half-written marker. + const temporary = `${markerFile}.tmp`; + yield* fileSystem.writeFileString(temporary, contents); + yield* fileSystem.rename(temporary, markerFile); +}); export class DesktopUpdateMarker extends Context.Service< DesktopUpdateMarker, diff --git a/apps/server/src/serviceLauncher.test.ts b/apps/server/src/serviceLauncher.test.ts index 1e504154398f4..4bfe776a3aba8 100644 --- a/apps/server/src/serviceLauncher.test.ts +++ b/apps/server/src/serviceLauncher.test.ts @@ -29,6 +29,7 @@ import { readServiceState, registerServiceTask, serviceLauncherIsRunning, + serviceLauncherIsServing, serviceLauncherLockPath, stateDirOwned, writeServiceState, @@ -954,8 +955,11 @@ process.exit(0); }); yield* Effect.promise(() => refused.promise); assert.isFalse(completed); + // An idle launcher must not make an SSH launch wait for a server it is not starting. + assert.isFalse(yield* Effect.promise(() => serviceLauncherIsServing(root))); otherOwner.close(); yield* Effect.promise(() => owned.promise); + assert.isTrue(yield* Effect.promise(() => serviceLauncherIsServing(root))); yield* Effect.promise(() => launcher.stop("SIGTERM")); yield* Effect.promise(() => running); assert.strictEqual(spawnedChildren.length - spawnsBefore, 2); diff --git a/apps/server/src/serviceLauncher.ts b/apps/server/src/serviceLauncher.ts index a0f7752025027..fad391896d307 100644 --- a/apps/server/src/serviceLauncher.ts +++ b/apps/server/src/serviceLauncher.ts @@ -30,6 +30,7 @@ import { serviceUnitLogPathOf, SERVICE_ENV_FILE, SERVICE_LAUNCHER_LOCK_FILE, + SERVICE_SERVING_LOCK_FILE, SERVICE_LAUNCHER_CONTEXT_ENV, SERVICE_LAUNCHER_PROTOCOL, SERVICE_STATE_FILE, @@ -758,11 +759,12 @@ export const serviceLauncherLockPath = (baseDir: string) => // An exclusive SQLite transaction, like the server's owner lock: the OS drops it when the holder dies. async function tryAcquireSqliteLock( lockPath: string, + busyTimeoutMs = 0, ): Promise<{ readonly close: () => void } | undefined> { const { DatabaseSync } = await import("node:sqlite"); const db = new DatabaseSync(lockPath); try { - db.exec("PRAGMA busy_timeout = 0; BEGIN EXCLUSIVE;"); + db.exec(`PRAGMA busy_timeout = ${busyTimeoutMs}; BEGIN EXCLUSIVE;`); return db; } catch (cause) { db.close(); @@ -778,6 +780,13 @@ export async function serviceLauncherIsRunning(lockPath: string): Promise + NodePath.join(baseDir, "runtime", SERVICE_SERVING_LOCK_FILE); + +/** Whether a launcher has, or is about to start, a server for this home. An idle one does not. */ +export const serviceLauncherIsServing = (baseDir: string): Promise => + serviceLauncherIsRunning(servingLockPath(baseDir)).catch(() => false); + /** Resolves once no launcher holds the lock; the caller bounds the wait through `signal`. */ export async function waitForServiceLauncherExit( lockPath: string, @@ -857,6 +866,7 @@ export class Launcher { #stateDirFreeProbes = 0; readonly #output: number | undefined; readonly #lockPath: string | undefined; + #servingLock: { readonly close: () => void } | undefined; readonly #stopChildOverIpc: boolean; constructor( @@ -910,6 +920,7 @@ export class Launcher { } } } finally { + this.#releaseServing(); process.off("SIGTERM", onSigterm); process.off("SIGINT", onSigint); } @@ -927,6 +938,19 @@ export class Launcher { return undefined; } + // Advisory: without it an SSH launch may start its own server, as it does beside no service. + async #claimServing(): Promise { + // An SSH launch probes this lock for a moment; wait out that probe. + this.#servingLock ??= await tryAcquireSqliteLock(servingLockPath(this.#baseDir), 250).catch( + () => undefined, + ); + } + + #releaseServing(): void { + this.#servingLock?.close(); + this.#servingLock = undefined; + } + #terminate(child: NodeChildProcess.ChildProcess, signal: NodeJS.Signals = "SIGTERM") { return terminateChild(child, signal, this.#stopChildOverIpc); } @@ -1022,6 +1046,7 @@ export class Launcher { // A stop during the probe must not arm a retry that outlives it. if (this.#stopRequested) return true; this.#clearTimer(); + this.#releaseServing(); this.#log(`[service-launcher] ${reason} Waiting for free space before retrying.\n`); this.#timer = setInterval( () => @@ -1029,6 +1054,7 @@ export class Launcher { if (this.#child !== null || this.#stopping) return; if ((await this.#serviceStartDiskSpaceBlocker()) !== undefined) return; this.#clearTimer(); + await this.#claimServing(); await retry(); }), DISK_SPACE_RETRY_MS, @@ -1057,6 +1083,7 @@ export class Launcher { } async #recover(): Promise { + await this.#claimServing(); // Clear old stop markers to preserve future tunnels; clear restart-pending only for the awaited version to avoid CLI races. await NodeFSP.rm(stopMarkerPath(this.#baseDir), { force: true }).catch(() => undefined); const restartPending = restartPendingPath(this.#baseDir); @@ -1426,6 +1453,7 @@ export class Launcher { await this.#repointServiceUnit(next.activeVersion); } } + this.#releaseServing(); this.#log( "[service-launcher] Another T3 Code server owns this T3 home. This service starts again once it stops.\n", ); @@ -1450,6 +1478,7 @@ export class Launcher { this.#stateDirFreeProbes = owned ? 0 : this.#stateDirFreeProbes + 1; if (this.#stateDirFreeProbes < 2) return; this.#clearTimer(); + await this.#claimServing(); this.#log("[service-launcher] The T3 home is free again. Starting the server.\n"); const update = this.#state.update; if (update?.status === "pending") await this.#startTrial(update); diff --git a/packages/ssh/src/launcherTestHarness.ts b/packages/ssh/src/launcherTestHarness.ts index fb307fffd2f88..b9b7f30d86e50 100644 --- a/packages/ssh/src/launcherTestHarness.ts +++ b/packages/ssh/src/launcherTestHarness.ts @@ -3,6 +3,7 @@ import * as NodeFS from "node:fs"; import * as NodePath from "node:path"; import * as NodeURL from "node:url"; import * as NodeChildProcess from "node:child_process"; +import * as NodeSqlite from "node:sqlite"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeServices from "@effect/platform-node/NodeServices"; import * as Effect from "effect/Effect"; @@ -58,6 +59,12 @@ export const captureLauncherFor = Effect.fn("test.captureLauncher")(function* ( export const captureLauncher = captureLauncherFor({ nodeScriptPath: "/fake/server.ts" }); +function holdLock(path: string) { + const db = new NodeSqlite.DatabaseSync(path); + db.exec("BEGIN EXCLUSIVE;"); + return { [Symbol.dispose]: () => db.close() }; +} + export function runLauncher( script: string, options: { @@ -68,6 +75,9 @@ export function runLauncher( readonly clockJump?: number; readonly readyAt?: number; readonly initialPort?: number; + // A boot service between two servers holds the home with no runtime record yet. + readonly serviceServing?: boolean; + readonly allowFailure?: boolean; // The desktop publishes runtime and removes the marker right after the first runtime read. readonly publishAfterFirstRuntimeRead?: boolean; }, @@ -192,6 +202,7 @@ if [ "$1" = --version ]; then printf 1.2.3; exit; fi [ "$1" = __ssh-helper ] case "$2" in update-remaining) printf 'marker %s\\n' "$(cat "$HOME/clock")" >> "$HOME/events"; exec "$TEST_REAL_NODE" --require "$HOME/clock.cjs" "$HOME/helper.ts" "$2" "$3" ;; + home-owned) exec "$TEST_REAL_NODE" "$HOME/helper.ts" "$2" "$3" ;; runtime-port | pick-port | wait-ready) exec "$HOME/bin/test-node" - "$3" ;; *) exit 1 ;; esac @@ -199,6 +210,9 @@ esac { mode: 0o755 }, ); } + using _serving = options.serviceServing + ? holdLock(NodePath.join(runtime, "service-serving.lock")) + : undefined; const result = NodeChildProcess.spawnSync("/bin/sh", ["-s", "--", "test-target"], { input: `kill() { if [ "$1" = -0 ]; then command kill "$@"; else @@ -222,12 +236,14 @@ ${script}`, TEST_PUBLISH_AFTER_FIRST_READ: options.publishAfterFirstRuntimeRead ? "1" : "0", }, }); - if (result.error || result.status !== 0) + if (result.error || (result.status !== 0 && !options.allowFailure)) throw new Error( `launcher failed: ${result.error ?? result.status}\n${result.stderr}\n${result.stdout}\n${NodeFS.readFileSync(NodePath.join(home, "events"), "utf8")}`, ); return { - output: JSON.parse(result.stdout.trim()), + status: result.status, + stderr: result.stderr, + output: result.status === 0 ? JSON.parse(result.stdout.trim()) : undefined, events: NodeFS.readFileSync(NodePath.join(home, "events"), "utf8").trim().split("\n"), now: Number(NodeFS.readFileSync(NodePath.join(home, "clock"), "utf8")), markerExists: NodeFS.existsSync(markerPath), diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 976f9d32c4b72..7c107140e02be 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -1520,6 +1520,33 @@ describe("SSH launch during desktop update", () => { }), ); + it.effect.each([false, true])( + "waits for a boot service between two servers (archive: %s)", + (archive) => + Effect.gen(function* () { + const script = yield* archive + ? captureLauncherFor({ archiveVersion: "1.2.3" }) + : captureLauncher; + const result = runLauncher(script, { archive, serviceServing: true, readyAt: 5_000 }); + assert.deepEqual(result.output, { remotePort: 4888, serverKind: "external" }); + assert.equal(result.now, 5_000); + assert.isFalse(result.events.some((event) => event.startsWith("start "))); + }), + ); + + it.effect("fails without starting a second server when the home's owner never answers", () => + Effect.gen(function* () { + const result = runLauncher(yield* captureLauncher, { + serviceServing: true, + allowFailure: true, + }); + assert.equal(result.status, 1); + assert.include(result.stderr, "no second server was started"); + assert.equal(result.now, 60_000); + assert.isFalse(result.events.some((event) => event.startsWith("start "))); + }), + ); + it.effect("starts immediately without a marker", () => Effect.gen(function* () { const result = runLauncher(yield* captureLauncher, {}); diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index 183eb9b9eac19..c3ff20293aa1d 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -82,20 +82,28 @@ const REMOTE_UPDATE_WAIT_MS = DESKTOP_UPDATE_IN_PROGRESS_TTL_MS + 2 * (REMOTE_REUSE_READY_TIMEOUT_MS + SSH_READY_PROBE_TIMEOUT_MS) + 1_000; +// A launch waits this long for a server that holds the home to answer; its +// last poll adds a reuse probe. +const REMOTE_OWNER_WAIT_MS = + REMOTE_READY_TIMEOUT_MS + REMOTE_REUSE_READY_TIMEOUT_MS + SSH_READY_PROBE_TIMEOUT_MS; // A launch holds the home lock through up to two reuse probes, readiness, two -// server exit waits, the runtime record wait, and the update wait; each probe -// may overrun its deadline by one request. +// server exit waits, the runtime record wait, the update wait, and the owner +// wait; each probe may overrun its deadline by one request. const REMOTE_LAUNCH_LOCK_WAIT_SECONDS = Math.ceil( (REMOTE_READY_TIMEOUT_MS + 2 * REMOTE_REUSE_READY_TIMEOUT_MS + 2 * REMOTE_PID_EXIT_WAIT_MS + REMOTE_RUNTIME_RECORD_WAIT_SECONDS * 1000 + 4 * SSH_READY_PROBE_TIMEOUT_MS + - REMOTE_UPDATE_WAIT_MS) / + REMOTE_UPDATE_WAIT_MS + + REMOTE_OWNER_WAIT_MS) / 1000, ); const REMOTE_LAUNCH_TIMEOUT_MS = - REMOTE_LAUNCH_LOCK_WAIT_SECONDS * 1000 + REMOTE_UPDATE_WAIT_MS + REMOTE_LAUNCH_BASE_TIMEOUT_MS; + REMOTE_LAUNCH_LOCK_WAIT_SECONDS * 1000 + + REMOTE_UPDATE_WAIT_MS + + REMOTE_OWNER_WAIT_MS + + REMOTE_LAUNCH_BASE_TIMEOUT_MS; export interface RemoteT3RunnerOptions { readonly nodeScriptPath?: string | null; @@ -708,6 +716,39 @@ server_running() { esac } @@T3_MANAGED_SERVER_RUNNING_SCRIPT@@ +# Both are SQLite locks the OS drops with their holder: a server holds +# server-owner.sqlite, and a boot service holds service-serving.lock from +# before it starts a server, between two of them during its own update, until +# it stops or idles behind another server. +home_owned() { + if [ "$T3_ARCHIVE_MODE" = "1" ]; then + "$RUNNER_FILE" __ssh-helper home-owned "$DEFAULT_SERVER_HOME" 2>/dev/null + return + fi + node - "$DEFAULT_SERVER_HOME" 2>/dev/null <<'NODE' +const fs = require("node:fs"); +const path = require("node:path"); +const { DatabaseSync } = require("node:sqlite"); +const held = (file) => { + if (!fs.existsSync(file)) return false; + const db = new DatabaseSync(file); + try { + db.exec("PRAGMA busy_timeout = 0; BEGIN EXCLUSIVE;"); + return false; + } catch (error) { + return error?.errcode === 5; + } finally { + db.close(); + } +}; +const home = process.argv[2]; +process.exitCode = + held(path.join(home, "runtime", "service-serving.lock")) || + held(path.join(home, "userdata", "server-owner.sqlite")) + ? 0 + : 1; +NODE +} resolve_default_runtime_port() { if [ "$T3_ARCHIVE_MODE" = "1" ]; then "$RUNNER_FILE" __ssh-helper runtime-port "$DEFAULT_RUNTIME_FILE" @@ -896,6 +937,25 @@ if [ -z "$REMOTE_PORT" ]; then REMOTE_MANAGED="" fi fi +# A server that holds the home without answering yet is still starting, and a +# boot service holds it between two servers while it updates. A second server +# would lose the home to it or take the home from it, so wait while the home is +# held and start one only once it is free. The managed server an update just +# stopped may still be exiting. The sleep count bounds a host clock that stops. +OWNER_WAITED=0 +OWNER_DEADLINE=$(($(date +%s) + @@T3_READY_TIMEOUT_SECONDS@@)) +while [ -z "$REMOTE_PORT" ] && ! managed_server_running "$(cat "$PID_FILE" 2>/dev/null || true)" && home_owned; do + if adopt_default_server; then break; fi + REMOTE_PID="" + REMOTE_PORT="" + REMOTE_MANAGED="" + if [ "$OWNER_WAITED" -ge @@T3_READY_TIMEOUT_SECONDS@@ ] || [ "$(date +%s)" -ge "$OWNER_DEADLINE" ]; then + printf 'A T3 server or boot service holds %s but did not become ready, so no second server was started. Retry once it has started.\\n' "$DEFAULT_SERVER_HOME" >&2 + exit 1 + fi + sleep 1 + OWNER_WAITED=$((OWNER_WAITED + 1)) +done if [ -z "$REMOTE_PORT" ]; then REMOTE_PORT="$(pick_port)" || true if [ -z "$REMOTE_PORT" ]; then @@ -1060,6 +1120,7 @@ function buildRemoteLaunchScript(input?: RemoteT3RunnerOptions): string { T3_DEFAULT_REMOTE_PORT: String(DEFAULT_REMOTE_PORT), T3_REMOTE_PORT_SCAN_WINDOW: String(REMOTE_PORT_SCAN_WINDOW), T3_READY_TIMEOUT_MS: String(REMOTE_READY_TIMEOUT_MS), + T3_READY_TIMEOUT_SECONDS: String(REMOTE_READY_TIMEOUT_MS / 1000), T3_REUSE_READY_TIMEOUT_MS: String(REMOTE_REUSE_READY_TIMEOUT_MS), T3_READY_PROBE_TIMEOUT_MS: String(SSH_READY_PROBE_TIMEOUT_MS), T3_PID_EXIT_WAIT_TICKS: String(REMOTE_PID_EXIT_WAIT_MS / 100), From ae49f4d2a7963590e7d6b67dbf98f549cb46f376 Mon Sep 17 00:00:00 2001 From: chise Date: Sat, 14 Nov 2026 18:30:39 +0700 Subject: [PATCH 059/192] fix(server): honor custom Claude thinking choices (#28024) Co-authored-by: chise <188340668+chisewaguri@users.noreply.github.com> Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/claudeModelOptions.test.ts | 35 ++++++++++++++----- apps/server/src/claudeModelOptions.ts | 9 +++-- .../server/src/provider/ClaudeModelCatalog.ts | 2 +- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/apps/server/src/claudeModelOptions.test.ts b/apps/server/src/claudeModelOptions.test.ts index b69b20db7ec4d..a7513a5931831 100644 --- a/apps/server/src/claudeModelOptions.test.ts +++ b/apps/server/src/claudeModelOptions.test.ts @@ -105,14 +105,33 @@ describe("compileClaudeModelSelection", () => { ).toMatchObject({ effort: "high", promptEffort: "ultrathink" }); }); - it("keeps explicit effort for models missing from the local catalog", () => { + it("ignores thinking for catalog models without a thinking option", () => { expect( - compileClaudeModelSelection( - selection("claude-pool-model-not-in-catalog", [{ id: "effort", value: "max" }]), - ).effort, - ).toBe("max"); - expect( - compileClaudeModelSelection(selection("claude-pool-model-not-in-catalog", [])).effort, - ).toBeUndefined(); + compileClaudeModelSelection(selection("claude-opus-5-5", [{ id: "thinking", value: false }])) + .settings, + ).not.toHaveProperty("alwaysThinkingEnabled"); }); + + it.each(["gateway-route", "gateway-route[1m]"])( + "keeps explicit effort and thinking for %s outside the local catalog", + (model) => { + for (const thinking of [true, false]) { + expect( + compileClaudeModelSelection( + selection(model, [ + { id: "effort", value: "max" }, + { id: "thinking", value: thinking }, + ]), + ), + ).toMatchObject({ + apiModelId: model, + effort: "max", + settings: { alwaysThinkingEnabled: thinking }, + }); + } + const unconfigured = compileClaudeModelSelection(selection(model, [])); + expect(unconfigured.effort).toBeUndefined(); + expect(unconfigured.settings).toEqual({}); + }, + ); }); diff --git a/apps/server/src/claudeModelOptions.ts b/apps/server/src/claudeModelOptions.ts index 12530db50ec5e..c3376ef7ed8dc 100644 --- a/apps/server/src/claudeModelOptions.ts +++ b/apps/server/src/claudeModelOptions.ts @@ -12,6 +12,7 @@ import { normalizeClaudeCatalogEffort, resolveClaudeCatalogApiModelId, resolveClaudeCatalogEffort, + resolveClaudeCatalogModel, resolveClaudeModelSlug, type ClaudeModelCatalog, } from "./provider/ClaudeModelCatalog.ts"; @@ -45,9 +46,11 @@ export function compileClaudeModelSelection( const fastMode = supportsBoolean("fastMode") ? (getModelSelectionBooleanOptionValue(selection, "fastMode") ?? false) : undefined; - const thinking = supportsBoolean("thinking") - ? getModelSelectionBooleanOptionValue(selection, "thinking") - : undefined; + const inCatalog = resolveClaudeCatalogModel(catalog, selection.model) !== undefined; + const thinking = + !inCatalog || supportsBoolean("thinking") + ? getModelSelectionBooleanOptionValue(selection, "thinking") + : undefined; const ultracode = descriptors.find((descriptor) => descriptor.id === "ultracode")?.currentValue; const settings = { ...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}), diff --git a/apps/server/src/provider/ClaudeModelCatalog.ts b/apps/server/src/provider/ClaudeModelCatalog.ts index 1afb1e1cc6149..e3d4ea4397c0e 100644 --- a/apps/server/src/provider/ClaudeModelCatalog.ts +++ b/apps/server/src/provider/ClaudeModelCatalog.ts @@ -223,7 +223,7 @@ export function claudeReportedModels( return models; } -function resolveClaudeCatalogModel( +export function resolveClaudeCatalogModel( catalog: ClaudeModelCatalog, slugOrAlias: string | null | undefined, ): ClaudeCatalogModel | undefined { From 8cb551a3cec68767054b67b790b8293ac1e7859a Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 12:32:43 +0100 Subject: [PATCH 060/192] fix(web): saved files show external edits after a failed reload (#36664) Co-authored-by: Julius Marminge --- .../files/projectFilesQueryState.ts | 21 +--- .../src/state/projectCommands.test.ts | 107 +++++++++++++++++- .../src/state/projectCommands.ts | 50 +++++--- .../saved-file-shows-external-edit.e2e.ts | 101 +++++++++++++++++ 4 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 scripts/scenarios/saved-file-shows-external-edit.e2e.ts diff --git a/apps/web/src/components/files/projectFilesQueryState.ts b/apps/web/src/components/files/projectFilesQueryState.ts index 4d7e9ae90d6b9..8b991d069d225 100644 --- a/apps/web/src/components/files/projectFilesQueryState.ts +++ b/apps/web/src/components/files/projectFilesQueryState.ts @@ -15,7 +15,6 @@ import { appAtomRegistry } from "~/rpc/atomRegistry"; import { useFilesystemReadAccess } from "~/state/filesystem"; import { projectEnvironment } from "~/state/projects"; import { useProjectPathSearch } from "~/state/queries"; -import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime"; import { revisionedQuery } from "~/state/revisionedQuery"; const EMPTY_PROJECT_FILE_PATH = ""; @@ -92,7 +91,7 @@ export function setProjectFileQueryData( unsavedFileMounts.set(atom, appAtomRegistry.mount(atom)); } appAtomRegistry.set(atom, { - confirmedAgainst: undefined, + saved: false, data: { relativePath, contents, @@ -128,7 +127,7 @@ export function getUnsavedProjectFileQueryData( relativePath: string, ): ProjectReadFileResult | null { const optimistic = appAtomRegistry.get(optimisticFileAtom(environmentId, cwd, relativePath)); - return optimistic?.confirmedAgainst === undefined ? (optimistic?.data ?? null) : null; + return !optimistic?.saved ? (optimistic?.data ?? null) : null; } export function confirmProjectFileQueryData( @@ -142,21 +141,13 @@ export function confirmProjectFileQueryData( if (optimisticFile?.data.contents !== contents) return false; const queryAtom = getProjectFileQueryAtom(environmentId, cwd, relativePath); - const confirmed = { + appAtomRegistry.set(atom, { ...optimisticFile, - confirmedAgainst: appAtomRegistry.get(queryAtom), - }; - appAtomRegistry.set(atom, confirmed); + saved: true, + confirmingQuery: queryAtom, + }); releaseUnsavedFile(atom); appAtomRegistry.refresh(queryAtom); - void executeAtomQuery(appAtomRegistry, queryAtom, { - reportDefect: false, - reportFailure: false, - }).then((result) => { - if (result._tag === "Success" && appAtomRegistry.get(atom) === confirmed) { - appAtomRegistry.set(atom, null); - } - }); return true; } diff --git a/packages/client-runtime/src/state/projectCommands.test.ts b/packages/client-runtime/src/state/projectCommands.test.ts index aa67ccd580267..c49e86d08bba8 100644 --- a/packages/client-runtime/src/state/projectCommands.test.ts +++ b/packages/client-runtime/src/state/projectCommands.test.ts @@ -3,6 +3,7 @@ import { AuthFilesystemWriteScope, EnvironmentId, ProjectId, + ProjectReadFileError, ProjectReadFileInput, ProjectReadFileResult, ThreadId, @@ -49,7 +50,7 @@ const makeHarness = Effect.fn("TestProjectCommands.makeHarness")(function* () { const metadataRequests: string[][] = []; const file = { byteLength: 5 }; const readResponse = { - wait: Effect.void as Effect.Effect, + wait: Effect.void as Effect.Effect, missing: false, }; const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ @@ -146,6 +147,110 @@ const makeHarness = Effect.fn("TestProjectCommands.makeHarness")(function* () { }); describe("writeFile metadata", () => { + it.effect("retires a saved overlay after an equal read succeeds, preserving newer drafts", () => + Effect.gen(function* () { + const h = yield* makeHarness(); + const target = { environmentId: ENVIRONMENT_ID, cwd: "/repo", relativePath: "foo" }; + const query = h.commands.readFile({ + environmentId: ENVIRONMENT_ID, + input: { cwd: target.cwd, relativePath: target.relativePath }, + }); + h.registry.mount(query); + const read = () => AtomRegistry.getResult(h.registry, query, { suspendOnWaiting: true }); + const original = yield* read(); + const overlay = h.commands.optimisticFile(target); + h.registry.mount(overlay); + const saved = { ...original, contents: "saved" }; + h.registry.set(overlay, { data: saved, saved: true, confirmingQuery: query }); + h.readResponse.wait = Effect.fail( + new ProjectReadFileError({ + cwd: "/repo", + relativePath: "foo", + failure: "operation_failed", + }), + ); + h.registry.refresh(query); + yield* Effect.exit(read()); + expect(h.registry.get(overlay)?.data.contents).toBe("saved"); + + h.readResponse.wait = Effect.void; + h.registry.refresh(query); + const refreshed = yield* read(); + // The production query shares equal file values across this failed confirmation. + expect(refreshed).toBe(original); + expect(h.registry.get(overlay)).toBeNull(); + + h.registry.set(overlay, { data: { ...saved, contents: "newer draft" }, saved: false }); + h.registry.refresh(query); + yield* read(); + expect(h.registry.get(overlay)?.data.contents).toBe("newer draft"); + }), + ); + + it.effect.each([true, false])( + "keeps a saved overlay when an older read uses different allowMissing options (confirmation: %s)", + (allowMissing) => + Effect.gen(function* () { + const h = yield* makeHarness(); + const target = { environmentId: ENVIRONMENT_ID, cwd: "/repo", relativePath: "foo" }; + const query = h.commands.readFile({ + environmentId: ENVIRONMENT_ID, + input: { + cwd: target.cwd, + relativePath: target.relativePath, + ...(allowMissing ? { allowMissing: true } : {}), + }, + }); + const otherQuery = h.commands.readFile({ + environmentId: ENVIRONMENT_ID, + input: { + cwd: target.cwd, + relativePath: target.relativePath, + ...(!allowMissing ? { allowMissing: true } : {}), + }, + }); + const read = (atom: typeof query) => + AtomRegistry.getResult(h.registry, atom, { suspendOnWaiting: true }); + h.registry.mount(query); + const original = yield* read(query); + const overlay = h.commands.optimisticFile(target); + h.registry.mount(overlay); + const started = yield* Deferred.make(); + const release = yield* Deferred.make(); + h.readResponse.wait = Deferred.succeed(started, undefined).pipe( + Effect.andThen(Deferred.await(release)), + ); + h.registry.mount(otherQuery); + const olderRead = yield* read(otherQuery).pipe(Effect.forkChild); + yield* Deferred.await(started); + h.registry.set(overlay, { + data: { ...original, contents: "saved" }, + saved: true, + confirmingQuery: query, + }); + h.readResponse.wait = Effect.fail( + new ProjectReadFileError({ + cwd: target.cwd, + relativePath: target.relativePath, + failure: "operation_failed", + }), + ); + h.registry.refresh(query); + yield* Effect.exit(read(query)); + expect(h.registry.get(overlay)?.data.contents).toBe("saved"); + + yield* Deferred.succeed(release, undefined); + yield* Fiber.join(olderRead); + expect(h.registry.get(overlay)?.data.contents).toBe("saved"); + expect(Option.getOrNull(AsyncResult.value(h.registry.get(query)))).toBe(original); + + h.readResponse.wait = Effect.void; + h.registry.refresh(query); + expect(yield* read(query)).toBe(original); + expect(h.registry.get(overlay)).toBeNull(); + }), + ); + it.effect("preserves optional read results without seeding metadata for an absent file", () => Effect.gen(function* () { const h = yield* makeHarness(); diff --git a/packages/client-runtime/src/state/projectCommands.ts b/packages/client-runtime/src/state/projectCommands.ts index caa706d86d46e..119b1de81fcb6 100644 --- a/packages/client-runtime/src/state/projectCommands.ts +++ b/packages/client-runtime/src/state/projectCommands.ts @@ -37,10 +37,16 @@ export type { UpdateProjectInput, } from "../operations/commands.ts"; -export interface OptimisticProjectFile { +export type OptimisticProjectFile = { readonly data: ProjectReadFileResult; - readonly confirmedAgainst: object | null | undefined; -} +} & ( + | { readonly saved: false } + | { + readonly saved: true; + /** Only this query's next successful answer can replace the saved contents. */ + readonly confirmingQuery: Atom.Atom; + } +); export interface OptimisticProjectFileTarget { readonly environmentId: EnvironmentId; @@ -86,6 +92,29 @@ export function createProjectEnvironmentAtoms( Atom.withLabel(`environment-data:projects:optimistic-file:${key}`), ), ); + const readFile = createEnvironmentRpcQueryAtomFamily(runtime, { + label: "environment-data:projects:read-file", + tag: WS_METHODS.projectsReadFile, + staleTimeMs: 30_000, + idleTtlMs: 5 * 60_000, + onAnswer: (target, _value, registry): Effect.Effect => + Effect.sync(() => { + const atom = optimisticFileFamily( + optimisticProjectFileKey({ environmentId: target.environmentId, ...target.input }), + ); + const overlay = registry.get(atom); + if (overlay?.saved && overlay.confirmingQuery === readFile(target)) + registry.set(atom, null); + }), + execute: (input) => + fileMetadata.seedAfterRead( + request(WS_METHODS.projectsReadFile, input), + (result, isCurrent) => + result.exists === false + ? Effect.void + : fileMetadata.rememberFile(input.cwd, result, isCurrent), + ), + }); const projectConcurrency = { mode: "serial" as const, key: ({ environmentId, input }: { environmentId: string; input: { projectId: string } }) => @@ -133,20 +162,7 @@ export function createProjectEnvironmentAtoms( tag: WS_METHODS.projectsListIgnoredFiles, staleTimeMs: 15_000, }), - readFile: createEnvironmentRpcQueryAtomFamily(runtime, { - label: "environment-data:projects:read-file", - tag: WS_METHODS.projectsReadFile, - staleTimeMs: 30_000, - idleTtlMs: 5 * 60_000, - execute: (input) => - fileMetadata.seedAfterRead( - request(WS_METHODS.projectsReadFile, input), - (result, isCurrent) => - result.exists === false - ? Effect.void - : fileMetadata.rememberFile(input.cwd, result, isCurrent), - ), - }), + readFile, optimisticFile: (target: OptimisticProjectFileTarget) => optimisticFileFamily(optimisticProjectFileKey(target)), create: createEnvironmentCommand(runtime, { diff --git a/scripts/scenarios/saved-file-shows-external-edit.e2e.ts b/scripts/scenarios/saved-file-shows-external-edit.e2e.ts new file mode 100644 index 0000000000000..fea75e9743b03 --- /dev/null +++ b/scripts/scenarios/saved-file-shows-external-edit.e2e.ts @@ -0,0 +1,101 @@ +/// +// The DOM lib types the functions this scenario runs in the page. +import { expect } from "e2e"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; + +import { runPromise, test, WEB } from "./t3.ts"; + +const chmod = (path: string, mode: number) => + runPromise(Effect.flatMap(FileSystem.FileSystem, (fs) => fs.chmod(path, mode))); + +test( + "A saved file shows a later external edit after its post-save read failed", + WEB, + async ({ t3, app, browser, screen }) => { + // Logs each file RPC's outcome in order, so the test can wait for the read after the save. + await browser.addInitScript(() => { + const log: Array = []; + const tags = new Map(); + const watched = new WeakSet(); + Object.assign(window, { fileRpcLog: log }); + const send = WebSocket.prototype.send; + WebSocket.prototype.send = function (data) { + if (typeof data === "string") { + for (const message of [JSON.parse(data)].flat()) { + if (message?._tag === "Request") tags.set(String(message.id), message.tag); + } + } + if (!watched.has(this)) { + watched.add(this); + this.addEventListener("message", (event) => { + if (typeof event.data !== "string") return; + for (const message of [JSON.parse(event.data)].flat()) { + const tag = message?._tag === "Exit" ? tags.get(String(message.requestId)) : null; + if (tag?.startsWith("projects.")) log.push(`${tag}:${message.exit._tag}`); + } + }); + } + return send.call(this, data); + }; + }); + const loggedAfter = (after: string, entry: string) => + browser.evaluate( + ({ after, entry }) => { + const log = (window as unknown as { fileRpcLog: Array }).fileRpcLog; + const start = log.lastIndexOf(after); + return start >= 0 && log.indexOf(entry, start + 1) > start; + }, + { after, entry }, + ); + // The file tree and editor render in shadow roots that locators do not reach. + const openTreeRow = (path: string) => + browser.evaluate((path) => { + for (const host of document.querySelectorAll("*")) { + const row = host.shadowRoot?.querySelector(`[data-item-path="${path}"]`); + if (row) { + row.click(); + return true; + } + } + return false; + }, path); + const editorText = () => + browser.evaluate(() => { + for (const host of document.querySelectorAll("*")) { + const content = host.shadowRoot?.querySelector("[data-content]"); + if (content) return content.textContent ?? ""; + } + return null; + }); + + const project = await t3.project("demo", { "notes.txt": "before save" }); + const notes = `${project}/notes.txt`; + const thread = await t3.thread({ project, title: "Notes", messages: ["first"] }); + await t3.open(thread); + await expect(screen.getByText("first fixture turn complete", { exact: false })).toBeVisible(); + await screen.getByRole("button", "Toggle right panel").tap(); + await screen.getByText("Files").tap(); + await expect.poll(() => openTreeRow("notes.txt")).toBe(true); + await expect.poll(editorText).toContain("before save"); + + // Requires a non-root server and in-place writes: autosave succeeds, confirmation read fails. + await chmod(notes, 0o200); + await browser.keyboard.press("ControlOrMeta+a"); + await browser.keyboard.type("saved draft"); + await expect + .poll(() => loggedAfter("projects.writeFile:Success", "projects.readFile:Failure")) + .toBe(true); + + await chmod(notes, 0o644); + await runPromise( + Effect.flatMap(FileSystem.FileSystem, (fs) => fs.writeFileString(notes, "external update")), + ); + await screen.getByRole("button", "Refresh workspace files").tap(); + await expect + .poll(() => loggedAfter("projects.readFile:Failure", "projects.readFile:Success")) + .toBe(true); + await app.screenshot("after-external-edit"); + await expect.poll(editorText).toContain("external update"); + }, +); From 1159aa299119b6b6d76405891c9240ae37861578 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 12:35:28 +0100 Subject: [PATCH 061/192] fix(client-runtime): preserve PR reference links across embedded media (#36660) Co-authored-by: Julius Marminge --- packages/client-runtime/package.json | 6 +- .../src/pullRequestMarkdown.test.ts | 83 +++++++++++++++++++ .../client-runtime/src/pullRequestMarkdown.ts | 68 ++++++++++++++- pnpm-lock.yaml | 12 +++ 4 files changed, 166 insertions(+), 3 deletions(-) diff --git a/packages/client-runtime/package.json b/packages/client-runtime/package.json index d1e74151e53e3..19989716bc3c6 100644 --- a/packages/client-runtime/package.json +++ b/packages/client-runtime/package.json @@ -590,11 +590,15 @@ "@t3tools/source-control-definitions": "workspace:*", "effect": "catalog:", "jszip": "3.10.1", + "remark-gfm": "catalog:", + "remark-parse": "catalog:", "three": "0.180.0", - "unbash": "^5.0.0" + "unbash": "^5.0.0", + "unified": "catalog:" }, "devDependencies": { "@effect/vitest": "catalog:", + "@types/mdast": "^4.0.4", "@types/three": "0.180.0", "vite-plus": "catalog:" } diff --git a/packages/client-runtime/src/pullRequestMarkdown.test.ts b/packages/client-runtime/src/pullRequestMarkdown.test.ts index 13327d8480a57..f616f35eef5fe 100644 --- a/packages/client-runtime/src/pullRequestMarkdown.test.ts +++ b/packages/client-runtime/src/pullRequestMarkdown.test.ts @@ -1,3 +1,7 @@ +import type { Root, RootContent } from "mdast"; +import remarkGfm from "remark-gfm"; +import remarkParse from "remark-parse"; +import { unified } from "unified"; import { describe, expect, it } from "vite-plus/test"; import { @@ -156,6 +160,85 @@ describe("pull request body segmentation", () => { }); }); +describe("reference links around lifted media", () => { + const recording = + "https://github.com/user-attachments/assets/2f8c1a90-1b2c-4d5e-8f90-abcdef123456"; + const parser = unified().use(remarkParse).use(remarkGfm); + const prose = (segments: ReadonlyArray): string[] => + segments.flatMap((segment) => + segment.kind === "markdown" + ? [segment.text] + : segment.kind === "table" + ? segment.rows.flat().flatMap((cell) => prose(cell.segments)) + : [], + ); + // Each segment renders as its own document; the first definition of a label wins, as in mdast-util-to-hast. + const referenceLinks = (body: string) => + prose(splitPullRequestBody(body)).flatMap((text) => { + const definitions = new Map(); + const references: Array<{ identifier: string; text: string }> = []; + const visit = (node: Root | RootContent) => { + if (node.type === "definition" && !definitions.has(node.identifier)) { + definitions.set(node.identifier, node.url); + } + if (node.type === "linkReference") { + references.push({ + identifier: node.identifier, + text: node.children.map((child) => ("value" in child ? child.value : "")).join(""), + }); + } + if ("children" in node) node.children.forEach(visit); + }; + visit(parser.parse(text)); + return references.map((reference) => [reference.text, definitions.get(reference.identifier)]); + }); + + it.each([ + [ + "use, then media, then definition", + `See [the docs][guide].\n\n${recording}\n\n[guide]: https://example.com/docs`, + ], + [ + "definition, then media, then use", + `[guide]: https://example.com/docs\n\n${recording}\n\nSee [the docs][guide].`, + ], + ...["\n", "\r\n", "\r"].map((lineEnding) => [ + `a definition wrapped over quoted lines with ${JSON.stringify(lineEnding)}`, + `> [guide]:${lineEnding}> https://example.com/docs${lineEnding}> "The guide"\n\n${recording}\n\nSee [the docs][guide].`, + ]), + [ + "a use inside a media table cell", + `
\n\nSee [the docs][guide].\n\n\n\n${recording}\n\n
\n\n[guide]: https://example.com/docs`, + ], + ])("resolves a reference defined across a video: %s", (_case, body) => { + expect(referenceLinks(body)).toEqual([["the docs", "https://example.com/docs"]]); + }); + + it("keeps the body's first definition of a label, wherever the media falls", () => { + expect( + referenceLinks( + `[guide]: https://example.com/first\n\n${recording}\n\nSee [the docs][guide].\n\n[GUIDE]: https://example.com/second`, + ), + ).toEqual([["the docs", "https://example.com/first"]]); + }); + + it("leaves a definition shown in code as code", () => { + expect( + referenceLinks( + `\`\`\`md\n[guide]: https://example.com/docs\n\`\`\`\n\n${recording}\n\nSee [the docs][guide].`, + ), + ).toEqual([]); + }); + + it("still writes a task tick back to its place in the body", () => { + const body = `[guide]: https://example.com/docs\n\n${recording}\n\n- [ ] Read [the docs][guide]`; + const segment = splitPullRequestBody(body).at(-1)!; + if (segment.kind !== "markdown") throw new Error("expected prose last"); + const marker = segment.text.indexOf("[ ]"); + expect(body.slice(segment.sourceOffset + marker)).toBe("[ ] Read [the docs][guide]"); + }); +}); + describe("media on custom hosts", () => { it.each([ ["https://drop.example.com/capture.PNG?token=abc#preview", "image"], diff --git a/packages/client-runtime/src/pullRequestMarkdown.ts b/packages/client-runtime/src/pullRequestMarkdown.ts index 2ca59143ad296..a508b789045b4 100644 --- a/packages/client-runtime/src/pullRequestMarkdown.ts +++ b/packages/client-runtime/src/pullRequestMarkdown.ts @@ -1,3 +1,8 @@ +import type { Root, RootContent } from "mdast"; +import remarkGfm from "remark-gfm"; +import remarkParse from "remark-parse"; +import { unified } from "unified"; + import { sourceControlClients, type ReferenceAutolink } from "./sourceControlClients.ts"; /** `id` is positional on purpose: the same attachment can be embedded twice in one body. */ @@ -6,7 +11,10 @@ export type PullRequestBodySegment = readonly id: string; readonly kind: "markdown"; readonly text: string; - /** Where `text` starts in the body, so an edit made to it can be written back. */ + /** + * `sourceOffset + i` is where `text[i]` sits in the body, so an edit can be written back. + * Holds past the body's reference definitions, which `text` may lead with. + */ readonly sourceOffset: number; } | { @@ -162,8 +170,64 @@ function mediaTableAt( return { end, rows: hasMedia ? rows : null }; } +const definitionParser = unified().use(remarkParse).use(remarkGfm); + +/** + * The first definition of each label in the body, as authored, each joined onto one line so it + * stands outside the quote or list it was written in. + */ +function referenceDefinitions(body: string): string[] { + const definitions = new Map(); + const visit = (node: Root | RootContent, quotes: number) => { + if (node.type === "definition" && !definitions.has(node.identifier)) { + const wrap = new RegExp(`(?:\\r\\n|\\r|\\n)(?:[ \\t]*>){0,${quotes}}[ \\t]*`, "gu"); + const source = body.slice(node.position?.start.offset, node.position?.end.offset); + definitions.set(node.identifier, source.replace(wrap, " ")); + } + if (!("children" in node)) return; + for (const child of node.children) visit(child, quotes + (node.type === "blockquote" ? 1 : 0)); + }; + visit(definitionParser.parse(body), 0); + return [...definitions.values()]; +} + +/** + * Each markdown segment renders as its own document, so it leads with the body's definitions. + * Leading, they win over a later redefinition in the segment, as the first one does in the body. + */ +function withDefinitions( + segment: PullRequestBodySegment, + definitions: string, +): PullRequestBodySegment { + if (segment.kind === "attachment") return segment; + if (segment.kind === "table") { + return { + ...segment, + rows: segment.rows.map((cells) => + cells.map((cell) => ({ + ...cell, + segments: cell.segments.map((inner) => withDefinitions(inner, definitions)), + })), + ), + }; + } + if (!segment.text.includes("[")) return segment; + return { + ...segment, + text: definitions + segment.text, + sourceOffset: segment.sourceOffset - definitions.length, + }; +} + export function splitPullRequestBody(body: string): ReadonlyArray { - return splitBodyAt(body, 0); + const segments = splitBodyAt(body, 0); + // One markdown segment is the whole body, which resolves its own references. + const whole = segments.length === 1 && segments[0]!.kind === "markdown"; + if (whole || !body.includes("]:")) return segments; + const definitions = referenceDefinitions(body); + if (definitions.length === 0) return segments; + const leading = `${definitions.join("\n")}\n\n`; + return segments.map((segment) => withDefinitions(segment, leading)); } /** `baseOffset` is where `body` starts in the whole body, so a table cell's edits write back. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99afb66977519..52dd48bd551c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1555,16 +1555,28 @@ importers: jszip: specifier: 3.10.1 version: 3.10.1 + remark-gfm: + specifier: 'catalog:' + version: 4.0.1(supports-color@8.1.1) + remark-parse: + specifier: 'catalog:' + version: 11.0.0(supports-color@8.1.1) three: specifier: 0.180.0 version: 0.180.0 unbash: specifier: ^5.0.0 version: 5.0.0 + unified: + specifier: 'catalog:' + version: 11.0.5 devDependencies: '@effect/vitest': specifier: 4.1.2 version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@6.0.3))(bufferutil@4.1.0)(effect@4.1.3)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.64.1)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + '@types/mdast': + specifier: ^4.0.4 + version: 4.0.4 '@types/three': specifier: 0.180.0 version: 0.180.0 From 913d7fc45ec5814fff25c819cfe4de193ea42d63 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 05:37:19 -0600 Subject: [PATCH 062/192] fix(web): PageUp and PageDown shortcut labels no longer read Pageup and Pagedown (#28319) Co-authored-by: Julius Marminge --- apps/web/src/keybindings.ts | 2 ++ apps/web/src/lib/snapShotShortcut.test.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/web/src/keybindings.ts b/apps/web/src/keybindings.ts index 849183b7a1a69..75f617d3cca86 100644 --- a/apps/web/src/keybindings.ts +++ b/apps/web/src/keybindings.ts @@ -222,6 +222,8 @@ export function formatShortcutKeyLabel(key: string): string { if (key === "arrowdown") return "Down"; if (key === "arrowleft") return "Left"; if (key === "arrowright") return "Right"; + if (key === "pageup") return "PageUp"; + if (key === "pagedown") return "PageDown"; return key.slice(0, 1).toUpperCase() + key.slice(1); } diff --git a/apps/web/src/lib/snapShotShortcut.test.ts b/apps/web/src/lib/snapShotShortcut.test.ts index f8bd382fec401..c59fdf515b676 100644 --- a/apps/web/src/lib/snapShotShortcut.test.ts +++ b/apps/web/src/lib/snapShotShortcut.test.ts @@ -12,6 +12,8 @@ describe("desktop-approved window capture shortcut labels", () => { ["space", ["Ctrl", "Space"]], ["plus", ["Ctrl", "+"]], ["Ctrl++", ["Ctrl", "+"]], + ["Page_Up", ["Ctrl", "PageUp"]], + ["Page_Down", ["⇧", "PageDown"]], ])("formats %s using the shared keycap labels", (label, expected) => { const shortcut = parseDesktopSnapShotShortcut(label); expect(shortcut).not.toBeNull(); From 1535f3f32e77cebc46ec0937e17e039669512e32 Mon Sep 17 00:00:00 2001 From: Sam <51256428+swsammy@users.noreply.github.com> Date: Sat, 14 Nov 2026 22:40:59 +1100 Subject: [PATCH 063/192] fix(desktop): theme changes no longer stop at windows without a title bar overlay (#28081) Co-authored-by: Julius Marminge --- .../src/electron/ElectronWindow.test.ts | 28 +++++++ apps/desktop/src/electron/ElectronWindow.ts | 16 +++- apps/desktop/src/window/DesktopWindow.test.ts | 74 +++++++++++++++---- apps/desktop/src/window/DesktopWindow.ts | 7 +- 4 files changed, 106 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/electron/ElectronWindow.test.ts b/apps/desktop/src/electron/ElectronWindow.test.ts index 9eb41ed4e2402..eac944722e73b 100644 --- a/apps/desktop/src/electron/ElectronWindow.test.ts +++ b/apps/desktop/src/electron/ElectronWindow.test.ts @@ -148,6 +148,34 @@ describe("ElectronWindow", () => { }).pipe(Effect.provide(layerTestFor("win32"))), ); + it.effect("reports which windows were created with a title bar overlay", () => + Effect.gen(function* () { + const framed = { isDestroyed: () => false }; + const overlay = { isDestroyed: () => false }; + browserWindowMock + .mockImplementationOnce(function FramedWindowMock() { + return framed; + }) + .mockImplementationOnce(function OverlayWindowMock() { + return overlay; + }); + const electronWindow = yield* ElectronWindow.ElectronWindow; + yield* electronWindow.create({}); + yield* electronWindow.create({ + titleBarStyle: "hidden", + titleBarOverlay: { color: "#01000000", height: 40 }, + }); + getAllWindowsMock.mockReturnValue([framed, overlay, { isDestroyed: () => false }]); + + const seen: Array = []; + yield* electronWindow.syncAllAppearance((_window, { titleBarOverlay }) => + Effect.sync(() => seen.push(titleBarOverlay)), + ); + + assert.deepEqual(seen, [false, true, false]); + }).pipe(Effect.provide(layerTestFor("win32"))), + ); + it.effect("preserves destroy failures and continues with later windows", () => Effect.gen(function* () { const cause = new Error("window destroy failed"); diff --git a/apps/desktop/src/electron/ElectronWindow.ts b/apps/desktop/src/electron/ElectronWindow.ts index 15a117a52b050..b5f89969e66c7 100644 --- a/apps/desktop/src/electron/ElectronWindow.ts +++ b/apps/desktop/src/electron/ElectronWindow.ts @@ -125,7 +125,10 @@ export class ElectronWindow extends Context.Service< readonly sendAll: (channel: string, ...args: readonly unknown[]) => Effect.Effect; readonly destroyAll: Effect.Effect; readonly syncAllAppearance: ( - sync: (window: Electron.BrowserWindow) => Effect.Effect, + sync: ( + window: Electron.BrowserWindow, + appearance: { readonly titleBarOverlay: boolean }, + ) => Effect.Effect, ) => Effect.Effect; } >()("@t3tools/desktop/electron/ElectronWindow") {} @@ -145,6 +148,7 @@ export const make = Effect.gen(function* () { yield* Effect.addFinalizer(() => Effect.sync(() => windowsForegroundFocus?.close())); const mainWindowRef = yield* Ref.make>(Option.none()); const auxiliaryWindows = new WeakSet(); + const titleBarOverlayWindows = new WeakSet(); const listWindows = Effect.try({ try: () => Electron.BrowserWindow.getAllWindows(), @@ -241,6 +245,9 @@ export const make = Effect.gen(function* () { try: () => { const window = new Electron.BrowserWindow(options); if (behavior?.auxiliary === true) auxiliaryWindows.add(window); + if (options.titleBarOverlay !== undefined && options.titleBarOverlay !== false) { + titleBarOverlayWindows.add(window); + } return window; }, catch: (cause) => new ElectronWindowCreateError({ options: diagnosticOptions, cause }), @@ -383,14 +390,17 @@ export const make = Effect.gen(function* () { } }), syncAllAppearance: Effect.fn("desktop.electron.window.syncAllAppearance")(function* ( - sync: (window: Electron.BrowserWindow) => Effect.Effect, + sync: ( + window: Electron.BrowserWindow, + appearance: { readonly titleBarOverlay: boolean }, + ) => Effect.Effect, ) { const windows = yield* listWindows; for (const window of windows) { if (yield* isWindowDestroyed(window)) { continue; } - yield* sync(window); + yield* sync(window, { titleBarOverlay: titleBarOverlayWindows.has(window) }); } }), }); diff --git a/apps/desktop/src/window/DesktopWindow.test.ts b/apps/desktop/src/window/DesktopWindow.test.ts index 497b315ef5225..0ddcc7946d32b 100644 --- a/apps/desktop/src/window/DesktopWindow.test.ts +++ b/apps/desktop/src/window/DesktopWindow.test.ts @@ -207,17 +207,20 @@ const layerElectronTheme = Layer.succeed(ElectronTheme.ElectronTheme, { onUpdated: () => Effect.void, } satisfies ElectronTheme.ElectronTheme["Service"]); -const layerDesktopEnvironment = DesktopEnvironment.layer(environmentInput).pipe( - Layer.provide( - Layer.mergeAll( - NodeServices.layer, - DesktopConfig.layerTest({ - T3CODE_PORT: "3773", - VITE_DEV_SERVER_URL: "http://127.0.0.1:5733", - }), +const layerDesktopEnvironmentFor = (platform: NodeJS.Platform) => + DesktopEnvironment.layer({ ...environmentInput, platform }).pipe( + Layer.provide( + Layer.mergeAll( + NodeServices.layer, + DesktopConfig.layerTest({ + T3CODE_PORT: "3773", + VITE_DEV_SERVER_URL: "http://127.0.0.1:5733", + }), + ), ), - ), -); + ); + +const layerDesktopEnvironment = layerDesktopEnvironmentFor(environmentInput.platform); const desktopWindowBoundsEquivalence = Schema.toEquivalence( DesktopAppSettings.DesktopWindowBoundsSchema, @@ -243,6 +246,11 @@ function layerTest(input: { readonly previewZoomReapplies?: number[]; readonly onReveal?: (window: Electron.BrowserWindow) => void; readonly focusedWindow?: Electron.BrowserWindow; + readonly platform?: NodeJS.Platform; + readonly appearanceWindows?: ReadonlyArray<{ + readonly window: Electron.BrowserWindow; + readonly titleBarOverlay: boolean; + }>; readonly primaryPasteEnabled?: boolean; readonly previewPasskeyBridgeEnabled?: boolean; }) { @@ -302,7 +310,14 @@ function layerTest(input: { reveal: (window) => Effect.sync(() => input.onReveal?.(window)), sendAll: () => Effect.void, destroyAll: input.destroyAll ?? Effect.void, - syncAllAppearance: (sync) => sync(input.window), + syncAllAppearance: (sync) => + input.appearanceWindows === undefined + ? sync(input.window, { titleBarOverlay: true }) + : Effect.forEach( + input.appearanceWindows, + ({ window, titleBarOverlay }) => sync(window, { titleBarOverlay }), + { discard: true }, + ), } satisfies ElectronWindow.ElectronWindow["Service"]); return DesktopWindow.layer.pipe( @@ -314,7 +329,9 @@ function layerTest(input: { recordMetrics: () => Effect.void, shutdown: Effect.void, }), - layerDesktopEnvironment, + input.platform === undefined + ? layerDesktopEnvironment + : layerDesktopEnvironmentFor(input.platform), layerDesktopAppSettings, layerDesktopClientSettings, layerDesktopServerExposure, @@ -423,7 +440,8 @@ const makeSplashScenario = (createOutcomes: readonly (Electron.BrowserWindow | n reveal: (window) => Ref.update(revealedWindows, (windows) => [...windows, window]), sendAll: () => Effect.void, destroyAll: Effect.void, - syncAllAppearance: (sync) => (fallbackWindow ? sync(fallbackWindow) : Effect.void), + syncAllAppearance: (sync) => + fallbackWindow ? sync(fallbackWindow, { titleBarOverlay: true }) : Effect.void, } satisfies ElectronWindow.ElectronWindow["Service"]; const layer = DesktopWindow.layer.pipe( @@ -1170,6 +1188,36 @@ describe("DesktopWindow", () => { }), ); + it.effect("updates title bar overlays only on windows created with one", () => + Effect.gen(function* () { + const framed = makeFakeBrowserWindow(); + vi.mocked(framed.window.setTitleBarOverlay).mockImplementation(() => { + throw new TypeError("Titlebar overlay is not enabled"); + }); + const overlay = makeFakeBrowserWindow(); + const layer = layerTest({ + window: overlay.window, + createCount: yield* Ref.make(0), + mainWindow: yield* Ref.make>(Option.none()), + platform: "win32", + appearanceWindows: [ + { window: framed.window, titleBarOverlay: false }, + { window: overlay.window, titleBarOverlay: true }, + ], + }); + + yield* Effect.gen(function* () { + const desktopWindow = yield* DesktopWindow.DesktopWindow; + yield* desktopWindow.syncAppearance; + }).pipe(Effect.provide(layer)); + + assert.deepEqual(vi.mocked(framed.window.setBackgroundColor).mock.calls, [["#ffffff"]]); + assert.lengthOf(vi.mocked(framed.window.setTitleBarOverlay).mock.calls, 0); + assert.deepEqual(vi.mocked(overlay.window.setBackgroundColor).mock.calls, [["#ffffff"]]); + assert.lengthOf(vi.mocked(overlay.window.setTitleBarOverlay).mock.calls, 1); + }), + ); + it.effect.each([ { window: "normal", write: "flush" }, { window: "normal", write: "debounce" }, diff --git a/apps/desktop/src/window/DesktopWindow.ts b/apps/desktop/src/window/DesktopWindow.ts index 43404e2d6cfb9..93b35eb9b1a32 100644 --- a/apps/desktop/src/window/DesktopWindow.ts +++ b/apps/desktop/src/window/DesktopWindow.ts @@ -343,6 +343,7 @@ function getWindowTitleBarOptions( function syncWindowAppearance( window: Electron.BrowserWindow, + hasTitleBarOverlay: boolean, shouldUseDarkColors: boolean, platform: NodeJS.Platform, ): Effect.Effect { @@ -353,7 +354,7 @@ function syncWindowAppearance( window.setBackgroundColor(getInitialWindowBackgroundColor(shouldUseDarkColors)); const { titleBarOverlay } = getWindowTitleBarOptions(shouldUseDarkColors, platform); - if (typeof titleBarOverlay === "object") { + if (hasTitleBarOverlay && typeof titleBarOverlay === "object") { window.setTitleBarOverlay(titleBarOverlay); } }); @@ -1202,8 +1203,8 @@ export const make = Effect.gen(function* () { }), syncAppearance: Effect.gen(function* () { const shouldUseDarkColors = yield* electronTheme.shouldUseDarkColors; - yield* electronWindow.syncAllAppearance((window) => - syncWindowAppearance(window, shouldUseDarkColors, environment.platform), + yield* electronWindow.syncAllAppearance((window, { titleBarOverlay }) => + syncWindowAppearance(window, titleBarOverlay, shouldUseDarkColors, environment.platform), ); }).pipe(Effect.withSpan("desktop.window.syncAppearance")), }); From bb9fde6fcda7f6f3416c3948ed802b56bd8d0cb5 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 05:43:28 -0600 Subject: [PATCH 064/192] fix(scripts): test:cached no longer crashes when vp writes no cache summary (#28651) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- scripts/lib/cached-tests.test.ts | 75 +++++++++++++++++++++++++------- scripts/lib/cached-tests.ts | 37 +++++++++++++++- scripts/test-cached.ts | 22 ++++------ 3 files changed, 104 insertions(+), 30 deletions(-) diff --git a/scripts/lib/cached-tests.test.ts b/scripts/lib/cached-tests.test.ts index 7079006ddd055..80d45d9a1a86c 100644 --- a/scripts/lib/cached-tests.test.ts +++ b/scripts/lib/cached-tests.test.ts @@ -6,7 +6,7 @@ import * as NodeOS from "node:os"; import * as NodePath from "node:path"; import * as NodeURL from "node:url"; -import { cachedTestTasks, matchesTestPath } from "./cached-tests.ts"; +import { cachedTestTasks, matchesTestPath, readRunSummary } from "./cached-tests.ts"; describe("cachedTestTasks", () => { it("selects every test for a checkout-root path", () => { @@ -22,9 +22,11 @@ describe("cachedTestTasks", () => { expect(matchesTestPath("apps/server-other/a.test.ts", ["apps/server"])).toBe(false); }); it("keeps a file's cache task stable when unrelated tests are added", () => { - const first = cachedTestTasks(JSON.stringify({ files: ["src/b.test.ts"], groupSize: 1 })); + const first = cachedTestTasks( + JSON.stringify({ run: "r", files: ["src/b.test.ts"], groupSize: 1 }), + ); const second = cachedTestTasks( - JSON.stringify({ files: ["src/a.test.ts", "src/b.test.ts"], groupSize: 1 }), + JSON.stringify({ run: "r", files: ["src/a.test.ts", "src/b.test.ts"], groupSize: 1 }), ); const key = Object.keys(first).find((key) => key !== "test-cache")!; expect(second[key]).toEqual(first[key]); @@ -32,24 +34,28 @@ describe("cachedTestTasks", () => { it("keeps shell metacharacters in filenames out of the command", () => { const tasks = cachedTestTasks( - JSON.stringify({ files: ["src/a'$(touch injected).test.ts"], groupSize: 1 }), + JSON.stringify({ run: "r", files: ["src/a'$(touch injected).test.ts"], groupSize: 1 }), ); const task = Object.entries(tasks).find(([key]) => key !== "test-cache")![1]; expect(task.command).toBe("vp test run 'src/a'\"'\"'$(touch injected).test.ts'"); }); - it("rejects paths outside the checkout and empty groups", () => { + it("rejects paths outside the checkout, empty groups, and unsafe run ids", () => { for (const request of [ - { files: ["../elsewhere.test.ts"], groupSize: 1 }, - { files: ["/absolute.test.ts"], groupSize: 1 }, - { files: ["src/a.test.ts"], groupSize: 0 }, + { run: "r", files: ["../elsewhere.test.ts"], groupSize: 1 }, + { run: "r", files: ["/absolute.test.ts"], groupSize: 1 }, + { run: "r", files: ["src/a.test.ts"], groupSize: 0 }, + { files: ["src/a.test.ts"], groupSize: 1 }, + { run: "r; touch injected", files: ["src/a.test.ts"], groupSize: 1 }, ]) { expect(() => cachedTestTasks(JSON.stringify(request))).toThrow(); } }); it("accepts spec files discovered by Vitest", () => { - const tasks = cachedTestTasks(JSON.stringify({ files: ["src/example.spec.ts"], groupSize: 1 })); + const tasks = cachedTestTasks( + JSON.stringify({ run: "r", files: ["src/example.spec.ts"], groupSize: 1 }), + ); expect( Object.values(tasks).some((task) => task.command === "vp test run 'src/example.spec.ts'"), ).toBe(true); @@ -59,11 +65,45 @@ describe("cachedTestTasks", () => { expect(cachedTestTasks(undefined)).toEqual({}); }); + it("reads only the task summary of the given run", async () => { + const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cached-tests-")); + const write = async (version: string, run: string) => { + const file = NodePath.join(directory, version, "last-summary.json"); + await NodeFSP.mkdir(NodePath.dirname(file)); + const tasks = cachedTestTasks(JSON.stringify({ run, files: ["a.test.ts"], groupSize: 1 })); + await NodeFSP.writeFile( + file, + JSON.stringify({ + tasks: Object.entries(tasks).map(([task_name, task]) => ({ + task_name, + command: task.command, + result: {}, + })), + }), + ); + }; + try { + expect(readRunSummary(NodePath.join(directory, "missing"), "own")).toBeUndefined(); + await NodeFSP.writeFile(NodePath.join(directory, "file"), ""); + expect(readRunSummary(NodePath.join(directory, "file"), "own")).toBeUndefined(); + await write("v18", "other"); + expect(readRunSummary(directory, "own")).toBeUndefined(); + await write("v19", "own"); + await NodeFSP.mkdir(NodePath.join(directory, "corrupt")); + await NodeFSP.writeFile(NodePath.join(directory, "corrupt", "last-summary.json"), "{"); + expect(readRunSummary(directory, "own")?.tasks).toHaveLength(3); + } finally { + await NodeFSP.rm(directory, { recursive: true, force: true }); + } + }); + it("reuses individual results, invalidates filesystem inputs, and never caches failures", async () => { const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cached-tests-")); const root = NodePath.resolve(import.meta.dirname, "../.."); + let runId = 0; const run = (files = ["a.test.ts", "b.test.ts"], uncached: Array = []) => new Promise((resolve, reject) => { + runId += 1; const child = NodeChildProcess.spawn( process.execPath, [NodePath.join(root, "node_modules/vite-plus/dist/bin.js"), "run", "test-cache"], @@ -73,7 +113,12 @@ describe("cachedTestTasks", () => { ...process.env, NODE_OPTIONS: "", VP_REMOTE_CACHE: "off", - T3_CACHED_TESTS: JSON.stringify({ files, groupSize: 1, uncached }), + T3_CACHED_TESTS: JSON.stringify({ + run: String(runId), + files, + groupSize: 1, + uncached, + }), VITE_CACHE_PATH: NodePath.join(directory, "cache"), PATH: `${NodePath.join(root, "node_modules/.bin")}${NodePath.delimiter}${process.env.PATH}`, }, @@ -83,12 +128,10 @@ describe("cachedTestTasks", () => { child.once("error", reject); child.once("close", resolve); }); - const summary = async () => { - const value: { tasks: Array<{ result: Record }> } = JSON.parse( - await NodeFSP.readFile(NodePath.join(directory, "cache/v18/last-summary.json"), "utf8"), - ); - return value.tasks.filter((task) => "CacheHit" in task.result).length; - }; + const summary = async () => + readRunSummary(NodePath.join(directory, "cache"), String(runId))!.tasks.filter( + (task) => task.result.CacheHit, + ).length; try { await NodeFSP.mkdir(NodePath.join(directory, "node_modules")); await NodeFSP.symlink( diff --git a/scripts/lib/cached-tests.ts b/scripts/lib/cached-tests.ts index a21d13bd83d83..de99cf1ca62d9 100644 --- a/scripts/lib/cached-tests.ts +++ b/scripts/lib/cached-tests.ts @@ -15,6 +15,36 @@ interface CachedTestTask { }; } +interface TaskRunSummary { + tasks: Array<{ + task_name: string; + command: string; + result: { CacheHit?: { source: string; saved_duration_ms: number } }; + }>; +} + +// vp keeps one last-summary.json per cache, rewritten by every `vp run`, so a run is +// recognized by an uncached task that every test depends on and that carries its id. +const runTask = "test-cache:run"; +const runCommand = (run: string) => `node -e "" ${run}`; + +export function readRunSummary(cacheRoot: string, run: string): TaskRunSummary | undefined { + try { + for (const entry of NodeFS.readdirSync(cacheRoot)) { + try { + const summary: TaskRunSummary = JSON.parse( + NodeFS.readFileSync(NodePath.join(cacheRoot, entry, "last-summary.json"), "utf8"), + ); + if (summary.tasks.some((task) => task.command === runCommand(run))) return summary; + } catch { + continue; + } + } + } catch { + return undefined; + } +} + export function matchesTestPath(file: string, filters: Array) { return ( filters.length === 0 || @@ -36,7 +66,10 @@ export function cachedTestTasks( !("groupSize" in request) || typeof request.groupSize !== "number" || !Number.isSafeInteger(request.groupSize) || - request.groupSize < 1 + request.groupSize < 1 || + !("run" in request) || + typeof request.run !== "string" || + !/^[\w-]+$/.test(request.run) ) { throw new Error("Invalid cached test request"); } @@ -95,6 +128,7 @@ export function cachedTestTasks( for (const group of groups) { const key = NodeCrypto.createHash("sha256").update(group.join("\0")).digest("hex"); tasks[`test-cache:${key}`] = { + dependsOn: [runTask], command: `${command} ${group.map((file) => `'${NodePath.relative(directory, NodePath.join(root, file)).replaceAll("'", "'\"'\"'")}'`).join(" ")}`, ...(directory === root ? {} : { cwd: NodePath.relative(root, directory) }), cache: uncached.includes(group[0]!) @@ -113,6 +147,7 @@ export function cachedTestTasks( } return { ...tasks, + [runTask]: { command: runCommand(request.run), cache: false }, "test-cache": { command: 'node -e ""', dependsOn: Object.keys(tasks), cache: false }, }; } diff --git a/scripts/test-cached.ts b/scripts/test-cached.ts index 5c52833961d51..2ca43954f8f63 100644 --- a/scripts/test-cached.ts +++ b/scripts/test-cached.ts @@ -5,7 +5,7 @@ import * as NodeFS from "node:fs"; import * as NodePath from "node:path"; import * as NodeUtil from "node:util"; -import { matchesTestPath } from "./lib/cached-tests.ts"; +import { matchesTestPath, readRunSummary } from "./lib/cached-tests.ts"; import { shardedTestFiles } from "./lib/sharded-tests.ts"; const root = NodePath.resolve(import.meta.dirname, ".."); @@ -66,9 +66,11 @@ if (files.length === 0) { console.log( `Cached tests: ${files.length} files, ${groupSize} per task, concurrency ${concurrency}`, ); +const run = NodeCrypto.randomBytes(16).toString("hex"); const env = { ...process.env, T3_CACHED_TESTS: JSON.stringify({ + run, files, groupSize, uncached: values.uncached.map((file) => NodePath.relative(root, NodePath.resolve(file))), @@ -90,24 +92,18 @@ process.exitCode = result.status ?? 1; if (verifyHits > 0) { const cacheRoot = process.env.VITE_CACHE_PATH || NodePath.join(root, "node_modules/.vite/task-cache"); - const summaryFiles = NodeFS.readdirSync(cacheRoot, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => NodePath.join(cacheRoot, entry.name, "last-summary.json")) - .filter((file) => NodeFS.existsSync(file)) - .sort((a, b) => NodeFS.statSync(b).mtimeMs - NodeFS.statSync(a).mtimeMs); - const summary: { - tasks: Array<{ - task_name: string; - result: { CacheHit?: { source: string; saved_duration_ms: number } }; - }>; - } = JSON.parse(NodeFS.readFileSync(summaryFiles[0]!, "utf8")); + const summary = readRunSummary(cacheRoot, run); + if (!summary) { + console.error(`No task summary from this run in ${cacheRoot}`); + process.exitCode ||= 1; + } const rank = (task: string) => NodeCrypto.createHash("sha256") .update( `${process.env.GITHUB_RUN_ID || "local"}:${process.env.GITHUB_RUN_ATTEMPT || "1"}:${task}`, ) .digest("hex"); - const hits = summary.tasks.filter((task) => task.result.CacheHit); + const hits = (summary?.tasks ?? []).filter((task) => task.result.CacheHit); const verify = hits .toSorted((a, b) => rank(a.task_name).localeCompare(rank(b.task_name))) .slice(0, verifyHits); From 8a45c51d8b2796ebda1621e51becef1645e41f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Miguel?= Date: Sat, 14 Nov 2026 11:44:14 +0000 Subject: [PATCH 065/192] fix(desktop): Browser Screenshot saves fresh pixels while T3 Code is hidden (#36671) Co-authored-by: Julius Marminge --- apps/desktop/src/preview/Manager.test.ts | 176 +++++++++++++++++++++++ apps/desktop/src/preview/Manager.ts | 99 ++++++++++++- 2 files changed, 273 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/preview/Manager.test.ts b/apps/desktop/src/preview/Manager.test.ts index 802cd315b8e60..b6387a06c22d8 100644 --- a/apps/desktop/src/preview/Manager.test.ts +++ b/apps/desktop/src/preview/Manager.test.ts @@ -349,6 +349,47 @@ const makeTestPreviewWebContents = ( } as unknown as TestPreviewWebContents; }; +const makeStillImage = (pixels = "fresh-png") => ({ + toPNG: () => Buffer.from(pixels), + toJPEG: () => Buffer.from(pixels), + getSize: () => ({ width: 100, height: 80 }), +}); + +const makeTestStillImageWebContents = (...args: Parameters) => { + const wc = makeTestPreviewWebContents(...args); + Object.assign(wc, { isDevToolsOpened: () => false }); + holdStillImagePaint(wc, Promise.resolve()); + return wc; +}; + +const holdStillImagePaint = ( + wc: TestPreviewWebContents, + paint: Promise | Array>, +) => { + vi.mocked(wc.debugger.sendCommand).mockImplementation(async (method) => { + if (method === "Page.getFrameTree") return { frameTree: { frame: { id: "main" } } }; + if (method === "Page.createIsolatedWorld") return { executionContextId: 7 }; + if (method === "Runtime.evaluate") { + await (Array.isArray(paint) ? paint.shift() : paint); + return { result: { value: true } }; + } + return undefined; + }); +}; + +const setupStill = (manager: PreviewManager.PreviewManager["Service"], paint = Promise.resolve()) => + Effect.gen(function* () { + const image = makeStillImage(); + const capturePage = vi.fn<() => Promise>().mockResolvedValue(image); + const wc = makeTestStillImageWebContents(capturePage); + holdStillImagePaint(wc, paint); + fromId.mockReturnValue(wc); + yield* manager.createTab("tab_still"); + yield* manager.registerWebview("tab_still", wc.id); + yield* manager.setColorScheme("tab_still", "system"); + return { image, capturePage, wc, capture: manager.captureScreenshot("tab_still") }; + }); + const setupRecordingRaceTabs = (manager: PreviewManager.PreviewManager["Service"]) => Effect.gen(function* () { const capturePage = vi.fn(async () => ({ @@ -1824,6 +1865,30 @@ describe("PreviewManager", () => { ), ); + effectIt.effect("captures final pixels after warmup fails and paint readiness settles", () => + withManager((manager) => + Effect.gen(function* () { + const paint = Promise.withResolvers(); + const { capturePage, wc, capture } = yield* setupStill(manager, paint.promise); + capturePage + .mockRejectedValueOnce(new Error("UnknownVizError")) + .mockRejectedValueOnce(new Error("UnknownVizError")) + .mockRejectedValueOnce(new Error("UnknownVizError")) + .mockResolvedValue(makeStillImage("final-png")); + const fiber = yield* capture.pipe(Effect.forkChild({ startImmediately: true })); + yield* TestClock.adjust(300); + expect(capturePage).toHaveBeenCalledTimes(3); + expect(writeFile).not.toHaveBeenCalled(); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false]]); + paint.resolve(); + const artifact = yield* Fiber.join(fiber); + expect(capturePage).toHaveBeenCalledTimes(4); + expect(writeFile.mock.calls).toEqual([[artifact.path, Buffer.from("final-png")]]); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false], [true]]); + }), + ), + ); + effectIt.effect("preserves both screenshots of the same site in the same millisecond", () => withManager((manager) => Effect.gen(function* () { @@ -1837,6 +1902,8 @@ describe("PreviewManager", () => { const capturePage = vi .fn<() => Promise>>() .mockResolvedValueOnce(pngImage(firstPng)) + .mockResolvedValueOnce(pngImage(firstPng)) + .mockResolvedValueOnce(pngImage(secondPng)) .mockResolvedValueOnce(pngImage(secondPng)); fromId.mockReturnValue(makeTestPreviewWebContents(capturePage)); yield* manager.createTab("tab_1"); @@ -1857,6 +1924,115 @@ describe("PreviewManager", () => { ), ); + effectIt.effect( + "waits for paint before capturing and restores rendering after overlapping requests", + () => + withManager((manager) => + Effect.gen(function* () { + const paints = [Promise.withResolvers(), Promise.withResolvers()]; + const pendingPaints = paints.map((paint) => paint.promise); + const { image, capturePage, wc, capture } = yield* setupStill(manager); + holdStillImagePaint(wc, pendingPaints); + const first = Promise.withResolvers(); + const second = Promise.withResolvers(); + capturePage + .mockResolvedValueOnce(makeStillImage("discarded-warmup")) + .mockResolvedValueOnce(makeStillImage("discarded-warmup")) + .mockReturnValueOnce(first.promise) + .mockReturnValueOnce(second.promise); + const hostThrottling = vi.fn(); + yield* manager.setMainWindow({ + isDestroyed: () => false, + once: vi.fn(), + webContents: { setBackgroundThrottling: hostThrottling }, + } as never); + const one = yield* capture.pipe(Effect.forkChild({ startImmediately: true })); + const two = yield* capture.pipe(Effect.forkChild({ startImmediately: true })); + yield* TestClock.adjust(0); + expect(capturePage).toHaveBeenCalledTimes(2); + expect(pendingPaints).toHaveLength(0); + expect(writeFile).not.toHaveBeenCalled(); + expect(hostThrottling.mock.calls).toEqual([[false]]); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false]]); + paints.forEach((paint) => paint.resolve()); + yield* TestClock.adjust(0); + expect(capturePage).toHaveBeenCalledTimes(4); + first.resolve(image); + yield* Fiber.join(one); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false]]); + second.resolve(image); + yield* Fiber.join(two); + const saved = writeFile.mock.calls.map(([, bytes]) => bytes); + expect(saved).toEqual([image.toPNG(), image.toPNG()]); + expect(hostThrottling.mock.calls).toEqual([[false], [true]]); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false], [true]]); + }), + ), + ); + + effectIt.effect("keeps picture-in-picture rendering after a still image completes", () => + withManager((manager) => + Effect.gen(function* () { + const { image, capturePage, wc, capture } = yield* setupStill(manager); + const pending = Promise.withResolvers(); + capturePage + .mockResolvedValueOnce(image) + .mockResolvedValueOnce(makeStillImage("discarded-warmup")) + .mockReturnValueOnce(pending.promise); + const { pictureInPictureWindow } = makeTestPictureInPictureWindow(); + browserWindowConstructor.mockImplementation(function () { + return pictureInPictureWindow; + }); + yield* manager.openPictureInPicture("tab_still"); + const still = yield* capture.pipe(Effect.forkChild({ startImmediately: true })); + yield* TestClock.adjust(0); + expect(capturePage).toHaveBeenCalledTimes(3); + expect(writeFile).not.toHaveBeenCalled(); + pending.resolve(image); + yield* Fiber.join(still); + expect(writeFile.mock.calls.map((call) => call[1])).toEqual([Buffer.from("fresh-png")]); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false]]); + yield* manager.closePictureInPicture("tab_still"); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false], [true]]); + }), + ), + ); + + effectIt.effect("keeps a pending still image rendering when recording ends", () => + withManager((manager) => + Effect.gen(function* () { + const { image, capturePage, wc, capture } = yield* setupStill(manager); + const pending = Promise.withResolvers(); + capturePage.mockResolvedValueOnce(image).mockReturnValue(pending.promise); + yield* manager.startRecording("tab_still"); + const fiber = yield* capture.pipe(Effect.forkChild({ startImmediately: true })); + yield* TestClock.adjust(0); + yield* manager.stopRecording("tab_still"); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false]]); + pending.resolve(image); + yield* Fiber.join(fiber); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false], [true]]); + }), + ), + ); + + effectIt.effect("saves available pixels when paint readiness never settles", () => + withManager((manager) => + Effect.gen(function* () { + const paint = Promise.withResolvers(); + const { capturePage, wc, capture } = yield* setupStill(manager, paint.promise); + const first = yield* Effect.exit(capture).pipe( + Effect.forkChild({ startImmediately: true }), + ); + yield* TestClock.adjust(1_001); + expect(Exit.isSuccess(yield* Fiber.join(first))).toBe(true); + expect(capturePage).toHaveBeenCalledTimes(2); + expect(writeFile.mock.calls.map((call) => call[1])).toEqual([Buffer.from("fresh-png")]); + expect(wc.setBackgroundThrottling.mock.calls).toEqual([[false], [true]]); + }), + ), + ); + effectIt.effect("keeps every recorded guest unthrottled until its frame capture stops", () => withManager((manager) => Effect.gen(function* () { diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index eba18ab631dd0..2c199e358fa5b 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -387,7 +387,8 @@ interface ManagedListeners { readonly webContents: Electron.WebContents; } -type FrameCaptureConsumer = "picture-in-picture" | "recording"; +// Each still-image request owns a separate, short-lived rendering lease. +type FrameCaptureConsumer = "picture-in-picture" | "recording" | symbol; interface FrameCaptureSession { readonly recordingInputOptions?: RecordingInputOptions; @@ -2651,7 +2652,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function const [createdAt, millis, image] = yield* Effect.all([ currentIso, currentMillis, - capturePageWithRetry( + captureStillImage( { operation: "captureScreenshot.capturePage", tabId, @@ -2699,6 +2700,100 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function }; }); + const captureStillImage = Effect.fn("PreviewManager.captureStillImage")(function* ( + errorContext: PreviewOperationContext, + tabId: string, + wc: Electron.WebContents, + ) { + const consumer = Symbol("still-image"); + return yield* Effect.acquireUseRelease( + startFrameCapture(tabId, consumer), + () => + Effect.gen(function* () { + // A DOM mutation can precede its compositor frame even in a shown + // window. Keep the original guest rendering through the next paint; + // a hidden guest otherwise returns old pixels or never completes. + const probe = Effect.fn("PreviewManager.probeStillImagePaint")(function* ( + send: ( + method: string, + params?: Record, + ) => Effect.Effect, + ) { + // CDP executes against the current document without Electron's + // did-stop-loading gate. Isolate scheduling globals from page code. + const tree = (yield* send("Page.getFrameTree")) as + | { frameTree?: { frame?: { id?: string } } } + | undefined; + const frameId = tree?.frameTree?.frame?.id; + if (typeof frameId !== "string") + return yield* new PreviewOperationError({ + ...errorContext, + cause: new Error("The preview paint frame is unavailable."), + }); + const world = (yield* send("Page.createIsolatedWorld", { + frameId, + worldName: "t3-preview-still-image", + })) as { executionContextId?: number } | undefined; + if (typeof world?.executionContextId !== "number") + return yield* new PreviewOperationError({ + ...errorContext, + cause: new Error("The preview paint context is unavailable."), + }); + const response = (yield* send("Runtime.evaluate", { + // Settle and release scheduled work when only timers keep running. + // The outer deadline still bounds a frozen renderer. + expression: `new Promise(resolve => { let frame; const timer = setTimeout(() => { cancelAnimationFrame(frame); resolve(false); }, ${CAPTURE_PAGE_ATTEMPT_TIMEOUT_MS}); frame = requestAnimationFrame(() => { frame = requestAnimationFrame(() => { clearTimeout(timer); resolve(true); }); }); })`, + contextId: world.executionContextId, + awaitPromise: true, + returnByValue: true, + })) as { result?: { value?: unknown }; exceptionDetails?: unknown } | undefined; + if (response?.exceptionDetails || response?.result?.value !== true) + return yield* new PreviewOperationError({ + ...errorContext, + cause: new Error("The preview paint boundary was not confirmed."), + }); + }); + const paintReady = Effect.gen(function* () { + // Only use an existing owned session; never attach or displace + // another debugger to obtain a screenshot. + const control = (yield* SynchronizedRef.get(controlSessionsRef)).get(wc.id); + if (!control) + return yield* new PreviewOperationError({ + ...errorContext, + cause: new Error("The preview paint session is unavailable."), + }); + return yield* probe((method, params) => + attemptPromise(errorContext, () => control.debugger.sendCommand(method, params)), + ); + }).pipe( + Effect.timeout(CAPTURE_PAGE_ATTEMPT_TIMEOUT_MS), + Effect.catch((error) => + Effect.logWarning( + "Preview paint readiness was not confirmed; capturing available pixels with mutation freshness unconfirmed.", + { tabId, webContentsId: wc.id, error }, + ), + ), + ); + // Minimized guests can suspend frames even with throttling disabled. + // A native capture can resume them; discard its pixels and read again + // after the bounded paint probe. + const warmup = capturePageWithRetry(errorContext, tabId, wc).pipe( + Effect.catchTags({ + PreviewOperationError: (error) => + Effect.logWarning("Preview warmup capture failed; continuing with final capture.", { + tabId, + webContentsId: wc.id, + error, + }), + }), + ); + yield* Effect.all([paintReady, warmup], { concurrency: 2, discard: true }); + return yield* capturePageWithRetry(errorContext, tabId, wc); + }), + () => stopFrameCapture(tabId, consumer), + ); + }); + const capturePreviewFrame = Effect.fn("PreviewManager.capturePreviewFrame")(function* ( tabId: string, ) { From 92c91aac8f40c454a740e861bd6b697cfc13600a Mon Sep 17 00:00:00 2001 From: Behzad Motaghi Date: Sat, 14 Nov 2026 12:47:19 +0100 Subject: [PATCH 066/192] fix(client-runtime): keep inherited qualifier values as PR search text (#28095) Co-authored-by: Julius Marminge --- packages/client-runtime/src/pullRequestQuery.test.ts | 4 ++++ packages/client-runtime/src/pullRequestQuery.ts | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/client-runtime/src/pullRequestQuery.test.ts b/packages/client-runtime/src/pullRequestQuery.test.ts index 7d73813f3733c..d9a9fcee65047 100644 --- a/packages/client-runtime/src/pullRequestQuery.test.ts +++ b/packages/client-runtime/src/pullRequestQuery.test.ts @@ -40,6 +40,10 @@ describe("reading qualifiers out of a typed query", () => { const parsed = parsePullRequestQuery("milestone:v2 draft:maybe status: parser"); expect(parsed.filters).toEqual({ labels: [["milestone:v2"]] }); expect(parsed.text).toBe("draft:maybe status: parser"); + expect(parsePullRequestQuery("review:constructor checks:__proto__ Status:toString")).toEqual({ + text: "review:constructor checks:__proto__ Status:toString", + filters: {}, + }); }); it("reads a quoted name holding a comma as the one label it is", () => { diff --git a/packages/client-runtime/src/pullRequestQuery.ts b/packages/client-runtime/src/pullRequestQuery.ts index b80202c94b068..f89abff3a5bda 100644 --- a/packages/client-runtime/src/pullRequestQuery.ts +++ b/packages/client-runtime/src/pullRequestQuery.ts @@ -77,14 +77,18 @@ export function parsePullRequestQuery(raw: string): { draft = (value.toLowerCase() === "true") !== negated ? "only" : "hide"; continue; case "review": { - const decision = negated ? undefined : REVIEW_VALUES[value.toLowerCase()]; + const key = value.toLowerCase(); + const decision = + !negated && Object.hasOwn(REVIEW_VALUES, key) ? REVIEW_VALUES[key] : undefined; if (decision === undefined) break; review = decision; continue; } case "status": case "checks": { - const state = negated ? undefined : CHECKS_VALUES[value.toLowerCase()]; + const key = value.toLowerCase(); + const state = + !negated && Object.hasOwn(CHECKS_VALUES, key) ? CHECKS_VALUES[key] : undefined; if (state === undefined) break; checks = state; continue; From 0acca1de27f932a07344da2656cba7e58aa059c7 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 12:47:31 +0100 Subject: [PATCH 067/192] fix(server): Resume keeps unaccepted replacement images (#36677) Co-authored-by: Julius Marminge --- .../ProviderTurnStartService.test.ts | 31 ++++++++++++++++--- .../orchestration-v2/manualContinuation.ts | 22 +++++++++---- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index 88a0b64cd80d8..556e03d128712 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -413,6 +413,7 @@ function makeLocalCommandHarness(input: { readonly unsentResume?: boolean; readonly repeatedResume?: boolean; readonly acceptedResume?: boolean; + readonly restartedResume?: boolean; readonly nativeForkFrom?: "idle" | "busy"; readonly switchedProvider?: boolean; readonly threadRuntimeMode?: OrchestrationV2ThreadProjection["thread"]["runtimeMode"]; @@ -814,6 +815,18 @@ function makeLocalCommandHarness(input: { providerThreadId: oldProviderThreadId, status: "interrupted", }, + ...(input.sideChat.restartedResume + ? [ + { + ...projection.attempts[0]!, + id: RunAttemptId.make("unaccepted-replacement"), + runId: RunId.make("stopped-run"), + attemptOrdinal: 2, + reason: "steering_restart" as const, + status: "interrupted" as const, + }, + ] + : []), ], providerTurns: [ ...projection.providerTurns, @@ -1583,6 +1596,14 @@ effectIt.effect.each([ unsentResume: true, acceptedResume: true, }, + { + contextRun: "delivered", + runtimeMode: "full-access", + context: "", + unsentResume: true, + acceptedResume: true, + restartedResume: true, + }, { contextRun: "current", threadRuntimeMode: "approval-required", @@ -1604,14 +1625,14 @@ effectIt.effect.each([ expect(harness.open).toHaveBeenCalledWith( expect.objectContaining({ runtimePolicy: expect.objectContaining({ runtimeMode }) }), ); + const resendsOriginal = + "unsentResume" in sideChat && + (!("acceptedResume" in sideChat) || "restartedResume" in sideChat); expect(harness.startTurn.mock.calls.map(([turn]) => turn.message.text)).toEqual([ - withInlineContext( - context, - "unsentResume" in sideChat && !("acceptedResume" in sideChat) ? "Original request" : "", - ), + withInlineContext(context, resendsOriginal ? "Original request" : ""), ]); expect(harness.startTurn.mock.calls[0]?.[0].resume === true).toBe( - context === "" && (!("unsentResume" in sideChat) || "acceptedResume" in sideChat), + context === "" && !resendsOriginal, ); expect(harness.projection().contextHandoffs).toMatchObject([ { diff --git a/apps/server/src/orchestration-v2/manualContinuation.ts b/apps/server/src/orchestration-v2/manualContinuation.ts index ee255cb154eaa..9119e2aa7dad5 100644 --- a/apps/server/src/orchestration-v2/manualContinuation.ts +++ b/apps/server/src/orchestration-v2/manualContinuation.ts @@ -8,16 +8,26 @@ export function unsentManualContinuationSource( if (sourceId === undefined) return undefined; const runs = new Map(projection.runs.map((source) => [source.id, source])); const acceptedAttempts = new Set(projection.providerTurns.map((turn) => turn.runAttemptId)); - const acceptedRuns = new Set( - projection.attempts - .filter((attempt) => acceptedAttempts.has(attempt.id)) - .map((attempt) => attempt.runId), - ); + const latestAttempt = ( + runId: OrchestrationV2Run["id"], + matches: (attempt: (typeof projection.attempts)[number]) => boolean, + ) => + Math.max( + 0, + ...projection.attempts + .filter((attempt) => attempt.runId === runId && matches(attempt)) + .map((attempt) => attempt.attemptOrdinal), + ); const visited = new Set(); while (sourceId !== undefined && !visited.has(sourceId)) { visited.add(sourceId); const source = runs.get(sourceId); - if (source === undefined || acceptedRuns.has(sourceId)) return undefined; + if (source === undefined) return undefined; + const accepted = latestAttempt(sourceId, (attempt) => acceptedAttempts.has(attempt.id)); + // An accepted attempt delivered everything before it; only a later steering restart's message is unsent. + const restarted = latestAttempt(sourceId, (attempt) => attempt.reason === "steering_restart"); + if (restarted > accepted) return source; + if (accepted > 0) return undefined; if (source.manualContinuationOfRunId === undefined) return source; sourceId = source.manualContinuationOfRunId; } From 6effe74c59544b8b7971253d3c4f1522d0a365c0 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 12:47:44 +0100 Subject: [PATCH 068/192] fix(web): show repeated table rows and cells in ODT previews (#36689) --- .../components/files/FilePreviewPanel.test.ts | 23 +++++++++++ .../files/extendedDocumentPreview.ts | 35 ++++++++++------- pnpm-lock.yaml | 3 ++ scripts/package.json | 1 + ...review-expands-repeated-table-cells.e2e.ts | 39 +++++++++++++++++++ scripts/scenarios/t3.ts | 11 ++++-- 6 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 scripts/scenarios/odt-preview-expands-repeated-table-cells.e2e.ts diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index d9361ba21244a..cd14df70f3c48 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -407,6 +407,29 @@ Third}`; }, ); + const approved = "Approved now"; + const approvedRow = `${"

Approved now

".repeat(2)}`; + it.each([ + [ + "expands", + `${`${approved}`.repeat(2)}${approved}`, + approvedRow.repeat(3), + ], + [ + "bounds", + 'x', + `${"

x

".repeat(50)}`.repeat(500), + ], + ])("%s repeated ODT table rows and cells", async (_, rows, html) => { + const archive = new JSZip().file( + "content.xml", + `${rows}`, + ); + const result = await openDocument(archive, "odt"); + if (result.type !== "document") throw new Error("Expected document"); + expect(result.pages[0]!.html).toBe(`${html}
`); + }); + it.each(["odt", "odp"] as const)("reads %s text and rejects XML entities", async (kind) => { const archive = new JSZip().file( "content.xml", diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index 6b840a00543a6..16bc937b0cf31 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -9,8 +9,8 @@ import type JSZip from "jszip"; import { type OfficeWorkerResult, type NotebookCell } from "./officePreviewProtocol"; -const ODS_PREVIEW_MAX_ROWS = 500; -const ODS_PREVIEW_MAX_COLUMNS = 50; +const TABLE_PREVIEW_MAX_ROWS = 500; +const TABLE_PREVIEW_MAX_COLUMNS = 50; // `text` and `attrs` hold decoded values: escaped text is decoded once, CDATA stays literal. type XmlNode = { name: string; attrs: Record; children: XmlNode[]; text: string }; @@ -295,13 +295,20 @@ async function odfHtml(node: XmlNode, archive: JSZip): Promise { }; const tag = tags[node.name]; if (!tag) return children; - reserveMarkup(tag.length * 2 + 5); - return `<${tag}>${children}`; + const count = + tag === "tr" + ? repeat(node.attrs["table:number-rows-repeated"], TABLE_PREVIEW_MAX_ROWS) + : tag === "td" + ? repeat(node.attrs["table:number-columns-repeated"], TABLE_PREVIEW_MAX_COLUMNS) + : 1; + const markup = `<${tag}>${children}`; + reserveMarkup(count * markup.length - children.length); + return markup.repeat(count); } function repeat(value: string | undefined, max: number) { const count = Number(value ?? 1); if (!Number.isSafeInteger(count) || count < 1) - throw new Error("This spreadsheet contains invalid repeated cells."); + throw new Error("This document contains invalid repeated content."); return Math.min(count, max); } export async function openDocument( @@ -337,29 +344,29 @@ export async function openDocument( ""; const count = repeat( cell.attrs["table:number-columns-repeated"], - ODS_PREVIEW_MAX_COLUMNS + 1, + TABLE_PREVIEW_MAX_COLUMNS + 1, ); - if (value && cells.length + count > ODS_PREVIEW_MAX_COLUMNS) truncated = true; - for (let i = 0; i < count && cells.length < ODS_PREVIEW_MAX_COLUMNS; i++) { + if (value && cells.length + count > TABLE_PREVIEW_MAX_COLUMNS) truncated = true; + for (let i = 0; i < count && cells.length < TABLE_PREVIEW_MAX_COLUMNS; i++) { if (value.length > 10_000) throw new Error("A spreadsheet cell is too large to preview."); cells.push(value); } } while (cells.at(-1) === "") cells.pop(); - const count = repeat(row.attrs["table:number-rows-repeated"], ODS_PREVIEW_MAX_ROWS + 1); + const count = repeat(row.attrs["table:number-rows-repeated"], TABLE_PREVIEW_MAX_ROWS + 1); if (!cells.length) { - pendingEmptyRows = Math.min(ODS_PREVIEW_MAX_ROWS + 1, pendingEmptyRows + count); + pendingEmptyRows = Math.min(TABLE_PREVIEW_MAX_ROWS + 1, pendingEmptyRows + count); continue; } - while (pendingEmptyRows > 0 && rows.length < ODS_PREVIEW_MAX_ROWS) { + while (pendingEmptyRows > 0 && rows.length < TABLE_PREVIEW_MAX_ROWS) { rows.push([]); pendingEmptyRows--; } pendingEmptyRows = 0; - if (rows.length + count > ODS_PREVIEW_MAX_ROWS) truncated = true; - if (rows.length >= ODS_PREVIEW_MAX_ROWS) break; - for (let i = 0; i < count && rows.length < ODS_PREVIEW_MAX_ROWS; i++) { + if (rows.length + count > TABLE_PREVIEW_MAX_ROWS) truncated = true; + if (rows.length >= TABLE_PREVIEW_MAX_ROWS) break; + for (let i = 0; i < count && rows.length < TABLE_PREVIEW_MAX_ROWS; i++) { cellBytes += cells.reduce((size, cell) => size + cell.length, 0); if (cellBytes > 8 * 1024 * 1024) throw new Error("This spreadsheet has too much text to preview."); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52dd48bd551c5..aaed5f657312d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2569,6 +2569,9 @@ importers: esbuild: specifier: 0.28.2 version: 0.28.2 + jszip: + specifier: 3.10.1 + version: 3.10.1 postject: specifier: 1.0.0-alpha.6 version: 1.0.0-alpha.6 diff --git a/scripts/package.json b/scripts/package.json index 716a2edb3762f..9eb10db4c227e 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -29,6 +29,7 @@ "@types/plist": "catalog:", "@types/pngjs": "6.0.5", "esbuild": "0.28.2", + "jszip": "3.10.1", "postject": "1.0.0-alpha.6", "typescript-legacy": "npm:typescript@~6.0.3", "vite-plus": "catalog:" diff --git a/scripts/scenarios/odt-preview-expands-repeated-table-cells.e2e.ts b/scripts/scenarios/odt-preview-expands-repeated-table-cells.e2e.ts new file mode 100644 index 0000000000000..c8efdb146e67d --- /dev/null +++ b/scripts/scenarios/odt-preview-expands-repeated-table-cells.e2e.ts @@ -0,0 +1,39 @@ +import { expect } from "e2e"; +import JSZip from "jszip"; + +import { test, WEB } from "./t3.ts"; + +// A 2 by 2 table stored the compact way ODF allows: one row repeated twice, one cell repeated twice. +async function repeatedTableOdt() { + const cell = + 'Approved'; + return new JSZip() + .file("mimetype", "application/vnd.oasis.opendocument.text", { compression: "STORE" }) + .file( + "content.xml", + `${cell}`, + ) + .generateAsync({ type: "uint8array" }); +} + +test( + "An ODT preview shows every cell of a table stored with repeated rows and cells", + WEB, + async ({ t3, app, screen, browser }) => { + const project = await t3.project("demo", { "decisions.odt": await repeatedTableOdt() }); + const thread = await t3.thread({ project, title: "Review decisions", messages: ["Start"] }); + await t3.open(thread); + await expect(screen.getByText("fixture turn complete", { exact: false })).toBeVisible(); + + await browser.keyboard.press("Control+p"); + await screen.getByPlaceholder("Search files…").fill("decisions.odt"); + await screen.getByRole("option", "decisions.odt", { exact: false }).tap(); + + // The first document preview loads its worker, which a cold Vite cache optimizes on demand. + await expect(screen.getByText("1 page", { exact: false })).toBeVisible({ timeout: 120_000 }); + const document = browser.frameLocator('iframe[title^="decisions.odt"]'); + await expect(document.getByRole("cell", "Approved").first()).toBeVisible(); + await app.screenshot("table"); + await expect(document.getByRole("cell", "Approved")).toHaveCount(4); + }, +); diff --git a/scripts/scenarios/t3.ts b/scripts/scenarios/t3.ts index 24bb6e73d38b3..6a0a95ce66329 100644 --- a/scripts/scenarios/t3.ts +++ b/scripts/scenarios/t3.ts @@ -15,7 +15,10 @@ import { run } from "./shell.ts"; export interface T3State { /** Creates a git repository holding `files` and adds it as a project. Returns its path. */ - readonly project: (title: string, files?: Readonly>) => Promise; + readonly project: ( + title: string, + files?: Readonly>, + ) => Promise; /** * Starts a thread in a project and plays one replayed turn per message before returning. * `model` is a dev replay recording: showcase, todo-list, approval, question, multi-turn. @@ -69,7 +72,7 @@ export function makeState(home: string, env: NodeJS.ProcessEnv = {}) { const project = Effect.fn("scenario.project")(function* ( title: string, - files: Readonly>, + files: Readonly>, ) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; @@ -82,7 +85,9 @@ export function makeState(home: string, env: NodeJS.ProcessEnv = {}) { const workspace = path.join(parent, title); for (const [file, content] of Object.entries({ "README.md": "", ...files })) { yield* fs.makeDirectory(path.dirname(path.join(workspace, file)), { recursive: true }); - yield* fs.writeFileString(path.join(workspace, file), content); + yield* typeof content === "string" + ? fs.writeFileString(path.join(workspace, file), content) + : fs.writeFile(path.join(workspace, file), content); } const git = (args: ReadonlyArray) => run("git", args, { cwd: workspace }); const author = ["-c", "user.name=Scenario", "-c", "user.email=scenario@example.invalid"]; From f161a4d2b59ef461c7283f0824c279d10e533ff9 Mon Sep 17 00:00:00 2001 From: Behzad Motaghi Date: Sat, 14 Nov 2026 12:47:56 +0100 Subject: [PATCH 069/192] fix(shared): preserve literal ampersands in citation Markdown paths (#28103) Co-authored-by: Julius Marminge --- packages/shared/src/codexFileCitations.test.ts | 2 +- packages/shared/src/codexFileCitations.ts | 1 + packages/shared/src/codexMarkdownDirectives.test.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/codexFileCitations.test.ts b/packages/shared/src/codexFileCitations.test.ts index bddbc8337315c..df52912d037cc 100644 --- a/packages/shared/src/codexFileCitations.test.ts +++ b/packages/shared/src/codexFileCitations.test.ts @@ -64,7 +64,7 @@ describe("codexFileCitationMarkdown", () => { path: "reports/*draft*_[copy]`<&.txt", }); expect(citation && codexFileCitationMarkdown(citation)).toBe( - "[\\*draft\\*\\_\\[copy\\]\\`\\<\\&.txt]()", + "[\\*draft\\*\\_\\[copy\\]\\`\\<\\&.txt]()", ); }); }); diff --git a/packages/shared/src/codexFileCitations.ts b/packages/shared/src/codexFileCitations.ts index d29996501ea68..15cf5ec13ece8 100644 --- a/packages/shared/src/codexFileCitations.ts +++ b/packages/shared/src/codexFileCitations.ts @@ -45,6 +45,7 @@ function markdownLabel(value: string): string { function markdownDestination(value: string): string { return value .replaceAll("\\", "%5C") + .replaceAll("&", "%26") .replaceAll("<", "%3C") .replaceAll(">", "%3E") .replaceAll("\r", "%0D") diff --git a/packages/shared/src/codexMarkdownDirectives.test.ts b/packages/shared/src/codexMarkdownDirectives.test.ts index 4db9e7c4b86d2..d4f4c5014a8a8 100644 --- a/packages/shared/src/codexMarkdownDirectives.test.ts +++ b/packages/shared/src/codexMarkdownDirectives.test.ts @@ -106,6 +106,16 @@ describe.each([ expect(link?.type).toBe("link"); expect(parseMarkdownFileLink(link?.url ?? "")).toEqual({ path, line: 7 }); }); + + it("keeps an entity-like ampersand in the path literal", () => { + const markdown = render( + ':codex-file-citation{path="/tmp/a&copy;.txt" line_range_start="7"}', + ); + const link = parseOrdinaryMarkdown(markdown).children?.[0]?.children?.[0]; + + expect(link?.type).toBe("link"); + expect(parseMarkdownFileLink(link?.url ?? "")).toEqual({ path: "/tmp/a©.txt", line: 7 }); + }); }); describe("native Markdown adapters", () => { From fd6d0ea022f1ff6c109f68a8f8db9d199142a0cf Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 05:48:35 -0600 Subject: [PATCH 070/192] fix(web): stashing waits for pasted images to finish compressing (#28308) Co-authored-by: Julius Marminge --- .../chat/useComposerStash.browser.test.tsx | 89 +++++++++++++++++++ .../src/components/chat/useComposerStash.ts | 11 +++ 2 files changed, 100 insertions(+) create mode 100644 apps/web/src/components/chat/useComposerStash.browser.test.tsx diff --git a/apps/web/src/components/chat/useComposerStash.browser.test.tsx b/apps/web/src/components/chat/useComposerStash.browser.test.tsx new file mode 100644 index 0000000000000..63b5d4615196d --- /dev/null +++ b/apps/web/src/components/chat/useComposerStash.browser.test.tsx @@ -0,0 +1,89 @@ +import { EnvironmentId, ThreadId } from "@t3tools/contracts"; +import { expect, it, vi } from "vite-plus/test"; + +import { act, render } from "../../../test/render"; +import { DraftId, useComposerDraftStore } from "../../composerDraftStore"; +import { removeLocalStorageItem } from "../../hooks/useLocalStorage"; +import { isMacPlatform } from "../../lib/utils"; +import { + PROMPT_STASH_STORAGE_KEY, + usePromptStashStore, + writePromptStashStorageForTest, +} from "../../promptStashStore"; +import { + addPreparingComposerImages, + countPreparingComposerImages, + removePreparingComposerImages, +} from "./preparingComposerImages"; +import { useComposerStash } from "./useComposerStash"; + +type StashInput = Parameters[0]; + +function Harness({ input }: { input: StashInput }) { + useComposerStash(input); + return null; +} + +it("keeps the draft when stashing while a pasted image is still compressing", async () => { + const target = DraftId.make("stash-preparing-image"); + using _draft = { + [Symbol.dispose]: () => useComposerDraftStore.getState().clearComposerContent(target), + }; + using _stash = { + [Symbol.dispose]: () => { + writePromptStashStorageForTest(""); + removeLocalStorageItem(PROMPT_STASH_STORAGE_KEY); + }, + }; + addPreparingComposerImages(target, [ + { + id: "pasted", + name: "shot.png", + file: new File(["png"], "shot.png", { type: "image/png" }), + hidden: false, + }, + ]); + using _preparing = { + [Symbol.dispose]: () => removePreparingComposerImages(target, new Set(["pasted"])), + }; + const environmentId = EnvironmentId.make("stash-env"); + const promptRef = { current: "Describe this screenshot" }; + const input = { + environmentId, + composerDraftTarget: target, + promptRef, + readAttachmentDraft: () => ({ images: [], files: [] }), + readTerminalContexts: () => [], + routeThreadRef: { environmentId, threadId: ThreadId.make("stash-thread") }, + projectSelectionRequired: false, + pendingUserInputs: [], + activePendingProgress: null, + prompt: promptRef.current, + composerMenuOpen: false, + isComposerCollapsedMobile: false, + composerPreviewAnnotations: [], + composerReviewComments: [], + composerThreadContexts: [], + attachmentTargetKey: target, + isComposerDraftTargetOpen: () => true, + composerEditorRef: { current: null }, + isRevertingCheckpoint: () => false, + pendingThreadImportsRef: { current: new Map() }, + setComposerCursor: vi.fn(), + setComposerTrigger: vi.fn(), + importContextRecords: () => new Map(), + expandMobileComposer: vi.fn(), + } satisfies StashInput; + await using _screen = await render(); + + const mod = isMacPlatform(navigator.platform) ? { metaKey: true } : { ctrlKey: true }; + await act(() => { + window.dispatchEvent( + new KeyboardEvent("keydown", { key: "s", ...mod, bubbles: true, cancelable: true }), + ); + }); + + expect(usePromptStashStore.getState().entries).toEqual([]); + expect(promptRef.current).toBe("Describe this screenshot"); + expect(countPreparingComposerImages(target)).toBe(1); +}); diff --git a/apps/web/src/components/chat/useComposerStash.ts b/apps/web/src/components/chat/useComposerStash.ts index 7d389d0ad9e64..1a765515775dd 100644 --- a/apps/web/src/components/chat/useComposerStash.ts +++ b/apps/web/src/components/chat/useComposerStash.ts @@ -33,6 +33,7 @@ import { compressImageForStash } from "../../lib/imageCompression"; import { formatInlineContextReference } from "~/lib/composerContextReferences"; import { isCommandPaletteOpen } from "../../commandPaletteBus"; import { pendingDraftWork } from "./pendingDraftWork"; +import { countPreparingComposerImages } from "./preparingComposerImages"; import { isResponseContextRecord, previewAnnotationContextRecord, @@ -502,6 +503,16 @@ export function useComposerStash(input: { } return; } + // The snapshot only holds compressed images; a still-compressing one would land in the + // emptied composer instead of travelling with this entry. + if (countPreparingComposerImages(attachmentTargetKey) > 0) { + toastManager.add({ + type: "info", + title: "Still compressing a pasted image.", + description: "Stash again once it finishes compressing.", + }); + return; + } const stashedFiles: PersistedComposerFileAttachment[] = []; for (const file of files) { if (composerFileNeedsReattach(file)) { From ead133ce70eb5302f2f58516bbfb283f2f5ccdfd Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 05:50:26 -0600 Subject: [PATCH 071/192] fix(web): SSH port field no longer turns 22.5 into port 22 (#28297) Co-authored-by: Julius Marminge --- .../settings/ConnectionsSettings.logic.test.ts | 9 +++++++++ .../src/components/settings/ConnectionsSettings.logic.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/settings/ConnectionsSettings.logic.test.ts b/apps/web/src/components/settings/ConnectionsSettings.logic.test.ts index 4cd58ef38af64..a664dc99a8f42 100644 --- a/apps/web/src/components/settings/ConnectionsSettings.logic.test.ts +++ b/apps/web/src/components/settings/ConnectionsSettings.logic.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vite-plus/test"; import { applyWslEnableSelection, isServerNetworkAccessible, + parseManualDesktopSshTarget, resolveAccessEnvironmentId, resolveTailscaleServeBaseUrl, selectQrEndpointOption, @@ -44,6 +45,14 @@ describe("togglePairingScopeSelection", () => { }); }); +describe("parseManualDesktopSshTarget", () => { + it.each(["22.5", "1e3", "2222garbage"])("rejects explicit port %j", (port) => { + expect(() => parseManualDesktopSshTarget({ host: "devbox", username: "", port })).toThrow( + "SSH port must be between 1 and 65535.", + ); + }); +}); + describe("resolveTailscaleServeBaseUrl", () => { it.each([ ["https://host.tail.ts.net:441/", 443, "https://host.tail.ts.net"], diff --git a/apps/web/src/components/settings/ConnectionsSettings.logic.ts b/apps/web/src/components/settings/ConnectionsSettings.logic.ts index 28d3b8f2df126..f78ef29347191 100644 --- a/apps/web/src/components/settings/ConnectionsSettings.logic.ts +++ b/apps/web/src/components/settings/ConnectionsSettings.logic.ts @@ -214,7 +214,7 @@ export function parseManualDesktopSshTarget(input: { const rawPort = input.port.trim(); if (rawPort.length > 0) { - port = Number.parseInt(rawPort, 10); + port = /^\d+$/u.test(rawPort) ? Number.parseInt(rawPort, 10) : Number.NaN; } if (hostname.length === 0) { From ae837398a2548064aac2001b715670c404ee5f30 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 05:55:29 -0600 Subject: [PATCH 072/192] fix(server): analytics record the thread binding a scheduled task actually got (#28761) Co-authored-by: Julius Marminge --- .../src/telemetry/ProviderDimensions.test.ts | 13 +++++++++++++ apps/server/src/telemetry/ProviderDimensions.ts | 15 ++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/server/src/telemetry/ProviderDimensions.test.ts b/apps/server/src/telemetry/ProviderDimensions.test.ts index d88010f600e35..00e59c11228cb 100644 --- a/apps/server/src/telemetry/ProviderDimensions.test.ts +++ b/apps/server/src/telemetry/ProviderDimensions.test.ts @@ -76,3 +76,16 @@ it("drops input strings outside the schema's literals, since rejected calls are handoffSettings("t3_thread_launch", { workspaceStrategy: { type: "/home/me/secret" } }, {}), ).toEqual({ targetChosen: false, workspace: "root" }); }); + +it("records the binding the saved scheduled task resolved to, and none for failures", () => { + expect(handoffSettings("schedule_task", {}, { boundThreadId: null })).toEqual({ + bindToCurrentThread: false, + }); + expect( + handoffSettings( + "schedule_task", + { bindToCurrentThread: true }, + { _tag: "OrchestratorMcpFailure", code: "invalid_request", message: "Different project." }, + ), + ).toEqual({}); +}); diff --git a/apps/server/src/telemetry/ProviderDimensions.ts b/apps/server/src/telemetry/ProviderDimensions.ts index f81a22e844aef..8352ed0d15cc0 100644 --- a/apps/server/src/telemetry/ProviderDimensions.ts +++ b/apps/server/src/telemetry/ProviderDimensions.ts @@ -128,9 +128,9 @@ function callerOrigin(input: { } /** - * Settings the agent chose for a handoff, read only from closed enums and - * booleans in the tool's arguments and result. Prompts, titles, and ids are - * never read. + * Settings for a handoff, recorded only as closed enums and booleans derived + * from the tool's arguments and result. Prompts, titles, and ids are never + * recorded. */ export function handoffSettings(tool: string, args: unknown, result: unknown): Fields { const input = asFields(args); @@ -166,8 +166,13 @@ export function handoffSettings(tool: string, args: unknown, result: unknown): F return defined({ delivery: stringField(output, "delivery") }); case "t3_thread_send_attachments": return { delivery: "auto" }; - case "schedule_task": - return { bindToCurrentThread: input.bindToCurrentThread !== false }; + case "schedule_task": { + // Omitting the input lets the server decide, so read the saved task. + const bound = output.boundThreadId; + return bound === null || typeof bound === "string" + ? { bindToCurrentThread: bound !== null } + : {}; + } default: return {}; } From e096deebf6ffd5f7ab72a5c4d01128cf7ae067f5 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 05:56:49 -0600 Subject: [PATCH 073/192] fix(web): the publish-repository path no longer submits on an IME composition Enter (#28277) Co-authored-by: Julius Marminge --- ...ctionsControl.composition.browser.test.tsx | 82 +++++++++++++++++++ apps/web/src/components/GitActionsControl.tsx | 12 ++- 2 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/components/GitActionsControl.composition.browser.test.tsx diff --git a/apps/web/src/components/GitActionsControl.composition.browser.test.tsx b/apps/web/src/components/GitActionsControl.composition.browser.test.tsx new file mode 100644 index 0000000000000..82e522045add8 --- /dev/null +++ b/apps/web/src/components/GitActionsControl.composition.browser.test.tsx @@ -0,0 +1,82 @@ +import { SourceControlProviderKind } from "@t3tools/contracts"; +import * as Option from "effect/Option"; +import { expect, it, vi } from "vite-plus/test"; +import { page, userEvent } from "vite-plus/test/browser"; + +import { act, render } from "../../test/render"; +import { PublishRepositoryDialog } from "./GitActionsControl"; + +const publish = vi.hoisted(() => ({ run: vi.fn() })); + +vi.mock("~/state/query", () => ({ + useEnvironmentQuery: () => ({ + data: { + versionControlSystems: [], + sourceControlProviders: [ + { + kind: SourceControlProviderKind.make("github"), + label: "GitHub", + status: "available", + version: Option.none(), + installHint: "Install gh", + detail: Option.none(), + auth: { + status: "authenticated", + account: Option.none(), + host: Option.none(), + detail: Option.none(), + }, + }, + ], + }, + }), +})); + +vi.mock("~/state/sourceControlActions", () => ({ + useGitStackedAction: vi.fn(), + useSourceControlActionRunning: vi.fn(), + useVcsInitAction: vi.fn(), + useVcsPullAction: vi.fn(), + useSourceControlPublishRepositoryAction: () => ({ + isAllowed: true, + isPending: false, + run: publish.run, + }), +})); + +function setup() { + publish.run.mockReset(); + publish.run.mockReturnValue(new Promise(() => {})); +} + +it("keeps composition Enter in the repository path from publishing", async () => { + setup(); + await using _screen = await render( + {}} + environmentId={null} + threadRef={null} + gitCwd="/repo" + offeredProviders={undefined} + />, + ); + await page.getByRole("button", { name: "Next" }).click(); + const path = page.getByRole("textbox", { name: "Repository" }); + await path.fill("octo/repo"); + + for (const nativeEvent of [{ isComposing: true }, { keyCode: 229 }]) { + const event = new KeyboardEvent("keydown", { + key: "Enter", + ...nativeEvent, + bubbles: true, + cancelable: true, + }); + await act(() => path.element().dispatchEvent(event)); + expect(publish.run).not.toHaveBeenCalled(); + expect(event.defaultPrevented).toBe(false); + } + + await userEvent.keyboard("{Enter}"); + expect(publish.run).toHaveBeenCalledOnce(); +}); diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 7ae210230a612..3fe8374d0e12f 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -466,7 +466,7 @@ interface PublishRepositoryDialogProps { readonly offeredProviders: OfferedSourceControlProviders | undefined; } -function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { +export function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { const openLink = useOpenLink(props.threadRef); const navigate = useNavigate(); const sourceControlDiscovery = useEnvironmentQuery( @@ -738,10 +738,14 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { setPublishRepositoryOverride(event.target.value); }} onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - submitPublishRepository(); + if (event.key !== "Enter") { + return; } + if (event.nativeEvent.isComposing || event.keyCode === 229) { + return; + } + event.preventDefault(); + submitPublishRepository(); }} placeholder={publishPathPlaceholder} disabled={publishRepositoryAction.isPending} From 96735d7311d857728a3e1171ae651b7510814803 Mon Sep 17 00:00:00 2001 From: tsmith165 Date: Sat, 14 Nov 2026 03:57:00 -0800 Subject: [PATCH 074/192] fix(web): picking a sidebar search result scrolls the list to that thread (#27109) Co-authored-by: tsmith165 <16846767+tsmith165@users.noreply.github.com> Co-authored-by: Julius Marminge --- .../components/Sidebar.drag.browser.test.tsx | 36 +++++++++++++++++++ .../components/sidebar/SidebarThreadRow.tsx | 11 +++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/Sidebar.drag.browser.test.tsx b/apps/web/src/components/Sidebar.drag.browser.test.tsx index c5811df12b765..8151de08a7110 100644 --- a/apps/web/src/components/Sidebar.drag.browser.test.tsx +++ b/apps/web/src/components/Sidebar.drag.browser.test.tsx @@ -16,6 +16,8 @@ import { type FakeDriver, } from "../../test/fakeEnvironment"; import { act, render } from "../../test/render"; +import { makeThreadFixture } from "../test-fixtures"; +import { SidebarSearchResultRow } from "./sidebar/SidebarThreadRow"; // The socket is the only fake layer; see test/appScenarios.renders.tsx. vi.mock("../../../../packages/client-runtime/src/connection/driver.ts", async (importOriginal) => { @@ -86,3 +88,37 @@ it("keeps the title when dragging a search result from a collapsed folder", asyn ).toBe(second.title); }); }); + +it("keeps focus in the search box when a click picks a search result", async () => { + const onSelect = vi.fn(); + await using screen = await render( + <> + +
    + {}} + onSelect={onSelect} + onFileDropThreads={() => {}} + /> +
+ , + ); + const search = screen.getByRole("textbox", { name: "Search threads" }); + await userEvent.click(search); + await userEvent.click(screen.getByRole("option")); + expect(onSelect).toHaveBeenCalledOnce(); + // Picking clears the results; a focused result would drop focus, and its recovery scrolls the list. + await expect.element(search).toHaveFocus(); +}); diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx index 9098782006ef3..db896481cf857 100644 --- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx @@ -1419,9 +1419,11 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP data-thread-item={threadKey} {...sortableRootProps} {...(fileDropHandlers ?? {})} + // The hints are the rendered heights inside py-0.5. Revealing the open thread measures rows + // that have not rendered yet, so a hint off by a pixel lands it a pixel short per row above. className={cn( "list-none py-0.5 [content-visibility:auto] [contain-intrinsic-size:auto_78px]", - metaLineEmpty && "[contain-intrinsic-size:auto_58px]", + metaLineEmpty && "[contain-intrinsic-size:auto_60px]", sortable?.isDragging && "relative z-20", )} > @@ -1746,6 +1748,13 @@ export const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props aria-label={accessibility.label} {...dragListeners} onMouseMove={props.onHighlight} + // Focus stays in the search box, as for Enter. A focused result would lose focus as + // the results close, and its recovery scrolls the list away from the opened thread. + // dnd-kit ignores a prevented press, so the drag listener runs first. + onPointerDown={(event) => { + dragListeners?.onPointerDown?.(event); + event.preventDefault(); + }} onClick={props.onSelect} className={cn( "group/sidebar-row group/v2-row flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 py-1 text-left text-sm outline-none", From c3517ec0bfe40f8be3cf9a453bb523dbcac4030a Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 04:01:54 -0800 Subject: [PATCH 075/192] fix(shared): theme CSS checks allow url() inside a quoted data: address (#36696) --- packages/shared/src/themeCss.test.ts | 17 ++++++++++++++++- packages/shared/src/themeCss.ts | 16 ++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/themeCss.test.ts b/packages/shared/src/themeCss.test.ts index e3a4f1efeb1b4..80656a8207bbb 100644 --- a/packages/shared/src/themeCss.test.ts +++ b/packages/shared/src/themeCss.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vite-plus/test"; -import { themeCssValueFetches } from "./themeCss.ts"; +import { themeCssLoadsRemoteFiles, themeCssValueFetches } from "./themeCss.ts"; + +// %23 decodes to # inside the SVG, so url(%23g) names its own gradient. +const SVG_WITH_OWN_GRADIENT = + "data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22%3E%3ClinearGradient id=%22g%22/%3E%3Crect fill=%22url(%23g)%22/%3E%3C/svg%3E"; describe("themeCssValueFetches", () => { it.each([ @@ -14,6 +18,9 @@ describe("themeCssValueFetches", () => { // A no-break space before the scheme makes this a path on the app's own origin. ["background-image", 'url("\u00a0data:/../../probe")', true], ["background-image", 'url("data:image/png;base64,AA")', false], + ["background-image", `url("${SVG_WITH_OWN_GRADIENT}")`, false], + // The url( sits inside a string, so the address after that string is a real remote url(). + ["content", `"url('data:" url("https://evil.example/a") "'"`, true], ["filter", 'url("#blur")', false], ["background-image", "var(--pattern)", false], ["color", "red", false], @@ -21,3 +28,11 @@ describe("themeCssValueFetches", () => { expect(themeCssValueFetches(value)).toBe(fetches); }); }); + +describe("themeCssLoadsRemoteFiles", () => { + it("allows a url() inside a data: address", () => { + expect( + themeCssLoadsRemoteFiles(`.repro { background-image: url('${SVG_WITH_OWN_GRADIENT}'); }`), + ).toBe(false); + }); +}); diff --git a/packages/shared/src/themeCss.ts b/packages/shared/src/themeCss.ts index 48b1259d0377b..a36fba06b9dd8 100644 --- a/packages/shared/src/themeCss.ts +++ b/packages/shared/src/themeCss.ts @@ -3,6 +3,18 @@ import { THEME_COLOR_ROLES } from "./themePalettes.ts"; const FETCHES_BY_STRING = /image-set\(|\bsrc\(/i; // Only CSS whitespace may come first. \s also matches U+00A0, which makes the address relative. const URL_NOT_LOCAL = /url\((?![ \t\n\f\r]*["']?(?:data:|#))/i; +// Read tokens left to right, as CSS does: a url( inside a string loads nothing, and a quote in an +// unquoted url( is not a string. A quoted url() that is not data: or #id keeps `url(` for the check. +const URL_OR_STRING = + /url\((?![ \t\n\f\r]*["'])[^)]*|url\([ \t\n\f\r]*("[^"\n\r\f]*"|'[^'\n\r\f]*')|"[^"\n\r\f]*"|'[^'\n\r\f]*'/gi; + +function fetchesByText(code: string): boolean { + const outsideStrings = code.replace(URL_OR_STRING, (token, urlString: string | undefined) => { + if (urlString !== undefined) return /^["'](?:data:|#)/i.test(urlString) ? "" : "url("; + return token.startsWith('"') || token.startsWith("'") ? "" : token; + }); + return FETCHES_BY_STRING.test(outsideStrings) || URL_NOT_LOCAL.test(outsideStrings); +} /** * Whether one declaration value can load a remote file. Clients drop those declarations. @@ -10,13 +22,13 @@ const URL_NOT_LOCAL = /url\((?![ \t\n\f\r]*["']?(?:data:|#))/i; * so every value that still has a backslash is refused. */ export function themeCssValueFetches(value: string): boolean { - return value.includes("\\") || FETCHES_BY_STRING.test(value) || URL_NOT_LOCAL.test(value); + return value.includes("\\") || fetchesByText(value); } /** A text check for early feedback. CSS escapes can dodge it; the client check is the guard. */ export function themeCssLoadsRemoteFiles(css: string): boolean { const code = css.replace(/\/\*[\s\S]*?\*\//g, ""); - return /@import/i.test(code) || FETCHES_BY_STRING.test(code) || URL_NOT_LOCAL.test(code); + return /@import/i.test(code) || fetchesByText(code); } /** From 0e5e0bb6527c5ac45c415ce4ae35dfecf9dd7ce0 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 06:02:29 -0600 Subject: [PATCH 076/192] fix(mobile): removing an environment no longer fails when a queued message can't be read (#28322) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../environment-owned-data-cleanup.test.ts | 74 +++++++++++++++++++ .../environment-owned-data-cleanup.ts | 48 ++++++++++++ apps/mobile/src/connection/platform.ts | 30 +------- 3 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 apps/mobile/src/connection/environment-owned-data-cleanup.test.ts create mode 100644 apps/mobile/src/connection/environment-owned-data-cleanup.ts diff --git a/apps/mobile/src/connection/environment-owned-data-cleanup.test.ts b/apps/mobile/src/connection/environment-owned-data-cleanup.test.ts new file mode 100644 index 0000000000000..efe489b0fde35 --- /dev/null +++ b/apps/mobile/src/connection/environment-owned-data-cleanup.test.ts @@ -0,0 +1,74 @@ +import { Persistence } from "@t3tools/client-runtime/platform"; +import { EnvironmentId } from "@t3tools/contracts"; +import { describe, expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; +import { vi } from "vite-plus/test"; + +const harness = vi.hoisted(() => ({ + clearOutbox: vi.fn<(environmentId: string) => Promise>(), + clearDrafts: vi.fn<(environmentId: string) => Promise>(), + clearComposerErrors: vi.fn(), +})); + +vi.mock("../state/thread-outbox-removal", () => ({ + clearThreadOutboxEnvironment: harness.clearOutbox, +})); +vi.mock("../state/use-composer-drafts", () => ({ + clearComposerDraftsEnvironment: harness.clearDrafts, +})); +vi.mock("../state/thread-composer-error", () => ({ + clearThreadComposerErrorsForEnvironment: harness.clearComposerErrors, +})); + +import * as EnvironmentOwnedDataCleanup from "./environment-owned-data-cleanup"; + +const ENVIRONMENT_ID = EnvironmentId.make("environment-1"); + +describe("EnvironmentOwnedDataCleanup", () => { + it.effect("waits for the other clears and logs a bounded warning when one rejects", () => { + const warnings: Array = []; + let releaseDrafts = () => {}; + let draftsCleared = false; + // The drafts clear finishes only after the outbox failure is logged. + const logger = Logger.make(({ message }) => { + warnings.push(message); + releaseDrafts(); + }); + return Effect.gen(function* () { + harness.clearOutbox.mockRejectedValue(new Error("Some queued messages could not be read.")); + harness.clearDrafts.mockImplementation(() => + new Promise((resolve) => { + releaseDrafts = resolve; + }).then(() => { + draftsCleared = true; + }), + ); + + const cleanup = yield* Persistence.EnvironmentOwnedDataCleanup; + yield* cleanup.clear(ENVIRONMENT_ID); + + expect(draftsCleared).toBe(true); + expect(warnings).toEqual([ + [ + "Could not clear mobile environment-owned data.", + { + environmentId: ENVIRONMENT_ID, + operation: "outbox", + errorType: "error", + errorName: "Error", + stack: expect.any(String), + }, + ], + ]); + }).pipe( + Effect.provide( + Layer.merge( + EnvironmentOwnedDataCleanup.layer, + Logger.layer([logger], { mergeWithExisting: false }), + ), + ), + ); + }); +}); diff --git a/apps/mobile/src/connection/environment-owned-data-cleanup.ts b/apps/mobile/src/connection/environment-owned-data-cleanup.ts new file mode 100644 index 0000000000000..28c6cda29a4d3 --- /dev/null +++ b/apps/mobile/src/connection/environment-owned-data-cleanup.ts @@ -0,0 +1,48 @@ +import { safeErrorLogAttributes } from "@t3tools/client-runtime/errors"; +import { Persistence } from "@t3tools/client-runtime/platform"; +import type { EnvironmentId } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; + +import { clearThreadComposerErrorsForEnvironment } from "../state/thread-composer-error"; +import { clearThreadOutboxEnvironment } from "../state/thread-outbox-removal"; +import { clearComposerDraftsEnvironment } from "../state/use-composer-drafts"; + +class EnvironmentOwnedDataClearError extends Schema.TaggedError()( + "EnvironmentOwnedDataClearError", + { cause: Schema.Defect() }, +) {} + +export const layer = Layer.succeed( + Persistence.EnvironmentOwnedDataCleanup, + Persistence.EnvironmentOwnedDataCleanup.of({ + clear: Effect.fn("Persistence.EnvironmentOwnedDataCleanup.clear")(function (environmentId) { + // Each clear settles on its own: a failure must not interrupt a clear that is still running. + const settle = ( + operation: "outbox" | "drafts", + clear: (environmentId: EnvironmentId) => Promise, + ) => + Effect.tryPromise({ + try: () => clear(environmentId), + catch: (cause) => new EnvironmentOwnedDataClearError({ cause }), + }).pipe( + Effect.catch((error) => + Effect.logWarning("Could not clear mobile environment-owned data.", { + environmentId, + operation, + ...safeErrorLogAttributes(error.cause), + }), + ), + ); + return Effect.all( + [ + settle("outbox", clearThreadOutboxEnvironment), + settle("drafts", clearComposerDraftsEnvironment), + Effect.sync(() => clearThreadComposerErrorsForEnvironment(environmentId)), + ], + { concurrency: "unbounded", discard: true }, + ); + }), + }), +); diff --git a/apps/mobile/src/connection/platform.ts b/apps/mobile/src/connection/platform.ts index 407016ab484d9..de461f35ab104 100644 --- a/apps/mobile/src/connection/platform.ts +++ b/apps/mobile/src/connection/platform.ts @@ -1,8 +1,4 @@ -import { - ClientCapabilities, - PlatformConnectionSource, - Persistence, -} from "@t3tools/client-runtime/platform"; +import { ClientCapabilities, PlatformConnectionSource } from "@t3tools/client-runtime/platform"; import { ConnectionBlockedError, ConnectionTransientError, @@ -25,10 +21,8 @@ import { authClientMetadata } from "../lib/authClientMetadata"; import * as Runtime from "../lib/runtime"; import * as MobileStorage from "../persistence/mobile-storage"; import { appAtomRegistry } from "../state/atom-registry"; -import { clearThreadOutboxEnvironment } from "../state/thread-outbox-removal"; -import { clearComposerDraftsEnvironment } from "../state/use-composer-drafts"; -import { clearThreadComposerErrorsForEnvironment } from "../state/thread-composer-error"; import { mobileApplicationActiveWakeup } from "./app-state-wakeups"; +import * as EnvironmentOwnedDataCleanup from "./environment-owned-data-cleanup"; import * as ConnectionStorage from "./storage"; function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "online" { @@ -255,22 +249,6 @@ const layerPlatformConnectionSource = Layer.succeed( const layerProvidedConnectionStorage = ConnectionStorage.layer.pipe(Layer.provide(Runtime.layer)); const layerProvidedCapabilities = layerCapabilities.pipe(Layer.provide(Runtime.layer)); -const layerEnvironmentOwnedDataCleanup = Layer.succeed( - Persistence.EnvironmentOwnedDataCleanup, - Persistence.EnvironmentOwnedDataCleanup.of({ - clear: Effect.fn("Persistence.EnvironmentOwnedDataCleanup.clear")((environmentId) => - Effect.all( - [ - Effect.promise(() => clearThreadOutboxEnvironment(environmentId)), - Effect.promise(() => clearComposerDraftsEnvironment(environmentId)), - Effect.sync(() => clearThreadComposerErrorsForEnvironment(environmentId)), - ], - { concurrency: "unbounded", discard: true }, - ), - ), - }), -); - type ConnectionPlatformLayerSource = | typeof layerProvidedConnectionStorage | typeof Runtime.layer @@ -278,7 +256,7 @@ type ConnectionPlatformLayerSource = | typeof layerWakeups | typeof layerProvidedCapabilities | typeof layerPlatformConnectionSource - | typeof layerEnvironmentOwnedDataCleanup; + | typeof EnvironmentOwnedDataCleanup.layer; export const layer: Layer.Layer< Layer.Success, @@ -291,5 +269,5 @@ export const layer: Layer.Layer< layerWakeups, layerProvidedCapabilities, layerPlatformConnectionSource, - layerEnvironmentOwnedDataCleanup, + EnvironmentOwnedDataCleanup.layer, ); From 19f643856ee36a142e8f5b6d6600374740daa669 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:04:13 +0100 Subject: [PATCH 077/192] fix(server): interrupted checkpoint restores no longer leave files staged (#36687) Co-authored-by: Julius Marminge --- apps/server/src/processRunner.test.ts | 25 ++++++++ apps/server/src/processRunner.ts | 10 +++- apps/server/src/vcs/GitVcsDriver.test.ts | 43 ++++++++++++++ apps/server/src/vcs/GitVcsDriver.ts | 75 +++++++++++++++--------- apps/server/src/vcs/VcsProcess.ts | 2 + 5 files changed, 125 insertions(+), 30 deletions(-) diff --git a/apps/server/src/processRunner.test.ts b/apps/server/src/processRunner.test.ts index 3754f25a4c775..6768fa23c763b 100644 --- a/apps/server/src/processRunner.test.ts +++ b/apps/server/src/processRunner.test.ts @@ -2,6 +2,8 @@ import { isWindowsCommandNotFound } from "@t3tools/provider-core/server/snapshot import { describe, expect, it } from "@effect/vitest"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Cause from "effect/Cause"; import * as Layer from "effect/Layer"; import * as PlatformError from "effect/PlatformError"; import * as Sink from "effect/Sink"; @@ -143,6 +145,29 @@ describe("runProcess", () => { }), ); + it.effect("reports child startup before delivering cancellation during acquisition", () => + Effect.gen(function* () { + let started = false; + const spawner = makeSpawner(() => + Effect.withFiber((fiber) => + Effect.sync(() => { + fiber.interruptUnsafe(); + return makeHandle({}); + }), + ), + ); + const result = yield* runWith(spawner)({ + command: "git", + args: ["restore"], + onSpawn: () => { + started = true; + }, + }).pipe(Effect.exit); + expect(Exit.isFailure(result) && Cause.hasInterruptsOnly(result.cause)).toBe(true); + expect(started).toBe(true); + }).pipe(Effect.scoped), + ); + it.effect("fails when output exceeds max buffer in default mode", () => Effect.gen(function* () { const spawner = makeSpawner(() => Effect.succeed(makeHandle({ stdout: "x".repeat(2048) }))); diff --git a/apps/server/src/processRunner.ts b/apps/server/src/processRunner.ts index a345c67eca881..4c7efd3cc9942 100644 --- a/apps/server/src/processRunner.ts +++ b/apps/server/src/processRunner.ts @@ -26,6 +26,8 @@ export interface ProcessRunInput { readonly stdin?: string | undefined; /** Receives every stdout chunk, including bytes beyond the buffered output limit. */ readonly onStdoutChunk?: ((chunk: Uint8Array) => void) | undefined; + /** Runs after successful spawn, before cancellation can interrupt acquisition. */ + readonly onSpawn?: (() => void) | undefined; readonly maxOutputBytes?: number | undefined; readonly outputMode?: "error" | "truncate" | undefined; readonly truncatedMarker?: string | undefined; @@ -290,7 +292,7 @@ const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* ( cause, }); - const child = yield* spawner + const spawn = spawner .spawn( ChildProcess.make(spawnCommand.command, spawnCommand.args, { ...((input.spawnCwd ?? input.cwd) ? { cwd: input.spawnCwd ?? input.cwd } : {}), @@ -309,6 +311,12 @@ const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* ( // Synchronous spawn throws arrive as defects; recover them too so callers see typed spawn failures. Effect.catchDefect((cause) => Effect.fail(toSpawnError(cause))), ); + const child = yield* input.onSpawn === undefined + ? spawn + : spawn.pipe( + Effect.tap(() => Effect.sync(() => input.onSpawn?.())), + Effect.uninterruptible, + ); const stdin = input.stdin; const onStdoutChunk = input.onStdoutChunk; diff --git a/apps/server/src/vcs/GitVcsDriver.test.ts b/apps/server/src/vcs/GitVcsDriver.test.ts index 3e95a3658ef69..8912e9c235135 100644 --- a/apps/server/src/vcs/GitVcsDriver.test.ts +++ b/apps/server/src/vcs/GitVcsDriver.test.ts @@ -400,6 +400,49 @@ it.effect.each(["failure", "interrupt"] as const)( }).pipe(Effect.scoped, Effect.provide(layerGitContract)), ); +it.effect.each([ + { stop: "before git restore spawns", gate: "restore", index: "staged", file: "later\n" }, + { stop: "after git restore ran", gate: "clean", index: "head", file: "unstaged\n" }, +] as const)( + "checkpoint restore interrupted $stop leaves the $index index", + ({ gate, index, file }) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.makeVcsDriverShape(); + const liveProcess = yield* VcsProcess.VcsProcess; + const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-restore-interrupt-" }); + const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd); + yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); + yield* fs.writeFileString(path.join(cwd, "file.txt"), "later\n"); + yield* git(["add", "file.txt"]); + const stagedTree = (yield* git(["write-tree"])).stdout; + const entered = yield* Deferred.make(); + const gatedDriver = yield* GitVcsDriver.makeVcsDriverShape().pipe( + Effect.provideService(VcsProcess.VcsProcess, { + run: (input) => + input.operation === "GitVcsDriver.checkpoints.restoreCheckpoint" && + input.args.includes(gate) + ? Deferred.succeed(entered, undefined).pipe(Effect.andThen(Effect.never)) + : liveProcess.run(input), + }), + ); + const fiber = yield* gatedDriver.checkpoints + .restoreCheckpoint({ cwd, checkpointRef }) + .pipe(Effect.exit, Effect.forkScoped); + yield* Deferred.await(entered); + yield* Fiber.interrupt(fiber); + const result = yield* Fiber.await(fiber); + assert.isTrue(Exit.isFailure(result) && Cause.hasInterruptsOnly(result.cause)); + + assert.strictEqual( + (yield* git(["write-tree"])).stdout, + index === "head" ? (yield* git(["rev-parse", "HEAD^{tree}"])).stdout : stagedTree, + ); + assert.strictEqual(yield* fs.readFileString(path.join(cwd, "file.txt")), file); + }).pipe(Effect.scoped, Effect.provide(layerGitContract)), +); + it.effect("checkpoint publication falls back to rename where hardlinks are unavailable", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts index 14147854a2ddb..e02ae5bdba20d 100644 --- a/apps/server/src/vcs/GitVcsDriver.ts +++ b/apps/server/src/vcs/GitVcsDriver.ts @@ -4,6 +4,7 @@ import * as Context from "effect/Context"; import * as SecureRandom from "effect/crypto/SecureRandom"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; @@ -569,6 +570,7 @@ const gitCommand = Effect.fn("GitVcsDriver.gitCommand")( args: ReadonlyArray, options?: { readonly stdin?: string; + readonly onSpawn?: () => void; readonly env?: NodeJS.ProcessEnv; readonly allowNonZeroExit?: boolean; readonly timeoutMs?: number; @@ -584,6 +586,7 @@ const gitCommand = Effect.fn("GitVcsDriver.gitCommand")( cwd, spawnCwd: globalThis.process.cwd(), ...(options?.stdin !== undefined ? { stdin: options.stdin } : {}), + ...(options?.onSpawn !== undefined ? { onSpawn: options.onSpawn } : {}), ...(options?.env !== undefined ? { env: options.env } : {}), ...(options?.allowNonZeroExit !== undefined ? { allowNonZeroExit: options.allowNonZeroExit } @@ -655,6 +658,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( const execute: VcsDriver.VcsDriver["Service"]["execute"] = (input) => gitCommand(vcsProcess, input.operation, input.cwd, input.args, { ...(input.stdin !== undefined ? { stdin: input.stdin } : {}), + ...(input.onSpawn !== undefined ? { onSpawn: input.onSpawn } : {}), ...(input.env !== undefined ? { env: input.env } : {}), ...(input.allowNonZeroExit !== undefined ? { allowNonZeroExit: input.allowNonZeroExit } : {}), ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}), @@ -1725,15 +1729,48 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( cwd: input.cwd, args: ["ls-files", "--cached", `--with-tree=${commitOid}`, "-z", "--", "."], }); - // An empty index and checkpoint have nothing for git restore's pathspec to match. - if (tracked.stdout.length > 0) { - const sparseConfig = yield* execute({ + const sparseConfig = + tracked.stdout.length > 0 + ? yield* execute({ + operation, + cwd: input.cwd, + args: ["config", "--bool", "core.sparseCheckout"], + allowNonZeroExit: true, + }) + : null; + const sparseCheckout = sparseConfig?.stdout.trim() === "true"; + // Restoring away the last tracked file can remove a nested workspace directory. + const recreateWorkspace = fileSystem.makeDirectory(input.cwd, { recursive: true }).pipe( + Effect.mapError( + (cause) => + new VcsProcessExitError({ + operation, + command: "git restore", + cwd: input.cwd, + exitCode: 0, + detail: `Could not recreate the checkpoint workspace: ${cause.message}`, + }), + ), + ); + const resetIndex = Effect.gen(function* () { + // On Windows clean can remove './' and exit 0. + yield* recreateWorkspace; + yield* execute({ operation, cwd: input.cwd, - args: ["config", "--bool", "core.sparseCheckout"], - allowNonZeroExit: true, + args: [...longPaths, "reset", "--quiet", "--", "."], }); - const sparseCheckout = sparseConfig.stdout.trim() === "true"; + }); + let mutationStarted = false; + const onSpawn = () => { + mutationStarted = true; + }; + // Queued commands preserve user staging; spawned children stop before this scope resets it. + yield* Effect.addFinalizer((exit) => + mutationStarted && Exit.hasInterrupts(exit) ? resetIndex.pipe(Effect.ignore) : Effect.void, + ); + // An empty index and checkpoint have nothing for git restore's pathspec to match. + if (tracked.stdout.length > 0) { const restore = [...longPaths, "restore", "--source", commitOid, "--worktree", "--staged"]; // restore leaves skip-worktree paths alone. Outside a sparse checkout every such bit was // set by hand and capture recorded the file, so override them all. @@ -1741,6 +1778,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( operation, cwd: input.cwd, args: [...restore, ...(sparseCheckout ? [] : ["--ignore-skip-worktree-bits"]), "--", "."], + onSpawn, }); if (sparseCheckout) { // Sparse exclusions stay excluded; only skip-worktree paths the rules select are manual. @@ -1812,28 +1850,6 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( } } } - // Restoring away the last tracked file can remove a nested workspace directory. - const recreateWorkspace = fileSystem.makeDirectory(input.cwd, { recursive: true }).pipe( - Effect.mapError( - (cause) => - new VcsProcessExitError({ - operation, - command: "git restore", - cwd: input.cwd, - exitCode: 0, - detail: `Could not recreate the checkpoint workspace: ${cause.message}`, - }), - ), - ); - const resetIndex = Effect.gen(function* () { - // On Windows clean can remove './' and exit 0. - yield* recreateWorkspace; - yield* execute({ - operation, - cwd: input.cwd, - args: [...longPaths, "reset", "--quiet", "--", "."], - }); - }); yield* recreateWorkspace; const clean = execute({ operation, @@ -1841,6 +1857,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( args: [...longPaths, "clean", "-fd", "--", "."], env: { LC_ALL: "C" }, allowNonZeroExit: true, + onSpawn, }); let cleaned = yield* clean; // Removing a new .gitignore exposes the files it hid; clean again until nothing is left. @@ -1894,7 +1911,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( yield* resetIndex; return true; - }), + }, Effect.scoped), diffCheckpoints: Effect.fn("GitVcsDriver.checkpoints.diffCheckpoints")(function* (input) { const operation = "GitVcsDriver.checkpoints.diffCheckpoints"; diff --git a/apps/server/src/vcs/VcsProcess.ts b/apps/server/src/vcs/VcsProcess.ts index 4bfdcc90037ec..da2f988ce1ed8 100644 --- a/apps/server/src/vcs/VcsProcess.ts +++ b/apps/server/src/vcs/VcsProcess.ts @@ -33,6 +33,7 @@ export interface VcsProcessInput { readonly spawnCwd?: string; readonly stdin?: string; readonly onStdoutChunk?: (chunk: Uint8Array) => void; + readonly onSpawn?: () => void; readonly env?: NodeJS.ProcessEnv; readonly allowNonZeroExit?: boolean; readonly timeoutMs?: number; @@ -232,6 +233,7 @@ export const make = Effect.gen(function* () { ...(input.spawnCwd !== undefined ? { spawnCwd: input.spawnCwd } : {}), ...(input.stdin !== undefined ? { stdin: input.stdin } : {}), ...(input.onStdoutChunk !== undefined ? { onStdoutChunk: input.onStdoutChunk } : {}), + ...(input.onSpawn !== undefined ? { onSpawn: input.onSpawn } : {}), env: { ...(input.command === "git" ? unsetInheritedRepositoryEnv(process.env, hostPlatform) From 71c2410a672d8417e299886794913414b1a85587 Mon Sep 17 00:00:00 2001 From: Claudesaul <139932571+Claudesaul@users.noreply.github.com> Date: Sat, 14 Nov 2026 07:04:24 -0500 Subject: [PATCH 078/192] fix(web): thread details slides in on close (#28929) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../components/chat/PanelLayoutControls.tsx | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/chat/PanelLayoutControls.tsx b/apps/web/src/components/chat/PanelLayoutControls.tsx index 4db623bcbb07d..7252ead1d0fb4 100644 --- a/apps/web/src/components/chat/PanelLayoutControls.tsx +++ b/apps/web/src/components/chat/PanelLayoutControls.tsx @@ -5,7 +5,7 @@ import { Maximize2Icon, Minimize2Icon, } from "lucide-react"; -import { memo, type ReactNode } from "react"; +import { memo, type ReactNode, useLayoutEffect, useRef } from "react"; import { cn } from "~/lib/utils"; @@ -15,6 +15,9 @@ import { useMobileSidebarSheetMounted } from "../ui/sidebar"; import { Toggle } from "../ui/toggle"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +/** Matches the right inset of `ThreadPanelHeaderControl`. */ +const THREAD_PANEL_HEADER_INSET_PX = 12; + export interface PanelLayoutControlsProps { showThreadPanelControl?: boolean; showTerminalControl?: boolean; @@ -68,7 +71,7 @@ export const PanelLayoutControls = memo(function PanelLayoutControls({ ); const threadPanelControl = ( - }> + }> {threadPanelPresentation === "popover" ? ( ) : ( @@ -195,9 +198,32 @@ export function WorkspaceTitlebarControls(props: { readonly onToggleMaximized: () => void; readonly children: ReactNode; }) { + const { rightPanelOpen, maximized } = props; + const controlsRef = useRef(null); + const previous = useRef({ rightPanelOpen, maximized }); + // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- starts the toggle's slide before the commit that swapped it in paints; the animation is cancelled on change or unmount. + useLayoutEffect(() => { + const { rightPanelOpen: wasOpen, maximized: wasMaximized } = previous.current; + previous.current = { rightPanelOpen, maximized }; + if (!wasOpen || rightPanelOpen || wasMaximized) return; + const toggle = controlsRef.current?.querySelector("[data-thread-panel-toggle]"); + const panel = toggle?.ownerDocument.querySelector("[data-preview-panel-mode='inline']"); + const timing = panel?.getAnimations()[0]?.effect?.getTiming(); + if (!toggle || !panel || !timing) return; + // The header's toggle was just removed, so slide this one in from where it sat. + const headerRight = panel.getBoundingClientRect().left - THREAD_PANEL_HEADER_INSET_PX; + const offset = headerRight - toggle.getBoundingClientRect().right; + const animation = toggle.animate( + [{ transform: `translateX(${offset}px)` }, { transform: "none" }], + { ...timing, easing: getComputedStyle(panel).transitionTimingFunction }, + ); + return () => animation.cancel(); + }, [rightPanelOpen, maximized]); + const mobileSidebarSheetMounted = useMobileSidebarSheetMounted(); return (
Date: Sat, 14 Nov 2026 20:06:32 +0800 Subject: [PATCH 079/192] fix(contracts): a null model selection options value is treated as omitted (#29149) Co-authored-by: Julius Marminge --- packages/contracts/src/modelSelection.test.ts | 14 ++++++++++++++ packages/contracts/src/modelSelection.ts | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/contracts/src/modelSelection.test.ts b/packages/contracts/src/modelSelection.test.ts index f719d82c511f3..632a7b595a139 100644 --- a/packages/contracts/src/modelSelection.test.ts +++ b/packages/contracts/src/modelSelection.test.ts @@ -32,6 +32,20 @@ it.effect("ModelSelection prefers explicit instanceId over legacy provider", () }), ); +it.effect("ModelSelection treats null options as omitted", () => + Effect.gen(function* () { + const parsed = yield* decodeModelSelection({ + instanceId: "codex", + model: "gpt-5", + options: null, + }); + assert.deepStrictEqual(parsed, { + instanceId: ProviderInstanceId.make("codex"), + model: "gpt-5", + }); + }), +); + it.effect( "ModelSelection decodes unknown driver kinds via legacy provider (rollback / fork invariant)", () => diff --git a/packages/contracts/src/modelSelection.ts b/packages/contracts/src/modelSelection.ts index e1ee73cfcbafa..0b4f7bc4833cc 100644 --- a/packages/contracts/src/modelSelection.ts +++ b/packages/contracts/src/modelSelection.ts @@ -34,7 +34,7 @@ export const ModelSelection = ModelSelectionSource.pipe( instanceId: instanceIdSource, model: raw.model, }; - if (raw.options !== undefined) base.options = raw.options; + if (raw.options !== undefined && raw.options !== null) base.options = raw.options; return Effect.succeed(base as typeof ModelSelectionWire.Encoded); }, encode: (value) => { From 250e502bfa1921f1ae5a102184e9f5f00c2fcb15 Mon Sep 17 00:00:00 2001 From: tcrapts <51760013+tcrapts@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:06:44 +0100 Subject: [PATCH 080/192] fix(mobile): scheduled-task edits made in quick succession are no longer lost (#27220) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- ...SettingsScheduledTasksRouteScreen.test.tsx | 264 ++++++++++++++++++ .../SettingsScheduledTasksRouteScreen.tsx | 217 ++++++++------ apps/mobile/vite.config.ts | 1 + 3 files changed, 401 insertions(+), 81 deletions(-) create mode 100644 apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx diff --git a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx new file mode 100644 index 0000000000000..b92c1ec725b47 --- /dev/null +++ b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx @@ -0,0 +1,264 @@ +import { RegistryContext } from "@effect/atom-react"; +import { + EnvironmentId, + ProjectId, + ProviderInstanceId, + ScheduledTaskId, + type ScheduledTask, +} from "@t3tools/contracts"; +import { Atom } from "effect/reactivity"; +import { act, type ReactNode } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; + +const { Children, mocks } = vi.hoisted(() => ({ + Children: ({ children }: { children?: ReactNode }) => children, + mocks: { upsert: vi.fn(), alert: vi.fn(), tasks: [] as ScheduledTask[] }, +})); +vi.mock("react-native", () => ({ + Platform: { OS: "ios" }, + View: Children, + TextInput: () => null, + Pressable: ({ children, onPress }: { children?: ReactNode; onPress: () => void }) => ( + + ), +})); +vi.mock("@react-navigation/native", () => ({ + useNavigation: () => ({ isFocused: () => false }), + usePreventRemove: () => {}, +})); +vi.mock("react-native-safe-area-context", () => ({ useSafeAreaInsets: () => ({ bottom: 0 }) })); +vi.mock("@expo/ui/community/datetime-picker", () => ({ DateTimePicker: () => null })); +vi.mock("../../components/ui/Dialog", () => ({ alert: mocks.alert })); +vi.mock("../../components/ui/AppText", () => ({ AppText: Children })); +vi.mock("../../components/ui/AppSymbol", () => ({ SymbolView: () => null })); +vi.mock("../../components/EnvironmentMachineSymbol", () => ({ + EnvironmentMachineSymbol: () => null, + ENVIRONMENT_MACHINE_SYMBOLS: {}, +})); +vi.mock("../../components/ui/Menu", () => ({ + Menu: ({ + actions, + onPressAction, + }: { + actions: readonly { id: string; title: string }[]; + onPressAction: (event: { nativeEvent: { event: string } }) => void; + }) => + actions.map(({ id, title }) => ( + + )), +})); +vi.mock("../../components/ui/ScreenScrollView", () => ({ ScreenScrollView: Children })); +vi.mock("./components/SettingsScreen", () => ({ SettingsScreen: Children })); +vi.mock("./components/SettingsSection", () => ({ SettingsSection: Children })); +vi.mock("./components/SettingsEnvironmentFilterHeader", () => ({ + useSettingsEnvironmentMenu: () => null, +})); +vi.mock("./components/ScheduledTaskPromptField", () => ({ ScheduledTaskPromptField: () => null })); +vi.mock("../layout/workspace/useSelectWorkspaceDetail", () => ({ + useSelectWorkspaceDetail: () => vi.fn(), +})); +vi.mock("../../state/threads", () => ({ threadFamilies: {} })); +vi.mock("../../lib/copyTextWithHaptic", () => ({ tryCopyTextWithHaptic: vi.fn() })); +vi.mock("../voice-input/useVoiceInputController", () => ({ + useVoiceInputController: () => ({ blocksSubmission: false }), +})); +vi.mock("../../components/ui/Button", () => ({ + Button: ({ label, onPress }: { label: string; onPress: () => void }) => ( + + ), +})); +vi.mock("../../components/ui/SegmentedControl", () => ({ + SegmentedControl: ({ + options, + onSelect, + }: { + options: readonly { value: string; label: string }[]; + onSelect: (value: string) => void; + }) => + options.map(({ value, label }) => ( + + )), +})); +vi.mock("../../components/ui/ThemedSwitch", () => ({ + ThemedSwitch: ({ + value, + onValueChange, + }: { + value: boolean; + onValueChange: (value: boolean) => void; + }) => , +})); +vi.mock("../../state/entities", () => ({ + useProjects: () => [ + { environmentId: "environment", id: "project", title: "Project" }, + { environmentId: "target", id: "target-project", title: "Target project" }, + ], + useServerConfigs: () => new Map(), + useEnvironmentServerConfig: () => null, + useThreadShell: () => null, +})); +vi.mock("./settings-environment-filter", () => ({ + useSettingsEnvironmentFilter: () => ({ + selectedTargets: [{ environmentId: "environment", label: "Environment" }], + availableTargets: [ + { environmentId: "environment", label: "Environment" }, + { environmentId: "target", label: "Target" }, + ], + selectedProjectKey: null, + projectGroups: [], + }), +})); +vi.mock("../../lib/modelOptions", () => ({ + buildModelOptions: () => [], + resolveDefaultableModelSelection: () => null, + resolveNewTaskModelSelection: () => null, +})); +vi.mock("../../state/session", () => ({ + readEnvironmentScope: () => true, + usePreparedConnection: () => ({ _tag: "None" }), +})); +vi.mock("../../state/server", () => ({ + serverEnvironment: { + scheduledTasksLive: () => ({}), + upsertScheduledTask: { permissionAtom: () => Atom.make(true) }, + }, +})); +vi.mock("../../state/query", () => ({ + useEnvironmentQuery: () => ({ data: { tasks: mocks.tasks, threads: [] } }), +})); +vi.mock("../../state/use-atom-command", () => ({ useAtomCommand: () => mocks.upsert })); + +import { appAtomRegistry } from "../../state/atom-registry"; +import { editDraft } from "./scheduledTaskDraft"; +import { + scheduledTaskEditorSessionAtom, + startScheduledTaskEditor, +} from "./scheduled-task-editor-state"; +import { SettingsScheduledTaskEditRouteScreen } from "./SettingsScheduledTasksRouteScreen"; + +let root: Root; +let container: HTMLDivElement; +beforeEach(() => { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + container = document.createElement("div"); + root = createRoot(container); + mocks.alert.mockReset(); + mocks.upsert.mockReset(); + mocks.upsert.mockResolvedValue({ _tag: "Success" }); + const task: ScheduledTask = { + id: ScheduledTaskId.make("task"), + title: "Task", + prompt: "Prompt", + enabled: true, + schedule: { type: "fixed_time", timeOfDay: "05:00" }, + projectId: ProjectId.make("project"), + threadId: null, + workspaceStrategy: { type: "root" }, + modelSelection: { + instanceId: ProviderInstanceId.make("codex"), + model: "model", + }, + runtimeMode: "full-access", + interactionMode: "default", + createdBy: "user", + creationSource: "mobile", + createdAt: "2026-10-22T00:00:00.000Z", + updatedAt: "2026-10-22T00:00:00.000Z", + nextRunAt: null, + lastRunAt: null, + lastRunStatus: "never", + lastRunError: null, + runCount: 0, + }; + mocks.tasks = [task]; + const draft = editDraft(task); + startScheduledTaskEditor({ + environmentId: EnvironmentId.make("environment"), + environmentLabel: "Environment", + draft: { ...draft, schedule: { ...draft.schedule, intervalMinutes: "60" } }, + }); + act(() => + root.render( + + + , + ), + ); +}); +afterEach(() => { + act(() => root.unmount()); + appAtomRegistry.set(scheduledTaskEditorSessionAtom, null); +}); +function button(label: string) { + const element = Array.from(container.querySelectorAll("button")).find( + (button) => button.textContent === label, + ); + if (!element) throw new Error(`Missing button: ${label}`); + return element; +} +describe("scheduled task batched edits", () => { + it("submits the selected interval when saved before React commits", async () => { + await act(async () => { + button("Interval").click(); + button("Save changes").click(); + }); + expect(mocks.alert).not.toHaveBeenCalled(); + expect(mocks.upsert).toHaveBeenCalledExactlyOnceWith({ + environmentId: "environment", + input: expect.objectContaining({ + id: "task", + requireExisting: true, + schedule: { type: "interval", everyMs: 3_600_000 }, + }), + }); + }); + it("retains the interval when enabled changes before React commits", async () => { + act(() => { + button("Interval").click(); + button("Toggle enabled").click(); + }); + expect(appAtomRegistry.get(scheduledTaskEditorSessionAtom)?.current?.draft.schedule.mode).toBe( + "interval", + ); + await act(async () => button("Save changes").click()); + expect(mocks.alert).not.toHaveBeenCalled(); + expect(mocks.upsert).toHaveBeenCalledExactlyOnceWith({ + environmentId: "environment", + input: expect.objectContaining({ + id: "task", + requireExisting: true, + enabled: false, + schedule: { type: "interval", everyMs: 3_600_000 }, + }), + }); + }); + it("keeps every webhook filter added before React commits", () => { + act(() => button("On webhook").click()); + act(() => { + button("Add filter").click(); + button("Add filter").click(); + }); + expect( + appAtomRegistry.get(scheduledTaskEditorSessionAtom)?.current?.draft.schedule.filters, + ).toHaveLength(2); + }); + it("keeps the target project when an old picker fires after changing environments", () => { + const editor = appAtomRegistry.get(scheduledTaskEditorSessionAtom)?.current; + if (!editor) throw new Error("Missing editor"); + act(() => startScheduledTaskEditor({ ...editor, draft: { ...editor.draft, task: null } })); + act(() => { + button("Select Target").click(); + button("Interval").click(); + button("Select Project").click(); + }); + expect(appAtomRegistry.get(scheduledTaskEditorSessionAtom)?.current).toMatchObject({ + environmentId: "target", + draft: { projectId: "target-project", schedule: { mode: "interval" } }, + }); + }); +}); diff --git a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx index 3f552c1f0c46c..8eeaf2e567ed1 100644 --- a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx @@ -267,11 +267,13 @@ function SelectRow(props: { function WebhookFilterRows(props: { readonly filters: ReadonlyArray; readonly disabled: boolean; - readonly onChange: (filters: ReadonlyArray) => void; + readonly onChange: ( + update: (filters: ReadonlyArray) => ReadonlyArray, + ) => void; }) { - const update = (index: number, patch: Partial) => - props.onChange( - props.filters.map((filter, at) => (at === index ? { ...filter, ...patch } : filter)), + const update = (id: WebhookFilterDraft["id"], patch: Partial) => + props.onChange((filters) => + filters.map((filter) => (filter.id === id ? { ...filter, ...patch } : filter)), ); return ( <> @@ -284,7 +286,9 @@ function WebhookFilterRows(props: { accessibilityLabel={`Remove filter ${index + 1}`} disabled={props.disabled} hitSlop={8} - onPress={() => props.onChange(removeWebhookFilterDraft(props.filters, filter.id))} + onPress={() => + props.onChange((filters) => removeWebhookFilterDraft(filters, filter.id)) + } className="active:opacity-70" > @@ -295,7 +299,7 @@ function WebhookFilterRows(props: { value={filter.path} placeholder="body.action" disabled={props.disabled} - onChange={(path) => update(index, { path })} + onChange={(path) => update(filter.id, { path })} /> { const known = WEBHOOK_FILTER_OPS.find((option) => option.op === id); - if (known) update(index, { op: known.op }); + if (known) update(filter.id, { op: known.op }); }} /> {filter.op === "exists" ? null : ( @@ -316,7 +320,7 @@ function WebhookFilterRows(props: { value={filter.value} placeholder={filter.op === "in" ? "opened, reopened" : "opened"} disabled={props.disabled} - onChange={(value) => update(index, { value })} + onChange={(value) => update(filter.id, { value })} /> )} @@ -325,7 +329,13 @@ function WebhookFilterRows(props: { props.onChange([...props.filters, emptyWebhookFilterDraft()])} + onPress={() => + props.onChange((filters) => + filters.length < MAX_WEBHOOK_FILTERS + ? [...filters, emptyWebhookFilterDraft()] + : filters, + ) + } className="min-h-14 flex-row items-center gap-3 border-t border-border-subtle px-4 py-3 active:opacity-70" > @@ -590,8 +600,19 @@ function SettingsScheduledTaskEditorScreen({ title }: { readonly title: string } voiceInput={voiceInput} /> } - setDraft={(draft) => { - if (!saving) setEditor({ ...editor, draft }); + setDraft={(update) => { + if (!saving) + setEditor((current) => + current ? { ...current, draft: update(current.draft) } : current, + ); + }} + setEnvironmentDraft={(update) => { + if (!saving) + setEditor((current) => + current && current.environmentId === editor.environmentId + ? { ...current, draft: update(current.draft) } + : current, + ); }} onSaved={() => { saved.current = true; @@ -599,18 +620,21 @@ function SettingsScheduledTaskEditorScreen({ title }: { readonly title: string } if (navigation.isFocused()) navigation.goBack(); }} onChangeEnvironment={(target) => { - if (target.environmentId === editor.environmentId) return; - setEditor({ - environmentId: target.environmentId, - environmentLabel: target.label, - draft: { - ...draftForEnvironment(target.environmentId), - title: editor.draft.title, - prompt: editor.draft.prompt, - schedule: editor.draft.schedule, - enabled: editor.draft.enabled, - }, - }); + setEditor((current) => + !current || target.environmentId === current.environmentId + ? current + : { + environmentId: target.environmentId, + environmentLabel: target.label, + draft: { + ...draftForEnvironment(target.environmentId), + title: current.draft.title, + prompt: current.draft.prompt, + schedule: current.draft.schedule, + enabled: current.draft.enabled, + }, + }, + ); }} /> ) : ( @@ -635,6 +659,7 @@ function TaskForm({ dictationPending, promptField, setDraft, + setEnvironmentDraft, onSaved, onChangeEnvironment, }: { @@ -646,10 +671,12 @@ function TaskForm({ readonly setSaving: (saving: boolean) => void; readonly dictationPending: boolean; readonly promptField: ReactNode; - readonly setDraft: (draft: Draft) => void; + readonly setDraft: (update: (current: Draft) => Draft) => void; + readonly setEnvironmentDraft: (update: (current: Draft) => Draft) => void; readonly onSaved: () => void; readonly onChangeEnvironment: (target: SettingsTarget) => void; }) { + const { readEditor } = useScheduledTaskEditor(); const navigation = useNavigation>(); const tasks = useEnvironmentQuery( serverEnvironment.scheduledTasksLive({ environmentId, input: {} }), @@ -699,6 +726,9 @@ function TaskForm({ draft.task?.schedule.type === "once"; const offersThreadModes = config?.environment.capabilities.scheduledTaskThreadModes === true; const save = async () => { + const current = readEditor(); + if (!current || current.environmentId !== environmentId) return; + const draft = current.draft; if ( !readEnvironmentScope(environmentId, AuthOrchestrationOperateScope) || submissionPending.current || @@ -853,7 +883,7 @@ function TaskForm({ disabled={saving} value={draft.title} placeholder="Check for issues" - onChange={(title) => setDraft({ ...draft, title })} + onChange={(title) => setDraft((current) => ({ ...current, title }))} /> {promptField} @@ -873,21 +903,23 @@ function TaskForm({ onSelect={(id) => { const project = projects.find((item) => item.id === id); if (!project) return; - const keepsExplicitModel = - draft.modelSelectionIsExplicit && - (draft.modelSelection === null || - !config?.settings || - resolveProjectProviderInstanceEnabled( - config.settings, - project.id, - draft.modelSelection.instanceId, - )); - setDraft({ - ...changeDraftProject(draft, project.id), - modelSelection: keepsExplicitModel - ? draft.modelSelection - : scheduledTaskDefaultModel(config, project), - modelSelectionIsExplicit: keepsExplicitModel, + setEnvironmentDraft((current) => { + const keepsExplicitModel = + current.modelSelectionIsExplicit && + (current.modelSelection === null || + !config?.settings || + resolveProjectProviderInstanceEnabled( + config.settings, + project.id, + current.modelSelection.instanceId, + )); + return { + ...changeDraftProject(current, project.id), + modelSelection: keepsExplicitModel + ? current.modelSelection + : scheduledTaskDefaultModel(config, project), + modelSelectionIsExplicit: keepsExplicitModel, + }; }); }} /> @@ -935,9 +967,9 @@ function TaskForm({ )} onSelect={(id) => { if (id === "new_thread") - setDraft({ ...unbindDraftFromThread(draft), threadMode: id }); + setDraft((current) => ({ ...unbindDraftFromThread(current), threadMode: id })); else if (id === "continue" || id === "fresh_session") - setDraft({ ...draft, threadMode: id }); + setDraft((current) => ({ ...current, threadMode: id })); }} /> ) : null} @@ -985,7 +1017,7 @@ function TaskForm({ ]} onSelect={(id) => { if (id === "worktree" || id === "root" || id === "existing_worktree") - setDraft({ ...draft, workspace: id }); + setDraft((current) => ({ ...current, workspace: id })); }} /> {draft.workspace === "worktree" ? ( @@ -1008,10 +1040,10 @@ function TaskForm({ value={draft.checkoutPath} borderTop onChange={(checkoutPath) => - setDraft({ - ...draft, - checkoutPath: resolvePastedPath(draft.checkoutPath, checkoutPath), - }) + setEnvironmentDraft((current) => ({ + ...current, + checkoutPath: resolvePastedPath(current.checkoutPath, checkoutPath), + })) } /> ) : null} @@ -1036,21 +1068,21 @@ function TaskForm({ onSelect={(mode) => { setTimePickerOpen(false); setRunAtPicker(null); - setDraft({ - ...draft, + setDraft((current) => ({ + ...current, prompt: - mode === "webhook" && !draft.prompt.trim() + mode === "webhook" && !current.prompt.trim() ? DEFAULT_WEBHOOK_PROMPT - : draft.prompt, + : current.prompt, schedule: { - ...draft.schedule, + ...current.schedule, mode, runAt: - mode === "once" && !draft.schedule.runAt + mode === "once" && !current.schedule.runAt ? new Date(Math.ceil(Date.now() / 3_600_000) * 3_600_000).toISOString() - : draft.schedule.runAt, + : current.schedule.runAt, }, - }); + })); }} /> @@ -1089,7 +1121,10 @@ function TaskForm({ onDismiss={() => setTimePickerOpen(false)} onValueChange={(_, selected) => { const timeOfDay = `${String(selected.getHours()).padStart(2, "0")}:${String(selected.getMinutes()).padStart(2, "0")}`; - setDraft({ ...draft, schedule: { ...draft.schedule, timeOfDay } }); + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, timeOfDay }, + })); }} /> ) : null} @@ -1118,19 +1153,21 @@ function TaskForm({ })), ]} onSelect={(id) => { - const weekdays = - id === "every_day" - ? DAYS.map((day) => day.index) - : id === "weekdays" - ? [1, 2, 3, 4, 5] - : (() => { - const day = DAYS.find((item) => String(item.index) === id); - if (!day) return draft.schedule.weekdays; - return draft.schedule.weekdays.includes(day.index) - ? draft.schedule.weekdays.filter((index) => index !== day.index) - : [...draft.schedule.weekdays, day.index]; - })(); - setDraft({ ...draft, schedule: { ...draft.schedule, weekdays } }); + setDraft((current) => { + const weekdays = + id === "every_day" + ? DAYS.map((day) => day.index) + : id === "weekdays" + ? [1, 2, 3, 4, 5] + : (() => { + const day = DAYS.find((item) => String(item.index) === id); + if (!day) return current.schedule.weekdays; + return current.schedule.weekdays.includes(day.index) + ? current.schedule.weekdays.filter((index) => index !== day.index) + : [...current.schedule.weekdays, day.index]; + })(); + return { ...current, schedule: { ...current.schedule, weekdays } }; + }); }} /> @@ -1175,10 +1212,10 @@ function TaskForm({ onValueChange={(_, selected) => { // Android shows a dialog that closes on pick; iOS stays inline. if (Platform.OS === "android") setRunAtPicker(null); - setDraft({ - ...draft, - schedule: { ...draft.schedule, runAt: selected.toISOString() }, - }); + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, runAt: selected.toISOString() }, + })); }} /> ) : null} @@ -1206,14 +1243,20 @@ function TaskForm({ disabled={saving} borderTop onChange={(maxDeliveryAgeMinutes) => - setDraft({ ...draft, schedule: { ...draft.schedule, maxDeliveryAgeMinutes } }) + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, maxDeliveryAgeMinutes }, + })) } /> - setDraft({ ...draft, schedule: { ...draft.schedule, filters } }) + onChange={(update) => + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, filters: update(current.schedule.filters) }, + })) } /> - setDraft({ ...draft, schedule: { ...draft.schedule, replayKey } }) + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, replayKey }, + })) } /> @@ -1236,7 +1282,10 @@ function TaskForm({ disabled={saving} borderTop onChange={(coalesceKey) => - setDraft({ ...draft, schedule: { ...draft.schedule, coalesceKey } }) + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, coalesceKey }, + })) } /> - setDraft({ ...draft, schedule: { ...draft.schedule, coalesceWindowSeconds } }) + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, coalesceWindowSeconds }, + })) } /> @@ -1264,7 +1316,10 @@ function TaskForm({ disabled={saving} borderTop onChange={(intervalMinutes) => - setDraft({ ...draft, schedule: { ...draft.schedule, intervalMinutes } }) + setDraft((current) => ({ + ...current, + schedule: { ...current.schedule, intervalMinutes }, + })) } /> {!(parseIntervalMinutes(draft.schedule.intervalMinutes) >= 1) ? ( @@ -1287,7 +1342,7 @@ function TaskForm({ setDraft({ ...draft, enabled })} + onValueChange={(enabled) => setDraft((current) => ({ ...current, enabled }))} /> diff --git a/apps/mobile/vite.config.ts b/apps/mobile/vite.config.ts index 2b688fee30ad2..df5edd11c914a 100644 --- a/apps/mobile/vite.config.ts +++ b/apps/mobile/vite.config.ts @@ -26,6 +26,7 @@ const reactTests = [ "src/features/files/ZipArchiveView.test.tsx", "src/features/pull-requests/guide/useReviewGuide.test.tsx", "src/features/settings/SettingsServerControlsRouteScreen.test.tsx", + "src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx", "src/features/settings/components/BranchNamingSettings.test.tsx", "src/features/settings/components/SettingsDragHandle.test.tsx", "src/features/threads/ThreadMarkdownImage.test.tsx", From dafd053a193505566bda72daf067b0706c773145 Mon Sep 17 00:00:00 2001 From: Tristan Knight Date: Sat, 14 Nov 2026 12:08:04 +0000 Subject: [PATCH 081/192] fix(mobile): closed PR numbers match their red icon in the thread list (#29327) Co-authored-by: Julius Marminge --- apps/mobile/src/state/thread-pr-presentation.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/mobile/src/state/thread-pr-presentation.ts b/apps/mobile/src/state/thread-pr-presentation.ts index a44f410b54ca3..a57d04fb24918 100644 --- a/apps/mobile/src/state/thread-pr-presentation.ts +++ b/apps/mobile/src/state/thread-pr-presentation.ts @@ -36,7 +36,7 @@ export interface ThreadPrPresentation { const PR_STATE_TEXT_CLASS: Record = { open: "text-adaptive-emerald-600-400", merged: "text-adaptive-violet-600-400", - closed: "text-foreground-muted", + closed: "text-adaptive-rose-600-400", }; export function presentThreadPr( @@ -101,12 +101,7 @@ export function presentThreadLinkedPullRequests( : linkedCount !== null ? `${linkedCount} linked ${changeRequest.plural}, overall ${badge.state}` : `#${link.number} ${changeRequest.singular} ${state === null ? "status pending" : isDraft ? "draft" : state}`, - textClassName: - state === null || isDraft - ? "text-foreground-muted" - : isMultiple && state === "closed" - ? "text-adaptive-rose-600-400" - : PR_STATE_TEXT_CLASS[state], + textClassName: state === null || isDraft ? "text-foreground-muted" : PR_STATE_TEXT_CLASS[state], }; } From 13a6782a6c4da6b54085cb7002e23e219cbd009b Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:12:04 +0100 Subject: [PATCH 082/192] fix(web): ODS time cells without display text show their value (#36685) Co-authored-by: Julius Marminge --- .../components/files/FilePreviewPanel.test.ts | 10 +++++++ .../files/extendedDocumentPreview.ts | 1 + .../ods-time-cell-shows-its-value.e2e.ts | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 scripts/scenarios/ods-time-cell-shows-its-value.e2e.ts diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index cd14df70f3c48..0d72574604552 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -377,6 +377,16 @@ Third}`; expect(result.sheets[0]!.rows[0]![0]).toBe("Research & design"); }); + it("reads an ODS time cell that has no display paragraph", async () => { + const archive = new JSZip().file( + "content.xml", + ``, + ); + const result = await openDocument(archive, "ods"); + if (result.type !== "ods") throw new Error("Expected workbook"); + expect(result.sheets[0]!.rows).toEqual([["PT03H30M00S"]]); + }); + it.each([1, 500])("ignores styled ODS filler after %s populated rows", async (count) => { const archive = new JSZip().file( "content.xml", diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index 16bc937b0cf31..68503c7424830 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -339,6 +339,7 @@ export async function openDocument( find(cell, "text:p", "office:annotation").map(text).join("\n") || cell.attrs["office:value"] || cell.attrs["office:date-value"] || + cell.attrs["office:time-value"] || cell.attrs["office:boolean-value"] || cell.attrs["office:string-value"] || ""; diff --git a/scripts/scenarios/ods-time-cell-shows-its-value.e2e.ts b/scripts/scenarios/ods-time-cell-shows-its-value.e2e.ts new file mode 100644 index 0000000000000..b6a12ff51acce --- /dev/null +++ b/scripts/scenarios/ods-time-cell-shows-its-value.e2e.ts @@ -0,0 +1,29 @@ +import { expect } from "e2e"; + +import { test, WEB } from "./t3.ts"; + +// A valid ODS whose only cell is typed time with no optional display paragraph. +const TIME_ONLY_ODS = + "UEsDBAoAAAAAACdXbl2FbDmKLgAAAC4AAAAIAAAAbWltZXR5cGVhcHBsaWNhdGlvbi92bmQub2FzaXMub3BlbmRvY3VtZW50LnNwcmVhZHNoZWV0UEsDBAoAAAAAACdXbl0AAAAAAAAAAAAAAAAJAAAATUVUQS1JTkYvUEsDBAoAAAAIACdXbl3Rr5gOtgAAAHABAAAVAAAATUVUQS1JTkYvbWFuaWZlc3QueG1sjZDRCsIwDEV/ZeR9q+KLlFXf/AL9gNJmWmjTsmZj+3ur4JyI4FtCcu+5SXucgq9G7LOLpGDbbKBCMtE6uiq4nE/1Ho6HNmhyHWaWr6IqMspLq2DoSUadXZakA2bJRsaEZKMZAhLLz335BC3dir+DFa1zHuui7uf3bjd4XyfNNwViZRHQOl3znFCBTsk7o7lYipFs88zVrOM0OfWobb4hMoj/iSYSP+Tlmh9sxonFY1xcxdfbDndQSwMECgAAAAgAJ1duXZwajlHfAAAACgIAAAsAAABjb250ZW50LnhtbI2RwWrDMAyGXyXo7sUjl2Hi9DZ6GQzaPYDrKKvBkULsbO3bz423kLANerEt6f/0S7jeXXpffOAYHJOGxwcJBZLl1tG7hrfjs3iCXVNz1zmLqmU79UhRWKaY7iLBFFSuaphGUmyCC4pMj0FFq3hA+qHUWq1mq5yJ5uTvxmfxhsZLvBtO2sx+T7HavIJlzxO31yUIw4imDWfE2NTZfT6L/L7ZaTicXRcDbARi5M9twqL3i7PxE4p4HRIdXY/LSLdAzFUNr0dZ7Sv5IuUByqYuf3VfZ1L018jlZqnyn69svgBQSwECFAAKAAAAAAAnV25dhWw5ii4AAAAuAAAACAAAAAAAAAAAAAAAAAAAAAAAbWltZXR5cGVQSwECFAAKAAAAAAAnV25dAAAAAAAAAAAAAAAACQAAAAAAAAAAABAAAABUAAAATUVUQS1JTkYvUEsBAhQACgAAAAgAJ1duXdGvmA62AAAAcAEAABUAAAAAAAAAAAAAAAAAewAAAE1FVEEtSU5GL21hbmlmZXN0LnhtbFBLAQIUAAoAAAAIACdXbl2cGo5R3wAAAAoCAAALAAAAAAAAAAAAAAAAAGQBAABjb250ZW50LnhtbFBLBQYAAAAABAAEAOkAAABsAgAAAAA="; + +test( + "An ODS time cell without a display paragraph shows its value", + WEB, + async ({ t3, app, screen }) => { + const project = await t3.project("demo", { + "shifts.ods": Buffer.from(TIME_ONLY_ODS, "base64"), + }); + const thread = await t3.thread({ project, title: "Shifts", messages: ["Start"] }); + await t3.open(thread); + + await screen.getByRole("button", "Toggle right panel").tap(); + await screen.getByRole("button", "Files F").tap(); + await screen.getByRole("treeitem", "shifts.ods").tap(); + // A cold Vite cache reloads the page the first time the office preview loads. + await expect(screen.getByText("Sheet 1 of 1", { exact: false })).toBeVisible({ + timeout: 60_000, + }); + await app.screenshot("sheet"); + await expect(screen.getByText("PT03H30M00S")).toBeVisible(); + }, +); From cb35acc5e6882975e60a10ce8fa17ff967f27256 Mon Sep 17 00:00:00 2001 From: Yarden Zamir Date: Sat, 14 Nov 2026 14:13:39 +0200 Subject: [PATCH 083/192] test(web): queued runs fork test mocks the quiet projection hook Upstream #24055 moved QueuedRunsControl to useQuietThreadProjection. --- .../src/components/chat/QueuedRunsControl.fork.browser.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/QueuedRunsControl.fork.browser.test.tsx b/apps/web/src/components/chat/QueuedRunsControl.fork.browser.test.tsx index c72b61a898494..e6d62083e3474 100644 --- a/apps/web/src/components/chat/QueuedRunsControl.fork.browser.test.tsx +++ b/apps/web/src/components/chat/QueuedRunsControl.fork.browser.test.tsx @@ -14,7 +14,7 @@ vi.mock(import("@t3tools/client-runtime/state/thread-workflows"), async (importO vi.mock(import("../../state/entities"), async (importOriginal) => ({ ...(await importOriginal()), - useThreadProjection: (() => state.projection) as never, + useQuietThreadProjection: (() => state.projection) as never, })); vi.mock(import("../../state/use-atom-command"), async (importOriginal) => ({ From 0fdc99c87051885520c28aab0b6dcb5e9bad135b Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 04:15:44 -0800 Subject: [PATCH 084/192] fix(web): notebook previews strip ANSI colors from saved tracebacks (#36703) --- apps/web/src/components/files/FilePreviewPanel.test.ts | 2 +- apps/web/src/components/files/extendedDocumentPreview.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index 0d72574604552..21630fcfb3c82 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -351,7 +351,7 @@ Third}`; }, { kind: "text", - text: "\u001b[31mTraceback (most recent call last):\n\u001b[0m\u001b[32mCell In[3], line 1\n----> 1 fail()\n\u001b[0m\u001b[39m\u001b[31mValueError: preview proof\u001b[0m\nfinal entry", + text: "Traceback (most recent call last):\nCell In[3], line 1\n----> 1 fail()\nValueError: preview proof\nfinal entry", }, ]); expect(() => openNotebook(bytes('{"nbformat":3,"cells":[]}'))).toThrow("version 4"); diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index 68503c7424830..6c1496ab40afb 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -6,6 +6,7 @@ import { import { XMLParser, XMLValidator } from "fast-xml-parser"; import { decodeHTMLAttribute, decodeHTMLStrict as decodeEntities } from "entities"; import type JSZip from "jszip"; +import { terminalOutputPlainText } from "@t3tools/shared/terminalOutput"; import { type OfficeWorkerResult, type NotebookCell } from "./officePreviewProtocol"; @@ -439,12 +440,14 @@ function notebookText(value: unknown, limit = 256_000): string { if (result.length > limit) throw new Error("A notebook cell is too large to preview."); return result; } -// IPython traceback frames may omit a final newline or follow it with ANSI SGR sequences. +// IPython traceback frames carry ANSI SGR colors and may omit a final newline. function tracebackLines(value: unknown): unknown { if (!Array.isArray(value)) return value; - const frames = value.filter((frame): frame is string => typeof frame === "string"); + const frames = value + .filter((frame): frame is string => typeof frame === "string") + .map(terminalOutputPlainText); return frames.map((frame, index) => - index && !/\n(?:\u001b\[[\d;:]*m)*$/.test(frames[index - 1]!) ? `\n${frame}` : frame, + index && !frames[index - 1]!.endsWith("\n") ? `\n${frame}` : frame, ); } export function openNotebook(bytes: ArrayBuffer): OfficeWorkerResult { From 414d938513250edb2df7178ba737e1e630656ab7 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:19:35 +0100 Subject: [PATCH 085/192] fix(server): publish Create PR branches to split push URLs (#36695) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 84 +++++++++++++++---- apps/server/src/vcs/GitVcsDriverCore.ts | 88 ++++++++++++++------ 2 files changed, 132 insertions(+), 40 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index b748d4f364c84..f07d0ef385cd4 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -7805,6 +7805,16 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { yield* git(originRemote, ["--git-dir=.", "rev-parse", "refs/heads/feature/push"]), publishedHead, ); + + const pushOnlyRemote = yield* makeTmpDir("git-push-only-remote-"); + yield* git(pushOnlyRemote, ["init", "--bare"]); + yield* git(cwd, ["remote", "set-url", "--push", "origin", pushOnlyRemote]); + const pushedToPushUrl = yield* driver.pushCurrentBranch(cwd, null); + assert.deepInclude(pushedToPushUrl, { status: "pushed", branch }); + assert.equal( + yield* git(pushOnlyRemote, ["--git-dir=.", "rev-parse", "refs/heads/feature/push"]), + head, + ); }), ); @@ -8206,29 +8216,73 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }), ); - it.effect("skips an up-to-date tracked branch the push remote already has", () => + it.effect.each([ + { pushUrls: [], status: "skipped_up_to_date" }, + { pushUrls: ["mirror"], status: "pushed" }, + { pushUrls: ["fork", "mirror"], status: "pushed" }, + ] as const)( + "skips an up-to-date tracked branch only when every push URL has it (pushurl $pushUrls)", + ({ pushUrls, status }) => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const upstreamRemote = yield* makeTmpDir("git-upstream-remote-"); + const remotes = { + fork: yield* makeTmpDir("git-fork-remote-"), + mirror: yield* makeTmpDir("git-mirror-remote-"), + }; + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* git(upstreamRemote, ["init", "--bare"]); + yield* git(remotes.fork, ["init", "--bare"]); + yield* git(remotes.mirror, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "upstream", upstreamRemote]); + yield* git(cwd, ["remote", "add", "fork", remotes.fork]); + yield* git(cwd, ["checkout", "-b", "topic"]); + yield* git(cwd, ["push", "-u", "upstream", "topic"]); + yield* git(cwd, ["push", "fork", "topic"]); + yield* git(cwd, ["config", "remote.pushDefault", "fork"]); + for (const name of pushUrls) { + yield* git(cwd, ["remote", "set-url", "--add", "--push", "fork", remotes[name]]); + } + + const pushed = yield* driver.pushCurrentBranch(cwd, null); + + assert.deepInclude(pushed, { status, upstreamBranch: "fork/topic", pushRemote: "fork" }); + const head = yield* git(cwd, ["rev-parse", "HEAD"]); + for (const name of pushUrls) { + assert.equal( + yield* git(remotes[name], ["--git-dir=.", "rev-parse", "refs/heads/topic"]), + head, + ); + } + assert.equal( + yield* git(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"]), + "upstream/topic", + ); + }), + ); + + it.effect("pushes an up-to-date branch whose tracked remote has a separate push URL", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); - const upstreamRemote = yield* makeTmpDir("git-upstream-remote-"); - const forkRemote = yield* makeTmpDir("git-fork-remote-"); + const fetchRemote = yield* makeTmpDir("git-fetch-remote-"); + const pushRemote = yield* makeTmpDir("git-push-remote-"); yield* initRepoWithCommit(cwd); const driver = yield* GitVcsDriver.GitVcsDriver; - yield* git(upstreamRemote, ["init", "--bare"]); - yield* git(forkRemote, ["init", "--bare"]); - yield* git(cwd, ["remote", "add", "upstream", upstreamRemote]); - yield* git(cwd, ["remote", "add", "fork", forkRemote]); + yield* git(fetchRemote, ["init", "--bare"]); + yield* git(pushRemote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "origin", fetchRemote]); yield* git(cwd, ["checkout", "-b", "topic"]); - yield* git(cwd, ["push", "-u", "upstream", "topic"]); - yield* git(cwd, ["push", "fork", "topic"]); - yield* git(cwd, ["config", "remote.pushDefault", "fork"]); + yield* git(cwd, ["push", "-u", "origin", "topic"]); + yield* git(cwd, ["remote", "set-url", "--push", "origin", pushRemote]); const pushed = yield* driver.pushCurrentBranch(cwd, null); - assert.deepInclude(pushed, { - status: "skipped_up_to_date", - upstreamBranch: "fork/topic", - pushRemote: "fork", - }); + assert.deepInclude(pushed, { status: "pushed", upstreamBranch: "origin/topic" }); + assert.equal( + yield* git(pushRemote, ["--git-dir=.", "rev-parse", "refs/heads/topic"]), + yield* git(cwd, ["rev-parse", "HEAD"]), + ); }), ); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 212ccb3a0b449..ccc671a7365ad 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1866,6 +1866,34 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* Effect.orElseSucceed(() => false), ); + // A fetch reads only the remote's first url, while a push writes every push URL (pushurl, else + // each url). A fetched ref is evidence of the push destination only when those are the same one. + const pushesOnlyToFetchUrl = Effect.fn("GitVcsDriver.pushesOnlyToFetchUrl")( + function* (cwd: string, remoteName: string) { + const [fetchUrl, pushUrls] = yield* Effect.all([ + runGitStdout("GitVcsDriver.pushesOnlyToFetchUrl.fetchUrl", cwd, [ + "remote", + "get-url", + "--", + remoteName, + ]), + runGitStdout("GitVcsDriver.pushesOnlyToFetchUrl.pushUrls", cwd, [ + "remote", + "get-url", + "--push", + "--all", + "--", + remoteName, + ]), + ]); + // Preserve whitespace in URLs and reject ambiguous multiline fetch URLs. + return ( + fetchUrl.endsWith("\n") && !fetchUrl.slice(0, -1).includes("\n") && pushUrls === fetchUrl + ); + }, + Effect.orElseSucceed(() => false), + ); + const ensureRemote: GitVcsDriver.GitVcsDriver["Service"]["ensureRemote"] = Effect.fn( "ensureRemote", )(function* (input) { @@ -2974,30 +3002,36 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const destination = yield* resolvePushDestination(cwd, branch, details.hasUpstream); if (hasNoLocalDelta && details.hasUpstream) { if (!destination?.pushRemote) { - return { - status: "skipped_up_to_date" as const, - branch, - ...(details.upstreamRef ? { upstreamBranch: details.upstreamRef } : {}), - }; - } - const { pushRemote, branchName } = destination; - const pushRemoteRef = `${pushRemote}/${branchName}`; - // The local ref can be stale when the branch was deleted or rewound elsewhere. - const pushRemoteHasHead = yield* fetchRemoteTrackingBranch({ - cwd, - remoteName: pushRemote, - remoteBranch: branchName, - }).pipe( - Effect.andThen(remoteRefContainsHead(cwd, pushRemoteRef)), - Effect.orElseSucceed(() => false), - ); - if (pushRemoteHasHead) { - return { - status: "skipped_up_to_date" as const, - branch, - upstreamBranch: pushRemoteRef, - pushRemote, - }; + // The tracking ref reflects the fetch URL, so it says nothing about a separate push URL. + if (!destination || (yield* pushesOnlyToFetchUrl(cwd, destination.remoteName))) { + return { + status: "skipped_up_to_date" as const, + branch, + ...(details.upstreamRef ? { upstreamBranch: details.upstreamRef } : {}), + }; + } + } else { + const { pushRemote, branchName } = destination; + const pushRemoteRef = `${pushRemote}/${branchName}`; + // The local ref can be stale when the branch was deleted or rewound elsewhere. + const pushRemoteHasHead = + (yield* pushesOnlyToFetchUrl(cwd, pushRemote)) && + (yield* fetchRemoteTrackingBranch({ + cwd, + remoteName: pushRemote, + remoteBranch: branchName, + }).pipe( + Effect.andThen(remoteRefContainsHead(cwd, pushRemoteRef)), + Effect.orElseSucceed(() => false), + )); + if (pushRemoteHasHead) { + return { + status: "skipped_up_to_date" as const, + branch, + upstreamBranch: pushRemoteRef, + pushRemote, + }; + } } } else if (hasNoLocalDelta) { const comparableBaseBranch = yield* resolveBaseBranchForNoUpstream(cwd, branch).pipe( @@ -3013,7 +3047,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* // The configured diff base can contain HEAD while the publish branch is still behind. if ( - yield* remoteRefContainsHead(cwd, `${destination.remoteName}/${destination.branchName}`) + (yield* remoteRefContainsHead( + cwd, + `${destination.remoteName}/${destination.branchName}`, + )) && + (yield* pushesOnlyToFetchUrl(cwd, destination.remoteName)) ) { return { status: "skipped_up_to_date" as const, From 0200dcf2423c1b954c9ebca092d112426009c04d Mon Sep 17 00:00:00 2001 From: Pyseph <45090858+Pyseph@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:20:36 +0100 Subject: [PATCH 086/192] perf(shared): queued SQLite reads stop waiting on one reader while another is free (#27979) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- packages/shared/src/nodeSqliteClient.test.ts | 75 ++++++++++++++++ packages/shared/src/nodeSqliteClient.ts | 91 ++++++++++++-------- 2 files changed, 131 insertions(+), 35 deletions(-) diff --git a/packages/shared/src/nodeSqliteClient.test.ts b/packages/shared/src/nodeSqliteClient.test.ts index b7b9b7a0866a2..aea1c956391df 100644 --- a/packages/shared/src/nodeSqliteClient.test.ts +++ b/packages/shared/src/nodeSqliteClient.test.ts @@ -15,6 +15,7 @@ import * as SqlError from "effect/sql/SqlError"; import * as Statement from "effect/sql/Statement"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import * as Stream from "effect/Stream"; +import * as TestClock from "effect/testing/TestClock"; import type * as Tracer from "effect/Tracer"; import { vi } from "vite-plus/test"; @@ -426,6 +427,80 @@ it.effect("serves another read while a read-only transaction is open", () => ), ); +it.effect("gives a queued read the first reader that frees up", () => + withReaderClient((sql) => + Effect.gen(function* () { + const holders = yield* Effect.forEach([0, 1, 2, 3], () => + Effect.gen(function* () { + const held = yield* Deferred.make(); + const release = yield* Deferred.make(); + const fiber = yield* sql + .withTransaction( + countEntries(sql).pipe( + Effect.andThen(Deferred.succeed(held, undefined)), + Effect.andThen(Deferred.await(release)), + ), + ) + .pipe(SqliteClient.readOnly, Effect.forkChild); + yield* Deferred.await(held); + return { fiber, release }; + }), + ); + const cancelled = yield* Effect.uninterruptibleMask((restore) => + SqliteClient.readOnly(restore(sql.withTransaction(countEntries(sql)))), + ).pipe((effect) => Effect.forkChild(effect, { startImmediately: true })); + yield* Fiber.interrupt(cancelled); + const cancelledRead = yield* Effect.uninterruptibleMask((restore) => + SqliteClient.readOnly(restore(countEntries(sql))), + ).pipe((effect) => Effect.forkChild(effect, { startImmediately: true })); + yield* Fiber.interrupt(cancelledRead); + const queuedSpan = yield* Deferred.make(); + const observeQueued: Statement.Transformer = (statement, _sql, _fiber, span) => + Deferred.succeed(queuedSpan, span).pipe(Effect.as(statement)); + const queued = yield* countEntries(sql).pipe( + Effect.provideService(Statement.CurrentTransformer, observeQueued), + Effect.provideService(Statement.SpanPropagationEnabled, true), + SqliteClient.readOnly, + (effect) => Effect.forkChild(effect, { startImmediately: true }), + ); + // Masked read-only work keeps its cancellation pending and still runs once a reader frees up. + let maskedRan = 0; + const maskedRuns = yield* Effect.forEach( + [ + Effect.scoped( + Effect.flatMap(sql.reserve, (connection) => + connection.executeUnprepared("SELECT 1", [], undefined), + ), + ).pipe(Effect.asVoid), + sql.withTransaction(countEntries(sql)).pipe(Effect.asVoid), + ], + (effect) => + effect.pipe( + Effect.andThen(Effect.sync(() => maskedRan++)), + Effect.uninterruptible, + SqliteClient.readOnly, + (masked) => Effect.forkChild(masked, { startImmediately: true }), + ), + ); + const interrupting = yield* Effect.forEach(maskedRuns, (fiber) => + Effect.forkChild(Fiber.interrupt(fiber), { startImmediately: true }), + ); + yield* TestClock.adjust("1 second"); + yield* Deferred.succeed(holders[1]!.release, undefined); + yield* Fiber.join(holders[1]!.fiber); + // Pinned to the first reader it saw, this read would wait for that reader's transaction. + assert.equal(yield* Fiber.join(queued), 1); + assert.equal((yield* Deferred.await(queuedSpan)).attributes.get("db.lock_wait_ms"), 1000); + for (const { fiber, release } of [holders[0]!, holders[2]!, holders[3]!]) { + yield* Deferred.succeed(release, undefined); + yield* Fiber.join(fiber); + } + yield* Effect.forEach(interrupting, Fiber.join); + assert.equal(maskedRan, 2); + }), + ), +); + // regression: #19226 read-only transactions run on the separate reader connection it.effect("rejects a write in a read-only transaction", () => withReaderClient((sql) => diff --git a/packages/shared/src/nodeSqliteClient.ts b/packages/shared/src/nodeSqliteClient.ts index ab14675c7bfc0..5cc46c542fbf5 100644 --- a/packages/shared/src/nodeSqliteClient.ts +++ b/packages/shared/src/nodeSqliteClient.ts @@ -875,11 +875,13 @@ const make = Effect.fn("makeWithDatabase")(function* ( ([acquire, lock]) => ({ acquire, lock, - claims: 0, + claimed: false, }), ), ) : []; + // One permit per slot, so a queued read waits for any reader, not one it picked while all were busy. + const readerAdmission = yield* Semaphore.make(readers.length); interface Target { readonly name: Acquired["connection"]; @@ -893,31 +895,45 @@ const make = Effect.fn("makeWithDatabase")(function* ( })); let activeWriter: ConnectionOwner | undefined; // Callers must run route uninterruptibly and pass `claimed` a finalizer that runs `release` once. - const route = (claimed: (release: Effect.Effect) => Effect.Effect) => + // Waits use the caller's `restore`, not readOnly's, which may have been captured outside a mask. + const route = ( + claimed: (release: Effect.Effect) => Effect.Effect, + restore: typeof Effect.interruptible, + ) => Effect.withFiber((fiber): Effect.Effect => { if (readers.length === 0 || readerPool.unavailable || !Context.get(fiber.context, ReadOnly)) { return writer; } - // Least claimed wins and ties go to the lowest slot, so idle slots never start a worker. - let slot = readers[0]!; - for (const candidate of readers) if (candidate.claims < slot.claims) slot = candidate; - slot.claims++; - const reader = slot; - return Effect.andThen( - claimed( - Effect.sync(() => { - reader.claims--; - }), - ), - Effect.flatMap(reader.acquire, (readerConnection) => - readerConnection === undefined - ? writer - : Effect.succeed({ - name: "reader", - lock: reader.lock, - connection: readerConnection, - }), - ), + const claim = Effect.andThen( + restore(readerAdmission.take(1)), + Effect.suspend(() => { + // Holding a permit leaves a slot idle; the lowest wins, so idle slots never start a worker. + const reader = readers.find((slot) => !slot.claimed)!; + reader.claimed = true; + const release = Effect.andThen( + Effect.sync(() => { + reader.claimed = false; + }), + readerAdmission.release(1), + ); + return Effect.as(claimed(release), reader); + }), + ); + return Effect.flatMap( + // A scheduler yield after taking the permit must not precede its release finalizer. + Effect.provideService(claim, Scheduler.PreventSchedulerYield, true), + (reader) => + Effect.flatMap( + Effect.provideService(reader.acquire, ReadOnly, restore), + (readerConnection) => + readerConnection === undefined + ? writer + : Effect.succeed({ + name: "reader", + lock: reader.lock, + connection: readerConnection, + }), + ), ); }); @@ -937,17 +953,21 @@ const make = Effect.fn("makeWithDatabase")(function* ( Effect.sync(() => { release = next; }); - return Effect.flatMap(route(claimed), (target) => restore(borrowTarget(target, use))).pipe( - Effect.ensuring(Effect.suspend(() => release)), - ); + // Started before routing, so lock wait includes waiting for reader admission. + return Effect.withFiber((fiber) => { + const start = fiber.getRef(Clock.Clock).monotonicTimeNanosUnsafe(); + return Effect.flatMap(route(claimed, restore), (target) => + restore(borrowTarget(target, use, start)), + ); + }).pipe(Effect.ensuring(Effect.suspend(() => release))); }); const borrowTarget = ( target: Target, use: (connection: Connection) => Effect.Effect, + start: bigint, ): Effect.Effect => Effect.withFiber((fiber) => { const clock = fiber.getRef(Clock.Clock); - const start = clock.monotonicTimeNanosUnsafe(); const ownerAtWaitStart = target.name === "writer" ? activeWriter : undefined; const owner = target.name === "writer" ? connectionOwner(fiber) : undefined; const useConnection = Effect.suspend(() => { @@ -974,17 +994,18 @@ const make = Effect.fn("makeWithDatabase")(function* ( Effect.withFiber((fiber) => Scope.addFinalizer(Context.getUnsafe(fiber.context, Scope.Scope), release), ); - const transactionAcquirer = Effect.uninterruptible(route(claimedUntilScopeCloses)).pipe( - Effect.flatMap((target) => - Effect.uninterruptibleMask((restore) => { - const fiber = Fiber.getCurrent()!; + // Started before routing, so lock wait includes waiting for reader admission. + const transactionAcquirer = Effect.uninterruptibleMask((maskRestore) => + Effect.withFiber((fiber) => { + const start = fiber.getRef(Clock.Clock).monotonicTimeNanosUnsafe(); + const pending = fiber.getRef(TransactionAcquire); + const restore = pending?.restore ?? maskRestore; + return Effect.flatMap(route(claimedUntilScopeCloses, restore), (target) => { const scope = Context.getUnsafe(fiber.context, Scope.Scope); const clock = fiber.getRef(Clock.Clock); - const start = clock.monotonicTimeNanosUnsafe(); - const pending = fiber.getRef(TransactionAcquire); const ownerAtWaitStart = target.name === "writer" ? activeWriter : undefined; const owner = target.name === "writer" ? connectionOwner(fiber) : undefined; - const acquire = Effect.tap((pending?.restore ?? restore)(target.lock.take(1)), () => { + const acquire = Effect.tap(restore(target.lock.take(1)), () => { if (target.name === "writer") activeWriter = owner; if (pending !== undefined) { pending.acquired = acquired(target, clock, start); @@ -1002,8 +1023,8 @@ const make = Effect.fn("makeWithDatabase")(function* ( Effect.provideService(acquire, Scheduler.PreventSchedulerYield, true), target.connection, ); - }), - ), + }); + }), ); const client = yield* Client.make({ From b28dc0eb1d8ac4b95292bdfc95ea2a3f9ad2600b Mon Sep 17 00:00:00 2001 From: Andrei <108398860+Yuruzuu@users.noreply.github.com> Date: Sat, 14 Nov 2026 20:31:57 +0800 Subject: [PATCH 087/192] perf(mobile): Changes tab no longer redraws every file when one changes (#25872) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/mobile/src/features/review/useReviewDiffData.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/mobile/src/features/review/useReviewDiffData.ts b/apps/mobile/src/features/review/useReviewDiffData.ts index d9ec656cbc773..18a22e96b62bb 100644 --- a/apps/mobile/src/features/review/useReviewDiffData.ts +++ b/apps/mobile/src/features/review/useReviewDiffData.ts @@ -142,13 +142,7 @@ export function useReviewDiffData(input: { }); const registry = useContext(RegistryContext); const { environmentId, cwd } = input; - const scope = JSON.stringify([ - environmentId, - cwd, - source?.kind, - source?.baseRef, - source?.diffHash, - ]); + const scope = JSON.stringify([environmentId, cwd, lazySource?.kind, source?.baseRef]); const [requested, setRequested] = useState({ scope, indices: [0, 1, 2] }); const indices = useMemo( () => new Set(requested.scope === scope ? requested.indices : [0, 1, 2]), From 243d66a295814a4bed1351b7eab7760e040cc8d5 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:33:30 +0100 Subject: [PATCH 088/192] fix(server): first commit dialog shows net file changes (#36716) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 16 +++---- apps/server/src/vcs/GitVcsDriverCore.ts | 42 ++++++------------- ...born-commit-dialog-shows-net-counts.e2e.ts | 36 ++++++++++++++++ 3 files changed, 55 insertions(+), 39 deletions(-) create mode 100644 scripts/scenarios/unborn-commit-dialog-shows-net-counts.e2e.ts diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index f07d0ef385cd4..ce6e19fbfbf58 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -4901,25 +4901,21 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }), ); - it.effect("reports staged file counts on unborn HEAD without failing", () => + it.effect("reports net file counts from the empty tree on unborn HEAD", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* GitVcsDriver.GitVcsDriver; yield* driver.initRepo({ cwd }); - yield* git(cwd, ["config", "user.email", "test@test.com"]); - yield* git(cwd, ["config", "user.name", "Test"]); yield* writeTextFile(cwd, "initial.ts", "// first file\n"); yield* git(cwd, ["add", "initial.ts"]); + yield* writeTextFile(cwd, "initial.ts", "// edited after staging\n"); - const status = yield* driver.statusDetails(cwd); + const status = yield* driver.statusDetailsLocal(cwd); assert.equal(status.isRepo, true); - assert.equal(status.workingTree.files.length, 1); - const file = status.workingTree.files[0]; - if (file) { - assert.equal(file.path, "initial.ts"); - assert.equal(file.insertions, 1); - } + assert.deepStrictEqual(status.workingTree.files, [ + { path: "initial.ts", insertions: 1, deletions: 0 }, + ]); }), ); }); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index ccc671a7365ad..ca44afa4b9b2a 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -2276,41 +2276,25 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* isMissingGitCwdError(error) ? Effect.succeed(null) : Effect.fail(error), }), ); + // Porcelain diff rewrites indexes despite GIT_OPTIONAL_LOCKS=0; plumbing with -M preserves rename detection. + const readNumstat = (ref: string) => + executeGitWithStableDiagnostics( + "GitVcsDriver.statusDetails.numstat", + cwd, + ["diff-index", "-M", "--numstat", "-z", ref, "--"], + { allowNonZeroExit: true, maxOutputBytes: REVIEW_METADATA_MAX_OUTPUT_BYTES }, + ); // diff-index does not read status output, so both spawn together; a non-repository discards it. const [statusResult, numstatResult, defaultBranch, hasPrimaryRemote] = yield* Effect.all( [ readStatus, - // Porcelain diff rewrites indexes despite GIT_OPTIONAL_LOCKS=0; plumbing with -M preserves rename detection. - executeGitWithStableDiagnostics( - "GitVcsDriver.statusDetails.numstat", - cwd, - ["diff-index", "-M", "--numstat", "-z", "HEAD", "--"], - { allowNonZeroExit: true, maxOutputBytes: REVIEW_METADATA_MAX_OUTPUT_BYTES }, - ).pipe( + readNumstat("HEAD").pipe( + Effect.filterOrElse( + (result) => result.exitCode === 0 || !isUnbornHeadStderr(result.stderr), + () => Effect.flatMap(readEmptyTreeHash(cwd), readNumstat), + ), Effect.flatMap((result) => { if (result.exitCode === 0) return Effect.succeed(parseReviewNumstat(result.stdout)); - if (isUnbornHeadStderr(result.stderr)) { - return Effect.map( - Effect.all([ - runGitStdoutWithOptions( - "GitVcsDriver.statusDetails.numstat.unborn", - cwd, - ["diff-files", "--numstat", "-z"], - { maxOutputBytes: REVIEW_METADATA_MAX_OUTPUT_BYTES }, - ), - runGitStdoutWithOptions( - "GitVcsDriver.statusDetails.numstat.unborn.staged", - cwd, - ["diff", "--cached", "--numstat", "-z"], - { maxOutputBytes: REVIEW_METADATA_MAX_OUTPUT_BYTES }, - ), - ]), - ([unstagedStdout, stagedStdout]) => [ - ...parseReviewNumstat(stagedStdout), - ...parseReviewNumstat(unstagedStdout), - ], - ); - } return Effect.fail( new GitCommandError({ ...gitCommandContext({ diff --git a/scripts/scenarios/unborn-commit-dialog-shows-net-counts.e2e.ts b/scripts/scenarios/unborn-commit-dialog-shows-net-counts.e2e.ts new file mode 100644 index 0000000000000..f7a72c97b2fdd --- /dev/null +++ b/scripts/scenarios/unborn-commit-dialog-shows-net-counts.e2e.ts @@ -0,0 +1,36 @@ +import { expect } from "e2e"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; + +import { run } from "./shell.ts"; +import { runPromise, test, WEB } from "./t3.ts"; + +test( + "The commit dialog before the first commit counts a file staged then edited once", + WEB, + async ({ t3, app, screen }) => { + const project = await t3.project("demo"); + const git = (...args: ReadonlyArray) => runPromise(run("git", args, { cwd: project })); + const write = (text: string) => + runPromise( + Effect.flatMap(FileSystem.FileSystem, (fs) => + fs.writeFileString(`${project}/new.txt`, `${text}\n`), + ), + ); + // No commits yet: staged "staged", then edited to "actual" without restaging. + await git("update-ref", "-d", "HEAD"); + await git("rm", "-r", "--cached", "--quiet", "."); + await write("staged"); + await git("add", "new.txt"); + await write("actual"); + const thread = await t3.thread({ project, title: "First commit", messages: ["first"] }); + + await t3.open(thread); + await screen.getByRole("button", "Git action options").tap(); + await screen.getByRole("menuitem", "Commit").tap(); + const row = screen.getByRole("button", "Open new.txt in editor"); + await expect(row).toBeVisible(); + await app.screenshot("commit-dialog"); + await expect(row).toHaveText(/\+1 \/ -0/); + }, +); From 740a7cc21fadae371ba1c9c2980049c5d7e7d4a3 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 06:33:42 -0600 Subject: [PATCH 089/192] fix(marketing): download page shows its Releases fallback when the GitHub API rejects the request (#28339) Co-authored-by: Julius Marminge --- apps/marketing/src/lib/releases.test.ts | 18 ++++++++++++++++++ apps/marketing/src/lib/releases.ts | 10 ++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 apps/marketing/src/lib/releases.test.ts diff --git a/apps/marketing/src/lib/releases.test.ts b/apps/marketing/src/lib/releases.test.ts new file mode 100644 index 0000000000000..6527342d674e5 --- /dev/null +++ b/apps/marketing/src/lib/releases.test.ts @@ -0,0 +1,18 @@ +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; + +import { fetchLatestRelease } from "./releases"; + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("fetchLatestRelease", () => { + it("rejects a GitHub API error response instead of returning it as a release", async () => { + vi.stubGlobal("sessionStorage", { getItem: () => null }); + vi.stubGlobal("fetch", async () => + Response.json({ message: "API rate limit exceeded" }, { status: 403 }), + ); + + await expect(fetchLatestRelease("stable")).rejects.toThrow("403"); + }); +}); diff --git a/apps/marketing/src/lib/releases.ts b/apps/marketing/src/lib/releases.ts index 7dc63bdaed859..f50103972cdd8 100644 --- a/apps/marketing/src/lib/releases.ts +++ b/apps/marketing/src/lib/releases.ts @@ -25,12 +25,18 @@ function cacheKey(channel: ReleaseChannel) { return `t3code-${channel}-release`; } +async function fetchJson(url: string): Promise { + const response = await fetch(url); + if (!response.ok) throw new Error(`GitHub API responded ${response.status}`); + return response.json(); +} + async function fetchStable(): Promise { - return fetch(LATEST_API_URL).then((r) => r.json()); + return fetchJson(LATEST_API_URL); } async function fetchNightly(): Promise { - const list: Release[] = await fetch(LIST_API_URL).then((r) => r.json()); + const list = await fetchJson(LIST_API_URL); const nightly = Array.isArray(list) ? list.find((release) => release.tag_name?.includes("-nightly.")) : undefined; From cb6670b01d2d1ecbaa6c0fa7e47c20e1ad67af29 Mon Sep 17 00:00:00 2001 From: Juan <64438564+jux310@users.noreply.github.com> Date: Sat, 14 Nov 2026 09:34:47 -0300 Subject: [PATCH 090/192] fix(server): a timed-out Git check no longer turns off threads without a project until restart (#36704) Co-authored-by: Julius Marminge --- .../src/project/ManagedProjectFolders.test.ts | 46 ++++++++++++++++++- .../src/project/ManagedProjectFolders.ts | 17 ++++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/apps/server/src/project/ManagedProjectFolders.test.ts b/apps/server/src/project/ManagedProjectFolders.test.ts index 011b54a6851b3..9a4a78888b72a 100644 --- a/apps/server/src/project/ManagedProjectFolders.test.ts +++ b/apps/server/src/project/ManagedProjectFolders.test.ts @@ -1,7 +1,13 @@ import { assert, it } from "@effect/vitest"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { CommandId, ProjectId, ThreadId, VcsProcessSpawnError } from "@t3tools/contracts"; +import { + CommandId, + ProjectId, + ThreadId, + VcsProcessSpawnError, + VcsProcessTimeoutError, +} from "@t3tools/contracts"; import * as HostProcess from "@t3tools/shared/HostProcess"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -174,6 +180,44 @@ it.effect("offers a Scratch folder when Git is not installed", () => ), ); +it.effect("offers the Scratch folder once a Git check that timed out is asked again", () => + withScratch( + ({ baseDir }) => + Effect.gen(function* () { + const path = yield* Path.Path; + const scratch = yield* ManagedProjectFolders.ManagedProjectFolders; + assert.isTrue(Option.isNone(yield* scratch.scratchRoot)); + assert.equal(yield* requireRoot, path.resolve(baseDir, "scratch")); + }), + { + vcsProcess: Layer.effect( + VcsProcess.VcsProcess, + Effect.map(VcsProcess.make, (real) => { + let timedOut = false; + return VcsProcess.VcsProcess.of({ + run: (input) => + Effect.suspend(() => { + if (timedOut) return real.run(input); + timedOut = true; + return Effect.fail( + new VcsProcessTimeoutError({ + operation: input.operation, + command: input.command, + cwd: input.cwd, + timeoutMs: 5_000, + }), + ); + }), + }); + }), + ).pipe( + Layer.provide(ProcessRunner.layer), + Layer.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)), + ), + }, + ), +); + it.effect("creates one Scratch project, even for concurrent first requests", () => withScratch(() => Effect.gen(function* () { diff --git a/apps/server/src/project/ManagedProjectFolders.ts b/apps/server/src/project/ManagedProjectFolders.ts index 007f784374c15..143bbf4a5c10d 100644 --- a/apps/server/src/project/ManagedProjectFolders.ts +++ b/apps/server/src/project/ManagedProjectFolders.ts @@ -268,15 +268,18 @@ const make = Effect.gen(function* () { return Effect.logWarning(unavailable.message).pipe(Effect.as(Result.fail(unavailable))); }, ), - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.interrupt - : Effect.succeed(Result.fail(new ScratchUnavailableError({ baseDir: config.baseDir }))), - ), ), - Duration.infinity, + // A failed git check (say, a timeout on a busy machine) is not an answer; ask again next time. + Exit.match({ onSuccess: () => Duration.infinity, onFailure: () => Duration.zero }), + ); + const scratchProbe = probe.pipe( + Effect.catchCause((cause) => + Cause.hasInterrupts(cause) + ? Effect.interrupt + : Effect.succeed(Result.fail(new ScratchUnavailableError({ baseDir: config.baseDir }))), + ), + Effect.onInterrupt(() => invalidate), ); - const scratchProbe = probe.pipe(Effect.onInterrupt(() => invalidate)); // A symlinked scratch folder lets the No project live at either the link or its target. const scratchProjectRoot = (root: string) => fileSystem.realPath(root).pipe( From 666631e94eb183b09183fa9f05dd3d87da3cbc34 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 13:38:02 +0100 Subject: [PATCH 091/192] fix(web): copied inline code keeps meaningful edge spaces (#36712) Co-authored-by: Grok 4.7 Co-authored-by: Julius Marminge --- apps/web/src/markdown-clipboard.test.ts | 18 ++++++++++++++++++ apps/web/src/markdown-clipboard.ts | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/web/src/markdown-clipboard.test.ts b/apps/web/src/markdown-clipboard.test.ts index e85e77b416f27..a6565f83ba96b 100644 --- a/apps/web/src/markdown-clipboard.test.ts +++ b/apps/web/src/markdown-clipboard.test.ts @@ -198,6 +198,24 @@ describe("serializeRenderedMarkdownFragment", () => { expect(serializeRenderedMarkdownFragment(asNode(container))).toBe("first line\nsecond line"); }); + it.each([ + ["backtick edges", "`value`"], + ["space edges", " value "], + ["only spaces", " "], + ])("copies inline code that parses back unchanged: %s", (_, value) => { + const container = new FakeElement("DIV").append( + new FakeElement("P").append( + new FakeText("Use "), + new FakeElement("CODE").append(new FakeText(value)), + new FakeText(" here."), + ), + ); + const paragraph = parseMarkdownWithMath(serializeRenderedMarkdownFragment(asNode(container))) + .children[0]; + if (paragraph?.type !== "paragraph") throw new Error("Expected a paragraph"); + expect(paragraph.children.find((child) => child.type === "inlineCode")?.value).toBe(value); + }); + it("copies alert and insight callouts as their Markdown instead of the rendered label", () => { // The shape ChatMarkdown renders for `> [!WARNING]\n> Back up first.`. const callout = new FakeElement("DIV", [], { diff --git a/apps/web/src/markdown-clipboard.ts b/apps/web/src/markdown-clipboard.ts index 91f16c86535a6..2b5592d154921 100644 --- a/apps/web/src/markdown-clipboard.ts +++ b/apps/web/src/markdown-clipboard.ts @@ -48,7 +48,9 @@ function wrapInlineCode(code: string): string { 0, ); const fence = "`".repeat(Math.max(1, longestRun + (longestRun > 0 ? 1 : 0))); - const pad = code.startsWith("`") || code.endsWith("`") ? " " : ""; + // CommonMark strips one edge space from content that has both and isn't all spaces. + const spaceEdges = code.startsWith(" ") && code.endsWith(" ") && /[^ ]/.test(code); + const pad = spaceEdges || code.startsWith("`") || code.endsWith("`") ? " " : ""; return `${fence}${pad}${code}${pad}${fence}`; } From b90ffeaf2887b41fdedecfd170d7701e3822d9b9 Mon Sep 17 00:00:00 2001 From: ahalekelly <7078138+ahalekelly@users.noreply.github.com> Date: Sat, 14 Nov 2026 04:41:07 -0800 Subject: [PATCH 092/192] fix(mobile): drop duplicate attachments, as on web (#29143) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/mobile/src/components/ComposerEditor.tsx | 2 +- .../features/threads/QuestionAttachments.tsx | 5 +-- apps/mobile/src/lib/composerImages.ts | 8 +++- .../src/state/recover-failed-thread-draft.ts | 2 +- apps/mobile/src/state/threadOutboxDelivery.ts | 2 +- .../state/use-composer-attachment-actions.ts | 5 +-- .../src/state/use-composer-drafts.test.ts | 37 ++++++++++++++++++- apps/mobile/src/state/use-composer-drafts.ts | 30 ++++++++++++--- .../src/state/use-thread-composer-state.ts | 2 +- 9 files changed, 71 insertions(+), 22 deletions(-) diff --git a/apps/mobile/src/components/ComposerEditor.tsx b/apps/mobile/src/components/ComposerEditor.tsx index f7118b95b425f..bae452bad7c48 100644 --- a/apps/mobile/src/components/ComposerEditor.tsx +++ b/apps/mobile/src/components/ComposerEditor.tsx @@ -76,7 +76,7 @@ export function ComposerEditor({ props.onChangeText(text); if (draftKey) { setComposerDraftContext(draftKey, restored.context); - appendComposerDraftAttachments(draftKey, restored.attachments, { allowOverflow: true }); + appendComposerDraftAttachments(draftKey, restored.attachments, { restore: true }); } }; const [selected, setSelected] = useState<{ source: string; start: number; end: number } | null>( diff --git a/apps/mobile/src/features/threads/QuestionAttachments.tsx b/apps/mobile/src/features/threads/QuestionAttachments.tsx index b8fa753188608..3cc26c42fd7fc 100644 --- a/apps/mobile/src/features/threads/QuestionAttachments.tsx +++ b/apps/mobile/src/features/threads/QuestionAttachments.tsx @@ -105,10 +105,7 @@ export function QuestionAttachments(props: { props.question.id, ); changeQuestionAttachmentPreparation(key, 1); - void convertPastedImagesToAttachments({ - uris, - existingCount: appAtomRegistry.get(composerDraftsAtom)[key]?.attachments.length ?? 0, - }) + void convertPastedImagesToAttachments({ uris }) .then(async (result) => { if ( scope.active && diff --git a/apps/mobile/src/lib/composerImages.ts b/apps/mobile/src/lib/composerImages.ts index 7f7718f540ca2..b0bcec2ad89c3 100644 --- a/apps/mobile/src/lib/composerImages.ts +++ b/apps/mobile/src/lib/composerImages.ts @@ -569,15 +569,19 @@ function isOwnedPastedImageUri(uri: string): boolean { } } +/** Leave out existingCount when the draft store caps after its duplicate check. */ export async function convertPastedImagesToAttachments(input: { readonly uris: ReadonlyArray; - readonly existingCount: number; + readonly existingCount?: number; }): Promise<{ readonly attachments: ReadonlyArray; readonly error: string | null; }> { const { File } = await import("expo-file-system"); - const remainingSlots = PROVIDER_SEND_TURN_MAX_ATTACHMENTS - input.existingCount; + const remainingSlots = + input.existingCount === undefined + ? Number.POSITIVE_INFINITY + : PROVIDER_SEND_TURN_MAX_ATTACHMENTS - input.existingCount; const attachments: DraftComposerImageAttachment[] = []; let error: string | null = null; diff --git a/apps/mobile/src/state/recover-failed-thread-draft.ts b/apps/mobile/src/state/recover-failed-thread-draft.ts index 5512dccbeb266..25573ee3363c7 100644 --- a/apps/mobile/src/state/recover-failed-thread-draft.ts +++ b/apps/mobile/src/state/recover-failed-thread-draft.ts @@ -26,7 +26,7 @@ export async function recoverFailedThreadDraft(message: QueuedThreadMessage): Pr appendComposerDraftAttachments( targetKey, source.attachments.filter((attachment) => !existingIds.has(attachment.id)), - { allowOverflow: true }, + { restore: true }, ); // Recovery may exceed caps; preserve every file and leave trimming to the editor. await flushComposerDrafts(); diff --git a/apps/mobile/src/state/threadOutboxDelivery.ts b/apps/mobile/src/state/threadOutboxDelivery.ts index ed9c33719a4d8..b3feeae3bb801 100644 --- a/apps/mobile/src/state/threadOutboxDelivery.ts +++ b/apps/mobile/src/state/threadOutboxDelivery.ts @@ -372,7 +372,7 @@ export async function recoverEditedCreationAfterDelivery( appendComposerDraftAttachments( draftKey, kept.attachments.filter((attachment) => !existingAttachmentIds.has(attachment.id)), - { allowOverflow: true }, + { restore: true }, ); updateComposerDraftSettings(draftKey, { ...(kept.modelSelection !== undefined ? { modelSelection: kept.modelSelection } : {}), diff --git a/apps/mobile/src/state/use-composer-attachment-actions.ts b/apps/mobile/src/state/use-composer-attachment-actions.ts index eeb8742010858..2352f3f7848eb 100644 --- a/apps/mobile/src/state/use-composer-attachment-actions.ts +++ b/apps/mobile/src/state/use-composer-attachment-actions.ts @@ -101,10 +101,7 @@ export function useComposerAttachmentActions(props: { const insertion = captureComposerDraftInsertion(key); await withDraftOperation(key, async (signal) => { try { - const result = await convertPastedImagesToAttachments({ - uris, - existingCount: countComposerDraftAttachmentsAfterSelection(key, insertion), - }); + const result = await convertPastedImagesToAttachments({ uris }); report( "Could not paste images", result.error, diff --git a/apps/mobile/src/state/use-composer-drafts.test.ts b/apps/mobile/src/state/use-composer-drafts.test.ts index f25037f40f0b4..cbbcf57b8b319 100644 --- a/apps/mobile/src/state/use-composer-drafts.test.ts +++ b/apps/mobile/src/state/use-composer-drafts.test.ts @@ -458,7 +458,7 @@ describe("mobile composer drafts", () => { const restored = history.restore(text, getComposerDraftSnapshot(key)); setComposerDraftText(key, text); setComposerDraftContext(key, restored.context); - appendComposerDraftAttachments(key, restored.attachments, { allowOverflow: true }); + appendComposerDraftAttachments(key, restored.attachments, { restore: true }); }; try { changeText(""); @@ -1054,12 +1054,45 @@ describe("mobile composer drafts", () => { const overflowRejected = appendComposerDraftAttachments( draftKey, [makeAttachment("restored-1")], - { allowOverflow: true }, + { restore: true }, ); expect(overflowRejected).toBe(0); expect(appAtomRegistry.get(composerDraftsAtom)[draftKey]?.attachments).toHaveLength(101); }); + it("drops duplicate attachments, releases their files, and keeps them on restore", async () => { + const outboxLoad = vi.spyOn(threadOutboxManager, "load").mockResolvedValue(true); + onTestFinished(() => outboxLoad.mockRestore()); + const cleanup = Promise.withResolvers(); + composerAttachmentCleanupMocks.remove.mockImplementationOnce(async () => { + cleanup.resolve(); + }); + const makeVideo = (id: string) => ({ + id, + type: "file" as const, + name: "clip.mov", + mimeType: "video/quicktime", + sizeBytes: 42, + fileUri: `file:///documents/t3-composer-attachments/${id}.mov`, + }); + const draftKey = "new-task:environment-1:project-duplicates"; + appAtomRegistry.set(composerDraftsAtom, { + [draftKey]: { text: "", attachments: [makeVideo("held")] }, + }); + + expect( + appendComposerDraftAttachments(draftKey, [makeVideo("again"), makeVideo("again-2")]), + ).toBe(0); + expect(appAtomRegistry.get(composerDraftsAtom)[draftKey]?.attachments).toEqual([ + makeVideo("held"), + ]); + await cleanup.promise; + expect(composerAttachmentCleanupMocks.remove).toHaveBeenCalledWith(makeVideo("again").fileUri); + + appendComposerDraftAttachments(draftKey, [makeVideo("restored")], { restore: true }); + expect(appAtomRegistry.get(composerDraftsAtom)[draftKey]?.attachments).toHaveLength(2); + }); + it("keeps shared attachment files until every draft releases them", async () => { const outboxLoad = vi.spyOn(threadOutboxManager, "load").mockResolvedValue(true); onTestFinished(() => outboxLoad.mockRestore()); diff --git a/apps/mobile/src/state/use-composer-drafts.ts b/apps/mobile/src/state/use-composer-drafts.ts index 7fb44be817f32..6fd7c1d10f5b1 100644 --- a/apps/mobile/src/state/use-composer-drafts.ts +++ b/apps/mobile/src/state/use-composer-drafts.ts @@ -1373,28 +1373,46 @@ export function appendComposerDraftText(draftKey: string, value: string): void { }); } -/** Restore with allowOverflow to preserve every attachment after failed sends. */ +function attachmentDedupKey(attachment: DraftComposerAttachment): string { + return `${attachment.mimeType}\u0000${attachment.sizeBytes}\u0000${attachment.name}`; +} + +/** + * Restore puts back attachments after failed sends, so it skips the duplicate check and the caps. + * Returns how many attachments the caps rejected. + */ export function appendComposerDraftAttachments( draftKey: string, - attachments: ReadonlyArray, + incoming: ReadonlyArray, options?: { - readonly allowOverflow?: boolean; + readonly restore?: boolean; readonly appendReference?: boolean; readonly insertion?: ComposerDraftInsertion; readonly maxAttachments?: number; }, ): number { - if (attachments.length === 0) { + if (incoming.length === 0) { return 0; } let rejected: ReadonlyArray = []; let removed: ReadonlyArray = []; + let duplicates: ReadonlyArray = []; updateComposerDrafts((current) => { const existing = normalizeDraft(current[draftKey]); const retained = options?.appendReference ? draftWithoutInsertionSelection(draftKey, existing, options.insertion) : existing; - const remaining = options?.allowOverflow + const keys = new Set(retained.attachments.map(attachmentDedupKey)); + const attachments = options?.restore + ? incoming + : incoming.filter((attachment) => { + const key = attachmentDedupKey(attachment); + if (keys.has(key)) return false; + keys.add(key); + return true; + }); + duplicates = incoming.filter((attachment) => !attachments.includes(attachment)); + const remaining = options?.restore ? attachments.length : Math.max( 0, @@ -1437,7 +1455,7 @@ export function appendComposerDraftAttachments( [draftKey]: draft, }; }); - scheduleUnusedComposerAttachmentCleanup([...rejected, ...removed]); + scheduleUnusedComposerAttachmentCleanup([...rejected, ...removed, ...duplicates]); return rejected.length; } diff --git a/apps/mobile/src/state/use-thread-composer-state.ts b/apps/mobile/src/state/use-thread-composer-state.ts index a3a191d54cbd1..3eb19861d99b4 100644 --- a/apps/mobile/src/state/use-thread-composer-state.ts +++ b/apps/mobile/src/state/use-thread-composer-state.ts @@ -832,7 +832,7 @@ export function useThreadComposerState(input: { context: draft.context, attachments: [], }); - appendComposerDraftAttachments(threadKey, attachments, { allowOverflow: true }); + appendComposerDraftAttachments(threadKey, attachments, { restore: true }); if (freshContext) setFreshSessionPending(threadKey, true); setThreadComposerError( threadKey, From e2f164d6bb7088ce5ad70f96464b276b6471a59c Mon Sep 17 00:00:00 2001 From: Alb11747 Date: Sat, 14 Nov 2026 07:44:59 -0500 Subject: [PATCH 093/192] fix(web): let subagent lists pass scrolling to the details panel (#28968) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- ...hreadRelationshipsControl.browser.test.tsx | 43 +++++++++++++++++++ .../chat/ThreadRelationshipsControl.tsx | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx b/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx index cafc2ac171cee..1fc292f7ff1ae 100644 --- a/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx +++ b/apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx @@ -1,3 +1,4 @@ +import "~/index.css"; import * as DateTime from "effect/DateTime"; import { EnvironmentId, NodeId, ThreadId } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; @@ -58,6 +59,7 @@ vi.mock(import("../../state/use-atom-command"), () => ({ import { render } from "../../../test/render"; import { makeThreadProjectionFixture } from "../../test-fixtures"; +import { ScrollArea } from "../ui/scroll-area"; import { ToastProvider } from "../ui/toast"; import { ThreadRelationshipsPanel } from "./ThreadRelationshipsControl"; @@ -406,3 +408,44 @@ it("hides a deleted fork and keeps unloaded and archived forks with their curren await expect.element(screen.getByText("Archived fork", { exact: true })).toBeVisible(); await expect.element(screen.getByText("deleted-fork", { exact: true })).not.toBeInTheDocument(); }); + +it.each([1, 18])("continues scrolling the panel past a list of %i subagents", async (count) => { + setup(); + const projection = makeThreadProjectionFixture(); + state.projection = projection; + state.relatedShells = Array.from({ length: count }, (_, index) => ({ + id: ThreadId.make(`subagent-${index}`), + title: `Subagent ${index}`, + forkedFrom: null, + lineage: { parentThreadId: projection.thread.id, relationshipToParent: "subagent" }, + activityRunStatus: "running", + status: "running", + })); + await using screen = await render( +
+ + +
+ +
, + ); + if (count > 6) { + await screen.getByRole("button", { name: "Show 12 more" }).click(); + await expect.element(screen.getByText("Subagent 17", { exact: true })).toBeInTheDocument(); + } + const list = screen.getByRole("list", { name: "Related threads" }).element(); + const viewport = screen + .getByTestId("panel") + .element() + .querySelector("[data-slot=scroll-area-viewport]")!; + list.scrollTop = list.scrollHeight; + if (count > 6) expect(list.scrollTop).toBeGreaterThan(0); + viewport.scrollTop = 0; + await userEvent.wheel(list, { delta: { y: 300 } }); + await vi.waitFor(() => expect(viewport.scrollTop).toBeGreaterThan(0)); +}); diff --git a/apps/web/src/components/chat/ThreadRelationshipsControl.tsx b/apps/web/src/components/chat/ThreadRelationshipsControl.tsx index 48213f9618c6d..95c6d8f0966dd 100644 --- a/apps/web/src/components/chat/ThreadRelationshipsControl.tsx +++ b/apps/web/src/components/chat/ThreadRelationshipsControl.tsx @@ -118,7 +118,7 @@ function ThreadLineageRowList(props: { {/* ScrollArea's max-height viewport measures badly inside this scrolling panel. */}
    {props.children}
From a678948483864e3cfe0e814d3506fced34ab7696 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 06:45:12 -0600 Subject: [PATCH 094/192] fix(web): pending-question digit shortcuts ignore dialogs, menus and selects (#28388) Co-authored-by: Julius Marminge --- ...oserPendingUserInputPanel.browser.test.tsx | 103 ++++++++++++++++++ .../chat/ComposerPendingUserInputPanel.tsx | 4 +- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/components/chat/ComposerPendingUserInputPanel.browser.test.tsx diff --git a/apps/web/src/components/chat/ComposerPendingUserInputPanel.browser.test.tsx b/apps/web/src/components/chat/ComposerPendingUserInputPanel.browser.test.tsx new file mode 100644 index 0000000000000..40022b45bc845 --- /dev/null +++ b/apps/web/src/components/chat/ComposerPendingUserInputPanel.browser.test.tsx @@ -0,0 +1,103 @@ +import { EnvironmentId, RuntimeRequestId, ThreadId } from "@t3tools/contracts"; +import type { ReactNode } from "react"; +import { expect, it, vi } from "vite-plus/test"; +import { userEvent } from "vite-plus/test/browser"; + +import { render } from "../../../test/render"; +import type { PendingUserInput } from "../../session-logic"; +import { Select, SelectTrigger } from "../ui/select"; +import { ComposerPendingUserInputPanel } from "./ComposerPendingUserInputPanel"; + +const threadRef = { environmentId: EnvironmentId.make("env"), threadId: ThreadId.make("thread") }; + +const prompt: PendingUserInput = { + requestId: RuntimeRequestId.make("question-shortcuts"), + createdAt: "2026-10-31T00:00:00.000Z", + dismissible: false, + responseCapability: "live", + questions: [ + { + id: "question", + header: "Rollout", + question: "How should this ship?", + options: [{ label: "Incremental", description: "One module at a time" }], + multiSelect: true, + }, + ], +}; + +it.each<{ focus: string; control: ReactNode; toggles: boolean }>([ + { focus: "a page button", control:
+ ), + toggles: false, + }, + { + focus: "a popup", + control: ( +
+
+ ), + toggles: false, + }, + { + focus: "a menu", + control: ( +
+
+ ), + toggles: false, + }, + { + focus: "a select", + control: ( + + ), + toggles: false, + }, + { + focus: "a closed select menu", + control: ( + + ), + toggles: false, + }, +])( + "digit shortcut with focus in $focus toggles an option: $toggles", + async ({ control, toggles }) => { + const onToggleOption = vi.fn(); + await using screen = await render( + <> + {control} + + , + ); + await expect.element(screen.getByText("Incremental")).toBeVisible(); + screen.getByLabelText("Focused").element().focus(); + + await userEvent.keyboard("1"); + + expect(onToggleOption.mock.calls).toEqual(toggles ? [["question", "Incremental"]] : []); + }, +); diff --git a/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx b/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx index 4101bed5713ad..ba811c37b73f8 100644 --- a/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx +++ b/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx @@ -159,7 +159,9 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard( } if ( target instanceof HTMLElement && - target.closest('[contenteditable]:not([contenteditable="false"])') + target.closest( + 'select, [contenteditable]:not([contenteditable="false"]), [role="dialog"], [aria-modal="true"], [role="menu"], [role="combobox"], [data-slot$="popup"]', + ) ) { return; } From d9973dc3f8388aa800167670d657369533e0b4c6 Mon Sep 17 00:00:00 2001 From: Derek Trimm <275381468+derektrimm@users.noreply.github.com> Date: Sat, 14 Nov 2026 06:45:25 -0600 Subject: [PATCH 095/192] fix(mobile): a shared file no longer fails to send with "Too much context" (#28535) Co-authored-by: Julius Marminge --- .../sharing/incoming-share-model.test.ts | 8 ++-- .../features/sharing/incoming-share-model.ts | 4 +- .../src/state/use-composer-drafts.test.ts | 38 ++++++++++++++++++- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/apps/mobile/src/features/sharing/incoming-share-model.test.ts b/apps/mobile/src/features/sharing/incoming-share-model.test.ts index 3e798975d743c..1dea547de53b4 100644 --- a/apps/mobile/src/features/sharing/incoming-share-model.test.ts +++ b/apps/mobile/src/features/sharing/incoming-share-model.test.ts @@ -54,7 +54,7 @@ describe("incoming native shares", () => { text: "Please explain this error\n\nhttps://example.com/issue/1", attachments: [ { - id: "share-1:image:3", + id: "share-1-image-3", type: "image", name: "Screenshot.png", mimeType: "image/png", @@ -131,7 +131,7 @@ describe("incoming native shares", () => { expect(result.attachments).toEqual([ { - id: "share-report:file:0", + id: "share-report-file-0", type: "file", name: "report.pdf", mimeType: "application/pdf", @@ -165,7 +165,7 @@ describe("incoming native shares", () => { expect(result.warnings).toEqual([]); expect(result.attachments).toEqual([ { - id: "share-video:file:0", + id: "share-video-file-0", type: "file", name: video.originalName, mimeType: video.mimeType, @@ -239,7 +239,7 @@ describe("incoming native shares", () => { expect(result.attachments).toEqual([ { - id: "share-android-report:file:0", + id: "share-android-report-file-0", type: "file", name: "report", mimeType: "application/pdf", diff --git a/apps/mobile/src/features/sharing/incoming-share-model.ts b/apps/mobile/src/features/sharing/incoming-share-model.ts index aa097713923b6..aa97294e15466 100644 --- a/apps/mobile/src/features/sharing/incoming-share-model.ts +++ b/apps/mobile/src/features/sharing/incoming-share-model.ts @@ -332,7 +332,7 @@ export async function buildIncomingShareDraft(input: { } } attachments.push({ - id: `${input.id}:file:${index}`, + id: `${input.id}-file-${index}`, type: "file", name, mimeType, @@ -397,7 +397,7 @@ export async function buildIncomingShareDraft(input: { } const dataUrl = `data:${mimeType};base64,${base64}`; attachments.push({ - id: `${input.id}:image:${index}`, + id: `${input.id}-image-${index}`, type: "image", name: resolved?.originalName ?? fallbackName(uri, index, mimeType), mimeType, diff --git a/apps/mobile/src/state/use-composer-drafts.test.ts b/apps/mobile/src/state/use-composer-drafts.test.ts index cbbcf57b8b319..925fc4eb3f4fb 100644 --- a/apps/mobile/src/state/use-composer-drafts.test.ts +++ b/apps/mobile/src/state/use-composer-drafts.test.ts @@ -28,6 +28,9 @@ import { } from "@t3tools/contracts"; import { onTestFinished, vi } from "vite-plus/test"; +import { buildIncomingShareDraft } from "../features/sharing/incoming-share-model"; +import { composerContextSendBlockReason } from "../lib/composerContext"; + const composerDraftFileMocks = vi.hoisted(() => { let document = JSON.stringify({ schemaVersion: 1, drafts: {} }); let readError: Error | null = null; @@ -2236,7 +2239,7 @@ describe("mobile composer drafts", () => { it("merges shared content into a project draft without duplicating retries", () => { const draftKey = "new-task:environment-1:project-1"; const sharedAttachment = { - id: "share-1:image:0", + id: "share-1-image-0", type: "image" as const, name: "Screenshot.png", mimeType: "image/png", @@ -2271,7 +2274,7 @@ describe("mobile composer drafts", () => { it("shows a shared non-image file as a composer chip", () => { const draftKey = "new-task:environment-1:project-1"; const sharedFile = { - id: "share-1:file:0", + id: "share-1-file-0", type: "file" as const, name: "backup.json", mimeType: "application/json", @@ -2306,6 +2309,37 @@ describe("mobile composer drafts", () => { expect(mergeComposerDraftContentState(once, draftKey, recovered)).toBe(once); }); + it("lets a shared file through the send check", async () => { + const { attachments } = await buildIncomingShareDraft({ + id: "share-1", + createdAt: "2026-07-15T10:00:00.000Z", + payloads: [ + { + shareType: "file", + value: "file:///shared/report.xlsx", + mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + originalName: "report.xlsx", + }, + ], + resolvedPayloads: [], + fileReader: { + readBase64: async () => "", + readSize: async () => 42, + removeOwnedFile: async () => {}, + }, + }); + const draftKey = "new-task:environment-1:project-1"; + + const merged = mergeComposerDraftContentState({}, draftKey, { + text: "", + attachments, + sourceShareId: "share-1", + })[draftKey]!; + + expect(merged.context?.records).toHaveLength(1); + expect(composerContextSendBlockReason(merged.context)).toBeNull(); + }); + it("preserves existing images when shared content exceeds the draft attachment limit", () => { const draftKey = "new-task:environment-1:project-1"; const image = (id: string) => ({ From 0e250fa6f6c0154e7fbe82265efaf0c2d44a75e6 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 06:49:35 -0600 Subject: [PATCH 096/192] fix(server): selected-file commits keep a conflicted multi-commit cherry-pick and reftable cherry-picks (#28912) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 125 +++++++++++++++---- apps/server/src/vcs/GitVcsDriverCore.ts | 30 ++++- 2 files changed, 127 insertions(+), 28 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index ce6e19fbfbf58..9498e56c314ee 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1,4 +1,5 @@ // @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not. +import * as NodeChildProcess from "node:child_process"; import * as NodeFS from "node:fs"; import * as NodePath from "node:path"; import * as NodeURL from "node:url"; @@ -342,6 +343,11 @@ const git = ( return result.stdout.trim(); }); +const [, gitMajor, gitMinor] = + /(\d+)\.(\d+)/u.exec(NodeChildProcess.execFileSync("git", ["--version"], { encoding: "utf8" })) ?? + []; +const gitHasReftable = Number(gitMajor) > 2 || (Number(gitMajor) === 2 && Number(gitMinor) >= 45); + const gitWithStdin = ( cwd: string, args: ReadonlyArray, @@ -388,11 +394,11 @@ const initRepoWithCommit = ( return { initialBranch: head.trim().replace(/^ref: refs\/heads\//, "") }; }); -const commitInitialRepo = (cwd: string) => +const commitInitialRepo = (cwd: string, initArgs: ReadonlyArray = []) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const pathService = yield* Path.Path; - yield* git(cwd, ["init"]); + yield* git(cwd, ["init", ...initArgs]); yield* fileSystem.writeFileString( pathService.join(cwd, ".git", "config"), "[user]\n\temail = test@test.com\n\tname = Test\n", @@ -7174,41 +7180,108 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { ); it.effect.each([ - { operation: "cherry-pick", head: "CHERRY_PICK_HEAD" }, - { operation: "revert", head: "REVERT_HEAD" }, - ])("rejects selected-file commits during a $operation", ({ operation, head }) => + { operation: "cherry-pick", head: "CHERRY_PICK_HEAD", refs: "files" }, + { operation: "revert", head: "REVERT_HEAD", refs: "files" }, + // Reftable keeps CHERRY_PICK_HEAD and REVERT_HEAD as refs with no file under .git. + { operation: "cherry-pick", head: "CHERRY_PICK_HEAD", refs: "reftable" }, + { operation: "revert", head: "REVERT_HEAD", refs: "reftable" }, + ])( + "rejects selected-file commits during a $operation ($refs)", + ({ operation, head, refs }, ctx) => + Effect.gen(function* () { + if (refs === "reftable" && !gitHasReftable) + return ctx.skip("Needs Git 2.45 or later for reftable."); + const cwd = yield* makeTmpDir(); + if (refs === "reftable") yield* commitInitialRepo(cwd, ["--ref-format=reftable"]); + else yield* initRepoWithCommit(cwd); + // Reftable leaves .git/HEAD as a stub, so ask Git for the branch. + const initialBranch = yield* git(cwd, ["symbolic-ref", "--short", "HEAD"]); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* git(cwd, ["checkout", "-b", "other"]); + yield* writeTextFile(cwd, "README.md", "other version\n"); + yield* writeTextFile(cwd, "picked.txt", "picked\n"); + yield* git(cwd, ["add", "."]); + yield* git(cwd, ["commit", "-m", "Other"]); + if (operation === "cherry-pick") yield* git(cwd, ["checkout", initialBranch]); + yield* writeTextFile(cwd, "README.md", "main version\n"); + yield* git(cwd, ["commit", "-am", "Main"]); + const originalHead = yield* git(cwd, ["rev-parse", "HEAD"]); + yield* git(cwd, ["branch", head]); + const result = yield* driver.execute({ + operation: `test.${operation}`, + cwd, + args: [operation, operation === "cherry-pick" ? "other" : "HEAD~1"], + allowNonZeroExit: true, + }); + assert.equal(result.exitCode, 1); + yield* writeTextFile(cwd, "README.md", "resolved version\n"); + yield* git(cwd, ["add", "README.md"]); + const selected = operation === "cherry-pick" ? "picked.txt" : "README.md"; + + const commit = yield* driver + .commit(cwd, "Partial", "", { stage: { filePaths: [selected] } }) + .pipe(Effect.result); + + assert.isTrue(Result.isFailure(commit), `Selected-file ${operation} commit succeeded`); + assert.equal(yield* git(cwd, ["rev-parse", "HEAD"]), originalHead); + assert.equal( + yield* git(cwd, [ + "-c", + "core.warnAmbiguousRefs=false", + "rev-parse", + "--verify", + "--symbolic-full-name", + head, + ]), + head, + ); + }), + ); + + it.effect("rejects selected-file commits during a multi-commit no-commit cherry-pick", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const { initialBranch } = yield* initRepoWithCommit(cwd); const driver = yield* GitVcsDriver.GitVcsDriver; - yield* git(cwd, ["checkout", "-b", "other"]); - yield* writeTextFile(cwd, "README.md", "other version\n"); - yield* writeTextFile(cwd, "picked.txt", "picked\n"); + const path = yield* Path.Path; + yield* writeTextFile(cwd, "a.txt", "base a\n"); + yield* writeTextFile(cwd, "b.txt", "base b\n"); yield* git(cwd, ["add", "."]); - yield* git(cwd, ["commit", "-m", "Other"]); - if (operation === "cherry-pick") yield* git(cwd, ["checkout", initialBranch]); - yield* writeTextFile(cwd, "README.md", "main version\n"); + yield* git(cwd, ["commit", "-m", "Base"]); + yield* git(cwd, ["checkout", "-b", "feature"]); + yield* writeTextFile(cwd, "a.txt", "feature a\n"); + yield* writeTextFile(cwd, "b.txt", "feature b\n"); + yield* git(cwd, ["commit", "-am", "Feature one"]); + yield* writeTextFile(cwd, "later.txt", "later\n"); + yield* git(cwd, ["add", "."]); + yield* git(cwd, ["commit", "-m", "Feature two"]); + yield* git(cwd, ["checkout", initialBranch]); + yield* writeTextFile(cwd, "a.txt", "main a\n"); + yield* writeTextFile(cwd, "b.txt", "main b\n"); yield* git(cwd, ["commit", "-am", "Main"]); - const originalHead = yield* git(cwd, ["rev-parse", "HEAD"]); - const result = yield* driver.execute({ - operation: `test.${operation}`, + const head = yield* git(cwd, ["rev-parse", "HEAD"]); + const started = yield* driver.execute({ + operation: "test.sequencer", cwd, - args: [operation, operation === "cherry-pick" ? "other" : "HEAD~1"], + args: ["cherry-pick", "--no-commit", "feature~1", "feature"], allowNonZeroExit: true, }); - assert.equal(result.exitCode, 1); - yield* writeTextFile(cwd, "README.md", "resolved version\n"); - yield* git(cwd, ["add", "README.md"]); + assert.equal(started.exitCode, 1); + yield* writeTextFile(cwd, "a.txt", "resolved a\n"); const gitDir = yield* git(cwd, ["rev-parse", "--absolute-git-dir"]); - const selected = operation === "cherry-pick" ? "picked.txt" : "README.md"; + assert.isFalse(NodeFS.existsSync(path.join(gitDir, "CHERRY_PICK_HEAD"))); + const names = ["index", "sequencer/todo", "sequencer/head"]; + const before = names.map((name) => NodeFS.readFileSync(path.join(gitDir, name))); - const commit = yield* driver - .commit(cwd, "Partial", "", { stage: { filePaths: [selected] } }) - .pipe(Effect.result); - - assert.isTrue(Result.isFailure(commit), `Selected-file ${operation} commit succeeded`); - assert.equal(yield* git(cwd, ["rev-parse", "HEAD"]), originalHead); - assert.isTrue(NodeFS.existsSync(`${gitDir}/${head}`)); + const error = yield* driver + .commit(cwd, "Partial", "", { stage: { filePaths: ["a.txt"] } }) + .pipe(Effect.flip); + assert.include(error.detail, "Cannot commit selected files during a cherry-pick or revert"); + assert.deepEqual( + names.map((name) => NodeFS.readFileSync(path.join(gitDir, name))), + before, + ); + assert.equal(yield* git(cwd, ["rev-parse", "HEAD"]), head); }), ); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index ca44afa4b9b2a..5b7fea284a0df 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -2546,14 +2546,40 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* { head: "MERGE_HEAD", operation: "merge" }, { head: "CHERRY_PICK_HEAD", operation: "cherry-pick" }, { head: "REVERT_HEAD", operation: "revert" }, + // A conflicted multi-commit `cherry-pick --no-commit` writes only the sequencer; reset + // would clear its unmerged entries, so `--continue` would walk past the conflict. + { head: "sequencer", operation: "cherry-pick or revert" }, ] as const; const headPaths = yield* runGitStdout("GitVcsDriver.commit.mergeHead", cwd, [ "rev-parse", ...inProgressHeads.flatMap(({ head }) => ["--git-path", head]), ]); const resolvedHeadPaths = headPaths.trim().split("\n"); - const inProgress = yield* Effect.findFirst(inProgressHeads, (_, index) => - fileSystem.exists(path.resolve(cwd, resolvedHeadPaths[index] ?? "")), + // Reftable keeps CHERRY_PICK_HEAD and REVERT_HEAD as refs with no file under .git. A + // same-named branch resolves to its full name; an ambiguity warning would blank stdout. + const operationRefs = new Set(["CHERRY_PICK_HEAD", "REVERT_HEAD"]); + const isOperationRef = (head: string) => + runGitStdout( + "GitVcsDriver.commit.operationRef", + cwd, + [ + "-c", + "core.warnAmbiguousRefs=false", + "rev-parse", + "--verify", + "--quiet", + "--symbolic-full-name", + head, + ], + true, + ).pipe(Effect.map((ref) => ref.trim() === head)); + const inProgress = yield* Effect.findFirst(inProgressHeads, ({ head }, index) => + fileSystem.exists(path.resolve(cwd, resolvedHeadPaths[index] ?? "")).pipe( + Effect.filterOrElse( + (exists) => exists || !operationRefs.has(head), + () => isOperationRef(head), + ), + ), ).pipe( Effect.mapError( (cause) => From 20d21cf1cab0c3976a97a9c0743d36eec3615bf5 Mon Sep 17 00:00:00 2001 From: RVZO6 <66844656+RVZO6@users.noreply.github.com> Date: Sat, 14 Nov 2026 07:50:21 -0500 Subject: [PATCH 097/192] fix(mobile): disabled send arrow no longer darkens where its strokes overlap (#29028) Co-authored-by: Julius Marminge --- .../mobile/src/components/ComposerToolbar.tsx | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/mobile/src/components/ComposerToolbar.tsx b/apps/mobile/src/components/ComposerToolbar.tsx index 4fd54085c45f1..fdbf27b6f2c25 100644 --- a/apps/mobile/src/components/ComposerToolbar.tsx +++ b/apps/mobile/src/components/ComposerToolbar.tsx @@ -285,6 +285,7 @@ export function ComposerActionButton(props: { }) { const { scale, smallIconSize } = useAndroidControlSizing(); const circleSize = Math.round((props.size === "large" ? 44 : 30) * scale); + const fadeIcon = props.disabled === true && props.variant !== "danger"; return ( - + {/* Fade the icon as one layer: a translucent tint, or opacity on a Tabler icon (which + copies it onto every path), stacks where the SVG strokes overlap. */} + + + ); From 5fa95d99af98f2f9928186acc409b583742d3df5 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 06:53:23 -0600 Subject: [PATCH 098/192] fix(opencode): wait for the whole listening line before reading the server address (#28348) Co-authored-by: Julius Marminge --- .../src/server/OpenCodeRuntime.cliParsers.test.ts | 10 ++++++++++ .../provider-opencode/src/server/OpenCodeRuntime.ts | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts index e4b61ec0c4458..5a7cec62257ab 100644 --- a/packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts +++ b/packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts @@ -4,6 +4,16 @@ import { describe, it } from "vite-plus/test"; import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; +describe("parseServerUrlFromOutput", () => { + it.each([ + ["opencode server listening on http://127.0.0.1:4096", null], + ["opencode server listening on http://127.0.0.1:40961\n", "http://127.0.0.1:40961"], + ["opencode server listening on http://127.0.0.1:40961\r\n", "http://127.0.0.1:40961"], + ])("reads only a newline-terminated ready line: %j", (output, expected) => { + NodeAssert.equal(OpenCodeRuntime.parseServerUrlFromOutput(output), expected); + }); +}); + describe("toOpenCodeFileParts", () => { const attachment = (mimeType: string, sizeBytes = 12) => ({ type: "file" as const, diff --git a/packages/provider-opencode/src/server/OpenCodeRuntime.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.ts index d3e791bce9088..ea2d7a7d76cac 100644 --- a/packages/provider-opencode/src/server/OpenCodeRuntime.ts +++ b/packages/provider-opencode/src/server/OpenCodeRuntime.ts @@ -368,8 +368,9 @@ export class OpenCodeRuntime extends Context.Service< } >()("@t3tools/provider-opencode/server/OpenCodeRuntime") {} -function parseServerUrlFromOutput(output: string): string | null { - for (const line of output.split("\n")) { +export function parseServerUrlFromOutput(output: string): string | null { + // The last element is an unfinished line; a chunk can end mid-URL. + for (const line of output.split("\n").slice(0, -1)) { const match = line.match(/server listening on\s+(https?:\/\/[^\s]+)/i); if (match?.[1]) return match[1]; } From 331bc9ad4865911e5be46a4f70f03729376c9d81 Mon Sep 17 00:00:00 2001 From: Yarden Zamir Date: Sat, 14 Nov 2026 14:53:35 +0200 Subject: [PATCH 099/192] fix(mobile): project default model switch uses the updater form of setDraft --- .../settings/SettingsScheduledTasksRouteScreen.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx index 42092851c0489..0eaa3192c91c5 100644 --- a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx @@ -931,16 +931,16 @@ function TaskForm({ value={draft.useProjectDefaultModel} disabled={saving || dictationPending || environmentUnavailable} onValueChange={(useProjectDefaultModel) => - setDraft({ - ...draft, + setDraft((current) => ({ + ...current, useProjectDefaultModel, modelSelection: - draft.modelSelection ?? + current.modelSelection ?? scheduledTaskDefaultModel( config, - projects.find((project) => project.id === draft.projectId) ?? null, + projects.find((project) => project.id === current.projectId) ?? null, ), - }) + })) } /> From 35483c198a720394bffed1a9dbc16b43b794b983 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 06:54:58 -0600 Subject: [PATCH 100/192] fix(opencode): show usage-limit warnings while OpenCode 2 retries (#28375) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../Adapters/OpenCode2AdapterV2.test.ts | 24 +++++++++++++++---- .../src/server/v2/adapter.ts | 24 +++++++++++-------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts index acf2f628f5126..73dbb42ca8752 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts @@ -677,11 +677,27 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { const { runtime, thread } = yield* resumed([ out("session.prompt", { sessionID: SESSION, text: "" }), promptAccepted, + event("session.retry.scheduled", { + sessionID: SESSION, + assistantMessageID: "msg_0eb735d5b001oAFVeY5jz3WD4Z", + attempt: 2, + at: 2_001, + error, + }), event("session.execution.failed", { sessionID: SESSION, error }), ]); - const terminal = yield* terminalOf(runtime).pipe(Effect.forkScoped); + const collected = yield* runtime.events.pipe( + Stream.takeUntil((event) => event.type === "turn.terminal"), + Stream.runCollect, + Effect.forkScoped, + ); yield* runtime.startTurn(turnInput(thread)); - const ended = yield* Fiber.join(terminal); + const seen = yield* Fiber.join(collected); + assert.deepInclude(errorRows(seen)[0], { + status: "running", + failure: { class: failureClass, code: error.type, message: error.message, retryable: true }, + }); + const ended = seen.find((event) => event.type === "turn.terminal"); assert.equal(ended?.status, "failed"); assert.deepEqual(ended?.failure, { class: failureClass, @@ -5479,7 +5495,7 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { error: rateLimited, }); const retrying = { - class: "provider_error", + class: "usage_limit", message: rateLimited.message, code: rateLimited.type, retryable: true, @@ -5661,7 +5677,7 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { ], gate, ); - const failed = { ...retrying, class: "usage_limit" as const, retryable: null }; + const failed = { ...retrying, retryable: null }; const lastRetry = { attempt: 2, maxAttempts: null, retryDelayMs: 4_000 }; const rows = errorRows(seen); assert.deepEqual( diff --git a/packages/provider-opencode/src/server/v2/adapter.ts b/packages/provider-opencode/src/server/v2/adapter.ts index 95cee021b2774..6604f84eb289e 100644 --- a/packages/provider-opencode/src/server/v2/adapter.ts +++ b/packages/provider-opencode/src/server/v2/adapter.ts @@ -590,6 +590,17 @@ const claudeUsageLimit = (message: string | undefined) => ? { resetAt: /\bResets at (\d{4}-\d\d-\d\dT[\d:.]+Z)\.?\s*$/.exec(message)?.[1] ?? null } : undefined; +// OpenCode types an empty balance as provider.quota too, but it answers +// that with HTTP 402 and it never resets. Usage windows and budgets are 429. +const isUsageLimit = (error: { + readonly type: string; + readonly status?: number | undefined; + readonly message: string; +}) => + (error.type === "provider.quota" && error.status !== 402) || + error.type === "provider.rate-limit" || + claudeUsageLimit(error.message) !== undefined; + const INTERRUPT_TIMEOUT = 10_000; const INSTRUCTIONS_KEY = "t3-code"; const RECONNECT_ATTEMPTS = 5; @@ -1465,7 +1476,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr failure: makeProviderFailure({ message: readableProviderErrorMessage(error.message, error.status, error.response?.body), code: error.type, - class: "provider_error", + class: isUsageLimit(error) ? "usage_limit" : "provider_error", retryable: true, }), startedAt: turn.retry?.startedAt ?? (yield* DateTime.now), @@ -2839,20 +2850,13 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr return yield* endExecution(state, turn, { status: "interrupted" }); case "session.execution.failed": { const { type, message, status, response } = event.data.error; - // OpenCode types an empty balance as provider.quota too, but it answers - // that with HTTP 402 and it never resets. Usage windows and budgets are 429. - const claudeLimit = claudeUsageLimit(message); - const usageLimit = - (type === "provider.quota" && status !== 402) || - type === "provider.rate-limit" || - claudeLimit !== undefined; return yield* endExecution(state, turn, { status: "failed", failure: makeProviderFailure({ message: readableProviderErrorMessage(message, status, response?.body), code: type, - class: usageLimit ? "usage_limit" : "provider_error", - resetAt: claudeLimit?.resetAt ?? null, + class: isUsageLimit(event.data.error) ? "usage_limit" : "provider_error", + resetAt: claudeUsageLimit(message)?.resetAt ?? null, }), }); } From 5e27bae22e628ff0fcdc9b329d66a5582a66d2d7 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 06:56:21 -0600 Subject: [PATCH 101/192] fix(web): a stashed prompt keeps its machine mentions (#28780) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/components/ChatView.browser.test.tsx | 119 +++++++++++++++++- .../src/components/chat/useComposerStash.ts | 26 +++- apps/web/test/fakeEnvironment.ts | 71 ++++++++++- 3 files changed, 213 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/ChatView.browser.test.tsx b/apps/web/src/components/ChatView.browser.test.tsx index 7b164e8115420..fe1a6b6c1bf19 100644 --- a/apps/web/src/components/ChatView.browser.test.tsx +++ b/apps/web/src/components/ChatView.browser.test.tsx @@ -1,6 +1,6 @@ import { createMemoryHistory } from "@tanstack/react-router"; import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime/environment"; -import { RunId, ThreadId } from "@t3tools/contracts"; +import { EnvironmentId, RunId, ThreadId } from "@t3tools/contracts"; import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings"; import type * as Context from "effect/Context"; import { expect, it, vi } from "vite-plus/test"; @@ -13,6 +13,8 @@ import "~/components/ChatView"; import { DraftId, useComposerDraftStore } from "~/composerDraftStore"; import { selectThreadDiffPanelSelection, useDiffPanelStore } from "~/diffPanelStore"; import { __setClientSettingsForTests } from "~/hooks/useSettings"; +import { environmentContextRecord } from "~/lib/composerContextRecords"; +import { usePromptStashStore } from "~/promptStashStore"; import { useRightPanelStore } from "~/rightPanelStore"; import { getRouter } from "~/router"; import { appAtomRegistry } from "~/rpc/atomRegistry"; @@ -20,12 +22,15 @@ import { environmentThreadDetails } from "~/state/threads"; import { ENVIRONMENT_ID, PROJECT_ID, + answerAssetUrls, assistantItem, dispatchedCommands, emitThreadEvent, + hasPendingAssetUrl, installFakeEnvironment, rejectPendingCommand, threadWithHistory, + threadWithQuestion, type FakeDriver, } from "../../test/fakeEnvironment"; import { act, fakeTimers, render } from "../../test/render"; @@ -45,6 +50,118 @@ vi.mock(import("~/rpc/requestLatencyState"), async (original) => ({ trackRpcRequestSent: () => {}, })); +it("a machine chip stashed in one thread restores with its machine in another", async () => { + const other = threadWithHistory("thread-stash-restore", 1); + const asking = threadWithQuestion("thread-stash-question", "Which file should change?"); + using _environment = installFakeEnvironment([other, asking]); + __setClientSettingsForTests({ + ...DEFAULT_CLIENT_SETTINGS, + onboardingCompletedAt: "2026-01-01T00:00:00.000Z", + }); + const draftId = DraftId.make("draft-stash-machine"); + const machine = environmentContextRecord(EnvironmentId.make("environment-build"), "Build box"); + const drafts = useComposerDraftStore.getState(); + drafts.setProjectDraftThreadId(scopeProjectRef(ENVIRONMENT_ID, PROJECT_ID), draftId, { + threadId: ThreadId.make("thread-stash-machine"), + }); + drafts.setPrompt(draftId, "Deploy on "); + drafts.addEnvironmentContexts(draftId, [machine]); + const router = getRouter(createMemoryHistory({ initialEntries: [`/draft/${draftId}`] })); + await router.load(); + await using _app = await render(, { + // AppRoot brings its own router. + router: false, + }); + const editor = page.getByTestId("composer-editor"); + const pressStashShortcut = async () => { + await userEvent.click(editor); + await userEvent.keyboard("{ControlOrMeta>}s{/ControlOrMeta}"); + }; + await vi.waitFor(() => expect(editor.element().textContent).toContain("Deploy on")); + // A draft can't stash until the shell snapshot lists its project. + await expect.element(page.getByText("render", { exact: true }).first()).toBeVisible(); + await pressStashShortcut(); + await vi.waitFor(() => expect(usePromptStashStore.getState().entries).toHaveLength(1)); + + // An answer carries no machines, so an open question refuses the restore and keeps the entry. + await router.navigate({ to: `/${ENVIRONMENT_ID}/${asking.id}` }); + await expect.element(page.getByText("Which file should change?")).toBeVisible(); + await userEvent.click(page.getByRole("button", { name: /^Stashed prompts/ })); + await userEvent.click(page.getByRole("button", { name: /^Restore stashed prompt/ })); + await vi.waitFor(() => + expect(document.body.textContent).toContain("Stashed machines can't go in an answer"), + ); + expect(usePromptStashStore.getState().entries).toHaveLength(1); + + await router.navigate({ to: `/${ENVIRONMENT_ID}/${other.id}` }); + await expect.element(page.getByText("Answer 0 for thread-stash-restore.")).toBeVisible(); + await pressStashShortcut(); + + await vi.waitFor(() => expect(editor.element().textContent).toContain("Deploy on")); + expect( + useComposerDraftStore.getState().getComposerDraft(scopeThreadRef(ENVIRONMENT_ID, other.id)) + ?.environmentContexts, + ).toEqual([machine]); + expect( + useComposerDraftStore.getState().getComposerDraft(draftId)?.environmentContexts ?? [], + ).toEqual([]); +}); + +it("a question that opens while a stashed file is checked keeps a machine stash", async () => { + const thread = threadWithHistory("thread-stash-late-question", 1); + const asking = threadWithQuestion("thread-stash-late-question", "Which file should change?"); + const [request] = asking.runtimeRequests ?? []; + const question = asking.items.at(-1); + if (!request || !question) throw new Error("threadWithQuestion has no question"); + using _environment = installFakeEnvironment([thread]); + __setClientSettingsForTests({ + ...DEFAULT_CLIENT_SETTINGS, + onboardingCompletedAt: "2026-01-01T00:00:00.000Z", + }); + usePromptStashStore.getState().stashEntry({ + id: "stash-late-question", + createdAt: "2026-01-01T00:00:00.000Z", + prompt: "Deploy on ", + attachments: [], + files: [ + { + id: "file-notes", + name: "notes.txt", + mimeType: "text/plain", + sizeBytes: 5, + attachmentId: "attachment-notes", + environmentId: ENVIRONMENT_ID, + }, + ], + droppedImageNames: [], + records: [environmentContextRecord(EnvironmentId.make("environment-build"), "Build box")], + }); + const router = getRouter( + createMemoryHistory({ initialEntries: [`/${ENVIRONMENT_ID}/${thread.id}`] }), + ); + await router.load(); + await using _app = await render(, { + // AppRoot brings its own router. + router: false, + }); + await expect.element(page.getByText("Answer 0 for thread-stash-late-question.")).toBeVisible(); + await userEvent.click(page.getByRole("button", { name: /^Stashed prompts/ })); + await userEvent.click(page.getByRole("button", { name: /^Restore stashed prompt/ })); + + // The question opens while the restore still waits on the server's file check. + await vi.waitFor(() => expect(hasPendingAssetUrl()).toBe(true)); + await emitThreadEvent(thread.id, { type: "turn-item.updated", payload: question }); + await emitThreadEvent(thread.id, { type: "runtime-request.updated", payload: request }); + await expect.element(page.getByText("Which file should change?")).toBeVisible(); + await answerAssetUrls(); + + await vi.waitFor(() => + expect(document.body.textContent).toContain("Stashed machines can't go in an answer"), + ); + expect(usePromptStashStore.getState().entries).toHaveLength(1); + usePromptStashStore.getState().takeEntry("stash-late-question"); +}); + it("a new draft does not load its reserved thread before the server lists it", async () => { using _environment = installFakeEnvironment([]); using fetchSpy = vi.spyOn(globalThis, "fetch"); diff --git a/apps/web/src/components/chat/useComposerStash.ts b/apps/web/src/components/chat/useComposerStash.ts index 1a765515775dd..f32b61fd9a704 100644 --- a/apps/web/src/components/chat/useComposerStash.ts +++ b/apps/web/src/components/chat/useComposerStash.ts @@ -145,6 +145,9 @@ export function useComposerStash(input: { (store) => store.clearComposerPromptAndImages, ); const setComposerDraftThreadContexts = useComposerDraftStore((store) => store.setThreadContexts); + const setComposerDraftEnvironmentContexts = useComposerDraftStore( + (store) => store.setEnvironmentContexts, + ); const [isStashMenuOpen, setIsStashMenuOpen] = useState(false); // Each stash bumps the key; the badge replays its one-shot pulse animation per key. const [stashPulseKey, setStashPulseKey] = useState(0); @@ -159,6 +162,23 @@ export function useComposerStash(input: { setStashPulseKey((key) => key + 1); }; + // Stable, so the check after the verify await sees a question that opened during it. + const refuseMachinesInAnswer = useStableCallback((menuEntry: PromptStashEntry) => { + // An answer to an agent's question carries no machines, so restoring here would drop them. + if ( + pendingUserInputs.length > 0 && + menuEntry.records?.some((record) => record.kind === "environment") + ) { + toastManager.add({ + type: "error", + title: "Stashed machines can't go in an answer", + description: "Restore this prompt after the agent's question is answered.", + }); + return true; + } + return false; + }); + const restoreStashEntry = async (menuEntry: PromptStashEntry) => { const filesToVerify = menuEntry.files ?? []; if (filesToVerify.some((file) => file.environmentId !== environmentId)) { @@ -169,6 +189,7 @@ export function useComposerStash(input: { }); return; } + if (refuseMachinesInAnswer(menuEntry)) return; setIsStashMenuOpen(false); // An expired upload restores as a needs-reattach row instead of a reference @@ -191,7 +212,8 @@ export function useComposerStash(input: { // so abort and leave the entry restorable where the user now is. if ( isRevertingCheckpoint() || - !isComposerDraftTargetOpen(composerTargetKey(composerDraftTarget)) + !isComposerDraftTargetOpen(composerTargetKey(composerDraftTarget)) || + refuseMachinesInAnswer(menuEntry) ) { return; } @@ -483,6 +505,7 @@ export function useComposerStash(input: { const stashedRecords: ComposerContextRecord[] = [ ...(replyTo ? [replyTo] : []), ...composerThreadContexts, + ...(getComposerDraft(composerDraftTarget)?.environmentContexts ?? []), ...readTerminalContexts().map(terminalContextRecord), ...composerReviewComments.map(reviewCommentContextRecord), ...composerPreviewAnnotations.map((annotation) => @@ -596,6 +619,7 @@ export function useComposerStash(input: { clearComposerDraftPromptAndImages(stashTarget); clearComposerDraftTerminalContexts(stashTarget); setComposerDraftThreadContexts(stashTarget, []); + setComposerDraftEnvironmentContexts(stashTarget, []); setComposerDraftReplyTo(stashTarget, null); for (const comment of composerReviewComments) { removeComposerDraftReviewComment(stashTarget, comment.id); diff --git a/apps/web/test/fakeEnvironment.ts b/apps/web/test/fakeEnvironment.ts index 24aba2f822ea1..6fc18e58dea8a 100644 --- a/apps/web/test/fakeEnvironment.ts +++ b/apps/web/test/fakeEnvironment.ts @@ -2,17 +2,20 @@ // session that answers the shell, thread and config subscriptions from fixtures. Other RPCs // stay pending so nothing retries on timers. Everything above the wire is the app's own. import { + type AssetCreateUrlResult, AuthStandardClientScopes, EnvironmentId, EventId, ExecutionEnvironmentDescriptor, MessageId, + NodeId, OrchestrationV2DispatchCommandError, ORCHESTRATION_PROTOCOL_VERSION, ORCHESTRATION_V2_WS_METHODS, ProjectId, ProviderInstanceId, RunId, + RuntimeRequestId, ServerConfig, ThreadId, TurnItemId, @@ -227,6 +230,7 @@ export interface FakeThread { readonly providerThreads?: ReadonlyArray; readonly runs: ReadonlyArray; readonly items: ReadonlyArray; + readonly runtimeRequests?: OrchestrationV2ThreadProjection["runtimeRequests"]; } /** A thread of `turns` user prompts, each answered in markdown with a list and inline code. */ @@ -256,6 +260,47 @@ export function threadWithHistory( return { id, title: `Thread ${name}`, modelSelection, runs, items }; } +/** A one-turn thread whose agent waits on an answer to one question. */ +export function threadWithQuestion(name: string, question: string): FakeThread { + const thread = threadWithHistory(name, 1); + const requestId = RuntimeRequestId.make(`${name}-request`); + return { + ...thread, + items: [ + ...thread.items, + { + ...base(thread.id, RunId.make(`${name}-run-0`), `${name}-question`, 2), + type: "user_input_request", + requestId, + responseMode: "message", + questions: [ + { + id: "choice", + header: "Choice", + question, + required: true, + allowCustomAnswer: false, + options: [{ label: "Continue", value: "continue", description: "Resume work" }], + }, + ], + }, + ], + runtimeRequests: [ + { + id: requestId, + nodeId: NodeId.make(`${name}-node`), + providerTurnId: null, + nativeRequestRef: null, + kind: "user_input", + status: "pending", + responseCapability: { type: "message" }, + createdAt: AT, + resolvedAt: null, + }, + ], + }; +} + function shellOf(thread: FakeThread): OrchestrationV2ThreadShell { const latest = thread.runs.at(-1); const modelSelection = thread.modelSelection ?? MODEL; @@ -341,7 +386,7 @@ function projectionOf(thread: FakeThread): OrchestrationV2ThreadProjection { providerSessions: [], providerThreads: [...(thread.providerThreads ?? [])], providerTurns: [], - runtimeRequests: [], + runtimeRequests: [...(thread.runtimeRequests ?? [])], messages: [], plans: [], turnItems: [...thread.items], @@ -368,6 +413,7 @@ let pendingDispatches: Array<{ readonly command: OrchestrationV2Command; readonly reply: Deferred.Deferred; }> = []; +let pendingAssetUrls: Array> = []; let sequence = 1; let vcsStatusSubscriber: Queue.Queue | null = null; @@ -396,6 +442,13 @@ const handlers: Record unknown> = { pendingDispatches.push({ command, reply }); return Deferred.await(reply); }), + // Held like any other unanswered call, until `answerAssetUrls`. + [WS_METHODS.assetsCreateUrl]: () => + Effect.suspend(() => { + const reply = Deferred.makeUnsafe(); + pendingAssetUrls.push(reply); + return Deferred.await(reply); + }), [WS_METHODS.subscribeTerminalMetadata]: () => subscribe([{ type: "snapshot", terminals: [] }], (queue) => { terminalMetadataSubscriber = queue; @@ -544,6 +597,7 @@ export function installFakeEnvironment( acceptCommands = false; holdCommand = () => false; pendingDispatches = []; + pendingAssetUrls = []; }, }; } @@ -564,6 +618,21 @@ export async function rejectPendingCommand(message: string): Promise { ); } +export function hasPendingAssetUrl(): boolean { + return pendingAssetUrls.length > 0; +} + +/** Answers every held asset URL request, as a server that still has the file. */ +export async function answerAssetUrls(): Promise { + const replies = pendingAssetUrls; + pendingAssetUrls = []; + for (const reply of replies) { + await Effect.runPromise( + Deferred.succeed(reply, { relativeUrl: "/api/assets/fake", expiresAt: 4_102_444_800_000 }), + ); + } +} + /** Commands the app dispatched to the environment, oldest first. */ export function dispatchedCommands(): ReadonlyArray { return dispatched; From a575d45286f0c6496a47f823485b598167f52437 Mon Sep 17 00:00:00 2001 From: Andrei <108398860+Yuruzuu@users.noreply.github.com> Date: Sat, 14 Nov 2026 20:56:33 +0800 Subject: [PATCH 102/192] fix(web,server): the branch label no longer flips back after a switch (#28575) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/vcs/VcsStatusBroadcaster.test.ts | 43 +++++++++++++ apps/server/src/vcs/VcsStatusBroadcaster.ts | 61 +++++++++++++----- apps/server/src/ws.ts | 14 ++++- .../components/BranchToolbar.logic.test.ts | 57 ++++++++++++++++- .../web/src/components/BranchToolbar.logic.ts | 30 +++++++++ .../BranchToolbarBranchSelector.tsx | 63 ++++++++++++++++--- 6 files changed, 240 insertions(+), 28 deletions(-) diff --git a/apps/server/src/vcs/VcsStatusBroadcaster.test.ts b/apps/server/src/vcs/VcsStatusBroadcaster.test.ts index 96e29039abbd2..0a336a66ca2ff 100644 --- a/apps/server/src/vcs/VcsStatusBroadcaster.test.ts +++ b/apps/server/src/vcs/VcsStatusBroadcaster.test.ts @@ -1068,6 +1068,49 @@ describe("VcsStatusBroadcaster", () => { }, ); + it.effect("publishes a checkout's branch before returning, ahead of the remote read", () => { + const state: Parameters[0] = { + currentLocalStatus: baseLocalStatus, + currentRemoteStatus: baseRemoteStatus, + localStatusCalls: 0, + remoteStatusCalls: 0, + localInvalidationCalls: 0, + remoteInvalidationCalls: 0, + }; + + return Effect.gen(function* () { + const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster; + yield* broadcaster.getStatus({ cwd: "/repo" }); + const subscribed = yield* Deferred.make(); + const localPublished = yield* Deferred.make(); + const remotePublished = yield* Deferred.make(); + yield* broadcaster.streamStatus({ cwd: "/repo", includeRemote: false }).pipe( + Stream.runForEach((event) => { + if (event._tag === "snapshot") return Deferred.succeed(subscribed, undefined); + if (event._tag === "localUpdated") return Deferred.succeed(localPublished, event.local); + return event.remote === null + ? Effect.void + : Deferred.succeed(remotePublished, event.remote); + }), + Effect.forkScoped, + ); + yield* Deferred.await(subscribed); + + const releaseRemote = yield* Deferred.make(); + state.beforeRemoteStatus = Deferred.await(releaseRemote); + state.currentLocalStatus = { ...baseLocalStatus, refName: "main" }; + state.currentRemoteStatus = remoteStatusWithPr; + yield* broadcaster.refreshAfterCheckout("/repo"); + + assert.equal((yield* Deferred.await(localPublished)).refName, "main"); + assert.isFalse(yield* Deferred.isDone(remotePublished)); + + yield* Deferred.succeed(releaseRemote, undefined); + assert.deepStrictEqual(yield* Deferred.await(remotePublished), remoteStatusWithPr); + assert.equal((yield* broadcaster.getStatus({ cwd: "/repo" })).pr?.number, 2978); + }).pipe(Effect.provide(layerTestFor(state)), Effect.scoped); + }); + it.effect("a remote poll refreshes Changes totals when divergence counts stay the same", () => { const state: Parameters[0] = { currentLocalStatus: { diff --git a/apps/server/src/vcs/VcsStatusBroadcaster.ts b/apps/server/src/vcs/VcsStatusBroadcaster.ts index 06cc4aff7dfa2..00edfc3634a67 100644 --- a/apps/server/src/vcs/VcsStatusBroadcaster.ts +++ b/apps/server/src/vcs/VcsStatusBroadcaster.ts @@ -236,6 +236,11 @@ export class VcsStatusBroadcaster extends Context.Service< readonly refreshLocalStatus: ( cwd: string, ) => Effect.Effect; + /** + * For a checkout someone is waiting on. Returns once the new branch is published, so the + * caller's reply does not outrun it, and finishes the slower remote read in the background. + */ + readonly refreshAfterCheckout: (cwd: string) => Effect.Effect; readonly refreshStatus: ( cwd: string, options?: { readonly localOnly?: boolean }, @@ -603,6 +608,30 @@ export const make = Effect.gen(function* () { ); }); + const refreshRemoteAndLocalTotals = Effect.fn("VcsStatusBroadcaster.refreshRemoteAndLocalTotals")( + function* (cwd: string, policyCwd: string) { + return yield* withRemoteWriteLock( + cwd, + Effect.gen(function* () { + const refreshUpstream = !Duration.isZero(yield* automaticFetchInterval); + // Bump the PR lookup epoch after earlier remote writers have finished. + yield* workflow.invalidateStatus(cwd); + const remoteRead = yield* readRemoteStatus(cwd, { refreshUpstream }); + // Fetch can move the base used for local Changes totals. + yield* refreshLocalStatusCore(cwd); + // Without a fetch, the cached behind count cannot justify an automatic pull. + const pulled = refreshUpstream + ? yield* maybeAutoPull(cwd, remoteRead, [policyCwd]) + : null; + if (pulled !== null) return mergeGitStatusParts(pulled.local, pulled.remote); + yield* updateCachedRemoteStatus(cwd, remoteRead, { publish: true }); + const parts = yield* getStatusParts(cwd); + return mergeGitStatusParts(parts.local, parts.remote); + }), + ); + }, + ); + const refreshStatus: VcsStatusBroadcaster["Service"]["refreshStatus"] = Effect.fn( "VcsStatusBroadcaster.refreshStatus", )(function* (rawCwd, options) { @@ -613,23 +642,22 @@ export const make = Effect.gen(function* () { const parts = yield* getStatusParts(cwd); return mergeGitStatusParts(parts.local, parts.remote); } - return yield* withRemoteWriteLock( - cwd, - Effect.gen(function* () { - const refreshUpstream = !Duration.isZero(yield* automaticFetchInterval); - // Bump the PR lookup epoch after earlier remote writers have finished. - yield* workflow.invalidateStatus(cwd); - const remoteRead = yield* readRemoteStatus(cwd, { refreshUpstream }); - // Fetch can move the base used for local Changes totals. - yield* refreshLocalStatusCore(cwd); - // Without a fetch, the cached behind count cannot justify an automatic pull. - const pulled = refreshUpstream ? yield* maybeAutoPull(cwd, remoteRead, [rawCwd]) : null; - if (pulled !== null) return mergeGitStatusParts(pulled.local, pulled.remote); - yield* updateCachedRemoteStatus(cwd, remoteRead, { publish: true }); - const parts = yield* getStatusParts(cwd); - return mergeGitStatusParts(parts.local, parts.remote); - }), + return yield* refreshRemoteAndLocalTotals(cwd, rawCwd); + }); + + const refreshAfterCheckout: VcsStatusBroadcaster["Service"]["refreshAfterCheckout"] = Effect.fn( + "VcsStatusBroadcaster.refreshAfterCheckout", + )(function* (rawCwd) { + const cwd = yield* withFileSystem(normalizeCwd(rawCwd)); + const localDone = yield* Deferred.make(); + // Owned by the broadcaster so a client disconnecting mid-switch still leaves status correct. + yield* refreshLocalStatusCore(cwd).pipe( + Effect.ensuring(Deferred.succeed(localDone, undefined)), + Effect.andThen(refreshRemoteAndLocalTotals(cwd, rawCwd)), + Effect.ignoreCause({ log: true }), + Effect.forkIn(broadcasterScope), ); + yield* Deferred.await(localDone); }); const refreshPullRequestStatus: VcsStatusBroadcaster["Service"]["refreshPullRequestStatus"] = @@ -1122,6 +1150,7 @@ export const make = Effect.gen(function* () { ), getStatus, refreshLocalStatus, + refreshAfterCheckout, refreshStatus: shareQueuedRefreshStatus, refreshPullRequestStatus, streamStatus, diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 16a8d856ab7e9..dfb96d3fc84f8 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -3172,9 +3172,19 @@ export const layerWsRpc = ( ), ), [WS_METHODS.vcsCreateRef]: (input) => - gitWorkflow.createRef(input).pipe(Effect.tap(() => refreshGitStatus(input.cwd))), + gitWorkflow + .createRef(input) + .pipe( + Effect.tap(() => + input.switchRef + ? vcsStatusBroadcaster.refreshAfterCheckout(input.cwd) + : refreshGitStatus(input.cwd), + ), + ), [WS_METHODS.vcsSwitchRef]: (input) => - gitWorkflow.switchRef(input).pipe(Effect.tap(() => refreshGitStatus(input.cwd))), + gitWorkflow + .switchRef(input) + .pipe(Effect.tap(() => vcsStatusBroadcaster.refreshAfterCheckout(input.cwd))), [WS_METHODS.vcsInit]: (input) => vcsProvisioning.initRepository(input).pipe(Effect.tap(() => refreshGitStatus(input.cwd))), [WS_METHODS.reviewGetBranchCommits]: (input) => review.getBranchCommits(input), diff --git a/apps/web/src/components/BranchToolbar.logic.test.ts b/apps/web/src/components/BranchToolbar.logic.test.ts index b757e36d19482..143b0a59b7143 100644 --- a/apps/web/src/components/BranchToolbar.logic.test.ts +++ b/apps/web/src/components/BranchToolbar.logic.test.ts @@ -1,4 +1,4 @@ -import { ProviderInstanceId, RunId, type VcsRef } from "@t3tools/contracts"; +import { EnvironmentId, ProviderInstanceId, RunId, type VcsRef } from "@t3tools/contracts"; import { describe, expect, it } from "vite-plus/test"; import { dedupeRemoteBranchesWithLocalMatches } from "@t3tools/shared/git"; import { @@ -13,6 +13,7 @@ import { resolveLockedWorkspaceLabel, sanitizeNewRefName, shouldIncludeBranchPickerItem, + isBranchHoldCurrent, } from "./BranchToolbar.logic"; describe("resolveDraftEnvModeAfterBranchChange", () => { @@ -332,6 +333,60 @@ describe("shouldIncludeBranchPickerItem", () => { }); }); +describe("isBranchHoldCurrent", () => { + const environmentId = EnvironmentId.make("env-a"); + const hold = { + branch: "feature", + environmentId, + checkouts: [{ cwd: "/repo", replaces: "main" }], + }; + const current = { canonicalBranch: "main", environmentId, cwd: "/repo" }; + + it("keeps showing the checked-out branch while status still reports the old one", () => { + expect(isBranchHoldCurrent(hold, current)).toBe(true); + }); + + it("lets go once status moves, even to a different branch than the checkout", () => { + for (const canonicalBranch of ["feature", "other", null]) { + expect(isBranchHoldCurrent(hold, { ...current, canonicalBranch })).toBe(false); + } + }); + + it("does not apply to another checkout that happens to be on the old branch", () => { + expect(isBranchHoldCurrent(hold, { ...current, cwd: "/other" })).toBe(false); + }); + + it("follows a thread moving from its worktree to the project root", () => { + const moveHold = { + branch: "main", + environmentId, + checkouts: [ + { cwd: "/repo-feature", replaces: "feature" }, + { cwd: "/repo", replaces: "dev" }, + ], + }; + expect( + isBranchHoldCurrent(moveHold, { + environmentId, + cwd: "/repo-feature", + canonicalBranch: "feature", + }), + ).toBe(true); + expect( + isBranchHoldCurrent(moveHold, { environmentId, cwd: "/repo", canonicalBranch: "dev" }), + ).toBe(true); + expect( + isBranchHoldCurrent(moveHold, { environmentId, cwd: "/repo", canonicalBranch: "main" }), + ).toBe(false); + }); + + it("does not apply to the same path on another environment", () => { + expect( + isBranchHoldCurrent(hold, { ...current, environmentId: EnvironmentId.make("env-b") }), + ).toBe(false); + }); +}); + describe("sanitizeNewRefName", () => { it("collapses a run of whitespace into a single dash", () => { expect(sanitizeNewRefName("new branch")).toBe("new-branch"); diff --git a/apps/web/src/components/BranchToolbar.logic.ts b/apps/web/src/components/BranchToolbar.logic.ts index 4531977c3b6c0..78b1bff4aff4d 100644 --- a/apps/web/src/components/BranchToolbar.logic.ts +++ b/apps/web/src/components/BranchToolbar.logic.ts @@ -199,6 +199,36 @@ export function resolveBranchToolbarValue(input: { return currentGitBranch ?? activeThreadBranch; } +export interface BranchHold { + readonly branch: string | null; + readonly environmentId: EnvironmentId; + /** + * Each checkout the thread can show before it catches up, with the branch its status reported + * before the switch. A move from a worktree to the project root lists both. + */ + readonly checkouts: ReadonlyArray<{ readonly cwd: string; readonly replaces: string | null }>; +} + +/** + * A finished checkout's branch stays on screen until status reports any change, because the status + * stream and the RPC reply are not ordered and showing status first would flip back to the old branch. + */ +export function isBranchHoldCurrent( + hold: BranchHold, + current: { + readonly canonicalBranch: string | null; + readonly environmentId: EnvironmentId; + readonly cwd: string | null; + }, +): boolean { + return ( + hold.environmentId === current.environmentId && + hold.checkouts.some( + (checkout) => checkout.cwd === current.cwd && checkout.replaces === current.canonicalBranch, + ) + ); +} + /** * The base a New worktree thread gets without the user picking one. Only a listed ref qualifies: * during a clone the status names an unborn HEAD that no ref backs. diff --git a/apps/web/src/components/BranchToolbarBranchSelector.tsx b/apps/web/src/components/BranchToolbarBranchSelector.tsx index ad139c40c0c57..02fa9de2e716c 100644 --- a/apps/web/src/components/BranchToolbarBranchSelector.tsx +++ b/apps/web/src/components/BranchToolbarBranchSelector.tsx @@ -45,7 +45,9 @@ import { parsePullRequestReference } from "@t3tools/client-runtime/pull-request- import { getSourceControlPresentation } from "../sourceControlPresentation"; import { useComposerMenuProps } from "./chat/composerEventScope"; import { + type BranchHold, deriveLocalBranchNameFromRemoteRef, + isBranchHoldCurrent, isWorktreeChangeBlocked, resolveBranchTriggerLabel, resolveBranchToolbarPrBranch, @@ -180,6 +182,7 @@ export function BranchToolbarBranchSelector({ branch: string | null, worktreePath: string | null, automatic = false, + onMetadataFailure?: () => void, ) => { if ( !activeThreadId || @@ -216,6 +219,8 @@ export function BranchToolbarBranchSelector({ worktreePath, ...(automatic ? { expectedEmpty: true } : {}), }, + }).then((result) => { + if (result._tag !== "Success") onMetadataFailure?.(); }); } if (hasServerThread) { @@ -326,8 +331,17 @@ export function BranchToolbarBranchSelector({ activeThreadBranch, currentGitBranch, }); + const [branchHold, setBranchHold] = useState(null); + const branchHoldCurrent = + branchHold !== null && + isBranchHoldCurrent(branchHold, { + canonicalBranch: canonicalActiveBranch, + environmentId, + cwd: branchCwd, + }); + if (branchHold !== null && !branchHoldCurrent) setBranchHold(null); const [resolvedActiveBranch, setOptimisticBranch] = useOptimistic( - canonicalActiveBranch, + branchHoldCurrent ? branchHold.branch : canonicalActiveBranch, (_currentBranch: string | null, optimisticBranch: string | null) => optimisticBranch, ); const listedActiveBranch = @@ -406,7 +420,6 @@ export function BranchToolbarBranchSelector({ startBranchActionTransition(async () => { await action(); branchRefState.refresh(); - branchStatusQuery.refresh(); }); }; @@ -468,11 +481,37 @@ export function BranchToolbarBranchSelector({ const nextBranchName = refName.isRemote ? (checkoutResult.value.refName ?? selectedBranchName) : selectedBranchName; - setOptimisticBranch( - setThreadBranch(nextBranchName, selectionTarget.nextWorktreePath) - ? nextBranchName - : previousBranch, + const movesCheckout = selectionTarget.checkoutCwd !== branchCwd; + let hold: BranchHold | null = null; + const applied = setThreadBranch( + nextBranchName, + selectionTarget.nextWorktreePath, + false, + // A failed move leaves the thread in its old checkout, which never had the new branch. + movesCheckout + ? () => setBranchHold((current) => (current === hold ? null : current)) + : undefined, ); + if (applied) { + hold = { + branch: nextBranchName, + environmentId, + // The thread keeps its old checkout until the metadata update moves it to the new one. + checkouts: [ + { cwd: branchCwd, replaces: canonicalActiveBranch }, + ...(movesCheckout + ? [ + { + cwd: selectionTarget.checkoutCwd, + replaces: projectStatusQuery.data?.refName ?? null, + }, + ] + : []), + ], + }; + setBranchHold(hold); + } + setOptimisticBranch(applied ? nextBranchName : previousBranch); return; } setOptimisticBranch(previousBranch); @@ -509,9 +548,15 @@ export function BranchToolbarBranchSelector({ }); if (createBranchResult._tag === "Success") { const nextBranchName = createBranchResult.value.refName; - setOptimisticBranch( - setThreadBranch(nextBranchName, activeWorktreePath) ? nextBranchName : previousBranch, - ); + const applied = setThreadBranch(nextBranchName, activeWorktreePath); + if (applied) { + setBranchHold({ + branch: nextBranchName, + environmentId, + checkouts: [{ cwd: branchCwd, replaces: canonicalActiveBranch }], + }); + } + setOptimisticBranch(applied ? nextBranchName : previousBranch); return; } setOptimisticBranch(previousBranch); From 77d3358a1f6f49a6a0da05d815c52ddf0485caee Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 04:56:47 -0800 Subject: [PATCH 103/192] fix(server): ACP teardown survives processes exiting during the /proc scan (#36719) --- .../acp/AcpSessionRuntime.processTree.test.ts | 28 +++++++++++++++++++ .../src/server/AcpSessionRuntime.ts | 7 +++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts b/apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts index 0c6bddbbf8567..6b9afa7f30c50 100644 --- a/apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts +++ b/apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts @@ -1,6 +1,7 @@ // @effect-diagnostics nodeBuiltinImport:off import * as NodeChildProcess from "node:child_process"; import * as NodeFS from "node:fs"; +import * as NodeModule from "node:module"; import * as NodePath from "node:path"; import { describe, expect, it, vi } from "@effect/vitest"; @@ -813,6 +814,33 @@ describe("terminatePosixOwnedProcessTree", () => { }), ); + it.skipIf(HostProcess.Platform.defaultValue() !== "linux")( + "snapshots /proc when a process exits while its entry is being typed", + () => { + const fs = NodeModule.createRequire(import.meta.url)("node:fs") as typeof NodeFS; + const realReaddirSync = fs.readdirSync; + // Node lstat()s a dirent of unknown type; a process that exits meanwhile makes it throw. + fs.readdirSync = ((path: NodeFS.PathLike, options?: unknown) => { + if (path === "/proc" && typeof options === "object" && options !== null) { + throw Object.assign(new Error("ENOENT: no such file or directory, lstat '/proc/4563'"), { + code: "ENOENT", + syscall: "lstat", + path: "/proc/4563", + }); + } + return Reflect.apply(realReaddirSync, fs, [path, options]); + }) as typeof NodeFS.readdirSync; + NodeModule.syncBuiltinESMExports(); + try { + const snapshot = AcpSessionRuntime.makePosixProcessTreeController("linux").snapshot(); + expect(snapshot.some((entry) => entry.pid === process.pid)).toBe(true); + } finally { + fs.readdirSync = realReaddirSync; + NodeModule.syncBuiltinESMExports(); + } + }, + ); + it("fails closed where a stable POSIX identity provider is unavailable", () => { try { AcpSessionRuntime.makePosixProcessTreeController("darwin"); diff --git a/packages/provider-acp/src/server/AcpSessionRuntime.ts b/packages/provider-acp/src/server/AcpSessionRuntime.ts index c603dd937fcaf..b8f089f410b6d 100644 --- a/packages/provider-acp/src/server/AcpSessionRuntime.ts +++ b/packages/provider-acp/src/server/AcpSessionRuntime.ts @@ -623,9 +623,10 @@ function makeLinuxProcessTreeController(): AcpPosixProcessTreeController { }), identity: readLinuxProcessIdentity, snapshot: () => - NodeFS.readdirSync("/proc", { withFileTypes: true }).flatMap((entry) => { - if (!entry.isDirectory() || !/^[1-9][0-9]*$/.test(entry.name)) return []; - const identity = readLinuxProcessIdentity(Number(entry.name)); + // By name only: withFileTypes lstats entries, which throws when a process exits mid-scan. + NodeFS.readdirSync("/proc").flatMap((entry) => { + if (!/^[1-9][0-9]*$/.test(entry)) return []; + const identity = readLinuxProcessIdentity(Number(entry)); return identity === undefined ? [] : [identity]; }), signalProcess: (pid, signal) => process.kill(pid, signal), From e72dd5055eb437c93c0b3ddf710246ecbfe1f16e Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 14:03:02 +0100 Subject: [PATCH 104/192] fix(client): closed terminal views release the shared grid (#36647) Co-authored-by: Julius Marminge --- .../client-runtime/src/state/runtime.test.ts | 36 +++++++++++++++++++ packages/client-runtime/src/state/terminal.ts | 2 ++ 2 files changed, 38 insertions(+) diff --git a/packages/client-runtime/src/state/runtime.test.ts b/packages/client-runtime/src/state/runtime.test.ts index 63f00b28fb173..639b63436b69d 100644 --- a/packages/client-runtime/src/state/runtime.test.ts +++ b/packages/client-runtime/src/state/runtime.test.ts @@ -705,6 +705,42 @@ describe("environment query lifecycle", () => { }), ); + // The server sizes a shared PTY to every attached viewer, so a lingering attach pins its grid. + it.effect("terminal attach detaches as soon as its last viewer leaves", () => + Effect.gen(function* () { + const harness = yield* makeEnvironmentQueryHarness(Effect.void); + const attached = yield* Deferred.make(); + const detached = yield* Deferred.make(); + const client = { + [WS_METHODS.terminalAttach]: () => + Stream.fromEffect(Deferred.succeed(attached, undefined)).pipe( + Stream.drain, + Stream.concat(Stream.never), + Stream.ensuring(Deferred.succeed(detached, undefined)), + ), + } as unknown as RpcSession.RpcSession["client"]; + yield* SubscriptionRef.set( + harness.supervisorSession, + Option.some({ ...QUERY_RPC_SESSION, client }), + ); + const atoms = createTerminalEnvironmentAtoms(harness.runtime); + const registry = AtomRegistry.make(); + yield* Effect.addFinalizer(() => Effect.sync(() => registry.dispose())); + const unsubscribe = registry.subscribe( + atoms.attach({ + environmentId: QUERY_ENVIRONMENT.environmentId, + input: { threadId: "thread-1", terminalId: "terminal-1", cwd: "/tmp/project" }, + }), + () => {}, + { immediate: true }, + ); + yield* Deferred.await(attached); + + unsubscribe(); + yield* Deferred.await(detached); + }), + ); + it.effect( "hides a session replacement, reports the relay backoff, and recovers on reconnect", () => diff --git a/packages/client-runtime/src/state/terminal.ts b/packages/client-runtime/src/state/terminal.ts index 7e87ef41f5edc..5eb7613ebb384 100644 --- a/packages/client-runtime/src/state/terminal.ts +++ b/packages/client-runtime/src/state/terminal.ts @@ -64,6 +64,8 @@ export function createTerminalEnvironmentAtoms( }), attach: createEnvironmentSubscriptionAtomFamily(runtime, { label: "environment-data:terminal:attach", + // The server sizes the PTY to every attached viewer, so a closed view must detach now. + idleTtlMs: 0, subscribe: (input: EnvironmentRpcInput) => { // Retry an unanswered first attach unchanged so it can still open the shell. let replied = false; From cc0a7bb9d8556cbe821b14e89ee9025b70192ebd Mon Sep 17 00:00:00 2001 From: Abhishek B R Date: Sat, 14 Nov 2026 18:39:16 +0530 Subject: [PATCH 105/192] fix(shared): file links with parentheses in the path keep their parent folder labels (#28644) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/web/src/components/ChatMarkdown.tsx | 2 ++ packages/shared/src/markdownLinks.test.ts | 15 ++++++++++ packages/shared/src/markdownLinks.ts | 29 ++++++++++++++++-- packages/shared/src/markdownPipeline.ts | 19 ++++++++++++ packages/shared/src/threadFindText.test.ts | 35 ++++++++++++++++++++++ packages/shared/src/threadFindText.ts | 5 +++- 6 files changed, 101 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx index 4cfae88da1745..a146aa9743797 100644 --- a/apps/web/src/components/ChatMarkdown.tsx +++ b/apps/web/src/components/ChatMarkdown.tsx @@ -19,6 +19,7 @@ import { CHAT_MARKDOWN_REMARK_PLUGINS, CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS, CHAT_MARKDOWN_REHYPE_PLUGINS, + decodeMarkdownLinkEscapes, remarkAssistantInsights, } from "@t3tools/shared/markdownPipeline"; import { useAtomValue } from "@effect/atom-react"; @@ -471,6 +472,7 @@ function fileLinkSourcesKey( ): string { const hrefs = extractMarkdownLinkHrefs(renderCodexInlineDirectivesAsMarkdown(text)) .filter((href) => !parseComposerContextHref(href)) + .map(decodeMarkdownLinkEscapes) .map(normalizeMarkdownLinkHrefKey) .filter((href) => resolveMarkdownFileLinkMeta(href, cwd, imageBaseDir ?? cwd)); const spans = extractInlineCodeSpans(text).filter((span) => diff --git a/packages/shared/src/markdownLinks.test.ts b/packages/shared/src/markdownLinks.test.ts index 1459826dfd0f3..d64f51ccd2a0a 100644 --- a/packages/shared/src/markdownLinks.test.ts +++ b/packages/shared/src/markdownLinks.test.ts @@ -162,6 +162,21 @@ describe("extractMarkdownLinkHrefs", () => { ])("extracts links from incomplete and nested text", (markdown, hrefs) => { expect(extractMarkdownLinkHrefs(markdown)).toEqual(hrefs); }); + + it.each([ + [ + "[a](/x/app/(a)/page.tsx) [b](/x/app/(b)/page.tsx)", + ["/x/app/(a)/page.tsx", "/x/app/(b)/page.tsx"], + ], + ['[a](/tmp/a(1).txt "title") after', ["/tmp/a(1).txt"]], + ["[a](a(b(c))d) [b](ok)", ["a(b(c))d", "ok"]], + ["[a](x\\)y) [b](x\\(y)", ["x\\)y", "x\\(y"]], + ["[a](unclosed(paren) [b](ok)", ["ok"]], + ["[a](" + "(".repeat(32) + "x" + ")".repeat(32) + ")", ["(".repeat(32) + "x" + ")".repeat(32)]], + ["[a](" + "(".repeat(33) + "x" + ")".repeat(33) + ") [b](ok)", ["ok"]], + ])("keeps balanced parentheses in a destination like the parser: %s", (markdown, hrefs) => { + expect(extractMarkdownLinkHrefs(markdown)).toEqual(hrefs); + }); }); describe("resolveMarkdownFileLinkTarget", () => { diff --git a/packages/shared/src/markdownLinks.ts b/packages/shared/src/markdownLinks.ts index f9bd71e23363b..8cc537194e194 100644 --- a/packages/shared/src/markdownLinks.ts +++ b/packages/shared/src/markdownLinks.ts @@ -383,10 +383,33 @@ function markdownLinkTailScanner(markdown: string) { }; } +// micromark's own nesting limit. It also keeps link scanning linear: a scan only runs past another +// link's opening parenthesis while that parenthesis counts toward this depth. +const MAX_BARE_DESTINATION_PAREN_DEPTH = 32; + +function bareMarkdownLinkDestinationEnd(markdown: string, start: number): number | null { + const delimiter = /[\s()\\]/g; + delimiter.lastIndex = start; + let depth = 0; + while (true) { + const index = delimiter.exec(markdown)?.index ?? markdown.length; + const char = markdown[index]; + if (char === "\\") { + if (/[()\\]/.test(markdown[index + 1] ?? "")) delimiter.lastIndex = index + 2; + } else if (char === "(") { + if (depth === MAX_BARE_DESTINATION_PAREN_DEPTH) return null; + depth += 1; + } else if (char === ")" && depth > 0) { + depth -= 1; + } else { + return depth === 0 ? index : null; + } + } +} + function markdownLinkDestinationScanner(markdown: string) { const whitespaceEnd = markdownRunEndFinder(markdown, /\S/g); const angleEnd = markdownRunEndFinder(markdown, /[>\n]/g); - const bareEnd = markdownRunEndFinder(markdown, /[\s)]/g); // Separate caches keep scans moving forward when angle destinations fall back to bare ones. const angleTail = markdownLinkTailScanner(markdown); const bareTail = markdownLinkTailScanner(markdown); @@ -400,8 +423,8 @@ function markdownLinkDestinationScanner(markdown: string) { if (end !== null) return { hrefStart: hrefStart + 1, hrefEnd, end }; } } - const hrefEnd = bareEnd(hrefStart); - if (hrefEnd === hrefStart) return null; + const hrefEnd = bareMarkdownLinkDestinationEnd(markdown, hrefStart); + if (hrefEnd === null || hrefEnd === hrefStart) return null; const end = bareTail(hrefEnd); return end === null ? null : { hrefStart, hrefEnd, end }; }; diff --git a/packages/shared/src/markdownPipeline.ts b/packages/shared/src/markdownPipeline.ts index a549045259e49..2b0953fbadba5 100644 --- a/packages/shared/src/markdownPipeline.ts +++ b/packages/shared/src/markdownPipeline.ts @@ -30,6 +30,7 @@ import { isWindowsDrivePathHref, normalizeMarkdownLinkDestination, } from "./markdownLinks.ts"; +import { isWindowsAbsolutePath } from "./path.ts"; import { THREAD_LINK_PROTOCOL } from "./threadLinks.ts"; export { remarkEnableOrderedListNumbers } from "./markdownOrderedListNumbers.ts"; @@ -867,3 +868,21 @@ export function chatMarkdownImageSources(markdown: string): Array { visit(imageSourceProcessor.runSync(imageSourceProcessor.parse(markdown)) as ImageSourceNode); return sources; } + +const linkDestinationProcessor = unified().use(remarkParse).use(remarkKeepWindowsPaths); + +// An angle destination cannot hold an unescaped `<` or `>`, or end on a lone backslash. +const ANGLE_DESTINATION_CONTENT = /^(?:[^\\<>]|\\.)*$/; + +/** Reads a destination from `extractMarkdownLinkHrefs` as the renderer's parser does. */ +export function decodeMarkdownLinkEscapes(href: string): string { + // Only the parser knows character references, so destinations that may have one go through it. + if (href.includes("&")) { + // The extractor drops angle brackets, and only that form keeps an unmatched `)` in the path. + const destination = ANGLE_DESTINATION_CONTENT.test(href) ? `<${href}>` : href; + const paragraph = linkDestinationProcessor.parse(`[](${destination})`).children[0]; + const link = paragraph?.type === "paragraph" ? paragraph.children[0] : undefined; + if (link?.type === "link") return link.url; + } + return isWindowsAbsolutePath(href) ? href : href.replace(/\\([!-/:-@[-`{-~])/g, "$1"); +} diff --git a/packages/shared/src/threadFindText.test.ts b/packages/shared/src/threadFindText.test.ts index 3dabff0d8b4d0..6228da3ee8613 100644 --- a/packages/shared/src/threadFindText.test.ts +++ b/packages/shared/src/threadFindText.test.ts @@ -87,6 +87,41 @@ it("searches displayed prose and file-chip labels without hidden paths", () => { "label main.ts · L3:C2", ]); expect(assistantSegments("[label](src/main.ts#L3C2)")).toEqual(["label"]); + for (const text of [ + "[a](/repo/app/(a)/page.tsx) and [b](/repo/app/(b)/page.tsx)", + String.raw`[a](/repo/app/\(a\)/page.tsx) and [b](/repo/app/\(b\)/page.tsx)`, + ]) { + expect(assistantSegments(text, "/repo")).toEqual([ + "a page.tsx · app/(a) and b page.tsx · app/(b)", + ]); + } + expect( + assistantSegments( + String.raw`[a](C:\repo\.a\page.tsx) and [b](C:\repo\.b\page.tsx)`, + String.raw`C:\repo`, + ), + ).toEqual(["a page.tsx · repo/.a and b page.tsx · repo/.b"]); + expect( + assistantSegments( + String.raw`[a](C:\repo\.a\x&y\page.tsx) and [b](C:\repo\.b\x&y\page.tsx)`, + String.raw`C:\repo`, + ), + ).toEqual(["a page.tsx · .a/x&y and b page.tsx · .b/x&y"]); + expect( + assistantSegments("[a](/repo/a&b/page.tsx) and [b](/repo/c&d/page.tsx)", "/repo"), + ).toEqual(["a page.tsx · repo/a&b and b page.tsx · repo/c&d"]); + expect( + assistantSegments("[a]() and [b]()", "/repo"), + ).toEqual(["a page.tsx · repo/a&b) and b page.tsx · repo/c&d)"]); + expect( + assistantSegments("[a](/repo/a&b>c/page.tsx) and [b](/repo/c&d>e/page.tsx)", "/repo"), + ).toEqual(["a page.tsx · repo/a&b>c and b page.tsx · repo/c&d>e"]); + expect( + assistantSegments( + String.raw`[a]() and [b]()`, + "/repo", + ), + ).toEqual(["a page.tsx · repo/a { diff --git a/packages/shared/src/threadFindText.ts b/packages/shared/src/threadFindText.ts index 7c2b9006a16d8..fd0f7ec505cca 100644 --- a/packages/shared/src/threadFindText.ts +++ b/packages/shared/src/threadFindText.ts @@ -38,6 +38,7 @@ import { CHAT_MARKDOWN_REMARK_PLUGINS, CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS, CHAT_USER_MARKDOWN_REMARK_PLUGINS, + decodeMarkdownLinkEscapes, } from "./markdownPipeline.ts"; // Inline wrappers (including Shiki token spans) must not split a search phrase. @@ -130,7 +131,9 @@ function markdownThreadFindText( : processor; const tree = renderer.runSync(renderer.parse(markdown), markdown); const paths = [ - ...extractMarkdownLinkHrefs(renderCodexInlineDirectivesAsMarkdown(markdown)), + ...extractMarkdownLinkHrefs(renderCodexInlineDirectivesAsMarkdown(markdown)).map( + decodeMarkdownLinkEscapes, + ), ...extractInlineCodeSpans(markdown).flatMap((span) => inlineCodeFilePathCandidate(span) ?? []), ].flatMap((href) => { const target = resolveMarkdownFileLinkTarget(href, cwd); From f84ad6f30b6b9b29e772ed4ff455cf38c57c52a8 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 14:10:20 +0100 Subject: [PATCH 106/192] fix(web): discarded drafts stay discarded after image compression (#36732) Co-authored-by: Julius Marminge --- .../chat/preparingComposerImages.ts | 10 ++ ...eComposerAttachmentIntake.browser.test.tsx | 96 +++++++++++++++- apps/web/src/lib/discardComposerDraft.ts | 6 + ...carded-thread-draft-stays-discarded.e2e.ts | 38 +++++++ scripts/scenarios/image-compression-hold.ts | 103 ++++++++++++++++++ 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 scripts/scenarios/discarded-thread-draft-stays-discarded.e2e.ts create mode 100644 scripts/scenarios/image-compression-hold.ts diff --git a/apps/web/src/components/chat/preparingComposerImages.ts b/apps/web/src/components/chat/preparingComposerImages.ts index def7f023e8612..0e571510f764b 100644 --- a/apps/web/src/components/chat/preparingComposerImages.ts +++ b/apps/web/src/components/chat/preparingComposerImages.ts @@ -81,3 +81,13 @@ export function removePreparingComposerImages( }); return removed; } + +/** Drops every image still preparing for the target, so its compression result is discarded. */ +export function cancelPreparingComposerImages(targetKey: string): void { + if (!usePreparingComposerImageStore.getState().byTargetKey[targetKey]) return; + usePreparingComposerImageStore.setState((state) => { + const byTargetKey = { ...state.byTargetKey }; + delete byTargetKey[targetKey]; + return { byTargetKey }; + }); +} diff --git a/apps/web/src/components/chat/useComposerAttachmentIntake.browser.test.tsx b/apps/web/src/components/chat/useComposerAttachmentIntake.browser.test.tsx index cfacb19bc4101..9060af0d9fac7 100644 --- a/apps/web/src/components/chat/useComposerAttachmentIntake.browser.test.tsx +++ b/apps/web/src/components/chat/useComposerAttachmentIntake.browser.test.tsx @@ -1,13 +1,18 @@ import { useEffect } from "react"; -import { ThreadId } from "@t3tools/contracts"; +import { EnvironmentId, ProjectId, ThreadId } from "@t3tools/contracts"; +import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime/environment"; import { expect, it, vi } from "vite-plus/test"; +import { AsyncResult } from "effect/reactivity"; import { act, render } from "../../../test/render"; import { type ComposerImageAttachment, DraftId, + composerTargetKey, useComposerDraftStore, } from "../../composerDraftStore"; +import { showThreadUndoNotice } from "../../hooks/showThreadUndoNotice"; +import { discardComposerDraft } from "../../lib/discardComposerDraft"; import { getPreparingComposerImages } from "./preparingComposerImages"; import { useComposerAttachmentIntake } from "./useComposerAttachmentIntake"; @@ -16,6 +21,7 @@ vi.mock("../../lib/imageCompression", async (importOriginal) => ({ ...(await importOriginal()), prepareImageForAttachment: compression.prepare, })); +vi.mock("../../hooks/showThreadUndoNotice", () => ({ showThreadUndoNotice: vi.fn() })); type IntakeInput = Parameters[0]; type Intake = ReturnType; @@ -123,3 +129,91 @@ it("prepares a repeated paste without a tile, and still keeps a different image" await act(() => different); expect(composerImagesRef.current).toHaveLength(2); }); + +// The thread row's discard runs after the composer left that thread; a draft session's discard +// unmounts its composer afterwards. +it.each(["thread", "session"] as const)( + "a %s draft discarded while an image compresses stays discarded and can be restored", + async (kind) => { + const store = useComposerDraftStore.getState(); + const environmentId = EnvironmentId.make(`intake-discard-${kind}`); + const threadId = ThreadId.make("thread"); + const target = + kind === "session" ? DraftId.make("intake-discard") : scopeThreadRef(environmentId, threadId); + if (typeof target === "string") { + store.setProjectDraftThreadId(scopeProjectRef(environmentId, ProjectId.make("p")), target); + } + store.setPrompt(target, "half-written prompt"); + using _draft = { + [Symbol.dispose]: () => { + if (typeof target === "string") store.clearDraftThread(target); + else store.clearComposerContent(target); + }, + }; + const targetKey = composerTargetKey(target); + + const compressing = deferred(); + const resumeCompression = deferred(); + compression.prepare.mockReset().mockImplementation(async (file: File) => { + compressing.resolve(); + await resumeCompression.promise; + return compress(file); + }); + + const input = { + activeThreadId: threadId, + activePendingProgress: null, + activePendingIsResponding: false, + pendingUserInputs: [], + supportsQuestionAttachments: false, + isLiteralPendingAnswer: false, + projectSelectionRequired: false, + questionAttachmentTarget: null, + attachmentTargetKey: targetKey, + fileStagingLimit: null, + promptRef: { current: "half-written prompt" }, + composerImagesRef: { current: [] }, + composerFilesRef: { current: [] }, + composerEditorRef: { current: null }, + isRevertingCheckpointRef: { current: false }, + setThreadError: () => {}, + countReservedAttachments: () => getPreparingComposerImages(targetKey).length, + addComposerImage: (image) => store.addImages(target, [image]), + addComposerImagesToDraft: (images) => store.addImages(target, images), + addComposerFilesToDraft: () => [], + removeComposerImageFromDraft: (id) => store.removeImage(target, id), + insertAttachmentReferences: () => false, + } satisfies IntakeInput; + let intake!: Intake; + await using screen = await render( + { + intake = value; + }} + />, + ); + const attaching = intake.addComposerAttachments([pastedImage()]); + await compressing.promise; + + if (kind === "session") { + discardComposerDraft(target); + await screen.unmount(); + } else { + await screen.unmount(); + discardComposerDraft(target); + } + await act(async () => { + resumeCompression.resolve(); + await attaching; + }); + + expect(getPreparingComposerImages(targetKey)).toHaveLength(0); + expect(useComposerDraftStore.getState().getComposerDraft(target)).toBeNull(); + const undo = vi.mocked(showThreadUndoNotice).mock.calls.at(-1)![0].undo; + expect(AsyncResult.isSuccess(await undo())).toBe(true); + const restored = useComposerDraftStore.getState().getComposerDraft(target); + expect(restored?.prompt).toBe("half-written prompt"); + expect(restored?.images).toEqual([]); + }, +); diff --git a/apps/web/src/lib/discardComposerDraft.ts b/apps/web/src/lib/discardComposerDraft.ts index ecfc44978f132..5c0703b1085f1 100644 --- a/apps/web/src/lib/discardComposerDraft.ts +++ b/apps/web/src/lib/discardComposerDraft.ts @@ -1,9 +1,11 @@ import * as Cause from "effect/Cause"; import { AsyncResult } from "effect/reactivity"; +import { cancelPreparingComposerImages } from "../components/chat/preparingComposerImages"; import { type ComposerThreadTarget, composerDraftHasUserContent, + composerTargetKey, resolveComposerDraftKey, useComposerDraftStore, } from "../composerDraftStore"; @@ -23,6 +25,10 @@ export function discardComposerDraft(target: ComposerThreadTarget): void { const discardsSession = typeof target === "string"; const claim = ThreadUndo.begin("discard", key); + // The composer may key attachments by the target or by its resolved draft id; a late + // compression under either would recreate the draft. + cancelPreparingComposerImages(composerTargetKey(target)); + cancelPreparingComposerImages(key); if (discardsSession) { store.clearDraftThread(target); } else { diff --git a/scripts/scenarios/discarded-thread-draft-stays-discarded.e2e.ts b/scripts/scenarios/discarded-thread-draft-stays-discarded.e2e.ts new file mode 100644 index 0000000000000..021c6db16979b --- /dev/null +++ b/scripts/scenarios/discarded-thread-draft-stays-discarded.e2e.ts @@ -0,0 +1,38 @@ +import { expect } from "e2e"; + +import { + finishImageCompression, + holdImageCompression, + pasteWideScreenshot, +} from "./image-compression-hold.ts"; +import { test, WEB } from "./t3.ts"; + +test( + "A thread draft discarded while its pasted image compresses stays discarded and can be restored", + WEB, + async ({ t3, app, browser, screen }) => { + await holdImageCompression(browser); + const project = await t3.project("demo"); + const source = await t3.thread({ project, title: "Screenshot work", messages: ["first"] }); + await t3.thread({ project, title: "Other work", messages: ["first"] }); + await t3.open(source); + await expect(screen.getByText("first fixture turn complete", { exact: false })).toBeVisible(); + + const composer = screen.getByRole("textbox"); + await composer.fill("Half-written prompt"); + await pasteWideScreenshot(browser); + // The sidebar offers a thread's discard only once that thread is no longer open. + await screen.getByRole("button", "Other work", { exact: false }).tap(); + // The row's controls show on hover, over its labels. + await screen.getByRole("button", "Screenshot work", { exact: false }).hover(); + await screen.getByRole("button", "Discard draft").tap(); + await finishImageCompression(browser); + await app.screenshot("after-compression"); + await expect(screen.getByRole("img", "Unsent draft")).toBeHidden(); + + await screen.getByRole("button", "undo", { exact: false }).tap(); + await screen.getByRole("button", "Screenshot work", { exact: false }).tap(); + await app.screenshot("after-undo"); + await expect(composer).toContainText("Half-written prompt"); + }, +); diff --git a/scripts/scenarios/image-compression-hold.ts b/scripts/scenarios/image-compression-hold.ts new file mode 100644 index 0000000000000..4446858b4a092 --- /dev/null +++ b/scripts/scenarios/image-compression-hold.ts @@ -0,0 +1,103 @@ +/// +// Holds pasted-image compression open in the page, so a scenario can act while an image is +// still preparing, then tells it when every image decode, encode and read has settled. +import { expect } from "e2e"; +import type { Browser } from "@e2e-dev/web"; + +interface CompressionHold { + readonly release: () => void; + released: boolean; + compressionDecodes: number; + inFlight: number; +} + +declare global { + interface Window { + compressionHold?: CompressionHold; + } +} + +/** Installs the hold before the app loads; call before `t3.open`. */ +export const holdImageCompression = (browser: Browser) => + browser.addInitScript(() => { + let release!: () => void; + const released = new Promise((resolve) => { + release = resolve; + }); + const hold: CompressionHold = { + release: () => { + hold.released = true; + release(); + }, + released: false, + compressionDecodes: 0, + inFlight: 0, + }; + window.compressionHold = hold; + const track = async (work: () => Promise) => { + hold.inFlight++; + try { + return await work(); + } finally { + hold.inFlight--; + } + }; + + const decode = window.createImageBitmap.bind(window); + window.createImageBitmap = ((source: ImageBitmapSource, ...rest: [ImageBitmapOptions?]) => + track(async () => { + // Only attachment compression decodes with an orientation; placeholder thumbnails do not. + if (rest[0]?.imageOrientation === undefined) return decode(source, ...rest); + await released; + const bitmap = await decode(source, ...rest); + hold.compressionDecodes++; + return bitmap; + })) as typeof createImageBitmap; + + const convertToBlob = OffscreenCanvas.prototype.convertToBlob; + OffscreenCanvas.prototype.convertToBlob = function (options) { + return track(() => convertToBlob.call(this, options)); + }; + const readAsDataURL = FileReader.prototype.readAsDataURL; + FileReader.prototype.readAsDataURL = function (blob) { + hold.inFlight++; + this.addEventListener("loadend", () => hold.inFlight--, { once: true }); + readAsDataURL.call(this, blob); + }; + }); + +/** Pastes a PNG wider than the 2000 px attachment limit into the focused composer. */ +export const pasteWideScreenshot = (browser: Browser) => + browser.evaluate(async () => { + const canvas = new OffscreenCanvas(2200, 20); + canvas.getContext("2d")!.fillRect(0, 0, 2200, 20); + const file = new File([await canvas.convertToBlob({ type: "image/png" })], "screenshot.png", { + type: "image/png", + }); + const clipboardData = new DataTransfer(); + clipboardData.items.add(file); + const editor = document.querySelector('[contenteditable="true"]'); + if (!editor) throw new Error("No composer editor to paste into"); + return editor.dispatchEvent( + new ClipboardEvent("paste", { clipboardData, bubbles: true, cancelable: true }), + ); + }); + +/** + * Lets the held compression finish and waits until it has. What follows the last tracked step + * is synchronous or microtask-only, so the app has applied the result by the next evaluate. + */ +export const finishImageCompression = async (browser: Browser) => { + await browser.evaluate(() => { + window.compressionHold!.release(); + return null; + }); + await expect + .poll(() => + browser.evaluate(() => { + const hold = window.compressionHold!; + return hold.released && hold.compressionDecodes > 0 && hold.inFlight === 0; + }), + ) + .toBe(true); +}; From 96ca66f218c326f805aca74e463907967990880f Mon Sep 17 00:00:00 2001 From: Abhishek B R Date: Sat, 14 Nov 2026 18:41:53 +0530 Subject: [PATCH 107/192] fix(server): a nested repository with no commits no longer breaks the review diff (#28632) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 29 ++++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 16 ++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 9498e56c314ee..e83a55d5a8fc0 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -3035,6 +3035,35 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }), ); + it.effect("keeps the review working beside a nested repository with no commits", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* git(cwd, ["init", "scaffold"]); + const committed = `${cwd}/committed`; + yield* (yield* FileSystem.FileSystem).makeDirectory(committed); + yield* initRepoWithCommit(committed); + yield* writeTextFile(cwd, "README.md", "# tracked change\n"); + yield* writeTextFile(cwd, "untracked.txt", "untracked contents\n"); + + const preview = yield* driver.getReviewDiffPreview({ cwd, ignoreWhitespace: false }); + const diff = preview.sources.find((source) => source.kind === "working-tree")?.diff ?? ""; + + assert.include(diff, "+# tracked change"); + assert.include(diff, "+untracked contents"); + assert.notInclude(diff, "scaffold"); + assert.include(diff, "diff --git a/committed b/committed"); + + const status = yield* driver.statusDetailsLocal(cwd, { includeBranchChanges: true }); + assert.deepStrictEqual(status.branchChanges, { + baseRef: null, + insertions: 3, + deletions: 1, + }); + }), + ); + it.effect("keeps untracked filenames with pathspec magic in the review", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 5b7fea284a0df..a1f4696569683 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -3375,7 +3375,21 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ); const stagedDeletions = new Set(stagedDeletionsStdout.split("\0").filter(Boolean)); // Staged deletions never enter the index, so their worktree copies must not spend the budget. - const candidates = untrackedPaths.filter((relativePath) => !stagedDeletions.has(relativePath)); + // A nested repository (listed with a trailing slash) with no commits cannot be indexed: + // Git before 2.53 fails the add, and later versions accept it and then fail every diff. + const candidates = yield* Effect.filter( + untrackedPaths.filter((relativePath) => !stagedDeletions.has(relativePath)), + (relativePath) => + relativePath.endsWith("/") + ? executeGit( + "GitVcsDriver.readUnifiedWorkingTreeReviewDiff.nestedRepositoryHead", + path.resolve(cwd, relativePath), + ["rev-parse", "--verify", "--quiet", "HEAD"], + { allowNonZeroExit: true }, + ).pipe(Effect.map((result) => result.exitCode !== 1)) + : Effect.succeed(true), + { concurrency: 16 }, + ); const sizes = yield* Effect.forEach( candidates, (relativePath) => From e26e21c06b03fc072cb66244902cedf629307a1a Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 05:17:14 -0800 Subject: [PATCH 108/192] fix(web,mobile): bound scheduled tasks no longer offer a model their runs ignore (#36628) --- .../SettingsScheduledTasksRouteScreen.tsx | 21 +++++++--- .../settings/scheduled-task-editor.tsx | 19 +++++++--- .../ScheduledTasksSettings.browser.test.tsx | 21 +++++++++- .../settings/ScheduledTasksSettings.tsx | 38 +++++++++++++------ 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx index 8eeaf2e567ed1..d71fbc8fd9d24 100644 --- a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx @@ -83,7 +83,11 @@ import { type ScheduledTaskDraft as Draft, } from "./scheduledTaskDraft"; import { settingsTargetsForProject } from "./settings-environment-filter.logic"; -import { useScheduledTaskDestinationLabel, useScheduledTaskEditor } from "./scheduled-task-editor"; +import { + useScheduledTaskDestinationLabel, + useScheduledTaskEditor, + useScheduledTaskThread, +} from "./scheduled-task-editor"; import { discardScheduledTaskEditor, markScheduledTaskEditorSaved, @@ -693,6 +697,10 @@ function TaskForm({ .sort((left, right) => Number(right.pinnedAt != null) - Number(left.pinnedAt != null)); const config = useEnvironmentServerConfig(environmentId); const destinationLabel = useScheduledTaskDestinationLabel(environmentId, draft.threadId); + const boundThread = useScheduledTaskThread(environmentId, draft.threadId); + // Bound runs use the thread's current model, so the locked row shows that one. + const shownSelection = + draft.threadId === null ? draft.modelSelection : (boundThread?.modelSelection ?? null); const modelOptions = buildModelOptions(config, null, undefined, draft.projectId); // Existing webhook tasks keep the option so they can still switch to a timer. const webhookDenial = managedCapabilityDenial(config?.managedPolicy, "webhookTasks"); @@ -929,14 +937,17 @@ function TaskForm({ value={ modelOptions.find( (option) => - option.selection.instanceId === draft.modelSelection?.instanceId && - option.selection.model === draft.modelSelection?.model, + option.selection.instanceId === shownSelection?.instanceId && + option.selection.model === shownSelection?.model, )?.label ?? - draft.modelSelection?.model ?? + shownSelection?.model ?? (modelOptions.length ? "Choose model" : "No models available") } onPress={() => navigation.navigate("SettingsScheduledTaskModel")} - disabled={saving || dictationPending || environmentUnavailable} + // The saved selection only seeds new threads; bound runs ignore it. + disabled={ + saving || dictationPending || environmentUnavailable || draft.threadId !== null + } /> diff --git a/apps/mobile/src/features/settings/scheduled-task-editor.tsx b/apps/mobile/src/features/settings/scheduled-task-editor.tsx index e99600ae533b8..d79ef40896e87 100644 --- a/apps/mobile/src/features/settings/scheduled-task-editor.tsx +++ b/apps/mobile/src/features/settings/scheduled-task-editor.tsx @@ -21,22 +21,29 @@ import { export const NEW_THREAD_DESTINATION_LABEL = "New thread each run"; -export function useScheduledTaskDestinationLabel( +export function useScheduledTaskThread( environmentId: EnvironmentId | null, threadId: ThreadId | null, -): string { +) { const ref = environmentId === null || threadId === null ? null : scopeThreadRef(environmentId, threadId); const shell = useThreadShell(ref); - // The shell can omit older settled threads; their family read still carries the title. + // The shell can omit older settled threads; their family read still carries the row. const family = useEnvironmentQuery( ref !== null && shell === null ? threadFamilies.familyAtom(ref.environmentId, ref.threadId) : null, ); - const thread = shell ?? family.data?.threads.find((row) => row.id === threadId); - if (ref === null) return NEW_THREAD_DESTINATION_LABEL; - return thread?.title.trim() || `Thread ${ref.threadId}`; + return shell ?? family.data?.threads.find((row) => row.id === threadId) ?? null; +} + +export function useScheduledTaskDestinationLabel( + environmentId: EnvironmentId | null, + threadId: ThreadId | null, +): string { + const thread = useScheduledTaskThread(environmentId, threadId); + if (environmentId === null || threadId === null) return NEW_THREAD_DESTINATION_LABEL; + return thread?.title.trim() || `Thread ${threadId}`; } /** The form draft lives in an atom, so it survives while its native picker routes are on top. */ diff --git a/apps/web/src/components/settings/ScheduledTasksSettings.browser.test.tsx b/apps/web/src/components/settings/ScheduledTasksSettings.browser.test.tsx index e2976652ea872..de655f374af5e 100644 --- a/apps/web/src/components/settings/ScheduledTasksSettings.browser.test.tsx +++ b/apps/web/src/components/settings/ScheduledTasksSettings.browser.test.tsx @@ -45,7 +45,13 @@ const task: ScheduledTask = { // Stands in for the thread family read the editor makes when the shell lacks the bound thread. const threadFamilyRead = { snapshotSequence: 1, - threads: [{ id: task.threadId, title: "Fix login bug" }], + threads: [ + { + id: task.threadId, + title: "Fix login bug", + modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.5" }, + }, + ], }; const state = vi.hoisted(() => ({ @@ -244,6 +250,19 @@ describe("ScheduledTasksSettings", () => { await expect.element(name).toHaveValue("Review stale issues"); }); + it("offers no model choice for a task bound to a thread, whose runs use the thread's model", async () => { + setup(); + await using _screen = await render(); + await expect.element(page.getByRole("dialog").getByText("Fix login bug")).toBeVisible(); + const enabledPicker = document.querySelector( + '[role="dialog"] [data-chat-provider-model-picker]:not(:disabled)', + ); + expect(enabledPicker).toBeNull(); + await expect + .element(page.getByRole("dialog").getByRole("button", { name: "gpt-5.5", exact: true })) + .toBeDisabled(); + }); + it("keeps an editor the user opened when the linked task loads later", async () => { setup(); state.tasksLoaded = false; diff --git a/apps/web/src/components/settings/ScheduledTasksSettings.tsx b/apps/web/src/components/settings/ScheduledTasksSettings.tsx index b5b01ddd58b51..5e83dd1baadd9 100644 --- a/apps/web/src/components/settings/ScheduledTasksSettings.tsx +++ b/apps/web/src/components/settings/ScheduledTasksSettings.tsx @@ -1188,6 +1188,16 @@ const THREAD_MODE_LABELS: Record = { fresh_session: "One thread, fresh session each run", }; +function useScheduledTaskThread(environmentId: EnvironmentId, threadId: ThreadId | null) { + const ref = threadId === null ? null : scopeThreadRef(environmentId, threadId); + const shell = useThreadShell(ref); + // The shell can omit older settled threads; their family read still carries the row. + const family = useEnvironmentQuery( + ref !== null && shell === null ? threadFamilies.familyAtom(environmentId, ref.threadId) : null, + ); + return shell ?? family.data?.threads.find((row) => row.id === threadId) ?? null; +} + function ScheduledTaskDestination({ environmentId, projectId, @@ -1209,12 +1219,7 @@ function ScheduledTaskDestination({ const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const ref = threadId === null ? null : scopeThreadRef(environmentId, threadId); - const shell = useThreadShell(ref); - // The shell can omit older settled threads; their family read still carries the title. - const family = useEnvironmentQuery( - ref !== null && shell === null ? threadFamilies.familyAtom(environmentId, ref.threadId) : null, - ); - const thread = shell ?? family.data?.threads.find((row) => row.id === threadId); + const thread = useScheduledTaskThread(environmentId, threadId); const currentLabel = ref !== null ? thread?.title.trim() || `Thread ${ref.threadId}` @@ -1428,11 +1433,21 @@ function ScheduledTaskEditorForm({ const activeInstanceId = activeSelection?.instanceId ?? firstInstance?.instanceId ?? ("" as ProviderInstanceId); const activeModel = activeSelection?.model ?? ""; + const boundThread = useScheduledTaskThread( + environmentId, + draft.threadId ? (draft.threadId as ThreadId) : null, + ); + // Bound runs use the thread's current model, so the locked picker shows that one. + const shownSelection = draft.threadId + ? (boundThread?.modelSelection ?? null) + : { instanceId: activeInstanceId, model: activeModel }; + const shownInstanceId = shownSelection?.instanceId ?? activeInstanceId; + const shownModel = shownSelection?.model ?? ""; const modelOptionsByInstance = getCustomModelOptionsByInstance( settings, providers, - activeInstanceId, - activeModel, + shownInstanceId, + shownModel, ); const reportFailure = (title: string, error: unknown) => { @@ -1777,9 +1792,10 @@ function ScheduledTaskEditorForm({
Date: Sat, 14 Nov 2026 13:18:03 +0000 Subject: [PATCH 109/192] fix(shared): honor Windows PATHEXT regardless of key casing (#29376) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- packages/shared/src/shell.test.ts | 33 +++++++++++++++++++------------ packages/shared/src/shell.ts | 6 +++++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/shared/src/shell.test.ts b/packages/shared/src/shell.test.ts index c1b109ec8b625..6df1169a9a7cc 100644 --- a/packages/shared/src/shell.test.ts +++ b/packages/shared/src/shell.test.ts @@ -558,19 +558,26 @@ effectIt.layer(NodeServices.layer)("resolveCommandPath", (it) => { const executable = path.join(cwd, "audit-command.cmd"); yield* fs.writeFileString(executable, "@echo off\n"); - const resolved = yield* resolveCommandPath(command, { - env: { PATH: cwd, PATHEXT: ".CMD" }, - }).pipe( - Effect.provideService(HostProcess.Platform, "win32"), - Effect.provideService(CommandResolutionCache, new Map()), - Effect.provideService(FileSystem.FileSystem, { - ...fs, - stat: (filePath) => - fs.stat(filePath === executable ? filePath : path.join(cwd, "missing")), - }), - ); - - expect(resolved).toBe(executable); + const decoy = path.join(cwd, "audit-command.exe"); + yield* fs.writeFileString(decoy, ""); + for (const key of ["PATHEXT", "PathExt", "pathext"]) { + const resolved = yield* resolveCommandPath(command, { + env: { PATH: cwd, [key]: ".CMD" }, + }).pipe( + Effect.provideService(HostProcess.Platform, "win32"), + Effect.provideService(CommandResolutionCache, new Map()), + Effect.provideService(FileSystem.FileSystem, { + ...fs, + stat: (filePath) => + fs.stat( + filePath === executable || filePath === decoy + ? filePath + : path.join(cwd, "missing"), + ), + }), + ); + expect(resolved).toBe(executable); + } }), ); diff --git a/packages/shared/src/shell.ts b/packages/shared/src/shell.ts index 45bf60391946a..317d0b58c945e 100644 --- a/packages/shared/src/shell.ts +++ b/packages/shared/src/shell.ts @@ -492,7 +492,11 @@ function resolvePathEnvironmentVariable(env: NodeJS.ProcessEnv): string { } function resolveWindowsPathExtensions(env: NodeJS.ProcessEnv): ReadonlyArray { - const rawValue = env.PATHEXT; + const rawValue = + env.PATHEXT ?? + Object.entries(env).find( + ([key, value]) => key.toUpperCase() === "PATHEXT" && value !== undefined, + )?.[1]; const fallback = [".COM", ".EXE", ".BAT", ".CMD"]; if (!rawValue) return fallback; From acd192fc64a3f18c026583e1a0b663698f1110b5 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 14:21:07 +0100 Subject: [PATCH 110/192] fix(web): removing an environment dismisses its notification toasts (#36726) Co-authored-by: Julius Marminge --- ...ificationCoordinator.badge.browser.test.ts | 10 +- .../ThreadNotificationCoordinator.tsx | 5 +- ...moved-environment-closes-its-toasts.e2e.ts | 133 ++++++++++++++++++ scripts/scenarios/start-app.ts | 4 +- 4 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 scripts/scenarios/removed-environment-closes-its-toasts.e2e.ts diff --git a/apps/web/src/components/ThreadNotificationCoordinator.badge.browser.test.ts b/apps/web/src/components/ThreadNotificationCoordinator.badge.browser.test.ts index 6c6986edf451e..7da8504990fe6 100644 --- a/apps/web/src/components/ThreadNotificationCoordinator.badge.browser.test.ts +++ b/apps/web/src/components/ThreadNotificationCoordinator.badge.browser.test.ts @@ -419,7 +419,7 @@ it.each([false, true])( }, ); -it("opening a thread closes only its in-app toasts, and disabling notifications closes the rest", async () => { +it("opening a thread or removing its environment closes only its in-app toasts, and disabling notifications closes the rest", async () => { await using app = setup(); state.inApp = true; focused = true; @@ -445,11 +445,11 @@ it("opening a thread closes only its in-app toasts, and disabling notifications app.openThread("one", "thread"); expect(state.closeToast.mock.calls).toEqual([["one-completed"], ["one-approval"]]); + app.setEnvironmentIds(["one"]); + expect(state.closeToast.mock.calls.slice(2)).toEqual([["two-completed"]]); + state.mode = "off"; state.inApp = false; app.changeSettings(); - expect(state.closeToast.mock.calls.slice(2).flat().toSorted()).toEqual([ - "one-other", - "two-completed", - ]); + expect(state.closeToast.mock.calls.slice(3)).toEqual([["one-other"]]); }); diff --git a/apps/web/src/components/ThreadNotificationCoordinator.tsx b/apps/web/src/components/ThreadNotificationCoordinator.tsx index 6e6b2b37b6830..73daf000cadd0 100644 --- a/apps/web/src/components/ThreadNotificationCoordinator.tsx +++ b/apps/web/src/components/ThreadNotificationCoordinator.tsx @@ -5,7 +5,7 @@ import { } from "@t3tools/client-runtime/state/models"; import { isShownUnderParent } from "@t3tools/client-runtime/state/threads"; import type { AnyRouter } from "@tanstack/react-router"; -import { threadKey } from "@t3tools/client-runtime/state/entities"; +import { parseThreadKey, threadKey } from "@t3tools/client-runtime/state/entities"; import type { EnvironmentId, OrchestrationV2ThreadShell, ThreadId } from "@t3tools/contracts"; import { monitorHoldsWakeCompletion } from "@t3tools/shared/orchestrationV2PendingBackgroundWork"; import type { ClientSettings } from "@t3tools/contracts/settings"; @@ -79,7 +79,7 @@ export function startThreadNotifications( pending.set(notification.tag, { environmentId, threadId, notification }); setNotificationBadge(pending.size); }; - // Toast ids by thread key, so hiding or opening a thread can close its open toasts. + // Toast ids by thread key, so hiding or opening a thread, or removing its environment, closes them. const openToasts = new Map>(); const forgetToast = (key: string, toastId: ToastId) => { const remaining = (openToasts.get(key) ?? []).filter((id) => id !== toastId); @@ -373,6 +373,7 @@ export function startThreadNotifications( const pruneRemovedEnvironments = () => { const activeIds = new Set(registry.get(environmentSummaries.environmentIdsAtom)); + closeToasts((key) => !activeIds.has(parseThreadKey(key).environmentId)); const count = pending.size; for (const [tag, entry] of pending) { if (activeIds.has(entry.environmentId)) continue; diff --git a/scripts/scenarios/removed-environment-closes-its-toasts.e2e.ts b/scripts/scenarios/removed-environment-closes-its-toasts.e2e.ts new file mode 100644 index 0000000000000..be5b0b9cbb0c3 --- /dev/null +++ b/scripts/scenarios/removed-environment-closes-its-toasts.e2e.ts @@ -0,0 +1,133 @@ +import * as NodeServices from "@effect/platform-node/NodeServices"; +import * as HostProcess from "@t3tools/shared/HostProcess"; +import * as NetService from "@t3tools/shared/Net"; +import { expect } from "e2e"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as Path from "effect/Path"; + +import { APP_DIR, T3_HOME } from "../e2e.config.ts"; +import { prepareHome, start } from "./start-app.ts"; +import { makeState, runPromise, test, WEB } from "./t3.ts"; + +const SECOND_HOME = `${T3_HOME}-second-environment`; + +/** A second server on its own T3 home, running until interrupted. `--dev-url` admits the web + * origin through CORS and registers the replay agent; `paired` gets the Add environment input. */ +const runSecondServer = Effect.fn("scenario.secondServer")(function* ( + devUrl: string, + paired: (target: { readonly host: string; readonly token: string }) => void, +) { + const path = yield* Path.Path; + const node = yield* HostProcess.ExecutablePath; + const hostEnvironment = yield* HostProcess.Environment; + const port = yield* (yield* NetService.NetService).reserveLoopbackPort(); + yield* prepareHome(SECOND_HOME); + const token = yield* Deferred.make(); + yield* start({ + cwd: APP_DIR, + command: node, + args: [ + "apps/server/src/bin.ts", + "serve", + "--host", + "127.0.0.1", + "--port", + String(port), + "--base-dir", + SECOND_HOME, + "--dev-url", + devUrl, + ], + env: { + PATH: `${path.join(SECOND_HOME, "bin")}:${hostEnvironment.PATH ?? ""}`, + HOME: hostEnvironment.HOME, + T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "0", + }, + onLine: (line) => { + const value = /^Token:\s*(\S+)/.exec(line)?.[1]; + return value === undefined ? Effect.void : Deferred.succeed(token, value).pipe(Effect.asVoid); + }, + }); + paired({ host: `http://127.0.0.1:${port}`, token: yield* Deferred.await(token) }); + return yield* Effect.never; +}); + +test( + "Removing an environment closes the in-app notifications it raised", + WEB, + async ({ t3, app, screen, browser }) => { + const project = await t3.project("demo"); + const primaryThread = await t3.thread({ + project, + title: "Finished here", + messages: ["first"], + }); + const primary = makeState(T3_HOME); + const second = makeState(SECOND_HOME); + const pairing = Promise.withResolvers<{ host: string; token: string }>(); + const server = Effect.runFork( + runSecondServer(app.baseUrl ?? "", pairing.resolve).pipe( + Effect.scoped, + Effect.provide([NodeServices.layer, NetService.layer]), + ), + ); + server.addObserver(() => pairing.reject(new Error("The second server stopped."))); + try { + const { host, token } = await pairing.promise; + const project = await second.state.project("remote-demo"); + const thread = await second.state.thread({ + project, + title: "Finished elsewhere", + messages: ["first"], + }); + + await t3.open("/settings/general"); + await screen.getByRole("switch", "In-app notifications").check(); + await app.open("/settings/connections"); + await screen.getByRole("button", "Add environment").tap(); + const dialog = screen.getByRole("dialog"); + await dialog.getByPlaceholder("backend.example.com").fill(host); + await dialog.getByPlaceholder("PAIRCODE").fill(token); + await dialog.getByRole("button", "Add environment").tap(); + const moreActions = screen.getByRole("button", "More actions for Scenario"); + await expect(moreActions).toBeVisible({ timeout: 60_000 }); + + // The client announces only a change it saw happen, so it must hold the thread first. + await expect(screen.getByText("Finished elsewhere").first()).toBeVisible({ timeout: 60_000 }); + await runPromise( + primary.cli(["thread", "send", primaryThread.threadId, "second", "--wait", "--json"]), + ); + const primaryToast = browser.locator( + '[data-slot="toast-description"]:has-text("Finished here")', + ); + await expect(primaryToast).toBeVisible({ timeout: 60_000 }); + await primaryToast.hover(); + await runPromise( + second.cli(["thread", "send", thread.threadId, "second", "--wait", "--json"]), + ); + const toast = browser.locator( + '[data-slot="toast-description"]:has-text("Finished elsewhere")', + ); + await expect(toast).toBeVisible({ timeout: 60_000 }); + + // A hovered toast does not time out. The removal goes by keyboard so the pointer stays. + await toast.hover(); + await moreActions.press("Enter"); + await screen.getByRole("menuitem", "Remove from this device…").press("Enter"); + await expect(toast).toBeVisible(); + await screen.getByRole("button", "Confirm").press("Enter"); + try { + await expect(toast).toBeHidden(); + // Both timers pause together; an older surviving toast rules out auto-dismissal. + await expect(primaryToast).toBeVisible(); + } finally { + await app.screenshot("after-removal"); + } + await expect(moreActions).toBeHidden({ timeout: 30_000 }); + } finally { + await Effect.runPromise(Fiber.interrupt(server)); + } + }, +); diff --git a/scripts/scenarios/start-app.ts b/scripts/scenarios/start-app.ts index 5dee3f26c138c..63f9cb9ddd08b 100644 --- a/scripts/scenarios/start-app.ts +++ b/scripts/scenarios/start-app.ts @@ -52,7 +52,7 @@ esac const encodeSettings = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); -const prepareHome = Effect.fn("startApp.prepareHome")(function* (home: string) { +export const prepareHome = Effect.fn("startApp.prepareHome")(function* (home: string) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fs.remove(home, { recursive: true, force: true }); @@ -85,7 +85,7 @@ const prepareHome = Effect.fn("startApp.prepareHome")(function* (home: string) { }); /** Starts a process in the caller's scope and hands each line it prints to `onLine`. */ -const start = Effect.fn("startApp.start")(function* (input: { +export const start = Effect.fn("startApp.start")(function* (input: { readonly cwd: string; readonly command: string; readonly args: ReadonlyArray; From 2b596b86907ad806f80b16b15b68af018e6a37f1 Mon Sep 17 00:00:00 2001 From: Nikolay Akinshin <49105276+karrambol@users.noreply.github.com> Date: Sat, 14 Nov 2026 16:21:55 +0300 Subject: [PATCH 111/192] fix(web): context meter no longer keeps the retired provider's usage (#28581) Co-authored-by: nikolay.akinshin Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/orchestration-v2/ProjectionStore.ts | 10 +- apps/web/src/lib/contextWindow.test.ts | 108 +++++++++++++----- .../client-runtime/src/state/contextWindow.ts | 22 +++- .../state/orchestrationV2Projection.test.ts | 76 ++++++++++++ .../src/state/orchestrationV2Projection.ts | 15 ++- packages/contracts/src/orchestrationV2.ts | 15 +++ 6 files changed, 204 insertions(+), 42 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index 3c1d353f061b4..32007af08573f 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -51,6 +51,7 @@ import type { import { IDLE_COMPACTION_TEXT, idleCompactionOf, + isQueuedProviderThreadPlaceholder, OrchestrationV2AppThreadJson as OrchestrationV2AppThreadJsonSchema, OrchestrationV2CheckpointJson as OrchestrationV2CheckpointJsonSchema, OrchestrationV2CheckpointScopeJson as OrchestrationV2CheckpointScopeJsonSchema, @@ -880,15 +881,6 @@ export function emptyProjection( }; } -function isQueuedProviderThreadPlaceholder(providerThread: OrchestrationV2ProviderThread): boolean { - return ( - providerThread.status === "not_loaded" && - providerThread.firstRunOrdinal === null && - providerThread.nativeThreadRef === null && - providerThread.providerSessionId === null - ); -} - export function applyToProjection( projection: OrchestrationV2ThreadProjection, event: OrchestrationV2DomainEvent, diff --git a/apps/web/src/lib/contextWindow.test.ts b/apps/web/src/lib/contextWindow.test.ts index f3540d3738596..91cdf18682e2a 100644 --- a/apps/web/src/lib/contextWindow.test.ts +++ b/apps/web/src/lib/contextWindow.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vite-plus/test"; +import type { ThreadStructureTurnItem } from "@t3tools/client-runtime/state/shell"; import { OrchestrationV2ProviderTurnTokenUsage } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; import { formatContextWindowCompactionMessage } from "../components/chat/compaction.logic"; @@ -7,6 +8,7 @@ import { ContextTransferId, ProviderDriverKind, ProviderInstanceId, + ProviderThreadId, RunId, ThreadId, } from "@t3tools/contracts"; @@ -19,35 +21,55 @@ import { const decodeTokenUsage = Schema.decodeSync(OrchestrationV2ProviderTurnTokenUsage); +const compactionEntry = (providerThreadId: string | null) => + ({ + item: { + id: "compaction-1" as never, + threadId: "thread-1" as never, + runId: null, + nodeId: null, + providerThreadId: providerThreadId as never, + providerTurnId: null, + nativeItemRef: null, + parentItemId: null, + ordinal: 1, + status: "completed", + title: null, + startedAt: null, + completedAt: null, + updatedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), + type: "compaction", + driver: null, + beforeTokenCount: 10_000, + afterTokenCount: 2_000, + }, + }) satisfies { item: ThreadStructureTurnItem }; + describe("V2 context window presentation", () => { it("uses retained compaction token data when available", () => { - const snapshot = deriveLatestContextWindowSnapshot([ - { - item: { - id: "compaction-1" as never, - threadId: "thread-1" as never, - runId: null, - nodeId: null, - providerThreadId: null, - providerTurnId: null, - nativeItemRef: null, - parentItemId: null, - ordinal: 1, - status: "completed", - title: null, - startedAt: null, - completedAt: null, - updatedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), - type: "compaction", - driver: null, - beforeTokenCount: 10_000, - afterTokenCount: 2_000, - }, - }, - ]); + const snapshot = deriveLatestContextWindowSnapshot([compactionEntry(null)]); expect(snapshot?.usedTokens).toBe(2_000); expect(snapshot?.totalProcessedTokens).toBe(10_000); }); + + it("ignores a retired provider thread's compaction after a provider switch", () => { + const activeProviderThread = { + id: ProviderThreadId.make("acp-thread"), + contextUsage: null, + updatedAt: DateTime.makeUnsafe("2026-10-19T12:10:00.000Z"), + }; + const retired = compactionEntry(ProviderThreadId.make("pi-thread")); + const current = compactionEntry(ProviderThreadId.make("acp-thread")); + expect(deriveLatestContextWindowSnapshot([retired], null, activeProviderThread)).toBeNull(); + expect( + deriveLatestContextWindowSnapshot([current], null, activeProviderThread)?.usedTokens, + ).toBe(2_000); + // Items without a provider thread predate the split and stay visible. + expect( + deriveLatestContextWindowSnapshot([compactionEntry(null)], null, activeProviderThread) + ?.usedTokens, + ).toBe(2_000); + }); }); describe("live provider-turn usage (#8144)", () => { @@ -75,12 +97,14 @@ describe("live provider-turn usage (#8144)", () => { describe("latestProviderTurnTokenUsage", () => { it("picks the newest report, not the last turn in snapshot order", () => { + const providerThreadId = ProviderThreadId.make("provider-thread"); const usage = (usedTokens: number, updatedAt: string) => ({ + providerThreadId, tokenUsage: { usedTokens, updatedAt }, }); const latest = latestProviderTurnTokenUsage([ usage(300_000, "2026-06-20T12:00:00.000Z"), - {}, + { providerThreadId }, usage(90_000, "2026-06-20T09:00:00.000Z"), ]); expect(latest?.usedTokens).toBe(300_000); @@ -143,13 +167,45 @@ describe("latestProviderTurnTokenUsage", () => { it("replaces inherited usage with the fork's own report", () => { const tokenUsage = { usedTokens: 42_000, updatedAt: "2026-10-19T12:10:00.000Z" }; - expect(latestProviderTurnTokenUsage([{ tokenUsage }], forkProjection())).toEqual(tokenUsage); + expect( + latestProviderTurnTokenUsage( + [{ providerThreadId: ProviderThreadId.make("provider-thread"), tokenUsage }], + forkProjection(), + ), + ).toEqual(tokenUsage); + }); + + it("prefers the active provider thread's older report over a newer retired one", () => { + const turns = [ + { + providerThreadId: ProviderThreadId.make("pi-thread"), + tokenUsage: decodeTokenUsage({ + usedTokens: 180_000, + maxTokens: 200_000, + updatedAt: "2026-10-19T12:10:00.000Z", + }), + }, + { + providerThreadId: ProviderThreadId.make("acp-thread"), + tokenUsage: decodeTokenUsage({ + usedTokens: 12_000, + maxTokens: 200_000, + updatedAt: "2026-10-19T11:00:00.000Z", + }), + }, + ]; + const liveUsage = latestProviderTurnTokenUsage(turns, null, { + id: ProviderThreadId.make("acp-thread"), + contextUsage: null, + }); + expect(liveUsage?.usedTokens).toBe(12_000); }); it.each([0, 42_000])( "prefers target provider-thread usage of %d tokens over inherited usage", (usedTokens) => { const providerThread = { + id: ProviderThreadId.make("provider-thread"), contextUsage: { usedTokens, maxTokens: 200_000, diff --git a/packages/client-runtime/src/state/contextWindow.ts b/packages/client-runtime/src/state/contextWindow.ts index 94be1e481ad42..a8f7e0f88c355 100644 --- a/packages/client-runtime/src/state/contextWindow.ts +++ b/packages/client-runtime/src/state/contextWindow.ts @@ -42,15 +42,19 @@ export type ContextWindowSnapshot = NullableContextWindowUsage & { readonly updatedAt: string; }; -/** Newest report by timestamp; snapshots order turns by provider thread. */ +/** + * Newest report by timestamp among the active provider thread's turns, since a provider switch leaves + * the previous provider's report behind. Without an active provider thread, every turn is a candidate. + */ export function latestProviderTurnTokenUsage( - turns: ReadonlyArray>, + turns: ReadonlyArray>, projection?: Pick | null, - providerThread?: Pick | null, + providerThread?: Pick | null, ): OrchestrationV2ProviderTurnTokenUsage | null { let latest: OrchestrationV2ProviderTurnTokenUsage | null = null; - for (const { tokenUsage } of turns) { + for (const { providerThreadId, tokenUsage } of turns) { if (tokenUsage === undefined) continue; + if (providerThread != null && providerThreadId !== providerThread.id) continue; // Ties go to the later entry, since live upserts append. if (latest === null || Date.parse(tokenUsage.updatedAt) >= Date.parse(latest.updatedAt)) { latest = tokenUsage; @@ -77,7 +81,7 @@ export function deriveLatestContextWindowSnapshot( readonly item: ThreadStructureTurnItem; }>, liveUsage?: OrchestrationV2ProviderTurnTokenUsage | null, - providerThread?: Pick | null, + providerThread?: Pick | null, ): ContextWindowSnapshot | null { if (liveUsage != null) { const usedTokens = Math.max(0, liveUsage.usedTokens); @@ -152,6 +156,14 @@ export function deriveLatestContextWindowSnapshot( continue; } const payload = entry.item; + // A retired provider thread's compaction says nothing about the active conversation. + if ( + providerThread != null && + payload.providerThreadId != null && + payload.providerThreadId !== providerThread.id + ) { + continue; + } const usedTokens = asFiniteNumber(payload.afterTokenCount); if (usedTokens === null || usedTokens < 0) { continue; diff --git a/packages/client-runtime/src/state/orchestrationV2Projection.test.ts b/packages/client-runtime/src/state/orchestrationV2Projection.test.ts index 9284b6f9b82d0..16301157a6a4e 100644 --- a/packages/client-runtime/src/state/orchestrationV2Projection.test.ts +++ b/packages/client-runtime/src/state/orchestrationV2Projection.test.ts @@ -3,6 +3,7 @@ import { type ThreadPullRequestLink, type OrchestrationV2DomainEvent, type OrchestrationV2Run, + type OrchestrationV2ProviderThread, type OrchestrationV2ThreadProjection, type OrchestrationV2TurnItem, MessageId, @@ -817,3 +818,78 @@ it("restores deduped bodies from the records it already holds, also in a partial ); expect(progressed?.subagents).toEqual([{ ...subagent, progress: "step 2" }]); }); + +describe("provider-thread.updated", () => { + const providerThread = ( + id: string, + overrides: Partial = {}, + ): OrchestrationV2ProviderThread => + ({ + id: ProviderThreadId.make(id), + driver: "codex", + providerInstanceId: ProviderInstanceId.make("codex"), + providerSessionId: null, + appThreadId: threadId, + ownerNodeId: null, + nativeThreadRef: null, + nativeConversationHeadRef: null, + status: "active", + firstRunOrdinal: 1, + lastRunOrdinal: 1, + handoffIds: [], + forkedFrom: null, + createdAt: now, + updatedAt: now, + ...overrides, + }) as OrchestrationV2ProviderThread; + const apply = (payload: unknown) => + applyOrchestrationV2ProjectionEvent(emptyProjection, { + id: "event-provider-thread", + type: "provider-thread.updated", + threadId, + occurredAt: now, + payload, + } as OrchestrationV2DomainEvent); + + it("moves the thread's active provider thread to the new one", () => { + // The shell already knows the old thread, as it would after a provider switch. + const projection = applyOrchestrationV2ProjectionEvent(emptyProjection, { + id: "event-previous-thread", + type: "provider-thread.updated", + threadId, + occurredAt: now, + payload: providerThread("provider-thread-previous"), + } as OrchestrationV2DomainEvent); + expect(projection?.thread.activeProviderThreadId).toBe( + ProviderThreadId.make("provider-thread-previous"), + ); + + const next = applyOrchestrationV2ProjectionEvent(projection, { + id: "event-next-thread", + type: "provider-thread.updated", + threadId, + occurredAt: now, + payload: providerThread("provider-thread-next"), + } as OrchestrationV2DomainEvent); + expect(next?.thread.activeProviderThreadId).toBe(ProviderThreadId.make("provider-thread-next")); + }); + + it("keeps the active provider thread for queued placeholders and other threads", () => { + const queued = apply( + providerThread("provider-thread-queued", { + status: "not_loaded", + firstRunOrdinal: null, + providerSessionId: null, + }), + ); + expect(queued?.thread.activeProviderThreadId).toBeNull(); + + const foreign = apply( + providerThread("provider-thread-foreign", { appThreadId: ThreadId.make("other-thread") }), + ); + expect(foreign?.thread.activeProviderThreadId).toBeNull(); + expect(foreign?.providerThreads.map((thread) => thread.id)).toEqual([ + ProviderThreadId.make("provider-thread-foreign"), + ]); + }); +}); diff --git a/packages/client-runtime/src/state/orchestrationV2Projection.ts b/packages/client-runtime/src/state/orchestrationV2Projection.ts index 76d1c9d6b9c15..37da408a1c35b 100644 --- a/packages/client-runtime/src/state/orchestrationV2Projection.ts +++ b/packages/client-runtime/src/state/orchestrationV2Projection.ts @@ -1,5 +1,8 @@ import { applyThreadPullRequestLinkUpdate } from "../threadPullRequests.ts"; -import { applyOrchestrationV2ThreadVisit } from "@t3tools/contracts"; +import { + applyOrchestrationV2ThreadVisit, + isQueuedProviderThreadPlaceholder, +} from "@t3tools/contracts"; import type { OrchestrationV2DomainEvent, OrchestrationV2Run, @@ -382,7 +385,15 @@ export function applyOrchestrationV2ProjectionEvent( ), }; case "provider-thread.updated": - return { ...base, providerThreads: upsertEntity(base.providerThreads, event.payload) }; + return { + ...base, + thread: + event.payload.appThreadId === base.thread.id && + !isQueuedProviderThreadPlaceholder(event.payload) + ? { ...base.thread, activeProviderThreadId: event.payload.id } + : base.thread, + providerThreads: upsertEntity(base.providerThreads, event.payload), + }; case "provider-turn.updated": return { ...base, diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index e4a57262d12dc..9923f0d929cb8 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -1257,6 +1257,21 @@ export function latestProviderTurnForAttempt< return latest; } +/** A queued provider thread reserves a seat for the next run; it does not own the thread yet. */ +export function isQueuedProviderThreadPlaceholder( + providerThread: Pick< + OrchestrationV2ProviderThread, + "status" | "firstRunOrdinal" | "nativeThreadRef" | "providerSessionId" + >, +): boolean { + return ( + providerThread.status === "not_loaded" && + providerThread.firstRunOrdinal === null && + providerThread.nativeThreadRef === null && + providerThread.providerSessionId === null + ); +} + export const OrchestrationV2RuntimeRequest = Schema.Struct({ id: RuntimeRequestId, nodeId: NodeId, From 49d63f1ee7c231b028a5eaa951cc004d5f01cf97 Mon Sep 17 00:00:00 2001 From: Yarden Zamir Date: Sat, 14 Nov 2026 15:22:33 +0200 Subject: [PATCH 112/192] test(mobile): scheduled task switch mock names each switch by its label --- .../settings/SettingsScheduledTasksRouteScreen.test.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx index b92c1ec725b47..ff6ca4e0f160d 100644 --- a/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx +++ b/apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.test.tsx @@ -86,12 +86,14 @@ vi.mock("../../components/ui/SegmentedControl", () => ({ })); vi.mock("../../components/ui/ThemedSwitch", () => ({ ThemedSwitch: ({ + accessibilityLabel, value, onValueChange, }: { + accessibilityLabel: string; value: boolean; onValueChange: (value: boolean) => void; - }) => , + }) => , })); vi.mock("../../state/entities", () => ({ useProjects: () => [ @@ -220,7 +222,7 @@ describe("scheduled task batched edits", () => { it("retains the interval when enabled changes before React commits", async () => { act(() => { button("Interval").click(); - button("Toggle enabled").click(); + button("Task enabled").click(); }); expect(appAtomRegistry.get(scheduledTaskEditorSessionAtom)?.current?.draft.schedule.mode).toBe( "interval", From 76748e942bc39d183d4fee29ca9f1d1b822fefa5 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 05:24:53 -0800 Subject: [PATCH 113/192] test(web): wait for Settings focus before testing the theme editor's Escape (#36754) --- .../ThemeEditorPanel.browser.test.tsx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx b/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx index 9ec6d782cbe81..3dff3c5706f10 100644 --- a/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx +++ b/apps/web/src/components/settings/ThemeEditorPanel.browser.test.tsx @@ -79,27 +79,33 @@ async function renderPanel() { async function renderBesideSettings() { const onSettingsOpenChange = vi.fn(); const onEditorOpenChange = vi.fn(); - const screen = await render( - <> - - - Settings - - - true} - editingTheme={null} - initialAppearance="light" - restoreTheme={() => {}} - /> - , + await using stack = new AsyncDisposableStack(); + stack.use( + await render( + <> + + + Settings + + + true} + editingTheme={null} + initialAppearance="light" + restoreTheme={() => {}} + /> + , + ), ); + // Settings moves focus in on a later frame; landing after the test focuses the editor, it steals it. + await expect.element(page.getByRole("button", { name: "Close" })).toHaveFocus(); + const screen = stack.move(); return { onSettingsOpenChange, onEditorOpenChange, - [Symbol.asyncDispose]: () => screen[Symbol.asyncDispose](), + [Symbol.asyncDispose]: () => screen.disposeAsync(), }; } From e53417b2d49b73a2e2467d2e6f0118c3891caab1 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 07:28:05 -0600 Subject: [PATCH 114/192] fix(server): selected-file commits work again with Git older than 2.25 (#28850) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 47 ++++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 30 +++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index e83a55d5a8fc0..be9faead2483f 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1560,6 +1560,53 @@ it.effect("reads worktrees from git that rejects worktree list -z", () => ), ); +it.effect("commits selected files with git that rejects --pathspec-from-file", () => + Effect.scoped( + Effect.gen(function* () { + const delegate = yield* ChildProcessSpawner.ChildProcessSpawner; + // Git before 2.25 has no `add --pathspec-from-file` and exits with a usage error. + const oldGitSpawner = ChildProcessSpawner.make((command) => { + if (!ChildProcess.isStandardCommand(command)) { + return Effect.die("expected a standard Git command"); + } + if (command.args.at(-1) === "version") { + return Effect.succeed(makeSuccessfulHandle("git version 2.24.4\n")); + } + if (command.args.some((arg) => String(arg).startsWith("--pathspec-from-file"))) { + return Effect.succeed(makeFailedHandle("error: unknown option `pathspec-from-file=-'")); + } + return delegate.spawn(command); + }); + const driver = yield* makeGitVcsDriverCore().pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, oldGitSpawner), + ); + yield* Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const selected = ["selected[1].txt", "-dash.txt", "with space.txt"]; + for (const file of selected) yield* writeTextFile(cwd, file, "selected\n"); + yield* writeTextFile(cwd, "left.txt", "left\n"); + + yield* driver.commit(cwd, "Selected", "", { stage: { filePaths: selected } }); + + assert.sameMembers( + (yield* git(cwd, ["show", "--pretty=", "--name-only", "-z", "HEAD"])) + .split("\0") + .filter(Boolean), + selected, + ); + assert.equal(yield* git(cwd, ["status", "--porcelain"]), "?? left.txt"); + }).pipe(Effect.provideService(GitVcsDriver.GitVcsDriver, driver)); + }), + ).pipe( + Effect.provide( + layerServerConfig.pipe( + Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)), + ), + ), + ), +); + it.effect("does not report a separate git dir as the worktree of its branch", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index a1f4696569683..220b3ce0fa08a 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -334,6 +334,14 @@ function splitNullSeparatedPaths(input: string, truncated: boolean): string[] { return parts.filter((value) => value.length > 0); } +// Parses "git version 2.24.3 (Apple Git-128)" or "git version 2.45.1.windows.1". +const gitSupportsPathspecFromFile = (versionOutput: string): boolean => { + const match = /^git version (\d+)\.(\d+)/.exec(versionOutput); + if (match === null) return false; + const major = Number(match[1]); + return major > 2 || (major === 2 && Number(match[2]) >= 25); +}; + export function splitNullSeparatedGitStdoutPaths( result: Pick, ): string[] { @@ -2518,6 +2526,18 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* })), ); + // git add takes --pathspec-from-file from Git 2.25; older Git keeps the argument form. + let pathspecFromFileSupported: boolean | undefined; + const supportsPathspecFromFile = Effect.fn("GitVcsDriver.supportsPathspecFromFile")(function* ( + cwd: string, + ) { + if (pathspecFromFileSupported !== undefined) return pathspecFromFileSupported; + const version = yield* executeGit("GitVcsDriver.version", cwd, ["version"]).pipe(Effect.option); + if (Option.isNone(version)) return false; + pathspecFromFileSupported = gitSupportsPathspecFromFile(version.value.stdout); + return pathspecFromFileSupported; + }); + const stageCommitChanges = Effect.fnUntraced(function* ( cwd: string, filePaths?: readonly string[], @@ -2604,14 +2624,18 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* } yield* runGit("GitVcsDriver.commit.reset", cwd, ["reset", "--quiet"]); } + // Status paths are repository-relative, while cwd can be a subdirectory. + const pathspecs = filePaths.map((file) => `:(top,literal)${file}`); + const fromStdin = yield* supportsPathspecFromFile(cwd); yield* runGit( "GitVcsDriver.commit.addSelected", cwd, - [...config, "add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], + fromStdin + ? [...config, "add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"] + : [...config, "add", "-A", "--", ...pathspecs], { ...(env ? { env } : {}), - // Status paths are repository-relative, while cwd can be a subdirectory. - stdin: filePaths.map((file) => `:(top,literal)${file}\0`).join(""), + ...(fromStdin ? { stdin: pathspecs.map((pathspec) => `${pathspec}\0`).join("") } : {}), }, ); } else { From 64d70f8828e840ccd1f0d483499d96e9b42bc99e Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 07:28:18 -0600 Subject: [PATCH 115/192] fix(mobile): sort pull request lists and timelines by time (#28391) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../PullRequestDiscussionScreen.tsx | 4 +-- .../pull-requests/PullRequestRouteScreen.tsx | 16 ++++------ .../pull-requests/PullRequestTimeline.tsx | 5 +-- .../features/pull-requests/pullRequestList.ts | 9 +++--- apps/mobile/src/lib/time.test.ts | 32 ++++++++++++++++++- apps/mobile/src/lib/time.ts | 12 +++++++ 6 files changed, 60 insertions(+), 18 deletions(-) diff --git a/apps/mobile/src/features/pull-requests/PullRequestDiscussionScreen.tsx b/apps/mobile/src/features/pull-requests/PullRequestDiscussionScreen.tsx index e92debeea0f1d..9d59324c1e4a6 100644 --- a/apps/mobile/src/features/pull-requests/PullRequestDiscussionScreen.tsx +++ b/apps/mobile/src/features/pull-requests/PullRequestDiscussionScreen.tsx @@ -21,7 +21,7 @@ import { AppText as Text, AppTextInput as TextInput } from "../../components/ui/ import { AppBar } from "../../components/ui/AppBar"; import { Button } from "../../components/ui/Button"; import { GlassSurface } from "../../components/ui/GlassSurface"; -import { relativeTime } from "../../lib/time"; +import { compareTimestamps, relativeTime } from "../../lib/time"; import { composerPullRequests } from "../../state/pull-requests"; import { useAtomCommand } from "../../state/use-atom-command"; import { useEnvironmentQuery } from "../../state/query"; @@ -147,7 +147,7 @@ function Discussion({ params }: { params: PullRequestParams & { readonly discuss ...new Map( [...extra, ...(thread?.comments ?? [])].map((comment) => [comment.id, comment]), ).values(), - ].sort((left, right) => left.createdAt.localeCompare(right.createdAt)); + ].sort((left, right) => compareTimestamps(left.createdAt, right.createdAt)); const scrollRef = useRef(null); const pendingTop = useRef(null); const positionedComment = useRef(null); diff --git a/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx b/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx index f3c47e2582858..8e33a5bec1007 100644 --- a/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx +++ b/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx @@ -142,15 +142,13 @@ function usePullRequestActivityRows(params: PullRequestParams, filter: ActivityF const filtered = result.data ? filterPullRequestActivity(result.data, filter) : null; const rows = (() => { if (!filtered) return []; - const rows = pullRequestTimelineRows(filtered.activity, params, navigation, { - permissions, - onChanged: result.refresh, - }); - if (filter.newestFirst) { - const notice = rows.at(-1)?.key === "truncated" ? rows.pop() : undefined; - rows.reverse(); - if (notice) rows.push(notice); - } + const rows = pullRequestTimelineRows( + filtered.activity, + params, + navigation, + { permissions, onChanged: result.refresh }, + filter.newestFirst, + ); if (!rows.length) rows.push({ key: "empty", diff --git a/apps/mobile/src/features/pull-requests/PullRequestTimeline.tsx b/apps/mobile/src/features/pull-requests/PullRequestTimeline.tsx index 4b7ba3a968187..586d9947510f3 100644 --- a/apps/mobile/src/features/pull-requests/PullRequestTimeline.tsx +++ b/apps/mobile/src/features/pull-requests/PullRequestTimeline.tsx @@ -17,7 +17,7 @@ import { Pressable, StyleSheet, View } from "react-native"; import { parseMarkdownDocument } from "../../lib/markdownDocument"; import { AppText as Text } from "../../components/ui/AppText"; import { SymbolView, type AppSymbolName } from "../../components/ui/AppSymbol"; -import { relativeTime } from "../../lib/time"; +import { byTimestamp, relativeTime } from "../../lib/time"; import { PullRequestAvatar } from "./PullRequestPresentation"; import { PullRequestMarkdown } from "./PullRequestMarkdown"; import { PullRequestCommentFooter } from "./PullRequestReactions"; @@ -218,6 +218,7 @@ export function pullRequestTimelineRows( params: PullRequestParams, navigation: Pick, "navigate" | "dispatch">, actions: CommentActions, + newestFirst: boolean, ) { const linkTarget = parsePullRequestAnchor(params.navigationUrl); const threads = pullRequestConversationThreads(activity); @@ -320,7 +321,7 @@ export function pullRequestTimelineRows( ), })), - ].sort((a, b) => a.date.localeCompare(b.date)); + ].sort(byTimestamp(newestFirst)); if (activity.commentsTruncated || activity.reviewThreadsTruncated) events.push({ key: "truncated", diff --git a/apps/mobile/src/features/pull-requests/pullRequestList.ts b/apps/mobile/src/features/pull-requests/pullRequestList.ts index 674d3b0e24fe3..5a4572dbde803 100644 --- a/apps/mobile/src/features/pull-requests/pullRequestList.ts +++ b/apps/mobile/src/features/pull-requests/pullRequestList.ts @@ -11,6 +11,7 @@ import { type PullRequestListStatsInput, } from "@t3tools/contracts"; import type { PullRequestListControls } from "../../persistence/mobile-preferences"; +import { compareTimestamps } from "../../lib/time"; export const PR_LIST_PAGE_SIZE = 30; export type { PullRequestListControls } from "../../persistence/mobile-preferences"; @@ -242,7 +243,7 @@ export function mergePullRequestListEntries( const held = entries.get(key); if (!held || (entry.observedAt ?? 0) >= (held.observedAt ?? 0)) entries.set(key, entry); } - return [...entries.values()].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); + return [...entries.values()].sort((a, b) => compareTimestamps(b.updatedAt, a.updatedAt)); } export interface PullRequestListPages { @@ -455,10 +456,10 @@ export function filterPullRequestList< } return [...unique.values()].sort((a, b) => controls.sort === "created" - ? b.createdAt.localeCompare(a.createdAt) + ? compareTimestamps(b.createdAt, a.createdAt) : controls.sort === "oldest" - ? a.updatedAt.localeCompare(b.updatedAt) - : b.updatedAt.localeCompare(a.updatedAt), + ? compareTimestamps(a.updatedAt, b.updatedAt) + : compareTimestamps(b.updatedAt, a.updatedAt), ); } diff --git a/apps/mobile/src/lib/time.test.ts b/apps/mobile/src/lib/time.test.ts index 8881b6bbab8b7..c9c24b9c8f97f 100644 --- a/apps/mobile/src/lib/time.test.ts +++ b/apps/mobile/src/lib/time.test.ts @@ -1,9 +1,39 @@ import { describe, expect, it } from "vite-plus/test"; -import { msUntilRelativeTimeChange, relativeTime } from "./time"; +import { byTimestamp, compareTimestamps, msUntilRelativeTimeChange, relativeTime } from "./time"; const at = "2026-10-19T12:00:00.000Z"; const timestamp = Date.parse(at); +describe("timestamp order", () => { + it("orders host timestamps by instant across offsets and fractional seconds, missing ones first", () => { + const dates = [ + "2026-07-05T00:30:00.5Z", + "2026-07-05T01:00:00+02:00", + "", + "2026-07-05T00:30:00Z", + ]; + + expect(dates.toSorted(compareTimestamps)).toEqual([ + "", + "2026-07-05T01:00:00+02:00", + "2026-07-05T00:30:00Z", + "2026-07-05T00:30:00.5Z", + ]); + }); + it("keeps equal instants in input order whichever way it sorts", () => { + const rows = [ + { id: "comment", date: "2026-07-05T00:30:00Z" }, + { id: "commit", date: "2026-07-04T20:30:00-04:00" }, + { id: "older", date: "2026-07-04T00:00:00Z" }, + ]; + const ids = (newestFirst: boolean) => + rows.toSorted(byTimestamp(newestFirst)).map((row) => row.id); + + expect(ids(false)).toEqual(["older", "comment", "commit"]); + expect(ids(true)).toEqual(["comment", "commit", "older"]); + }); +}); + describe("relative time", () => { it.each([ [59_999, "<1m", "1m"], diff --git a/apps/mobile/src/lib/time.ts b/apps/mobile/src/lib/time.ts index 8635ccb37138b..5a32e16fc75c9 100644 --- a/apps/mobile/src/lib/time.ts +++ b/apps/mobile/src/lib/time.ts @@ -1,3 +1,15 @@ +// Host timestamps may carry UTC offsets or mixed fractions, so text order is not time order. +export function compareTimestamps(left: string, right: string): number { + return (Date.parse(left) || 0) - (Date.parse(right) || 0); +} + +export function byTimestamp(newestFirst: boolean) { + return (left: { readonly date: string }, right: { readonly date: string }) => + newestFirst + ? compareTimestamps(right.date, left.date) + : compareTimestamps(left.date, right.date); +} + export function relativeTime(input: string, nowMs: number = Date.now()): string { const timestamp = Date.parse(input); if (Number.isNaN(timestamp)) { From db0ae7b6e1b7f4ba03aaa191374556429b146f47 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 07:32:39 -0600 Subject: [PATCH 116/192] fix(acp): a blank line from an ACP agent no longer ends the session (#28851) Co-authored-by: Julius Marminge --- packages/effect-acp/src/protocol.test.ts | 25 ++++++++++++++++++++++++ packages/effect-acp/src/protocol.ts | 4 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/effect-acp/src/protocol.test.ts b/packages/effect-acp/src/protocol.test.ts index 19f58292ac469..6469b2ce5dbe6 100644 --- a/packages/effect-acp/src/protocol.test.ts +++ b/packages/effect-acp/src/protocol.test.ts @@ -531,6 +531,31 @@ it.layer(NodeServices.layer)("effect-acp protocol", (it) => { }), ); + it.effect("skips blank lines between agent messages", () => + Effect.gen(function* () { + const { stdio, input } = yield* makeInMemoryStdio(); + const outcome = yield* Deferred.make(); + const transport = yield* AcpProtocol.makeAcpPatchedProtocol({ + stdio, + serverRequestMethods: new Set(), + onTermination: (error) => Deferred.succeed(outcome, error._tag).pipe(Effect.asVoid), + }); + yield* transport.incoming.pipe( + Stream.runForEach((notification) => Deferred.succeed(outcome, notification._tag)), + Effect.forkScoped, + ); + + const update = encodeUnknownJsonString({ + jsonrpc: "2.0", + method: "session/update", + params: { sessionId: "session-1", update: { sessionUpdate: "plan", entries: [] } }, + }); + yield* Queue.offer(input, encoder.encode(`\n\r\n \t\n${update}\n\n`)); + + assert.equal(yield* Deferred.await(outcome), "SessionUpdate"); + }), + ); + it.effect("logs decode failures without copying the cause or wire payload", () => Effect.gen(function* () { const secret = "acp-wire-secret-sentinel"; diff --git a/packages/effect-acp/src/protocol.ts b/packages/effect-acp/src/protocol.ts index 490c27d8e0b08..d8745bbfe7b13 100644 --- a/packages/effect-acp/src/protocol.ts +++ b/packages/effect-acp/src/protocol.ts @@ -173,7 +173,7 @@ const decodeElicitationComplete = Schema.decodeUnknownEffect( ); const parserFactory = RpcSerialization.ndJsonRpc(); // ndJsonRpc skips lines that are not JSON. A malformed agent line has to end the -// session, so frames are split here and each one goes through the strict codec. +// session, so frames are split here and each nonblank one goes through the strict codec. const makeStrictNdJsonRpcParser = () => { const codec = RpcSerialization.jsonRpc().makeUnsafe(); const decoder = new TextDecoder(); @@ -197,7 +197,7 @@ const makeStrictNdJsonRpcParser = () => { } const lines = (buffer + chunk.slice(0, lastNewline)).split("\n"); buffer = chunk.slice(lastNewline + 1); - return lines.flatMap(decodeLine); + return lines.flatMap((line) => (line.trim() === "" ? [] : decodeLine(line))); }, // A truncated final line is left to the exit status, which explains a crash better. end: (): ReadonlyArray => { From 3d9aeaf7570c71f19c51271dd55003cbd156d29a Mon Sep 17 00:00:00 2001 From: Fuksss Date: Sat, 14 Nov 2026 14:36:30 +0100 Subject: [PATCH 117/192] fix(shared): preview semicolon-separated CSV files as tables (#36738) Co-authored-by: Julius Marminge --- .../src/components/files/largeText.worker.ts | 29 ++++++++++++-- packages/shared/src/delimitedPreview.test.ts | 33 ++++++++++++++++ packages/shared/src/delimitedPreview.ts | 39 ++++++++++++++++++- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/files/largeText.worker.ts b/apps/web/src/components/files/largeText.worker.ts index 4ac5207685f9f..bbd15be9d9140 100644 --- a/apps/web/src/components/files/largeText.worker.ts +++ b/apps/web/src/components/files/largeText.worker.ts @@ -1,4 +1,4 @@ -import { parseDelimitedPreview } from "@t3tools/shared/delimitedPreview"; +import { csvPreviewDelimiter, parseDelimitedPreview } from "@t3tools/shared/delimitedPreview"; import { LARGE_TEXT_MAX_BYTES, LARGE_TEXT_MAX_ROWS, @@ -12,7 +12,7 @@ const chunks: { start: number; blob: Blob }[] = []; const offsets: Uint32Array[] = [new Uint32Array(65536)]; let entries = 1; let size = 0; -let delimiter: "," | "\t" | null = null; +let delimiter: "," | ";" | "\t" | null = null; let complete = false; let columns = 1; function reply(value: TextPreviewResult) { @@ -51,6 +51,7 @@ async function page(index: number) { maxRows: PREVIEW_PAGE_ROWS, maxColumns: LARGE_TEXT_MAX_COLUMNS, maxCellLength: 1024 * 1024, + detectCsv: false, }).rows : text .replace(/\r?\n$|\r$/, "") @@ -89,8 +90,30 @@ async function open(input: Extract) { let rowColumns = 1; let first = false; try { + let prefix: Uint8Array | undefined; + if (delimiter === ",") { + const parts: Uint8Array[] = []; + let length = 0; + let quoted = false; + let breaks = 0; + let previous = 0; + while (length <= 1024 * 1024 && breaks < 2) { + const next = await reader.read(); + if (next.done) break; + parts.push(next.value); + length += next.value.length; + for (const byte of next.value) { + if (byte === 34) quoted = !quoted; + else if (!quoted && (byte === 13 || (byte === 10 && previous !== 13))) breaks++; + previous = byte; + } + } + prefix = new Uint8Array(await new Blob(parts).arrayBuffer()); + delimiter = csvPreviewDelimiter(new TextDecoder().decode(prefix)); + } while (true) { - const { value, done } = await reader.read(); + const { value, done } = prefix ? { value: prefix, done: false } : await reader.read(); + prefix = undefined; if (done) break; if (size + value.length > LARGE_TEXT_MAX_BYTES) throw new Error("Text previews support files up to 256 MB."); diff --git a/packages/shared/src/delimitedPreview.test.ts b/packages/shared/src/delimitedPreview.test.ts index 1f571b9405196..f18ecfdc485ae 100644 --- a/packages/shared/src/delimitedPreview.test.ts +++ b/packages/shared/src/delimitedPreview.test.ts @@ -21,6 +21,39 @@ describe("delimited file previews", () => { ["", "three"], ]); }); + it("detects semicolon-separated CSV from its first records outside quotes", () => { + expect(parseDelimitedPreview("a;b\r\n1;2,5", ",").rows).toEqual([ + ["a", "b"], + ["1", "2,5"], + ]); + expect(parseDelimitedPreview('name,notes\nAlice,"a;b;c"', ",").rows).toEqual([ + ["name", "notes"], + ["Alice", "a;b;c"], + ]); + expect(parseDelimitedPreview('"a, b";notes\nAlice;"say ""hi""\nagain"', ",").rows).toEqual([ + ["a, b", "notes"], + ["Alice", 'say "hi"\nagain'], + ]); + expect(parseDelimitedPreview("a,b;c", ",").rows).toEqual([["a", "b;c"]]); + expect(parseDelimitedPreview("id,notes;more;extra\n1,plain\n2,ordinary", ",").rows).toEqual([ + ["id", "notes;more;extra"], + ["1", "plain"], + ["2", "ordinary"], + ]); + expect( + parseDelimitedPreview("id,notes;more;extra\n1,plain;other;last\n2,ordinary;next;end", ",") + .rows, + ).toEqual([ + ["id", "notes;more;extra"], + ["1", "plain;other;last"], + ["2", "ordinary;next;end"], + ]); + expect(parseDelimitedPreview('12",name\n24",a;b;c', ",").rows).toEqual([ + ['12"', "name"], + ['24"', "a;b;c"], + ]); + expect(parseDelimitedPreview("a;b\tvalue", "\t").rows).toEqual([["a;b", "value"]]); + }); describe.each([",", "\t"] as const)("with delimiter %j", (delimiter) => { it("preserves a final quoted empty record without requiring a line ending", () => { for (const ending of ["", "\n", "\r\n"]) { diff --git a/packages/shared/src/delimitedPreview.ts b/packages/shared/src/delimitedPreview.ts index b9698ca7a0be1..6197f278b2411 100644 --- a/packages/shared/src/delimitedPreview.ts +++ b/packages/shared/src/delimitedPreview.ts @@ -12,13 +12,16 @@ export function filePreviewDelimiter(file: { name: string; mimeType?: string }): export function parseDelimitedPreview( text: string, - delimiter: "," | "\t", - limits: { maxRows: number; maxColumns: number; maxCellLength: number } = { + delimiter: "," | ";" | "\t", + limits: { maxRows: number; maxColumns: number; maxCellLength: number; detectCsv?: boolean } = { maxRows: 100, maxColumns: 30, maxCellLength: 2000, }, ) { + if (delimiter === "," && limits.detectCsv !== false) { + delimiter = csvPreviewDelimiter(text); + } const rows: string[][] = []; let row: string[] = []; let cell = ""; @@ -71,3 +74,35 @@ export function parseDelimitedPreview( } return { rows, truncated: truncated || quoted }; } + +export function csvPreviewDelimiter(text: string) { + // Semicolons win only when they are consistent across the first two records and commas are not. + const commas = [0, 0]; + const semicolons = [0, 0]; + let record = 0; + let second = false; + let quoted = false; + let fieldStart = true; + for (let index = text.charCodeAt(0) === 0xfeff ? 1 : 0; index < text.length; index++) { + const char = text[index]; + if (quoted) { + if (char !== '"') continue; + if (text[index + 1] === '"') index++; + else quoted = false; + } else if (char === '"' && fieldStart) quoted = true; + else if (char === "\n" || char === "\r") { + if (record === 1) break; + if (char === "\r" && text[index + 1] === "\n") index++; + record = 1; + fieldStart = true; + continue; + } else if (char === ",") commas[record]!++; + else if (char === ";") semicolons[record]!++; + second ||= record === 1; + fieldStart = char === "," || char === ";"; + } + if (semicolons[0]! <= commas[0]!) return ","; + if (!second) return ";"; + const consistentCommas = commas[0]! > 0 && commas[1] === commas[0]; + return semicolons[1] === semicolons[0] && !consistentCommas ? ";" : ","; +} From 34a7c0d85d60a2b25e21ec815b8b7e6394141cf6 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 14:39:36 +0100 Subject: [PATCH 118/192] fix(cursor): preserve catalog aliases in model discovery (#36752) Co-authored-by: Julius Marminge --- .../provider-cursor/src/server/status.test.ts | 17 +++++++++++++++-- packages/provider-cursor/src/server/status.ts | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/provider-cursor/src/server/status.test.ts b/packages/provider-cursor/src/server/status.test.ts index 9854fb7c26978..82651cc254326 100644 --- a/packages/provider-cursor/src/server/status.test.ts +++ b/packages/provider-cursor/src/server/status.test.ts @@ -138,20 +138,33 @@ describe("Cursor SDK model discovery", () => { ); }); - it("filters invalid and duplicate SDK model entries", () => { + it("keeps catalog aliases and filters invalid and duplicate SDK model entries", () => { + const composer = { + id: "composer-2", + displayName: "Composer 2", + aliases: [" composer-latest ", "", "composer"], + } satisfies SDKModel; expect( buildCursorDiscoveredModelsFromSdk([ sdkParameterizedModel, { ...sdkParameterizedModel, displayName: "Duplicate" }, { id: "", displayName: "Invalid" }, + composer, ]), - ).toEqual([ + ).toStrictEqual([ { slug: "claude-opus-4-8", name: "Opus 4.8", isCustom: false, capabilities: buildCursorCapabilitiesFromSdkModel(sdkParameterizedModel), }, + { + slug: "composer-2", + name: "Composer 2", + aliases: ["composer-latest", "composer"], + isCustom: false, + capabilities: buildCursorCapabilitiesFromSdkModel(composer), + }, ]); }); }); diff --git a/packages/provider-cursor/src/server/status.ts b/packages/provider-cursor/src/server/status.ts index 0c4d8ae031aaf..7c0167579f20b 100644 --- a/packages/provider-cursor/src/server/status.ts +++ b/packages/provider-cursor/src/server/status.ts @@ -172,10 +172,12 @@ export function buildCursorDiscoveredModelsFromSdk( return []; } seen.add(slug); + const aliases = model.aliases?.map((alias) => alias.trim()).filter(Boolean) ?? []; return [ { slug, name, + ...(aliases.length > 0 ? { aliases } : {}), isCustom: false, capabilities: buildCursorCapabilitiesFromSdkModel(model), } satisfies ServerProviderModel, From 68bc28196d00cfa681caa18e401c9c9e17273da2 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 14:40:59 +0100 Subject: [PATCH 119/192] fix(desktop): Settings opens while the separate preview window has focus (#36747) Co-authored-by: Grok 4.7 Co-authored-by: Julius Marminge --- apps/desktop/src/window/DesktopWindow.test.ts | 42 ++++++++++++++++++- apps/desktop/src/window/DesktopWindow.ts | 6 +-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/window/DesktopWindow.test.ts b/apps/desktop/src/window/DesktopWindow.test.ts index 0ddcc7946d32b..f66586503e24d 100644 --- a/apps/desktop/src/window/DesktopWindow.test.ts +++ b/apps/desktop/src/window/DesktopWindow.test.ts @@ -246,6 +246,7 @@ function layerTest(input: { readonly previewZoomReapplies?: number[]; readonly onReveal?: (window: Electron.BrowserWindow) => void; readonly focusedWindow?: Electron.BrowserWindow; + readonly fallbackWindow?: Electron.BrowserWindow; readonly platform?: NodeJS.Platform; readonly appearanceWindows?: ReadonlyArray<{ readonly window: Electron.BrowserWindow; @@ -299,7 +300,9 @@ function layerTest(input: { Effect.as(input.window), ), main: Ref.get(input.mainWindow), - currentMainOrFirst: Ref.get(input.mainWindow), + currentMainOrFirst: Ref.get(input.mainWindow).pipe( + Effect.map(Option.orElse(() => Option.fromNullishOr(input.fallbackWindow ?? null))), + ), focusedMainOrFirst: input.focusedWindow === undefined ? Ref.get(input.mainWindow) @@ -844,7 +847,7 @@ describe("DesktopWindow", () => { }), ); - it.effect("reloads and inspects the main window even when a popup has focus", () => + it.effect("routes menu commands to the main window even when a popup has focus", () => Effect.gen(function* () { const fakeWindow = makeFakeBrowserWindow(); const popup = makeFakeBrowserWindow(); @@ -864,7 +867,10 @@ describe("DesktopWindow", () => { yield* desktopWindow.runMainContentsCommand("reload"); yield* desktopWindow.runMainContentsCommand("forceReload"); yield* desktopWindow.runMainContentsCommand("toggleDevTools"); + yield* desktopWindow.dispatchMenuAction("open-settings"); + assert.deepEqual(fakeWindow.send.mock.calls, [["desktop:menu-action", "open-settings"]]); + assert.equal(popup.send.mock.calls.length, 0); assert.equal(fakeWindow.reload.mock.calls.length, 1); assert.equal(fakeWindow.reloadIgnoringCache.mock.calls.length, 1); assert.equal(fakeWindow.toggleDevTools.mock.calls.length, 1); @@ -875,6 +881,38 @@ describe("DesktopWindow", () => { }), ); + it.effect("recreates main for Settings when only an unregistered popup survives", () => + Effect.gen(function* () { + const main = makeFakeBrowserWindow(); + const auxiliary = makeFakeBrowserWindow(); + const createCount = yield* Ref.make(0); + const mainWindow = yield* Ref.make>(Option.none()); + + yield* Effect.gen(function* () { + const windows = yield* DesktopWindow.DesktopWindow; + yield* windows.dispatchMenuAction("open-settings"); + assert.deepEqual(main.send.mock.calls, [["desktop:menu-action", "open-settings"]]); + assert.equal(auxiliary.send.mock.calls.length, 0); + assert.equal(yield* Ref.get(createCount), 1); + assert.strictEqual(yield* windows.ensureMain, main.window); + assert.equal(yield* Ref.get(createCount), 1); + }).pipe( + Effect.provide( + layerTest({ + window: main.window, + createCount, + mainWindow, + fallbackWindow: auxiliary.window, + desktopSettings: { + ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, + localEnvironmentEnabled: false, + }, + }), + ), + ); + }), + ); + it.effect("does not open main or splash windows once quitting has started", () => Effect.gen(function* () { const fake = makeFakeBrowserWindow(); diff --git a/apps/desktop/src/window/DesktopWindow.ts b/apps/desktop/src/window/DesktopWindow.ts index 93b35eb9b1a32..7be14bc720838 100644 --- a/apps/desktop/src/window/DesktopWindow.ts +++ b/apps/desktop/src/window/DesktopWindow.ts @@ -425,7 +425,6 @@ export const make = Effect.gen(function* () { ); const currentMainWindow = electronWindow.currentMainOrFirst.pipe(Effect.flatMap(withoutSplash)); - const focusedMainWindow = electronWindow.focusedMainOrFirst.pipe(Effect.flatMap(withoutSplash)); const zoomWindow = Effect.fn("desktop.window.zoomWindow")(function* ( window: Electron.BrowserWindow, @@ -1019,7 +1018,7 @@ export const make = Effect.gen(function* () { const ensureMain = Effect.gen(function* () { if (yield* Ref.get(desktopState.quitting)) return Option.none(); - const existingWindow = yield* currentMainWindow; + const existingWindow = yield* electronWindow.main; if (Option.isSome(existingWindow)) { return existingWindow; } @@ -1106,7 +1105,8 @@ export const make = Effect.gen(function* () { { reveal = true }: { readonly reveal?: boolean } = {}, ) { if (yield* Ref.get(desktopState.quitting)) return; - const existingWindow = yield* reveal ? focusedMainWindow : electronWindow.main; + // Only the main renderer loads the app preload that listens on these channels. + const existingWindow = yield* electronWindow.main; if (Option.isNone(existingWindow) && (!reveal || (yield* waitingForBackend))) return; const window = Option.isSome(existingWindow) ? existingWindow : yield* ensureMain; if (Option.isNone(window) || window.value.isDestroyed()) return; From 629242b8d3ab6f1c626c5098bf932345d7926285 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 07:44:12 -0600 Subject: [PATCH 120/192] fix(server): long scheduled-task titles no longer end in a broken character (#28831) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/mcp/OrchestratorMcpService.test.ts | 53 +++++++++++++++---- apps/server/src/mcp/OrchestratorMcpService.ts | 8 +-- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/apps/server/src/mcp/OrchestratorMcpService.test.ts b/apps/server/src/mcp/OrchestratorMcpService.test.ts index fb087724dbdb7..e1a77578e2891 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.test.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.test.ts @@ -3375,7 +3375,7 @@ describe("OrchestratorMcpService provider resolution", () => { git: Partial, saved: Ref.Ref>, task: ScheduledTask = existing, - modes?: Ref.Ref>, + inputs?: Ref.Ref>, ) => OrchestratorMcpService.layer.pipe( Layer.provide(Layer.mock(ThreadLaunchService.ThreadLaunchService)({})), @@ -3407,9 +3407,9 @@ describe("OrchestratorMcpService provider resolution", () => { upsert: (input) => Ref.update(saved, (strategies) => [...strategies, input.workspaceStrategy]).pipe( Effect.andThen( - modes === undefined + inputs === undefined ? Effect.void - : Ref.update(modes, (all) => [...all, input.threadMode]), + : Ref.update(inputs, (all) => [...all, input]), ), Effect.as({ task }), ), @@ -3438,16 +3438,48 @@ describe("OrchestratorMcpService provider resolution", () => { }), ); + it.effect.each([ + { + name: "drops an emoji the limit splits", + prompt: `${"a".repeat(79)}😀 rest`, + title: "a".repeat(79), + }, + { + name: "keeps an emoji that ends at the limit", + prompt: `${"a".repeat(78)}😀 rest`, + title: `${"a".repeat(78)}😀`, + }, + ])("a prompt-derived title $name", ({ prompt, title }) => + Effect.gen(function* () { + const inputs = yield* Ref.make>([]); + const mcp = yield* OrchestratorMcpService.OrchestratorMcpService.pipe( + Effect.provide( + service( + masterRepository, + yield* Ref.make>([]), + existing, + inputs, + ), + ), + ); + yield* mcp.scheduleTask(client, { projectId, prompt, schedule }); + assert.deepEqual( + (yield* Ref.get(inputs)).map((input) => input.title), + [title], + ); + }), + ); + it.effect("keeps one thread when fresh sessions are turned off before the first run", () => Effect.gen(function* () { - const modes = yield* Ref.make>([]); + const inputs = yield* Ref.make>([]); const mcp = yield* OrchestratorMcpService.OrchestratorMcpService.pipe( Effect.provide( service( masterRepository, yield* Ref.make>([]), { ...existing, threadMode: "fresh_session" }, - modes, + inputs, ), ), ); @@ -3459,7 +3491,10 @@ describe("OrchestratorMcpService provider resolution", () => { scheduledTaskId: existing.id, bindToCurrentThread: false, }); - assert.deepEqual(yield* Ref.get(modes), ["continue", "new_thread"]); + assert.deepEqual( + (yield* Ref.get(inputs)).map((input) => input.threadMode), + ["continue", "new_thread"], + ); }), ); @@ -3467,7 +3502,7 @@ describe("OrchestratorMcpService provider resolution", () => { "refuses unbinding into fresh sessions in one call, which would keep the thread", () => Effect.gen(function* () { - const modes = yield* Ref.make>([]); + const inputs = yield* Ref.make>([]); const mcp = yield* OrchestratorMcpService.OrchestratorMcpService.pipe( Effect.provide( service( @@ -3478,7 +3513,7 @@ describe("OrchestratorMcpService provider resolution", () => { threadId: ThreadId.make("thread:scheduled-base"), threadMode: "continue", }, - modes, + inputs, ), ), ); @@ -3490,7 +3525,7 @@ describe("OrchestratorMcpService provider resolution", () => { }) .pipe(Effect.flip); assert.include(refused.message, "first unbind it"); - assert.deepEqual(yield* Ref.get(modes), []); + assert.deepEqual(yield* Ref.get(inputs), []); }), ); diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index d7ef5f788f0ba..2d0e7847389af 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -2161,9 +2161,11 @@ const make = Effect.gen(function* () { } const modelSelection = parent?.thread.modelSelection ?? (yield* projectDefaultModelSelection(project)); - const derivedTitle = input.prompt.split("\n")[0]?.trim() ?? ""; - const title = - input.title ?? (derivedTitle.length > 0 ? derivedTitle.slice(0, 80) : "Scheduled task"); + // Never end on a lone high surrogate, which renders as a broken character. + const derivedTitle = (input.prompt.split("\n")[0]?.trim() ?? "") + .slice(0, 80) + .replace(/[\uD800-\uDBFF]$/, ""); + const title = input.title ?? (derivedTitle.length > 0 ? derivedTitle : "Scheduled task"); const upsertInput: ScheduledTaskUpsertInput = { title, prompt: input.prompt, From 616067de84b8826f201a3704928369fbfb1958fa Mon Sep 17 00:00:00 2001 From: preet Date: Sat, 14 Nov 2026 19:17:39 +0530 Subject: [PATCH 121/192] fix(web): pressing Enter in the Add CLIProxyAPI hub dialog adds the hub (#36755) Co-authored-by: Julius Marminge --- ...AddUsageLimitSourceDialog.browser.test.tsx | 48 +++++++++++++++++++ .../settings/AddUsageLimitSourceDialog.tsx | 6 ++- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/components/settings/AddUsageLimitSourceDialog.browser.test.tsx diff --git a/apps/web/src/components/settings/AddUsageLimitSourceDialog.browser.test.tsx b/apps/web/src/components/settings/AddUsageLimitSourceDialog.browser.test.tsx new file mode 100644 index 0000000000000..1390dc2c22fe3 --- /dev/null +++ b/apps/web/src/components/settings/AddUsageLimitSourceDialog.browser.test.tsx @@ -0,0 +1,48 @@ +import { EnvironmentId } from "@t3tools/contracts"; +import { page, userEvent } from "vite-plus/test/browser"; +import { describe, expect, it, vi } from "vite-plus/test"; + +import { render } from "../../../test/render"; + +const updateSettings = vi.hoisted(() => vi.fn()); + +vi.mock(import("../../hooks/useSettings"), async (original) => ({ + ...(await original()), + useUpdateEnvironmentSettings: (() => updateSettings) as never, +})); +vi.mock(import("./SecretSourceInput"), async (original) => ({ + ...(await original()), + useSecretSources: () => [], +})); + +import { AddUsageLimitSourceDialog } from "./AddUsageLimitSourceDialog"; + +describe("AddUsageLimitSourceDialog", () => { + it("adds the hub when Enter is pressed in a field", async () => { + updateSettings.mockClear(); + const onOpenChange = vi.fn(); + await using _screen = await render( + , + ); + + await userEvent.type(page.getByLabelText("Hub URL"), "https://hub.example.ts.net:8318"); + await userEvent.type(page.getByLabelText("Management key"), "secret-key{Enter}"); + + expect(updateSettings).toHaveBeenCalledOnce(); + expect(updateSettings).toHaveBeenCalledWith({ + usageLimitSources: { + "cliproxy-hub.example.ts.net-8318": expect.objectContaining({ + url: "https://hub.example.ts.net:8318", + managementKey: "secret-key", + }), + }, + }); + expect(onOpenChange).toHaveBeenCalledWith(false); + }); +}); diff --git a/apps/web/src/components/settings/AddUsageLimitSourceDialog.tsx b/apps/web/src/components/settings/AddUsageLimitSourceDialog.tsx index 2098055289e02..3fb983f4838d8 100644 --- a/apps/web/src/components/settings/AddUsageLimitSourceDialog.tsx +++ b/apps/web/src/components/settings/AddUsageLimitSourceDialog.tsx @@ -1,5 +1,5 @@ import type { EnvironmentId, SecretReference, UnifiedSettings } from "@t3tools/contracts"; -import { useState } from "react"; +import { useId, useState } from "react"; import { useUpdateEnvironmentSettings } from "../../hooks/useSettings"; import { Button } from "../ui/button"; @@ -31,6 +31,7 @@ export function AddUsageLimitSourceDialog({ readonly environmentLabel: string; readonly sources: UnifiedSettings["usageLimitSources"]; }) { + const formId = useId(); const updateSettings = useUpdateEnvironmentSettings(environmentId); const [label, setLabel] = useState(""); const [url, setUrl] = useState(""); @@ -84,6 +85,7 @@ export function AddUsageLimitSourceDialog({
{ event.preventDefault(); @@ -134,7 +136,7 @@ export function AddUsageLimitSourceDialog({ > Cancel - From 3b816cf6c7b8c180273f8bc65062b94e42b13339 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 05:49:35 -0800 Subject: [PATCH 122/192] fix(web): ChatGPT sign-in error no longer points to a missing redirect URL field (#36753) --- apps/web/src/components/settings/CodexSetupSection.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/web/src/components/settings/CodexSetupSection.tsx b/apps/web/src/components/settings/CodexSetupSection.tsx index cbbe3d51a4ed5..a09b1c0b25d8c 100644 --- a/apps/web/src/components/settings/CodexSetupSection.tsx +++ b/apps/web/src/components/settings/CodexSetupSection.tsx @@ -483,9 +483,7 @@ function ManagedCodexSetup({ await ensureLocalApi().shell.openExternal(authorizationUrl); } } catch { - setError( - "Could not finish sign-in on this computer. Try again or paste the redirect URL below.", - ); + setError("Could not finish sign-in on this computer. Try again."); } }; From 12102c74a323c67bcbec189ae23dc50c62740895 Mon Sep 17 00:00:00 2001 From: Nabil Belahouane Date: Sat, 14 Nov 2026 08:53:25 -0500 Subject: [PATCH 123/192] fix(web,mobile): Linked environments shows links made outside the app (#28907) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../LinkedEnvironmentsSection.test.tsx | 169 ++++++++++++++++++ .../connection/LinkedEnvironmentsSection.tsx | 24 ++- apps/mobile/vite.config.ts | 1 + ...inkedEnvironmentsSettings.browser.test.tsx | 128 +++++++++++++ .../settings/LinkedEnvironmentsSettings.tsx | 14 ++ 5 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 apps/mobile/src/features/connection/LinkedEnvironmentsSection.test.tsx create mode 100644 apps/web/src/components/settings/LinkedEnvironmentsSettings.browser.test.tsx diff --git a/apps/mobile/src/features/connection/LinkedEnvironmentsSection.test.tsx b/apps/mobile/src/features/connection/LinkedEnvironmentsSection.test.tsx new file mode 100644 index 0000000000000..964d499fd30c4 --- /dev/null +++ b/apps/mobile/src/features/connection/LinkedEnvironmentsSection.test.tsx @@ -0,0 +1,169 @@ +import { RegistryContext } from "@effect/atom-react"; +import { + NavigationContext, + type NavigationProp, + type ParamListBase, +} from "@react-navigation/native"; +import { EnvironmentId } from "@t3tools/contracts"; +import { AtomRegistry } from "effect/reactivity"; +import { act, type ReactNode } from "react"; +import { createRoot } from "react-dom/client"; +import { expect, it, vi } from "vite-plus/test"; + +// Each listing probes every linked machine, so the test counts the fetches it starts and cuts. +const fetches = vi.hoisted(() => ({ + started: 0, + interrupted: 0, + gate: Promise.resolve(), + open: () => {}, +})); + +vi.mock("@react-navigation/native", async () => { + const { createRequire } = await import("node:module"); + const { realpathSync } = await import("node:fs"); + const require = createRequire(import.meta.url); + const nativeRequire = createRequire( + realpathSync(require.resolve("@react-navigation/native/package.json")), + ); + // Use the shipped focus hook without importing the React Native view layer. + const { NavigationContext, useFocusEffect }: typeof import("@react-navigation/native") = + nativeRequire("@react-navigation/core"); + return { NavigationContext, useFocusEffect }; +}); +const appState = vi.hoisted(() => ({ listeners: new Set<(state: string) => void>() })); + +vi.mock("react-native", () => ({ + AppState: { + addEventListener: (_event: "change", listener: (state: string) => void) => { + appState.listeners.add(listener); + return { remove: () => appState.listeners.delete(listener) }; + }, + }, + View: ({ children }: { children?: ReactNode }) => children, +})); +vi.mock("../../components/ui/AppText", () => ({ + AppText: ({ children }: { children?: ReactNode }) => children, +})); +vi.mock("../settings/components/SettingsSection", () => ({ + SettingsSection: ({ children }: { children?: ReactNode }) => children, +})); +vi.mock("../../state/entities", () => ({ + useEnvironmentServerConfig: () => ({ + environment: { capabilities: { peerLinks: true } }, + settings: { linkedEnvironmentsEnabled: true }, + }), +})); +vi.mock("../../state/session", () => ({ useEnvironmentScope: () => true })); +vi.mock("../../state/peerLinks", async () => { + const { Atom } = await import("effect/reactivity"); + const EffectModule = await import("effect/Effect"); + // Waits for the gate, as a list waits on its slowest linked machine. + const list = Atom.make( + EffectModule.gen(function* () { + fetches.started += 1; + yield* EffectModule.promise(() => fetches.gate); + return { links: [] }; + }).pipe( + EffectModule.onInterrupt(() => + EffectModule.sync(() => { + fetches.interrupted += 1; + }), + ), + ), + ); + return { peerLinkEnvironment: { list: () => list } }; +}); + +import { LinkedEnvironmentsSection } from "./LinkedEnvironmentsSection"; + +function createNavigation() { + const listeners = { focus: new Set<() => void>(), blur: new Set<() => void>() }; + let focused = true; + return { + value: { + isFocused: () => focused, + addListener: (event: keyof typeof listeners, listener: () => void) => { + listeners[event].add(listener); + return () => listeners[event].delete(listener); + }, + } as unknown as NavigationProp, + focus(next: boolean) { + focused = next; + act(() => { + for (const listener of listeners[next ? "focus" : "blur"]) listener(); + }); + }, + }; +} + +function setup() { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + fetches.started = 0; + fetches.interrupted = 0; + fetches.gate = new Promise((resolve) => { + fetches.open = resolve; + }); + const registry = AtomRegistry.make(); + const root = createRoot(document.createElement("div")); + const navigation = createNavigation(); + act(() => + root.render( + + + + + , + ), + ); + return { + focus: navigation.focus, + moveApp: (state: "active" | "background") => + act(() => { + for (const listener of appState.listeners) listener(state); + }), + settle: () => + act(async () => { + fetches.open(); + await fetches.gate; + }), + [Symbol.dispose]() { + act(() => root.unmount()); + registry.dispose(); + }, + }; +} + +it("asks again on focus only once the list it is waiting for has answered", async () => { + using screen = setup(); + // The first focus comes while the first fetch runs, which must not be cut and restarted. + expect(fetches).toMatchObject({ started: 1, interrupted: 0 }); + screen.focus(false); + screen.focus(true); + expect(fetches).toMatchObject({ started: 1, interrupted: 0 }); + + await screen.settle(); + screen.focus(false); + screen.focus(true); + expect(fetches).toMatchObject({ started: 2, interrupted: 0 }); +}); + +it("asks again when the app comes back to the front on this screen", async () => { + using screen = setup(); + await screen.settle(); + expect(fetches).toMatchObject({ started: 1, interrupted: 0 }); + fetches.gate = new Promise((resolve) => { + fetches.open = resolve; + }); + + screen.moveApp("background"); + screen.moveApp("active"); + expect(fetches).toMatchObject({ started: 2, interrupted: 0 }); + + // Coming back while that answer is still out must not cut it and start again. + screen.moveApp("background"); + screen.moveApp("active"); + expect(fetches).toMatchObject({ started: 2, interrupted: 0 }); +}); diff --git a/apps/mobile/src/features/connection/LinkedEnvironmentsSection.tsx b/apps/mobile/src/features/connection/LinkedEnvironmentsSection.tsx index 9153c9fa6a1c8..f447e2760277e 100644 --- a/apps/mobile/src/features/connection/LinkedEnvironmentsSection.tsx +++ b/apps/mobile/src/features/connection/LinkedEnvironmentsSection.tsx @@ -1,9 +1,10 @@ +import { RegistryContext } from "@effect/atom-react"; import { offersLinkedEnvironments } from "@t3tools/client-runtime/link-request"; import { peerLinkHealth } from "@t3tools/client-runtime/state/peerLinks"; import { useFocusEffect } from "@react-navigation/native"; import { AuthAccessReadScope, type EnvironmentId } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; -import { useCallback, useState } from "react"; +import { useCallback, useContext, useState } from "react"; import { AppState, View } from "react-native"; import { AppText as Text } from "../../components/ui/AppText"; @@ -20,8 +21,25 @@ export function LinkedEnvironmentsSection(props: { const config = useEnvironmentServerConfig(props.environmentId); const canRead = useEnvironmentScope(props.environmentId, AuthAccessReadScope); const supported = canRead && offersLinkedEnvironments(config); - const links = useEnvironmentQuery( - supported ? peerLinkEnvironment.list({ environmentId: props.environmentId, input: {} }) : null, + const listAtom = supported + ? peerLinkEnvironment.list({ environmentId: props.environmentId, input: {} }) + : null; + const links = useEnvironmentQuery(listAtom); + const registry = useContext(RegistryContext); + // Links change outside this client, and the shared list only revalidates when built, so + // showing the screen and bringing the app back ask again. + useFocusEffect( + useCallback(() => { + if (listAtom === null) return; + const askAgain = () => { + if (!registry.get(listAtom).waiting) registry.refresh(listAtom); + }; + askAgain(); + const subscription = AppState.addEventListener("change", (state) => { + if (state === "active") askAgain(); + }); + return () => subscription.remove(); + }, [listAtom, registry]), ); const [nowMs, setNowMs] = useState(Date.now); const hasLinks = supported && (links.data?.links.length ?? 0) > 0; diff --git a/apps/mobile/vite.config.ts b/apps/mobile/vite.config.ts index df5edd11c914a..902b116b7fd08 100644 --- a/apps/mobile/vite.config.ts +++ b/apps/mobile/vite.config.ts @@ -47,6 +47,7 @@ const reactTests = [ "src/features/home/home-route-visibility.test.tsx", "src/features/layout/workspace/useSearchScrollRestoration.test.tsx", "src/features/connection/ConnectionsNewRouteScreen.test.tsx", + "src/features/connection/LinkedEnvironmentsSection.test.tsx", "src/features/devices/device-stream.browser.test.ts", "src/features/keyboard/keyboardControllerState.test.tsx", ]; diff --git a/apps/web/src/components/settings/LinkedEnvironmentsSettings.browser.test.tsx b/apps/web/src/components/settings/LinkedEnvironmentsSettings.browser.test.tsx new file mode 100644 index 0000000000000..19b40be7f0e5b --- /dev/null +++ b/apps/web/src/components/settings/LinkedEnvironmentsSettings.browser.test.tsx @@ -0,0 +1,128 @@ +import { RegistryContext, useAtomValue } from "@effect/atom-react"; +import { EnvironmentId, type PeerLinkSummary } from "@t3tools/contracts"; +import * as DateTime from "effect/DateTime"; +import { AsyncResult, AtomRegistry } from "effect/reactivity"; +import { describe, expect, it, vi } from "vite-plus/test"; + +import "~/index.css"; +import { peerLinkEnvironment } from "~/state/peerLinks"; +import { render } from "../../../test/render"; +import { LinkedEnvironmentsSettings } from "./LinkedEnvironmentsSettings"; + +const hostId = EnvironmentId.make("mac"); +const vmLink: PeerLinkSummary = { + environmentId: EnvironmentId.make("vm"), + label: "boat-task", + urls: ["http://boat-task.example.ts.net:3773"], + access: "full-access", + linkedAt: DateTime.makeUnsafe("2026-11-01T00:00:00.000Z"), + expiresAt: DateTime.makeUnsafe("2099-01-01T00:00:00.000Z"), + lastReachedAt: null, + lastError: null, + status: "reachable", +}; + +// What the host's `peerLinks.list` answers. The CLI and agents link machines without this client. +const server = vi.hoisted(() => ({ + links: [] as Array, + started: 0, + interrupted: 0, + answer: Promise.resolve(), +})); + +function setup() { + server.links = []; + server.started = 0; + server.interrupted = 0; + server.answer = Promise.resolve(); +} + +vi.mock(import("~/state/peerLinks"), async (original) => { + const actual = await original(); + const { Atom: AtomModule } = await import("effect/reactivity"); + const EffectModule = await import("effect/Effect"); + // The real list query's options: stale-while-revalidate, revalidating only when it is built. + const list = AtomModule.make( + EffectModule.gen(function* () { + server.started += 1; + yield* EffectModule.promise(() => server.answer); + return { links: [...server.links] }; + }).pipe( + EffectModule.onInterrupt(() => + EffectModule.sync(() => { + server.interrupted += 1; + }), + ), + ), + ).pipe(AtomModule.swr({ staleTime: 10_000, revalidateOnMount: true })); + const denied = AtomModule.make(false); + return { + ...actual, + peerLinkEnvironment: { + ...actual.peerLinkEnvironment, + list: (() => list) as never, + link: { ...actual.peerLinkEnvironment.link, permissionAtom: () => denied }, + unlink: { ...actual.peerLinkEnvironment.unlink, permissionAtom: () => denied }, + }, + }; +}); + +/** Stands in for the chat composer, which reads the same list while a thread is open. */ +function ComposerReader() { + const result = useAtomValue(peerLinkEnvironment.list({ environmentId: hostId, input: {} })); + return AsyncResult.isSuccess(result) ?

Composer loaded

: null; +} + +describe("LinkedEnvironmentsSettings", () => { + it("shows a link made elsewhere while the composer keeps the list loaded", async () => { + setup(); + const registry = AtomRegistry.make(); + using _registry = { [Symbol.dispose]: () => registry.dispose() }; + await using composer = await render( + + + , + ); + await expect.element(composer.getByText("Composer loaded")).toBeVisible(); + + server.links = [vmLink]; + await using screen = await render( + + + , + ); + + await expect.element(screen.getByRole("heading", { name: "boat-task" })).toBeVisible(); + }); + + it("leaves a list that is still loading to answer", async () => { + setup(); + // useLiveRefresh remembers each view's last read across mounts, so this view gets its own host. + const loadingHostId = EnvironmentId.make("mac-loading"); + let answer = () => {}; + server.answer = new Promise((resolve) => { + answer = resolve; + }); + server.links = [vmLink]; + const registry = AtomRegistry.make(); + using _registry = { [Symbol.dispose]: () => registry.dispose() }; + await using screen = await render( + + + , + ); + await expect.element(screen.getByRole("status")).toBeVisible(); + + answer(); + await expect.element(screen.getByRole("heading", { name: "boat-task" })).toBeVisible(); + expect(server).toMatchObject({ started: 1, interrupted: 0 }); + }); +}); diff --git a/apps/web/src/components/settings/LinkedEnvironmentsSettings.tsx b/apps/web/src/components/settings/LinkedEnvironmentsSettings.tsx index 2014a1db6e359..6a3631a66523a 100644 --- a/apps/web/src/components/settings/LinkedEnvironmentsSettings.tsx +++ b/apps/web/src/components/settings/LinkedEnvironmentsSettings.tsx @@ -43,6 +43,7 @@ import { ITEM_ROW_CLASSNAME, ITEM_ROW_INNER_CLASSNAME } from "./itemRows"; import { LinkAccessPicker } from "./LinkAccessPicker"; import { LinkAddressWarning, LinkMachineSelect } from "./LinkMachineSelect"; import { SettingsRow, SettingsSection } from "./settingsLayout"; +import { useLiveRefresh } from "../../hooks/useLiveRefresh"; import { useNowMinute } from "../../hooks/useNowMinute"; import { searchableSetting } from "./settingsSearch"; @@ -59,6 +60,19 @@ export function LinkedEnvironmentsSettings({ readonly isSearchTarget: boolean; }) { const links = useEnvironmentQuery(peerLinkEnvironment.list({ environmentId, input: {} })); + // Links made outside this client bump nothing the shared list reads, so showing the panel + // asks again. Listing probes every linked environment, too costly for a timer nobody asked for. + // A list still loading counts as read now, so the next return to the window asks again. + useLiveRefresh( + () => { + if (!links.isPending) links.refresh(); + }, + { + key: `linked-environments:${environmentId}`, + intervalMs: null, + refreshOnShow: true, + }, + ); const canLink = useAtomValue(peerLinkEnvironment.link.permissionAtom(environmentId)); const search = searchableSetting("linked-environments"); return ( From 719042d94255b0e77ffc3d3c3183c6fe5e59d5c1 Mon Sep 17 00:00:00 2001 From: mmmattG <237165041+mmmattG@users.noreply.github.com> Date: Sat, 14 Nov 2026 21:57:40 +0800 Subject: [PATCH 124/192] fix(server): MCP-launched threads no longer flash in Inbox before Working (#36682) Co-authored-by: Julius Marminge --- .../src/mcp/OrchestratorMcpService.test.ts | 13 ++- apps/server/src/mcp/OrchestratorMcpService.ts | 31 ++++-- .../src/orchestration-v2/Orchestrator.ts | 37 +++++- .../ThreadLaunchService.test.ts | 41 ++++++- .../orchestration-v2/ThreadLaunchService.ts | 105 ++++++++++-------- .../ThreadManagementService.test.ts | 7 ++ .../ThreadManagementService.ts | 9 +- packages/contracts/src/orchestrationV2.ts | 16 +++ 8 files changed, 195 insertions(+), 64 deletions(-) diff --git a/apps/server/src/mcp/OrchestratorMcpService.test.ts b/apps/server/src/mcp/OrchestratorMcpService.test.ts index e1a77578e2891..2b58eddee2cab 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.test.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.test.ts @@ -2253,7 +2253,7 @@ describe("OrchestratorMcpService provider resolution", () => { cause: new Error("refused by the orchestrator"), }) as never, ) - : Effect.succeed({} as never), + : Effect.succeed({ storedEvents: [] } as never), }), ServerSettings.layerTest(), providerRegistryLayer([ @@ -2637,6 +2637,17 @@ describe("OrchestratorMcpService provider resolution", () => { }); const titled = yield* orchestrator.getThreadProjection(emojiAtLimit.threads[0]!.threadId); assert.equal(titled.thread.title, `${"a".repeat(76)}...`); + // One commit holds a prompted thread and its run, so no client sees it idle between. + const promptedCommits = yield* orchestrator + .streamStoredEventsFrom({ threadId: titled.thread.id }) + .pipe( + Stream.filter( + ({ event }) => event.type === "thread.created" || event.type === "run.created", + ), + Stream.take(2), + Stream.runCollect, + ); + assert.equal(promptedCommits[0]?.commandId, promptedCommits[1]?.commandId); yield* settings.updateSettings({ projectSettingsOverrides: { diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index 2d0e7847389af..d3160184f2a3f 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -2994,7 +2994,20 @@ const make = Effect.gen(function* () { title: request.title, index, }); - yield* threadManagement + const initialMessage = + request.prompt === undefined + ? undefined + : { + senderThreadId: scope.thread.threadId, + messageId: stableMessageId({ + scope, + requestKey: key, + index, + }), + text: request.prompt, + attachments: [], + }; + const created = yield* threadManagement .dispatch({ type: "thread.create", createdBy: "agent", @@ -3011,6 +3024,7 @@ const make = Effect.gen(function* () { ...(parent.thread.linkOrigin === undefined ? {} : { linkOrigin: parent.thread.linkOrigin }), + ...(initialMessage === undefined ? {} : { initialMessage }), }) .pipe( Effect.mapError((error) => @@ -3024,9 +3038,14 @@ const make = Effect.gen(function* () { const resolvedModelSelection = (yield* threadManagement .getThreadRecords(threadId, []) .pipe(Effect.mapError(threadManagementFailure))).thread.modelSelection; - if (request.prompt !== undefined) { + // A replayed creation that carried no prompt has no run yet. + if ( + initialMessage !== undefined && + !created.storedEvents.some(({ event }) => event.type === "run.created") + ) { yield* threadManagement .dispatch({ + ...initialMessage, type: "message.dispatch", createdBy: "agent", creationSource: "mcp", @@ -3037,14 +3056,6 @@ const make = Effect.gen(function* () { index, }), threadId, - senderThreadId: scope.thread.threadId, - messageId: stableMessageId({ - scope, - requestKey: key, - index, - }), - text: request.prompt, - attachments: [], modelSelection: resolvedModelSelection, dispatchMode: { type: "start_immediately" }, }) diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts index c6d1ab4654110..6049beffadcfe 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.ts @@ -795,6 +795,28 @@ function metadataUpdateContinuation( }; } +// A later command for the first message would leave the new thread committed with no run, +// and every client would show it idle until that command landed. +function threadCreateInitialMessage( + command: Extract, +): Extract | undefined { + if (command.initialMessage === undefined) return undefined; + const { workspaceStrategy, ...message } = command.initialMessage; + return { + ...message, + type: "message.dispatch", + commandId: command.commandId, + threadId: command.threadId, + modelSelection: command.modelSelection, + dispatchMode: + workspaceStrategy === undefined + ? { type: "start_immediately" } + : { type: "defer_start", workspaceStrategy }, + createdBy: command.createdBy, + creationSource: command.creationSource, + }; +} + // Provider wakes can start follow-up work; monitor updates only report progress. // Dispatch takes the parent's lock for follow-ups because intake writes the parent. function mayStartSubagentFollowup( @@ -6882,6 +6904,10 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio ? projectionStore .getThreadRecords(command.threadId, ["messages"], { messageRoles: ["user"] }) .pipe( + // A thread this command creates has no stored messages yet. + Effect.catchTags({ + ProjectionStoreThreadNotFoundError: () => Effect.succeed({ messages: [] }), + }), Effect.map(({ messages }) => [...messages, ...userMessages]), Effect.map((all) => all.length > 0 && all.every(isNativeMaintenanceCommand)), mapDispatchError(command), @@ -14760,9 +14786,18 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio | undefined; let updateDelegatedCompletionOnlyRunId: RunId | undefined; switch (command.type) { - case "thread.create": + case "thread.create": { yield* dispatchThreadCreate(command, events); + const initialMessage = threadCreateInitialMessage(command); + if (initialMessage !== undefined) { + cancelUnsettledEffects = yield* dispatchMessage( + withoutGuestAttribution(initialMessage), + events, + effects, + ); + } break; + } case "thread.visit": yield* dispatchThreadVisit(command, events); break; diff --git a/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts b/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts index 33d5fb657fa7b..ad255802013ec 100644 --- a/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts +++ b/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts @@ -633,7 +633,7 @@ it.effect.each([" /COMPACT "])( ); assert.isNull((yield* threads.getThreadProjection(launched.threadId)).thread.lastVisitedAt); assert.isFalse( - (yield* outbox.listByCommandId(CommandId.make("compact-title:initial-message"))).some( + (yield* outbox.listByCommandId(CommandId.make("compact-title"))).some( (effect) => effect.request.type === "thread-title.generate", ), ); @@ -776,7 +776,7 @@ it.effect("arms durable title generation after accepting the first message", () generateTitle: true, }; const launched = yield* launches.launch(input); - const generationCommandId = CommandId.make("command:launch:title-generation:initial-message"); + const generationCommandId = input.commandId; const projection = yield* threads.getThreadProjection(launched.threadId); assert.equal(projection.thread.title, "Generate my title"); @@ -850,9 +850,9 @@ it.effect("arms durable title generation after accepting the first message", () }), ); -it.effect("does not update a reused thread title when the initial message is rejected", () => +it.effect("a rejected initial message leaves no title change and no new thread", () => Effect.gen(function* () { - const harness = makeHarness(); + const harness = makeHarness({ adapters: [adapter, claudeAdapter] }); yield* Effect.gen(function* () { const launches = yield* ThreadLaunch.ThreadLaunchService; const threads = yield* ThreadManagement.ThreadManagementService; @@ -897,6 +897,24 @@ it.effect("does not update a reused thread title when the initial message is rej assert.isUndefined(projection.thread.titleRegeneration); assert.isEmpty(projection.messages); assert.isEmpty(yield* outbox.listByCommandId(CommandId.make(`${commandId}:initial-message`))); + + // The orchestrator refuses this goal command, after the launch's own checks pass. + const newThreadId = ThreadId.make("thread:launch:rejected-new-thread"); + const rejected = yield* launches + .launch({ + ...launchInput({ + command: "command:launch:rejected-new-thread", + thread: newThreadId, + message: "/goal pause", + }), + modelSelection: claudeModelSelection, + }) + .pipe(Effect.flip); + if (rejected._tag !== "ThreadLaunchError") { + assert.fail(`expected ThreadLaunchError, got ${rejected._tag}`); + } + assert.equal(rejected.operation, "create-thread"); + assert.isNull(yield* threads.getThreadShell(newThreadId)); }).pipe(Effect.provide(harness.layer)); }), ); @@ -1680,6 +1698,21 @@ it.effect("replays a server-allocated launch", () => workspace: { type: "worktree", baseRef: "main" }, }); const first = yield* launches.launch(rest); + // One commit holds the thread and its first run, so no client sees the thread idle between. + const launchCommits = yield* threads + .streamStoredEventsFrom({ threadId: first.threadId }) + .pipe( + Stream.filter( + (stored) => + stored.event.type === "thread.created" || stored.event.type === "run.created", + ), + Stream.take(2), + Stream.runCollect, + ); + assert.deepEqual( + Array.from(launchCommits, (stored) => stored.commandId), + [rest.commandId, rest.commandId], + ); yield* Deferred.await(setupEntered); const retry = yield* launches.launch(rest); assert.equal(first.threadId, retry.threadId); diff --git a/apps/server/src/orchestration-v2/ThreadLaunchService.ts b/apps/server/src/orchestration-v2/ThreadLaunchService.ts index 67f3b5402de78..bc29745e80065 100644 --- a/apps/server/src/orchestration-v2/ThreadLaunchService.ts +++ b/apps/server/src/orchestration-v2/ThreadLaunchService.ts @@ -1133,6 +1133,26 @@ const make = Effect.gen(function* () { const initialBranch = yield* launchBranch(workspaceStrategy, project.workspaceRoot); const initialWorktreePath = workspaceStrategy.type === "existing_worktree" ? workspaceStrategy.worktreePath : null; + const initialMessage = + input.initialMessage === undefined + ? undefined + : { + messageId: + input.initialMessage.messageId ?? + (yield* ids.allocate + .message({ threadId: candidateThreadId, ordinal: 1 }) + .pipe(Effect.mapError(mapError(input, "dispatch-message", candidateThreadId)))), + text: input.initialMessage.text, + ...(input.initialMessage.scheduledTaskId === undefined + ? {} + : { scheduledTaskId: input.initialMessage.scheduledTaskId }), + ...(input.initialMessage.senderThreadId === undefined + ? {} + : { senderThreadId: input.initialMessage.senderThreadId }), + attachments: input.initialMessage.attachments, + ...(input.initialMessage.context ? { context: input.initialMessage.context } : {}), + ...(input.generateTitle === true ? { titleSeed: input.title } : {}), + }; const claimDispatch = input.reuseExistingThread === true ? threads.dispatch({ @@ -1161,6 +1181,9 @@ const make = Effect.gen(function* () { : { delegatedFrom: input.delegatedFrom }), createdBy: input.createdBy, creationSource: input.creationSource, + ...(initialMessage === undefined + ? {} + : { initialMessage: { ...initialMessage, workspaceStrategy } }), }); const claimed = yield* claimDispatch.pipe( Effect.mapError( @@ -1180,53 +1203,43 @@ const make = Effect.gen(function* () { let runId: RunId | null = null; let messageSequence: number | undefined; let messageWasAlreadyAccepted = false; - if (input.initialMessage !== undefined) { - const messageCommandId = CommandId.make(`${input.commandId}:initial-message`); - const messageReceipt = yield* readReceipt(input, messageCommandId); - messageWasAlreadyAccepted = Option.isSome(messageReceipt); - const messageId = - input.initialMessage.messageId ?? - (yield* ids.allocate - .message({ threadId, ordinal: 1 }) - .pipe(Effect.mapError(mapError(input, "dispatch-message", threadId)))); - const dispatched = yield* threads - .dispatch({ - type: "message.dispatch", - commandId: messageCommandId, - threadId, - messageId, - text: input.initialMessage.text, - ...(input.initialMessage.scheduledTaskId === undefined - ? {} - : { scheduledTaskId: input.initialMessage.scheduledTaskId }), - ...(input.initialMessage.senderThreadId === undefined - ? {} - : { senderThreadId: input.initialMessage.senderThreadId }), - attachments: input.initialMessage.attachments, - ...(input.initialMessage.context ? { context: input.initialMessage.context } : {}), - ...(input.generateTitle === true ? { titleSeed: input.title } : {}), - modelSelection: input.modelSelection, - dispatchMode: { type: "defer_start", workspaceStrategy }, - createdBy: input.createdBy, - creationSource: input.creationSource, - }) - .pipe( - // An agent's launch can land on a thread that a replay, or a concurrent - // launch with the same key, created with broader modes. The orchestrator - // refuses the message under the thread's lock; the user's own clients - // launch into their own drafts, whatever modes those hold. - input.createdBy === "agent" - ? Effect.provideService(DispatchModeLimit, { - runtimeMode: input.runtimeMode, - interactionMode: input.interactionMode, - }) - : (effect) => effect, - Effect.mapError(mapError(input, "dispatch-message", threadId)), - ); + if (initialMessage !== undefined) { + // A new thread's claim carries its first message. A reused thread, or a replayed + // claim that carried none, takes it in a command of its own. + let accepted = claimed; + if (!claimed.storedEvents.some((stored) => stored.event.type === "run.created")) { + const messageCommandId = CommandId.make(`${input.commandId}:initial-message`); + const messageReceipt = yield* readReceipt(input, messageCommandId); + messageWasAlreadyAccepted = Option.isSome(messageReceipt); + accepted = yield* threads + .dispatch({ + ...initialMessage, + type: "message.dispatch", + commandId: messageCommandId, + threadId, + modelSelection: input.modelSelection, + dispatchMode: { type: "defer_start", workspaceStrategy }, + createdBy: input.createdBy, + creationSource: input.creationSource, + }) + .pipe( + // An agent's launch can land on a thread that a replay, or a concurrent + // launch with the same key, created with broader modes. The orchestrator + // refuses the message under the thread's lock; the user's own clients + // launch into their own drafts, whatever modes those hold. + input.createdBy === "agent" + ? Effect.provideService(DispatchModeLimit, { + runtimeMode: input.runtimeMode, + interactionMode: input.interactionMode, + }) + : (effect) => effect, + Effect.mapError(mapError(input, "dispatch-message", threadId)), + ); + } // A concurrent retry can read no receipt and still replay the dispatch. - messageWasAlreadyAccepted ||= dispatched.replayed === true; - messageSequence = dispatched.sequence; - const runCreated = dispatched.storedEvents.find( + messageWasAlreadyAccepted ||= accepted.replayed === true; + messageSequence = accepted.sequence; + const runCreated = accepted.storedEvents.find( (stored) => stored.event.type === "run.created", ); runId = runCreated?.event.type === "run.created" ? runCreated.event.payload.id : null; diff --git a/apps/server/src/orchestration-v2/ThreadManagementService.test.ts b/apps/server/src/orchestration-v2/ThreadManagementService.test.ts index c2502902ef8e3..d1ae8ab555161 100644 --- a/apps/server/src/orchestration-v2/ThreadManagementService.test.ts +++ b/apps/server/src/orchestration-v2/ThreadManagementService.test.ts @@ -68,6 +68,12 @@ it("stamps authoritative provenance on commands that create threads or messages" threadId: ThreadId.make("thread:elsewhere"), title: "Somewhere else", }, + // A client's message goes through intake; only the server folds one into the creation. + initialMessage: { + messageId: MessageId.make("message:thread-management:forged"), + text: "Forged", + attachments: [], + }, }; const stamped = ThreadManagementService.withCreationProvenance(command, { @@ -80,6 +86,7 @@ it("stamps authoritative provenance on commands that create threads or messages" }); expect(stamped).not.toHaveProperty("linkOrigin"); expect(stamped).not.toHaveProperty("delegatedFrom"); + expect(stamped).not.toHaveProperty("initialMessage"); }); const layerReceiptsTest = CommandReceiptStore.layer.pipe( diff --git a/apps/server/src/orchestration-v2/ThreadManagementService.ts b/apps/server/src/orchestration-v2/ThreadManagementService.ts index 482674b772d43..a6aad880cd88a 100644 --- a/apps/server/src/orchestration-v2/ThreadManagementService.ts +++ b/apps/server/src/orchestration-v2/ThreadManagementService.ts @@ -84,8 +84,13 @@ export function withCreationProvenance( ): OrchestrationV2Command { switch (command.type) { case "thread.create": { - // Only the server stamps a link's origin, so a client cannot set one. - const { linkOrigin: _linkOrigin, delegatedFrom: _delegatedFrom, ...rest } = command; + // Only the server stamps a link's origin or folds in a first message, so a client cannot. + const { + linkOrigin: _linkOrigin, + delegatedFrom: _delegatedFrom, + initialMessage: _initialMessage, + ...rest + } = command; return { ...rest, ...provenance }; } case "message.dispatch": diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index 9923f0d929cb8..ee950ecb43036 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -3109,6 +3109,22 @@ export const OrchestrationV2Command = Schema.Union([ metadata: Schema.optional(OrchestrationV2ProviderThreadNativeMetadata), }), ), + /** + * Only the server sets this. The thread and its first run commit together, so no client sees + * the thread idle before its run exists. The run waits for `workspaceStrategy` when given one. + */ + initialMessage: Schema.optional( + Schema.Struct({ + messageId: MessageId, + text: Schema.String, + attachments: Schema.Array(ChatAttachment), + context: Schema.optional(OrchestrationMessageContext), + titleSeed: Schema.optional(TrimmedNonEmptyString), + scheduledTaskId: Schema.optional(ScheduledTaskId), + senderThreadId: Schema.optional(ThreadId), + workspaceStrategy: Schema.optional(OrchestrationV2ThreadLaunchWorkspaceStrategy), + }), + ), }), Schema.Struct({ type: Schema.Literal("thread.archive"), From 566370f2eeedc9537ec3a841fdfb1eb048d908c1 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 07:59:35 -0600 Subject: [PATCH 125/192] fix(server): storage cleanup no longer reports idle threads on a branchless checkout as busy (#29027) Co-authored-by: Julius Marminge --- apps/server/src/storageCleanup.test.ts | 12 ++++++++---- apps/server/src/storageCleanup.ts | 6 ++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/server/src/storageCleanup.test.ts b/apps/server/src/storageCleanup.test.ts index 2a98c2ac399e6..532cd31ce6d80 100644 --- a/apps/server/src/storageCleanup.test.ts +++ b/apps/server/src/storageCleanup.test.ts @@ -1080,6 +1080,8 @@ describe("subagent worktree ownership", () => { it.effect.each([ "idle", "grandchild", + "branchless", + "branchless-running", "null-branch", "stale-branch", "isolated-owner", @@ -1111,7 +1113,7 @@ describe("subagent worktree ownership", () => { }, }); const root = shell({ - branch: "feature", + branch: ["branchless", "branchless-running"].includes(scenario) ? null : "feature", worktreePath: fixture.worktree, settledAt: scenario === "child-settled-only" ? null : at(-20 * DAY_MS), ...(scenario === "isolated-owner" || scenario === "fork-owner" @@ -1153,7 +1155,7 @@ describe("subagent worktree ownership", () => { : "subagent", ...(scenario === "side-chat" ? { sideChat: true } : {}), }, - status: scenario === "running" ? "running" : "completed", + status: ["running", "branchless-running"].includes(scenario) ? "running" : "completed", pendingBackgroundTasks: scenario === "pending" ? [{ taskId: "task", kind: "command" }] : [], latestRunCompletedAt: scenario === "recent-activity" || scenario === "recent-inactive" @@ -1202,7 +1204,7 @@ describe("subagent worktree ownership", () => { outcome: removed ? "removed" : "kept", reason: removed ? "Settled for 20 days" - : scenario === "running" || scenario === "pending" + : ["running", "pending", "branchless-running"].includes(scenario) ? "Thread is running or has pending work" : shared ? "Shared by 2 threads" @@ -1214,7 +1216,9 @@ describe("subagent worktree ownership", () => { ? "Inactive for 2 of 8 days" : ["recent-grace", "recent-settle", "child-settled-now"].includes(scenario) ? "Settled moments ago" - : "Not settled", + : scenario === "branchless" + ? "Thread has no branch" + : "Not settled", }); }).pipe(Effect.provide(cleanupTestLayer)), ); diff --git a/apps/server/src/storageCleanup.ts b/apps/server/src/storageCleanup.ts index 257fef03ae52d..0d487dc68cd34 100644 --- a/apps/server/src/storageCleanup.ts +++ b/apps/server/src/storageCleanup.ts @@ -1186,11 +1186,9 @@ const make = Effect.gen(function* () { if (members.length > 1 && worktreeOwner(members) === undefined) return keep(`shared by ${members.length} threads`); if (project === undefined) return keep("project is unavailable"); - if ( - (thread.branch === null && !scratch) || - !group.every((member) => storageCleanupThreadIdle(member, now, serverSettings)) - ) + if (!group.every((member) => storageCleanupThreadIdle(member, now, serverSettings))) return keep(waitForStop("thread is running or has pending work")); + if (thread.branch === null && !scratch) return keep("thread has no branch"); if (group.some((member) => member.branch !== thread.branch)) return keep("a subagent thread's branch differs from this worktree's branch"); if (yield* hasTerminal(worktreePaths)) return keep(waitForStop("open terminal")); From 9441a7285c5f3466e743d6fa1489fbefed9043f1 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 08:00:45 -0600 Subject: [PATCH 126/192] fix(mobile): Reconnect and Retry now on a switched-off environment switch it back on (#28628) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../connection/useConnectionController.ts | 4 - .../connection/useRetryEnvironment.test.tsx | 108 ++++++++++++++++++ .../connection/useRetryEnvironment.ts | 17 +++ .../files/WorkspaceFilePreviewError.tsx | 5 +- .../src/features/review/ReviewSheet.tsx | 5 +- .../terminal/ThreadTerminalRouteScreen.tsx | 4 +- .../state/use-remote-environment-registry.ts | 4 +- apps/mobile/vite.config.ts | 1 + 8 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 apps/mobile/src/features/connection/useRetryEnvironment.test.tsx create mode 100644 apps/mobile/src/features/connection/useRetryEnvironment.ts diff --git a/apps/mobile/src/features/connection/useConnectionController.ts b/apps/mobile/src/features/connection/useConnectionController.ts index 626892d447569..64deaaf8d1e72 100644 --- a/apps/mobile/src/features/connection/useConnectionController.ts +++ b/apps/mobile/src/features/connection/useConnectionController.ts @@ -38,7 +38,6 @@ export function useConnectionController() { const updateBearer = useAtomCommand(updateBearerConnection, { reportFailure: false }); const registerEnvironment = useAtomCommand(environmentCatalog.register, "environment register"); const removeEnvironmentMutation = useAtomCommand(environmentCatalog.remove, "environment remove"); - const retryEnvironmentMutation = useAtomCommand(environmentCatalog.retryNow, "environment retry"); const setEnvironmentEnabledMutation = useAtomCommand( environmentCatalog.setEnabled, "environment toggle", @@ -84,8 +83,6 @@ export function useConnectionController() { ); const removeEnvironment = (environmentId: EnvironmentId) => removeEnvironmentMutation(environmentId); - const retryEnvironment = (environmentId: EnvironmentId) => - retryEnvironmentMutation(environmentId); const setEnvironmentEnabled = (environmentId: EnvironmentId, enabled: boolean) => setEnvironmentEnabledMutation({ environmentId, enabled }); const updateEnvironment = ( @@ -111,7 +108,6 @@ export function useConnectionController() { connectPairingUrl, connectRelayEnvironment, removeEnvironment, - retryEnvironment, setEnvironmentEnabled, updateEnvironment, refreshRelayEnvironments, diff --git a/apps/mobile/src/features/connection/useRetryEnvironment.test.tsx b/apps/mobile/src/features/connection/useRetryEnvironment.test.tsx new file mode 100644 index 0000000000000..da94dedf2e78a --- /dev/null +++ b/apps/mobile/src/features/connection/useRetryEnvironment.test.tsx @@ -0,0 +1,108 @@ +import { RegistryContext } from "@effect/atom-react"; +import { EnvironmentId } from "@t3tools/contracts"; +import { + EnvironmentRegistry, + PrimaryConnectionTarget, + type ConnectionCatalogEntry, +} from "@t3tools/client-runtime/connection"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as SubscriptionRef from "effect/SubscriptionRef"; +import { AtomRegistry } from "effect/reactivity"; +import { act, useLayoutEffect } from "react"; +import { createRoot } from "react-dom/client"; +import { describe, expect, it } from "@effect/vitest"; +import { vi } from "vite-plus/test"; + +const fixture = vi.hoisted(() => ({ + environment: undefined as + | Pick< + EnvironmentRegistry.EnvironmentRegistry["Service"], + "entries" | "retryNow" | "setEnabled" + > + | undefined, +})); + +vi.mock("../../connection/runtime", async () => { + const { Atom } = await import("effect/reactivity"); + const Layer = await import("effect/Layer"); + const { EnvironmentRegistry } = await import("@t3tools/client-runtime/connection"); + return { + connectionAtomRuntime: Atom.runtime( + Layer.suspend(() => + Layer.succeed( + EnvironmentRegistry.EnvironmentRegistry, + fixture.environment as unknown as EnvironmentRegistry.EnvironmentRegistry["Service"], + ), + ), + ), + }; +}); + +import { useRetryEnvironment } from "./useRetryEnvironment"; + +const environmentId = EnvironmentId.make("retry-environment"); + +describe("useRetryEnvironment", () => { + it.effect.each([ + { enabled: false, retries: 0 }, + { enabled: true, retries: 1 }, + ])("Retry now with the environment enabled: $enabled", ({ enabled, retries }) => + Effect.gen(function* () { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + const target = new PrimaryConnectionTarget({ + environmentId, + label: "Retry", + httpBaseUrl: "https://retry.test", + wsBaseUrl: "wss://retry.test", + }); + const entries = yield* SubscriptionRef.make< + ReadonlyMap + >(new Map([[environmentId, { target, profile: Option.none(), enabled }]])); + let retryCount = 0; + fixture.environment = { + entries, + retryNow: () => + Effect.sync(() => { + retryCount += 1; + }), + setEnabled: (id, next) => + SubscriptionRef.update(entries, (current) => + new Map(current).set(id, { ...current.get(id)!, enabled: next }), + ), + }; + + const registry = AtomRegistry.make(); + const root = createRoot(document.createElement("div")); + using _mounted = { + [Symbol.dispose]() { + act(() => root.unmount()); + registry.dispose(); + }, + }; + let retryEnvironment!: ReturnType; + function Probe() { + const value = useRetryEnvironment(); + useLayoutEffect(() => { + retryEnvironment = value; + }); + return null; + } + yield* Effect.promise(async () => { + await act(async () => + root.render( + + + , + ), + ); + await act(async () => { + await retryEnvironment(environmentId); + }); + }); + + const entry = (yield* SubscriptionRef.get(entries)).get(environmentId); + expect({ enabled: entry?.enabled, retries: retryCount }).toEqual({ enabled: true, retries }); + }), + ); +}); diff --git a/apps/mobile/src/features/connection/useRetryEnvironment.ts b/apps/mobile/src/features/connection/useRetryEnvironment.ts new file mode 100644 index 0000000000000..b5d8edc749230 --- /dev/null +++ b/apps/mobile/src/features/connection/useRetryEnvironment.ts @@ -0,0 +1,17 @@ +import { useAtomValue } from "@effect/atom-react"; +import type { EnvironmentId } from "@t3tools/contracts"; + +import { environmentCatalog } from "../../connection/catalog"; +import { useAtomCommand } from "../../state/use-atom-command"; + +export function useRetryEnvironment() { + const catalog = useAtomValue(environmentCatalog.catalogValueAtom); + const retryNow = useAtomCommand(environmentCatalog.retryNow, "environment retry"); + const setEnabled = useAtomCommand(environmentCatalog.setEnabled, "environment toggle"); + + return (environmentId: EnvironmentId) => + // A retry does nothing while the environment is switched off. + catalog.entries.get(environmentId)?.enabled === false + ? setEnabled({ environmentId, enabled: true }) + : retryNow(environmentId); +} diff --git a/apps/mobile/src/features/files/WorkspaceFilePreviewError.tsx b/apps/mobile/src/features/files/WorkspaceFilePreviewError.tsx index 63c3e7be0c65e..eccac90d9abea 100644 --- a/apps/mobile/src/features/files/WorkspaceFilePreviewError.tsx +++ b/apps/mobile/src/features/files/WorkspaceFilePreviewError.tsx @@ -2,11 +2,10 @@ import type { EnvironmentId } from "@t3tools/contracts"; import { View } from "react-native"; import { EmptyState } from "../../components/ui/EmptyState"; -import { environmentCatalog } from "../../connection/catalog"; import type { AssetUrlFailureReason } from "../../state/asset-url-state"; -import { useAtomCommand } from "../../state/use-atom-command"; import { useEnvironmentPresentation } from "../../state/presentation"; import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; +import { useRetryEnvironment } from "../connection/useRetryEnvironment"; export function WorkspaceFilePreviewError(props: { readonly environmentId: EnvironmentId | null; @@ -15,7 +14,7 @@ export function WorkspaceFilePreviewError(props: { }) { const { environmentId, onRetry } = props; const environment = useEnvironmentPresentation(environmentId); - const retryEnvironment = useAtomCommand(environmentCatalog.retryNow, "environment retry"); + const retryEnvironment = useRetryEnvironment(); const retryConnection = () => { if (environmentId !== null) void retryEnvironment(environmentId); onRetry(); diff --git a/apps/mobile/src/features/review/ReviewSheet.tsx b/apps/mobile/src/features/review/ReviewSheet.tsx index 21bf033b761be..7cf0716277b80 100644 --- a/apps/mobile/src/features/review/ReviewSheet.tsx +++ b/apps/mobile/src/features/review/ReviewSheet.tsx @@ -26,11 +26,10 @@ import { import { PaneListItem } from "../../components/ui/PaneListItem"; import { ScreenContent } from "../../components/ui/ScreenContent"; import { SheetPane } from "../../components/ui/SheetPane"; -import { environmentCatalog } from "../../connection/catalog"; import { useEnvironmentPresentation } from "../../state/presentation"; -import { useAtomCommand } from "../../state/use-atom-command"; import { useThreadDraftForThread } from "../../state/use-thread-composer-state"; import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; +import { useRetryEnvironment } from "../connection/useRetryEnvironment"; import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; import { Inspector, @@ -351,7 +350,7 @@ export function ReviewSheet(props: ReviewSheetProps) { const { themeAppearance: selectedTheme } = useAppearancePreferences(); const { environmentId, threadId } = props.route.params; const environment = useEnvironmentPresentation(environmentId); - const retryEnvironment = useAtomCommand(environmentCatalog.retryNow, "environment retry"); + const retryEnvironment = useRetryEnvironment(); const isEnvironmentReady = environment.presentation?.connection.phase === "connected"; const { draftMessage, draftContext } = useThreadDraftForThread({ environmentId, threadId }); const reviewCache = useReviewCacheForThread({ environmentId, threadId }); diff --git a/apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx b/apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx index 90fac17f8004a..173e0cb62c624 100644 --- a/apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx +++ b/apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx @@ -40,7 +40,6 @@ import { LoadingScreen } from "../../components/LoadingScreen"; import { ScreenContent } from "../../components/ui/ScreenContent"; import { Button } from "../../components/ui/Button"; import { IconButton } from "../../components/ui/IconButton"; -import { environmentCatalog } from "../../connection/catalog"; import { useEnvironmentPresentation } from "../../state/presentation"; import { terminalEnvironment } from "../../state/terminal"; import { environmentSession, readEnvironmentScope } from "../../state/session"; @@ -64,6 +63,7 @@ import { import { useThreadSelection } from "../../state/use-thread-selection"; import { useSelectedThreadProjection } from "../../state/use-thread-detail"; import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; +import { useRetryEnvironment } from "../connection/useRetryEnvironment"; import { TerminalSurface, type TerminalSurfaceHandle } from "./NativeTerminalSurface"; import { getMobileTerminalTheme } from "./terminalTheme"; import { useTerminalExitNavigation } from "./useTerminalExitNavigation"; @@ -381,7 +381,7 @@ function ThreadTerminal(props: { const navigation = useNavigation(); const writeTerminal = useAtomCommand(terminalEnvironment.write, "terminal write"); const clearTerminal = useAtomCommand(terminalEnvironment.clear, "terminal clear"); - const retryEnvironment = useAtomCommand(environmentCatalog.retryNow, "environment retry"); + const retryEnvironment = useRetryEnvironment(); const selectedThreadDetail = useSelectedThreadProjection(); const selectedThreadDetailWorktreePath = selectedThreadDetail?.projection.thread.worktreePath ?? null; diff --git a/apps/mobile/src/state/use-remote-environment-registry.ts b/apps/mobile/src/state/use-remote-environment-registry.ts index a1488220d6952..0b4163aafbd95 100644 --- a/apps/mobile/src/state/use-remote-environment-registry.ts +++ b/apps/mobile/src/state/use-remote-environment-registry.ts @@ -9,6 +9,7 @@ import { runAtomCommand } from "@t3tools/client-runtime/state/runtime"; import { hasCloudPublicConfig } from "../features/cloud/publicConfig"; import { useConnectionController } from "../features/connection/useConnectionController"; +import { useRetryEnvironment } from "../features/connection/useRetryEnvironment"; import { environmentPresentations } from "./presentation"; import { useWorkspaceConnectionState, useWorkspaceEnvironments } from "./workspace"; import type { SavedRemoteConnection } from "../lib/connection"; @@ -166,8 +167,7 @@ export function useRemoteConnections() { const onConnectPress = (pairingUrl?: string, expectedEnvironmentId?: EnvironmentId) => pairEnvironment(pairingUrl ?? connectionPairingUrl, expectedEnvironmentId); - const onReconnectEnvironment = (environmentId: EnvironmentId) => - controller.retryEnvironment(environmentId); + const onReconnectEnvironment = useRetryEnvironment(); const onSetEnvironmentEnabled = (environmentId: EnvironmentId, enabled: boolean) => controller.setEnvironmentEnabled(environmentId, enabled); const onUpdateEnvironment = ( diff --git a/apps/mobile/vite.config.ts b/apps/mobile/vite.config.ts index 902b116b7fd08..05a2254fc6b27 100644 --- a/apps/mobile/vite.config.ts +++ b/apps/mobile/vite.config.ts @@ -46,6 +46,7 @@ const reactTests = [ "src/features/home/home-list-options.test.tsx", "src/features/home/home-route-visibility.test.tsx", "src/features/layout/workspace/useSearchScrollRestoration.test.tsx", + "src/features/connection/useRetryEnvironment.test.tsx", "src/features/connection/ConnectionsNewRouteScreen.test.tsx", "src/features/connection/LinkedEnvironmentsSection.test.tsx", "src/features/devices/device-stream.browser.test.ts", From 77a39e9a37ceb82559a218ab2520c4deba0ea786 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 15:12:39 +0100 Subject: [PATCH 127/192] fix(server): cancelling worktree setup removes its unclaimed worktree and branch (#36760) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriver.ts | 5 +- apps/server/src/vcs/GitVcsDriverCore.test.ts | 196 +++++++++++++++++- apps/server/src/vcs/GitVcsDriverCore.ts | 112 ++++++++-- apps/server/src/vcs/worktreeCheckoutCache.ts | 17 +- ...worktree-setup-removes-its-worktree.e2e.ts | 93 +++++++++ 5 files changed, 399 insertions(+), 24 deletions(-) create mode 100644 scripts/scenarios/cancelled-worktree-setup-removes-its-worktree.e2e.ts diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts index e02ae5bdba20d..9e0c2b50f2743 100644 --- a/apps/server/src/vcs/GitVcsDriver.ts +++ b/apps/server/src/vcs/GitVcsDriver.ts @@ -124,7 +124,10 @@ export interface ExecuteGitProgress { } export interface CreateWorktreeProgress { - /** Git refuses existing paths; claimed paths belong to this call and can be removed on cancel. */ + /** + * Runs uninterruptibly once the worktree exists; from then on the caller owns its cleanup. + * For new-branch calls on Git supporting add --reason, cancellation before the claim removes what it made. + */ readonly onWorktreeClaimed?: (path: string) => Effect.Effect; readonly onCheckoutProgress?: (input: { percent: number; diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index be9faead2483f..100ce5fe48671 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1,6 +1,7 @@ // @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not. import * as NodeChildProcess from "node:child_process"; import * as NodeFS from "node:fs"; +import * as NodeNet from "node:net"; import * as NodePath from "node:path"; import * as NodeURL from "node:url"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; @@ -556,7 +557,10 @@ it.effect.each([ assert.equal(created.failure.exitCode, hookExitCode); } const metadataQueries = spawnedArgs.filter((args) => args.includes("--git-common-dir")); - assert.deepStrictEqual(metadataQueries, [["rev-parse", "--git-common-dir"]]); + assert.deepStrictEqual( + metadataQueries, + Array.from({ length: 2 }, () => ["rev-parse", "--git-common-dir"]), + ); assert.isFalse(spawnedArgs.some((args) => args.includes("symbolic-ref"))); invalidationFailure = "none"; @@ -619,7 +623,7 @@ it.effect.each(["branch", "detached", "nested", "aliased-root", "aliased-nested" newRefName: "feature/cold", }); - assert.equal(spawnedArgs.filter((args) => args.includes("--git-common-dir")).length, 1); + assert.equal(spawnedArgs.filter((args) => args.includes("--git-common-dir")).length, 2); assert.isFalse(spawnedArgs.some((args) => args.includes("--show-toplevel"))); assert.isFalse(spawnedArgs.some((args) => args.includes("symbolic-ref"))); spawnedArgs.length = 0; @@ -681,7 +685,10 @@ it.effect.each(["bare", "unborn"] as const)( assert.equal(result.failure.operation, "GitVcsDriver.createWorktree.createBranch"); assert.equal(result.failure.detail, "git branch failed"); } - assert.equal(spawnedArgs.filter((args) => args.includes("--git-common-dir")).length, 1); + assert.equal( + spawnedArgs.filter((args) => args.includes("--git-common-dir")).length, + state === "bare" ? 2 : 1, + ); assert.isFalse(spawnedArgs.some((args) => args[0] === "symbolic-ref")); spawnedArgs.length = 0; const refs = yield* driver.listRefs({ cwd }); @@ -1832,7 +1839,8 @@ it.effect("lets a worktree checkout that is still working run past five minutes" Effect.gen(function* () { if (!ChildProcess.isStandardCommand(command)) return yield* Effect.die("expected Git command"); - if (!command.args.includes("worktree")) return makeSuccessfulHandle(""); + if (!command.args.includes("worktree") || command.args.includes("-h")) + return makeSuccessfulHandle(""); yield* Deferred.succeed(checkoutStarted, undefined); return ChildProcessSpawner.makeHandle({ ...makeSuccessfulHandle(""), @@ -2282,6 +2290,7 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { const spawner = ChildProcessSpawner.make((command) => { if ( ChildProcess.isStandardCommand(command) && + !command.args.includes("-h") && (command.args.includes("worktree") || command.args[0] === "branch" || command.args[0] === "checkout") @@ -5405,6 +5414,172 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }); describe("worktree operations", () => { + it.effect.each(["current", "legacy"] as const)( + "preserves another creator's worktree on its new branch at the same path (%s)", + (version) => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const { initialBranch } = yield* initRepoWithCommit(cwd); + const path = NodePath.join(yield* makeTmpDir("git-worktrees-"), "contested"); + const delegate = yield* ChildProcessSpawner.ChildProcessSpawner; + const reachedAdd = yield* Deferred.make(); + let paused = false; + const spawner = ChildProcessSpawner.make((command) => + Effect.gen(function* () { + if ( + ChildProcess.isStandardCommand(command) && + command.args.includes("worktree") && + command.args.includes("add") + ) { + if (command.args.includes("-h")) { + return version === "legacy" + ? makeFailedHandle("usage: git worktree add [--lock]") + : yield* delegate.spawn(command); + } + if (!paused) { + paused = true; + yield* Deferred.succeed(reachedAdd, undefined); + return yield* Effect.never; + } + } + return yield* delegate.spawn(command); + }), + ); + const driver = yield* makeGitVcsDriverCore().pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + Effect.provide(layerServerConfig), + ); + const creator = yield* driver + .createWorktree({ + cwd, + path, + refName: initialBranch, + newRefName: "contested", + }) + .pipe(Effect.forkChild); + yield* Deferred.await(reachedAdd); + yield* git(cwd, ["worktree", "add", "--", path, "contested"]); + yield* writeTextFile(path, "owned-by-B.txt", "keep this work\n"); + yield* Fiber.interrupt(creator); + assert.include(yield* git(cwd, ["worktree", "list", "--porcelain"]), path); + assert.equal(yield* git(path, ["branch", "--show-current"]), "contested"); + assert.equal( + yield* (yield* FileSystem.FileSystem).readFileString( + NodePath.join(path, "owned-by-B.txt"), + ), + "keep this work\n", + ); + const created = yield* driver.createWorktree({ + cwd, + path: `${path}-retry`, + refName: initialBranch, + newRefName: "retry", + }); + assert.equal(yield* git(created.worktree.path, ["branch", "--show-current"]), "retry"); + assert.notInclude(yield* git(cwd, ["worktree", "list", "--porcelain"]), "locked"); + }), + ); + + it.effect.each(["main", "linked"] as const)( + "removes its worktree and branch when cancelled before the claim (%s)", + (checkout) => + Effect.gen(function* () { + const repo = yield* makeTmpDir(); + const { initialBranch } = yield* initRepoWithCommit(repo); + const cwd = + checkout === "main" + ? repo + : NodePath.join(yield* makeTmpDir("git-linked-"), "checkout"); + if (checkout === "linked") { + yield* git(repo, ["config", "worktree.useRelativePaths", "true"]); + yield* git(repo, ["worktree", "add", "--detach", cwd]); + } + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.GitVcsDriver; + const worktreePath = path.join(yield* makeTmpDir("git-worktrees-"), "cancelled ü"); + const hookPath = path.join(repo, ".git/hooks/post-checkout"); + const connected = Promise.withResolvers(); + const sockets = new Set(); + const server = yield* Effect.acquireRelease( + Effect.sync(() => + NodeNet.createServer((socket) => { + sockets.add(socket); + connected.resolve(); + }), + ), + (server) => + Effect.sync(() => { + for (const socket of sockets) socket.destroy(); + server.close(); + }), + ); + yield* Effect.promise( + () => + new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }), + ); + const address = server.address(); + assert.isTrue(address !== null && typeof address !== "string"); + if (address === null || typeof address === "string") return; + const gate = path.join(cwd, "hook-gate.cjs"); + yield* fs.writeFileString( + gate, + `const s = require('node:net').connect(${address.port}, '127.0.0.1'); s.on('connect', () => process.stderr.write('Updating files: 50% (1/1000)\\n')); s.resume();`, + ); + yield* writeTextFile( + repo, + ".git/hooks/post-checkout", + `#!/bin/sh\nexec "${process.execPath.replaceAll("\\", "/")}" "${gate.replaceAll("\\", "/")}"\n`, + ); + yield* fs.chmod(hookPath, 0o755); + const hookStarted = yield* Deferred.make(); + let claims = 0; + const input = { + cwd, + path: worktreePath, + refName: initialBranch, + newRefName: "t3/cancel", + }; + const worktrees = git(cwd, ["worktree", "list", "--porcelain"]); + + const cancelled = yield* driver + .createWorktree(input, { + progress: { + onCheckoutProgress: ({ total }) => + total === 1000 ? Deferred.succeed(hookStarted, undefined) : Effect.void, + onWorktreeClaimed: () => Effect.sync(() => void claims++), + }, + }) + .pipe(Effect.forkChild); + yield* Deferred.await(hookStarted); + yield* Effect.promise(() => connected.promise); + assert.include(yield* worktrees, worktreePath); + yield* Fiber.interrupt(cancelled); + + assert.equal(claims, 0); + assert.notInclude(yield* worktrees, worktreePath); + assert.equal(yield* git(cwd, ["branch", "--list", "t3/cancel"]), ""); + assert.isFalse(yield* fs.exists(worktreePath)); + + // A retry gets the same path and branch, and a claimed worktree outlives a later cancel. + yield* fs.remove(hookPath); + const claimed = yield* Deferred.make(); + const retry = yield* driver + .createWorktree(input, { + progress: { onWorktreeClaimed: () => Deferred.succeed(claimed, undefined) }, + }) + .pipe(Effect.forkChild); + yield* Deferred.await(claimed); + yield* Fiber.interrupt(retry); + assert.include(yield* worktrees, worktreePath); + assert.notInclude(yield* worktrees, "locked"); + assert.equal(yield* git(worktreePath, ["branch", "--show-current"]), "t3/cancel"); + }), + ); + it.effect("uses parallel checkout without skipping filters or hooks", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); @@ -5461,6 +5636,19 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { `${expected}\n`, ); } + yield* writeTextFile( + cwd, + ".git/hooks/post-checkout", + '#!/bin/sh\ngit worktree unlock "$PWD" 2>/dev/null || :\ngit worktree lock --reason user-kept "$PWD"\n', + ); + const lockedPath = path.join(yield* makeTmpDir("git-worktrees-"), "hook-locked"); + yield* driver.createWorktree({ + cwd, + path: lockedPath, + refName: initialBranch, + newRefName: "feature/hook-locked", + }); + assert.include(yield* git(cwd, ["worktree", "list", "--porcelain"]), "locked user-kept"); }), ); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 220b3ce0fa08a..a2b985efed2a5 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1,7 +1,9 @@ import * as Cache from "effect/Cache"; +import * as Cause from "effect/Cause"; import * as Clock from "effect/Clock"; import * as Data from "effect/Data"; import * as Digest from "effect/crypto/Digest"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as DateTime from "effect/DateTime"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; @@ -981,6 +983,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const { worktreesDir, worktreeCheckoutCacheDir } = yield* ServerConfig.ServerConfig; const isServerInternalPath = yield* makeIsServerInternalPath; const digest = yield* Digest.Digest; + const random = yield* SecureRandom.SecureRandom; const hostPlatform = yield* HostProcess.Platform; const serviceScope = yield* Effect.scope; const statusFetches = new Map>(); @@ -4727,6 +4730,40 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* yield* refresh; }); + // Git writes the lock reason during registration, before checkout or user hooks can run. + const releaseWorktreeRegistration = Effect.fn("releaseWorktreeRegistration")(function* ( + cwd: string, + reason: string, + remove: boolean, + ) { + const commonDir = yield* readGitCommonDir(cwd); + if (commonDir === null) return; + const registrations = path.join(commonDir, "worktrees"); + const entries = yield* fileSystem.readDirectory(registrations).pipe( + Effect.catchIf( + (error) => error.reason._tag === "NotFound", + () => Effect.succeed([]), + ), + ); + for (const entry of entries) { + const adminDir = path.join(registrations, entry); + const locked = yield* fileSystem + .readFileString(path.join(adminDir, "locked")) + .pipe(Effect.orElseSucceed(() => "")); + if (locked.replace(/\r?\n$/, "") !== reason) continue; + // gitdir can be relative to the registration with worktree.useRelativePaths enabled. + const gitdir = yield* fileSystem.readFileString(path.join(adminDir, "gitdir")); + const registered = path.dirname(path.resolve(adminDir, gitdir.replace(/\r?\n$/, ""))); + yield* runGit("GitVcsDriver.createWorktree.unlock", cwd, [ + "worktree", + "unlock", + "--", + registered, + ]); + if (remove) yield* removeWorktree({ cwd, path: registered, force: true }); + } + }); + const createWorktree: GitVcsDriver.GitVcsDriver["Service"]["createWorktree"] = Effect.fn( "createWorktree", )(function* (input, options) { @@ -4771,6 +4808,20 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* } } const newRefName = input.newRefName; + // Older Git still creates worktrees normally, but cannot prove ownership for cancellation. + const addHelp = newRefName + ? yield* executeGit( + "GitVcsDriver.createWorktree.capabilities", + input.cwd, + ["worktree", "add", "-h"], + { allowNonZeroExit: true }, + ) + : null; + const lockReason = + addHelp && `${addHelp.stdout}\n${addHelp.stderr}`.includes("--reason") + ? `t3-create-${yield* random.uuidV4()}` + : undefined; + if (newRefName) { // Kept apart from the checkout so the shared-config lock only covers the tracking write. yield* createBranchAt( @@ -4804,6 +4855,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* `checkout.workers=${checkoutWorkers}`, "worktree", "add", + ...(lockReason ? ["--lock", "--reason", lockReason] : []), "--", worktreePath, checkoutRef, @@ -4831,30 +4883,66 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* : {}), }, ); + // Set inside the claim's uninterruptible region: a pending cancel still surfaces as it ends. + let claimed = false; + const onWorktreeClaimed = progress?.onWorktreeClaimed; yield* ( fastCheckout ? checkoutCache - .create({ cwd: input.cwd, worktreePath, refName: checkoutRef, checkoutWorkers }) + .create({ + cwd: input.cwd, + worktreePath, + refName: checkoutRef, + checkoutWorkers, + lockReason, + }) .pipe(Effect.flatMap((cloned) => (cloned ? Effect.void : stockCheckout))) : stockCheckout ).pipe( - Effect.onError(() => - newRefName - ? deleteLocalBranch({ cwd: input.cwd, refName: newRefName, force: true }).pipe( + Effect.andThen( + fastCheckout ? checkoutCache.schedule(worktreePath, cacheGeneration) : Effect.void, + ), + Effect.andThen( + onWorktreeClaimed + ? Effect.uninterruptible( + onWorktreeClaimed(worktreePath).pipe( + Effect.andThen( + Effect.sync(() => { + claimed = true; + }), + ), + ), + ) + : Effect.void, + ), + Effect.onError((cause) => + newRefName && !claimed + ? // A failed hook keeps its worktree, as Git does; only a cancel abandons it. + (Cause.hasInterrupts(cause) + ? lockReason + ? releaseWorktreeRegistration(input.cwd, lockReason, true) + : Effect.void + : Effect.void + ).pipe( + Effect.andThen( + deleteLocalBranch({ cwd: input.cwd, refName: newRefName, force: true }), + ), Effect.catch((cause) => - Effect.logWarning("could not remove the branch of a failed worktree", { cause }), + Effect.logWarning("could not clean up a failed worktree or its branch", { cause }), + ), + ) + : Effect.void, + ), + Effect.onExit(() => + lockReason + ? releaseWorktreeRegistration(input.cwd, lockReason, false).pipe( + Effect.catch((cause) => + Effect.logWarning("could not unlock a created worktree", { cause }), ), ) : Effect.void, ), ); - if (fastCheckout) { - yield* checkoutCache.schedule(worktreePath, cacheGeneration); - } - - if (progress?.onWorktreeClaimed) { - yield* progress.onWorktreeClaimed(worktreePath); - } // worktree add leaves submodules empty, potentially omitting project skills, tools or source. const hasSubmodules = yield* fileSystem diff --git a/apps/server/src/vcs/worktreeCheckoutCache.ts b/apps/server/src/vcs/worktreeCheckoutCache.ts index a72d6d859fc54..8a51189fe6d69 100644 --- a/apps/server/src/vcs/worktreeCheckoutCache.ts +++ b/apps/server/src/vcs/worktreeCheckoutCache.ts @@ -511,6 +511,7 @@ export const makeWorktreeCheckoutCache = Effect.fn("WorktreeCheckoutCache.make") readonly worktreePath: string; readonly refName: string; readonly checkoutWorkers: string; + readonly lockReason?: string | undefined; }) { if (!(yield* gitSupported)) return false; const worktreePath = path.resolve(input.cwd, input.worktreePath); @@ -613,13 +614,14 @@ export const makeWorktreeCheckoutCache = Effect.fn("WorktreeCheckoutCache.make") if (moved === null || (yield* fileSystem.exists(target.indexHook))) return null; return target; }); - const rollback = git("rollback", input.cwd, [ - "worktree", - "remove", - "--force", - "--", - worktreePath, - ]).pipe( + const rollback = ( + input.lockReason + ? git("unlockRollback", input.cwd, ["worktree", "unlock", "--", worktreePath]) + : Effect.void + ).pipe( + Effect.andThen( + git("rollback", input.cwd, ["worktree", "remove", "--force", "--", worktreePath]), + ), Effect.flatMap((removed) => removed === null ? Effect.logWarning("could not remove a partially created worktree", { worktreePath }) @@ -633,6 +635,7 @@ export const makeWorktreeCheckoutCache = Effect.fn("WorktreeCheckoutCache.make") "worktree", "add", "--no-checkout", + ...(input.lockReason ? ["--lock", "--reason", input.lockReason] : []), "--", worktreePath, input.refName, diff --git a/scripts/scenarios/cancelled-worktree-setup-removes-its-worktree.e2e.ts b/scripts/scenarios/cancelled-worktree-setup-removes-its-worktree.e2e.ts new file mode 100644 index 0000000000000..13277b9bbbb25 --- /dev/null +++ b/scripts/scenarios/cancelled-worktree-setup-removes-its-worktree.e2e.ts @@ -0,0 +1,93 @@ +import * as NodeNet from "node:net"; +import { expect } from "e2e"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Schema from "effect/Schema"; + +import { APP_DIR, T3_HOME } from "../e2e.config.ts"; +import { run } from "./shell.ts"; +import { runPromise, test, WEB } from "./t3.ts"; + +const decodeLaunched = Schema.decodeUnknownEffect( + Schema.fromJsonString(Schema.Struct({ threadId: Schema.String })), +); + +test( + "Cancelling worktree setup during its post-checkout hook removes the new worktree", + WEB, + async ({ t3, app, screen }) => { + const project = await t3.project("demo"); + const git = (...args: ReadonlyArray) => + runPromise(run("git", args, { cwd: project })).then(({ stdout }) => stdout); + const connected = Promise.withResolvers(); + const sockets = new Set(); + const server = NodeNet.createServer((socket) => { + sockets.add(socket); + connected.resolve(); + }); + using _gateServer = { + [Symbol.dispose]() { + for (const socket of sockets) socket.destroy(); + server.close(); + }, + }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + if (address === null || typeof address === "string") throw new Error("Missing hook gate port"); + await runPromise( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const hook = `${project}/.git/hooks/post-checkout`; + const gate = `${project}/hook-gate.cjs`; + yield* fs.writeFileString( + gate, + `const s = require('node:net').connect(${address.port}, '127.0.0.1'); s.on('connect', () => process.stderr.write('Updating files: 50% (1/1000)\\n')); s.resume();`, + ); + yield* fs.writeFileString( + hook, + `#!/bin/sh\nexec "${process.execPath.replaceAll("\\", "/")}" "${gate.replaceAll("\\", "/")}"\n`, + ); + yield* fs.chmod(hook, 0o755); + }), + ); + const started = await runPromise( + run( + process.execPath, + [ + "apps/server/src/bin.ts", + "thread", + "start", + project, + "hello", + "--worktree", + "main", + "--provider", + "devReplay", + "--model", + "showcase", + "--json", + "--base-dir", + T3_HOME, + ], + { cwd: APP_DIR, env: { PATH: process.env.PATH, HOME: process.env.HOME } }, + ).pipe(Effect.flatMap(({ stdout }) => decodeLaunched(stdout))), + ); + + await connected.promise; + await t3.open(started); + await expect(screen.getByText("50%")).toBeVisible(); + await screen.getByRole("button", "Cancel").tap(); + await app.screenshot("after-cancel"); + + // The project's own checkout is the only one left once the cancel has cleaned up. + await expect + .poll( + async () => (await git("worktree", "list", "--porcelain")).match(/^worktree /gm)?.length, + ) + .toBe(1); + await expect.poll(() => git("branch", "--list", "t3/*")).toBe(""); + }, +); From 5fe2f7e4567d9e530ded0dabc9d706db27d77b84 Mon Sep 17 00:00:00 2001 From: Mokhles elheni <144564734+M0xoo@users.noreply.github.com> Date: Sat, 14 Nov 2026 15:13:54 +0100 Subject: [PATCH 128/192] fix(web): diagnostics no longer shows near-second spans as "1000 ms" (#29377) Co-authored-by: Julius Marminge --- .../settings/DiagnosticsSettings.logic.test.ts | 9 +++++++++ .../settings/DiagnosticsSettings.logic.ts | 5 +++++ .../settings/DiagnosticsSettings.tsx | 18 +++++++----------- 3 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 apps/web/src/components/settings/DiagnosticsSettings.logic.test.ts create mode 100644 apps/web/src/components/settings/DiagnosticsSettings.logic.ts diff --git a/apps/web/src/components/settings/DiagnosticsSettings.logic.test.ts b/apps/web/src/components/settings/DiagnosticsSettings.logic.test.ts new file mode 100644 index 0000000000000..bfd087a7d7a4b --- /dev/null +++ b/apps/web/src/components/settings/DiagnosticsSettings.logic.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { formatDiagnosticsDuration } from "./DiagnosticsSettings.logic"; + +describe("formatDiagnosticsDuration", () => { + it("prints durations that round up to a second in seconds", () => { + expect([999.4, 999.6, 1_000].map(formatDiagnosticsDuration)).toEqual(["999 ms", "1 s", "1 s"]); + }); +}); diff --git a/apps/web/src/components/settings/DiagnosticsSettings.logic.ts b/apps/web/src/components/settings/DiagnosticsSettings.logic.ts new file mode 100644 index 0000000000000..14df20742b5b5 --- /dev/null +++ b/apps/web/src/components/settings/DiagnosticsSettings.logic.ts @@ -0,0 +1,5 @@ +export function formatDiagnosticsDuration(value: number): string { + const milliseconds = Math.round(value); + if (milliseconds < 1_000) return `${milliseconds} ms`; + return `${(value / 1_000).toFixed(value >= 10_000 ? 1 : 2).replace(/\.0+$/, "")} s`; +} diff --git a/apps/web/src/components/settings/DiagnosticsSettings.tsx b/apps/web/src/components/settings/DiagnosticsSettings.tsx index 900f7fc9ec9ac..df899e2949867 100644 --- a/apps/web/src/components/settings/DiagnosticsSettings.tsx +++ b/apps/web/src/components/settings/DiagnosticsSettings.tsx @@ -39,6 +39,7 @@ import { ScrollArea } from "../ui/scroll-area"; import { Toggle, ToggleGroup } from "../ui/toggle-group"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { toastManager } from "../ui/toast"; +import { formatDiagnosticsDuration } from "./DiagnosticsSettings.logic"; import { ExpandableText } from "./ExpandableText"; import { ResourceTelemetryDiagnostics } from "./ResourceTelemetryDiagnostics"; import { SettingsPageContainer, SettingsSection } from "./settingsLayout"; @@ -52,11 +53,6 @@ function formatCount(value: number): string { return NUMBER_FORMAT.format(value); } -function formatDuration(value: number): string { - if (value < 1_000) return `${Math.round(value)} ms`; - return `${(value / 1_000).toFixed(value >= 10_000 ? 1 : 2).replace(/\.0+$/, "")} s`; -} - function RelativeTimeNoWrap(props: { value: DateTime.Utc }) { const relative = useRelativeTimeState(DateTime.formatIso(props.value)); if (relative.status !== "relative") return null; @@ -1020,7 +1016,7 @@ function DiagnosticsSettingsBody() { resourceData ? resourceData.sampleIntervalMs === 0 ? "On demand" - : formatDuration(resourceData.sampleIntervalMs) + : formatDiagnosticsDuration(resourceData.sampleIntervalMs) : "..." } /> @@ -1101,7 +1097,7 @@ function DiagnosticsSettingsBody() { value={data ? formatCount(data.slowSpanCount) : "..."} tooltip={ data - ? `Spans with a duration of ${formatDuration(data.slowSpanThresholdMs)} or longer.` + ? `Spans with a duration of ${formatDiagnosticsDuration(data.slowSpanThresholdMs)} or longer.` : "Spans at or above the configured slow-span threshold." } tone={data && data.slowSpanCount > 0 ? "warning" : "default"} @@ -1157,7 +1153,7 @@ function DiagnosticsSettingsBody() { - {formatDuration(failure.durationMs)} + {formatDiagnosticsDuration(failure.durationMs)} @@ -1213,7 +1209,7 @@ function DiagnosticsSettingsBody() { {span.name} - {formatDuration(span.durationMs)} + {formatDiagnosticsDuration(span.durationMs)} @@ -1313,10 +1309,10 @@ function DiagnosticsSettingsBody() { {formatCount(span.failureCount)} - {formatDuration(span.averageDurationMs)} + {formatDiagnosticsDuration(span.averageDurationMs)} - {formatDuration(span.maxDurationMs)} + {formatDiagnosticsDuration(span.maxDurationMs)} ))} From 313f601fb2366fad7dd26b7b55844c7c5d750cf0 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 15:19:52 +0100 Subject: [PATCH 129/192] fix(server): Browser tab titles follow page title changes (#36764) Co-authored-by: Julius Marminge --- apps/server/src/preview/ServerBrowser.test.ts | 216 ++++++++++++++---- apps/server/src/preview/ServerBrowser.ts | 116 ++++++++-- .../browser-tab-follows-page-title.e2e.ts | 47 ++++ 3 files changed, 318 insertions(+), 61 deletions(-) create mode 100644 scripts/scenarios/browser-tab-follows-page-title.e2e.ts diff --git a/apps/server/src/preview/ServerBrowser.test.ts b/apps/server/src/preview/ServerBrowser.test.ts index 05696820799b3..2bd586e30bdde 100644 --- a/apps/server/src/preview/ServerBrowser.test.ts +++ b/apps/server/src/preview/ServerBrowser.test.ts @@ -32,7 +32,7 @@ import * as PubSub from "effect/PubSub"; import * as Queue from "effect/Queue"; import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; -import type { BrowserContext, Page } from "playwright-core"; +import { chromium, type BrowserContext, type Page } from "playwright-core"; import { beforeEach, expect, vi } from "vite-plus/test"; import * as ServerConfig from "../config.ts"; @@ -52,6 +52,7 @@ vi.mock("./ServerBrowserContexts.ts", () => ({ this.onClose = options.onContextClose; } async contextFor(profileId: string, isolationKey?: string) { + if (realContextOverride) return realContextOverride; contextRequests.push({ profileId, isolated: isolationKey !== undefined }); if (contextFailure) throw contextFailure; await contextGate?.promise; @@ -122,15 +123,18 @@ function makeSession( if (method === "Runtime.callFunctionOn") return { result: { value: PNG_HEADER } }; if (method === "Page.getFrameTree") return { frameTree: { frame: { id: "main" } } }; if (method === "Page.createIsolatedWorld") { + const worldContexts: Record = { + "t3-preview-viewport": 9, + "t3-preview-title": 11, + "t3-preview": 1, + }; return { - executionContextId: - (input as { worldName?: string }).worldName === "t3-preview-viewport" - ? 9 - : (input as { worldName?: string }).worldName === "t3-preview" - ? 1 - : 7, + executionContextId: worldContexts[(input as { worldName: string }).worldName] ?? 7, }; } + // A title watch settles only when the document's title changes. + if (method === "Runtime.evaluate" && (input as { contextId?: number }).contextId === 11) + return new Promise(() => {}); if (method === "Runtime.evaluate" && (input as { contextId?: number }).contextId === 1) { return { result: { value: [] } }; } @@ -383,6 +387,7 @@ function makeEncoderPage() { const contexts: ReturnType[] = []; let encoderPageOverride: Page | undefined; +let realContextOverride: BrowserContext | undefined; const encoderStreams: Array<{ frameRate: number | undefined; bitsPerSecond: number }> = []; let decodeGate: PromiseWithResolvers | null = null; const contextRequests: Array<{ profileId: string; isolated: boolean }> = []; @@ -569,6 +574,7 @@ const openDesktopViewer = Effect.gen(function* () { beforeEach(() => { contexts.length = 0; encoderPageOverride = undefined; + realContextOverride = undefined; encoderStreams.length = 0; decodeGate = null; contextRequests.length = 0; @@ -2018,47 +2024,50 @@ it.effect( ).pipe(Effect.provide(layer)), ); -it.effect("a navigation that fails while a load reads its title keeps its failure", () => - Effect.scoped( - Effect.gen(function* () { - const { tabId } = yield* ready; - const manager = yield* Manager.PreviewManager; - const events = yield* manager.subscribeEvents; - const page = contexts[0]!.page; - const heldTitle = Promise.withResolvers(); - page.title.mockImplementationOnce(() => heldTitle.promise); - yield* Effect.promise(() => page.goto("https://example.com/first")); - const failing = { - url: () => "https://example.com/missing", - method: () => "GET", - isNavigationRequest: () => true, - frame: () => page, - failure: () => ({ errorText: "net::ERR_NAME_NOT_RESOLVED" }), - }; - page.emit("request", failing); - page.emit("requestfailed", failing); - heldTitle.resolve("Held"); - // A load at the same await depth reports after the held one, so its Success marks the end. - page.emit("load"); - - const statuses: Array = []; - while (!statuses.at(-1)?.startsWith("Success")) { - const event = yield* PubSub.take(events); - if (event.type === "failed" && event.tabId === tabId) { - statuses.push(`LoadFailed ${event.url}`); - } else if (event.type === "navigated" && event.tabId === tabId) { - const status = event.snapshot.navStatus; - if (status._tag === "Success") statuses.push(`Success ${status.title}`); - else if (status._tag === "Loading") statuses.push(`Loading ${status.url}`); +it.effect.each(["before", "during"] as const)( + "a navigation that fails while a load reads its title keeps its failure (request %s read)", + (requestTiming) => + Effect.scoped( + Effect.gen(function* () { + const { tabId } = yield* ready; + const manager = yield* Manager.PreviewManager; + const events = yield* manager.subscribeEvents; + const page = contexts[0]!.page; + const heldTitle = Promise.withResolvers(); + page.title.mockImplementationOnce(() => heldTitle.promise); + const failing = { + url: () => "https://example.com/missing", + method: () => "GET", + isNavigationRequest: () => true, + frame: () => page, + failure: () => ({ errorText: "net::ERR_NAME_NOT_RESOLVED" }), + }; + if (requestTiming === "before") page.emit("request", failing); + yield* Effect.promise(() => page.goto("https://example.com/first")); + if (requestTiming === "during") page.emit("request", failing); + page.emit("requestfailed", failing); + heldTitle.resolve("Held"); + // A load at the same await depth reports after the held one, so its Success marks the end. + page.emit("load"); + + const statuses: Array = []; + while (!statuses.at(-1)?.startsWith("Success")) { + const event = yield* PubSub.take(events); + if (event.type === "failed" && event.tabId === tabId) { + statuses.push(`LoadFailed ${event.url}`); + } else if (event.type === "navigated" && event.tabId === tabId) { + const status = event.snapshot.navStatus; + if (status._tag === "Success") statuses.push(`Success ${status.title}`); + else if (status._tag === "Loading") statuses.push(`Loading ${status.url}`); + } } - } - expect(statuses).toEqual([ - "Loading https://example.com/missing", - "LoadFailed https://example.com/missing", - "Success test page", - ]); - }), - ).pipe(Effect.provide(layer)), + expect(statuses).toEqual([ + "Loading https://example.com/missing", + "LoadFailed https://example.com/missing", + "Success test page", + ]); + }), + ).pipe(Effect.provide(layer)), ); it.effect.each([ @@ -3965,3 +3974,116 @@ it.effect("a popup opens in its opener's appearance", () => }), ).pipe(Effect.provide(layer)), ); + +it.live("a page's title change without navigation reaches its session", () => + Effect.gen(function* () { + const native = yield* Effect.acquireRelease( + Effect.promise(() => chromium.launch({ headless: true })), + (browser) => Effect.promise(() => browser.close()), + ); + const context = yield* Effect.promise(() => native.newContext()); + yield* Effect.promise(() => + context.route("**/*", (route) => + route.fulfill({ contentType: "text/html", body: "Original" }), + ), + ); + let watched = Promise.withResolvers(); + const newSession = context.newCDPSession.bind(context); + vi.spyOn(context, "newCDPSession").mockImplementation(async (page) => { + const cdp = await newSession(page); + const send = cdp.send.bind(cdp); + vi.spyOn(cdp, "send").mockImplementation((...args) => { + const result = send(...args); + if ( + args[0] === "Runtime.evaluate" && + args[1] && + "awaitPromise" in args[1] && + args[1].awaitPromise + ) { + void send("Runtime.evaluate", { expression: "0" }).then(() => { + watched.resolve(); + }); + } + return result; + }); + return cdp; + }); + realContextOverride = context; + yield* Effect.gen(function* () { + yield* ServerBrowser.ServerBrowser; + const manager = yield* Manager.PreviewManager; + const events = yield* manager.subscribeEvents; + yield* manager.open({ + threadId: testThread.threadId, + runtime: "server", + url: "https://fixture.test/", + }); + const successes: Array<{ title: string; url: string; back: boolean; forward: boolean }> = []; + const navigatedTo = Effect.fn(function* (title: string) { + for (;;) { + const event = yield* PubSub.take(events); + if (event.type !== "navigated") continue; + const { navStatus } = event.snapshot; + if (navStatus._tag === "Success") { + successes.push({ + title: navStatus.title, + url: navStatus.url, + back: event.snapshot.canGoBack, + forward: event.snapshot.canGoForward, + }); + if (navStatus.title === title) return navStatus; + } + } + }); + expect((yield* navigatedTo("Original"))._tag).toBe("Success"); + const page = context.pages()[0]!; + yield* Effect.promise(() => watched.promise); + watched = Promise.withResolvers(); + yield* Effect.promise(() => + page.evaluate("document.head.replaceWith(document.head.cloneNode(true))"), + ); + for (const title of ["Replaced", "Updated", "Final"]) { + if (title === "Final") + yield* Effect.promise(() => + page.evaluate( + "document.documentElement.replaceWith(document.documentElement.cloneNode(true))", + ), + ); + yield* Effect.promise(() => + page.evaluate( + `document.title = ${JSON.stringify(title)}; ${title === "Updated" ? "history.pushState({}, '', '/next')" : ""}`, + ), + ); + expect(yield* navigatedTo(title)).toEqual({ + _tag: "Success", + url: title === "Replaced" ? "https://fixture.test/" : "https://fixture.test/next", + title, + }); + yield* Effect.promise(() => watched.promise); + watched = Promise.withResolvers(); + } + expect(successes.filter(({ title }) => title === "Updated")).toEqual([ + { title: "Updated", url: "https://fixture.test/next", back: true, forward: false }, + ]); + yield* Effect.promise(() => + context.route("**/drawing.svg", (route) => + route.fulfill({ + contentType: "image/svg+xml", + body: 'Drawing', + }), + ), + ); + yield* Effect.promise(() => page.goto("https://fixture.test/drawing.svg")); + yield* navigatedTo("Drawing"); + yield* Effect.promise(() => watched.promise); + yield* Effect.promise(() => + page.evaluate("document.querySelector('title').textContent = 'Renamed drawing'"), + ); + expect(yield* navigatedTo("Renamed drawing")).toEqual({ + _tag: "Success", + url: "https://fixture.test/drawing.svg", + title: "Renamed drawing", + }); + }).pipe(Effect.provide(layer)); + }).pipe(Effect.scoped), +); diff --git a/apps/server/src/preview/ServerBrowser.ts b/apps/server/src/preview/ServerBrowser.ts index 5c96327c76a56..7aa74a9d172b7 100644 --- a/apps/server/src/preview/ServerBrowser.ts +++ b/apps/server/src/preview/ServerBrowser.ts @@ -13,6 +13,7 @@ import { isBuiltInBrowserProfileId, resolveBrowserProfiles, type PreviewReportProfilesInput, + type PreviewReportStatusInput, PREVIEW_AUTOMATION_SERVER_OPERATIONS, PreviewViewportSetting as PreviewViewportSettingSchema, PROVIDER_SEND_TURN_MAX_FILE_BYTES, @@ -55,6 +56,7 @@ import { resolvePreviewViewport } from "@t3tools/shared/previewViewport"; import * as NodeCrypto from "node:crypto"; import * as NodeFSP from "node:fs/promises"; import * as NodePath from "node:path"; +import * as NodeUtil from "node:util"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; @@ -387,6 +389,9 @@ interface ServerTab { pendingInitialRequestUrl: string | null; navigationGeneration: number; stoppedGeneration: number | null; + /** The reported title a watch in the main document's isolated world waits to change from. */ + titleWatch: { readonly contextId: number; readonly title: string } | null; + lastReport: PreviewReportStatusInput | null; abortedNavigation: { readonly url: string; @@ -477,6 +482,41 @@ const editingCommand = (key: string, modifiers: number) => { return command ? { commands: [command] } : null; }; +const TITLE_LIMIT = 512; +const TITLE_WORLD = "t3-preview-title"; +// Observe root replacement without subscribing to body subtree mutations. +const titleChange = (reported: string) => `new Promise((resolve) => { + const changed = () => document.title.slice(0, ${TITLE_LIMIT}) !== ${JSON.stringify(reported)}; + if (changed()) return resolve(); + let head; + let root; + let title; + const observer = new MutationObserver(() => { + if (changed()) { + observer.disconnect(); + resolve(); + } else if (head !== document.head || root !== document.documentElement || title !== document.querySelector('title')) { + observe(); + } + }); + const observe = () => { + observer.disconnect(); + observer.observe(document, { childList: true }); + root = document.documentElement; + head = document.head; + title = document.querySelector('title'); + if (root) observer.observe(root, { childList: true }); + if (head) observer.observe(head, { subtree: true, childList: true, characterData: true }); + if (title && !head?.contains(title)) { + observer.observe(title, { subtree: true, childList: true, characterData: true }); + for (let ancestor = title.parentElement; ancestor; ancestor = ancestor.parentElement) { + observer.observe(ancestor, { childList: true }); + } + } + }; + observe(); +})`; + const CLIPBOARD_TEXT_LIMIT = 1024 * 1024; const CLIPBOARD_WORLD = "t3-preview-clipboard"; // Chromium shares its clipboard across contexts; capture real copy/cut keys in a page-inaccessible world instead. @@ -683,26 +723,32 @@ const make = Effect.gen(function* () { }; const report = (tab: ServerTab, navStatus: PreviewNavStatus) => { + const generation = tab.navigationGeneration; + const loadError = tab.loadError; void tab.cdp .send("Page.getNavigationHistory") .catch(() => null) .then((history) => { // A status the dead page still had in flight must not replace the crash. if (tab.crash) return; + if ( + navStatus._tag === "Success" && + (tab.navigationGeneration !== generation || tab.loadError !== loadError) + ) + return; const index = history?.currentIndex ?? 0; const count = history?.entries.length ?? 0; - runFork( - manager - .reportStatus({ - threadId: tab.threadId, - tabId: tab.tabId, - serverControlled: true, - navStatus, - canGoBack: index > 0, - canGoForward: index < count - 1, - }) - .pipe(Effect.ignore), - ); + const input = { + threadId: tab.threadId, + tabId: tab.tabId, + serverControlled: true, + navStatus, + canGoBack: index > 0, + canGoForward: index < count - 1, + }; + if (NodeUtil.isDeepStrictEqual(tab.lastReport, input)) return; + tab.lastReport = input; + runFork(manager.reportStatus(input).pipe(Effect.ignore)); }); }; @@ -712,13 +758,53 @@ const make = Effect.gen(function* () { if (url === "about:blank" || url.startsWith("chrome-error://")) return; // A navigation that starts and fails during the title read leaves the URL unchanged. const generation = tab.navigationGeneration; - const title = (await tab.page.title().catch(() => "")).slice(0, 512); - if (tab.navigationGeneration !== generation || tab.loading || tab.page.url() !== url) return; + const loadError = tab.loadError; + const title = (await tab.page.title().catch(() => "")).slice(0, TITLE_LIMIT); + if ( + tab.navigationGeneration !== generation || + tab.loadError !== loadError || + tab.loading || + tab.page.url() !== url + ) + return; report(tab, { _tag: "Success", url: ServerBrowserPage.withoutDataPayload(url).slice(0, 2048), title, }); + void watchTitle(tab, title); + }; + + // A title change fires no page event, so one watch per document reports the next one. + const watchTitle = async (tab: ServerTab, title: string) => { + // Chromium keeps one world per name and document, so a new document gets a new context. + const world = await tab.cdp + .send("Page.getFrameTree") + .then(({ frameTree }) => + tab.cdp.send("Page.createIsolatedWorld", { + frameId: frameTree.frame.id, + worldName: TITLE_WORLD, + }), + ) + .catch(() => null); + if (!world) return; + const watch = { contextId: world.executionContextId, title }; + if (tab.titleWatch?.contextId === watch.contextId && tab.titleWatch.title === title) return; + tab.titleWatch = watch; + const changed = await tab.cdp + .send("Runtime.evaluate", { + contextId: watch.contextId, + expression: titleChange(title), + awaitPromise: true, + }) + .then( + ({ exceptionDetails }) => exceptionDetails === undefined, + () => false, + ); + if (tab.titleWatch !== watch) return; + tab.titleWatch = null; + // A dropped tab's page can still change its title while it unloads. + if (changed && !tab.closing && !tab.loadError) await reportLoaded(tab); }; const reportLiveTabs = () => { @@ -1083,6 +1169,8 @@ const make = Effect.gen(function* () { pendingInitialRequestUrl: null, navigationGeneration: 0, stoppedGeneration: null, + titleWatch: null, + lastReport: null, abortedNavigation: null, captureLock: Promise.resolve(), capturing: 0, diff --git a/scripts/scenarios/browser-tab-follows-page-title.e2e.ts b/scripts/scenarios/browser-tab-follows-page-title.e2e.ts new file mode 100644 index 0000000000000..af97290441073 --- /dev/null +++ b/scripts/scenarios/browser-tab-follows-page-title.e2e.ts @@ -0,0 +1,47 @@ +// @effect-diagnostics-next-line nodeBuiltinImport:off - one fixture page for the server's browser to load +import * as NodeHttp from "node:http"; + +import { expect } from "e2e"; + +import { test, WEB } from "./t3.ts"; + +// A page that renames itself on a click, with no navigation. +const PAGE = ` +Original title + + Click to rename +`; + +test( + "A Browser tab's label follows a title change that makes no navigation", + WEB, + async ({ t3, app, browser, screen }) => { + const server = NodeHttp.createServer((_request, response) => { + response.writeHead(200, { "content-type": "text/html" }); + response.end(PAGE); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + try { + const address = server.address(); + if (address === null || typeof address === "string") throw new Error("No page port."); + const project = await t3.project("demo"); + const thread = await t3.thread({ project, title: "Rename check", messages: ["first"] }); + await t3.open(thread); + + await screen.getByRole("button", "Toggle right panel").tap(); + await screen.getByRole("button", "Browser B").tap(); + await screen + .getByRole("textbox", "Search or enter URL") + .fill(`http://127.0.0.1:${address.port}/`); + await browser.keyboard.press("Enter"); + await expect(screen.getByText("Original title")).toBeVisible(); + + // The page draws on the canvas beside its input proxy. + await browser.locator('canvas:has(~ textarea[aria-label="Browser page"])').tap(); + await expect(screen.getByText("Updated title")).toBeVisible(); + await app.screenshot("after-title-change"); + } finally { + await new Promise((resolve) => server.close(resolve)); + } + }, +); From 10a44f5553a52ff29ceffe2a70304315aadf3d4b Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 15:27:18 +0100 Subject: [PATCH 130/192] fix(web): RTF previews keep named spaces, hyphens and joiners (#36767) Co-authored-by: Julius Marminge --- .../components/files/FilePreviewPanel.test.ts | 16 +++++++- .../files/extendedDocumentPreview.ts | 41 ++++++++++++++----- ...preview-keeps-named-text-characters.e2e.ts | 34 +++++++++++++++ 3 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 scripts/scenarios/rtf-preview-keeps-named-text-characters.e2e.ts diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index 21630fcfb3c82..cc21768bbcfb9 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -134,6 +134,20 @@ describe("workspaceAssetResource", () => { describe("document preview contents", () => { const bytes = (text: string) => new TextEncoder().encode(text).buffer; + it.each([ + ["control symbols", String.raw`{\rtf1 Jean\_Luc\-X\~Y}`, "Jean\u2011Luc\u00adX\u00a0Y"], + [ + "named spaces, joiners and direction marks", + String.raw`{\rtf1 A\tab B\emspace C\enspace D\qmspace E\zwj F\zwnj G\zwbo H\ltrmark I\rtlmark J}`, + "A\tB\u2003C\u2002D\u2005E\u200dF\u200cG\u200bH\u200eI\u200fJ", + ], + ["a Unicode fallback", String.raw`{\rtf1 Jean\u8209\_Luc}`, "Jean\u2011Luc"], + ])("keeps the text of %s", (_name, source, expected) => { + const result = openRtf(bytes(source)); + if (result.type !== "document") throw new Error("Expected document"); + expect(result.pages[0]!.html.replaceAll(/<[^>]*>/g, "")).toBe(expected); + }); + it.each(["?", String.raw`\'3f`, String.raw`\\`])( "consumes skipped rich text Unicode fallback %s", (fallback) => { @@ -162,7 +176,7 @@ describe("document preview contents", () => { it("decodes rich text formatting and Unicode without showing hidden destinations", () => { const result = openRtf( bytes( - String.raw`{\rtf1\ansi{\fonttbl{\f0 Hidden font;}}Hello \b bold\b0\par Unicode: \u945?{\*\unknown hidden}{\field{\*\fldinst HYPERLINK "https://example.com"}{\fldrslt visible}}}`, + String.raw`{\rtf1\ansi{\fonttbl{\f0 Hidden font;}}Hello \b bold\b0\par Unicode: \u945?{\*\unknown hidden\_\emspace\zwj}{\field{\*\fldinst HYPERLINK "https://example.com"}{\fldrslt visible}}}`, ), ); expect(result.type).toBe("document"); diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index 6c1496ab40afb..a8b16fdd63bbb 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -492,6 +492,31 @@ export function openNotebook(bytes: ArrayBuffer): OfficeWorkerResult { return { type: "notebook", language, cells }; } +// Characters RTF writes as control symbols (`\~`) and control words (`\emspace`). +const RTF_CHARACTERS = new Map([ + ["~", "\u00a0"], + ["_", "\u2011"], + ["-", "\u00ad"], + ["tab", "\t"], + ["cell", "\t"], + ["endash", "–"], + // oxlint-disable-next-line t3code/no-em-dash-copy -- Preserve the RTF document's punctuation. + ["emdash", "—"], + ["bullet", "•"], + ["lquote", "‘"], + ["rquote", "’"], + ["ldblquote", "“"], + ["rdblquote", "”"], + ["enspace", "\u2002"], + ["emspace", "\u2003"], + ["qmspace", "\u2005"], + ["zwbo", "\u200b"], + ["zwnj", "\u200c"], + ["zwj", "\u200d"], + ["ltrmark", "\u200e"], + ["rtlmark", "\u200f"], +]); + export function openRtf(bytes: ArrayBuffer): OfficeWorkerResult { if (bytes.byteLength > 8 * 1024 * 1024) throw new Error("Rich text preview limited to files under 8 MB."); @@ -593,7 +618,8 @@ export function openRtf(bytes: ArrayBuffer): OfficeWorkerResult { } const match = /^([a-z]+)(-?\d+)? ?/i.exec(source.slice(i)); if (!match) { - if (next === "~") emit("\u00a0"); + const character = RTF_CHARACTERS.get(next!); + if (character) emit(character); // A backslash before a line break is \par; TextEdit ends paragraphs this way. else if ((next === "\n" || next === "\r") && !state.skip) html += "
"; i++; @@ -686,15 +712,10 @@ export function openRtf(bytes: ArrayBuffer): OfficeWorkerResult { fallback = state.uc; } else if (!state.skip && word === "page") finishPage(); else if (!state.skip && (word === "par" || word === "line" || word === "row")) html += "
"; - else if (word === "tab" || word === "cell") emit("\t"); - else if (word === "endash") emit("–"); - // oxlint-disable-next-line t3code/no-em-dash-copy -- Preserve the RTF document's punctuation. - else if (word === "emdash") emit("—"); - else if (word === "bullet") emit("•"); - else if (word === "lquote") emit("‘"); - else if (word === "rquote") emit("’"); - else if (word === "ldblquote") emit("“"); - else if (word === "rdblquote") emit("”"); + else { + const character = RTF_CHARACTERS.get(word!); + if (character) emit(character); + } } else if (char !== "\r" && char !== "\n" && char) emitByte(char.charCodeAt(0)); } if (stack.length) throw new Error("This rich text document is damaged."); diff --git a/scripts/scenarios/rtf-preview-keeps-named-text-characters.e2e.ts b/scripts/scenarios/rtf-preview-keeps-named-text-characters.e2e.ts new file mode 100644 index 0000000000000..3049a197f4bf0 --- /dev/null +++ b/scripts/scenarios/rtf-preview-keeps-named-text-characters.e2e.ts @@ -0,0 +1,34 @@ +import { expect } from "e2e"; + +import { test, WEB } from "./t3.ts"; + +// Java's RTFEditorKit writing "AccountNameJeanLuc". +const JDK_RTF = String.raw`{\rtf1\ansi +{\fonttbl\f0\fnil Monospaced;} + +Account\emspace Name\enspace Jean\_ Luc\par +} +`; + +test( + "An RTF preview keeps the spaces and hyphens written as named control words", + WEB, + async ({ t3, app, screen, browser }) => { + const project = await t3.project("demo", { "contacts.rtf": JDK_RTF }); + const thread = await t3.thread({ project, title: "Review contacts", messages: ["Start"] }); + await t3.open(thread); + await expect(screen.getByText("fixture turn complete", { exact: false })).toBeVisible(); + + await browser.keyboard.press("Control+p"); + await screen.getByPlaceholder("Search files…").fill("contacts.rtf"); + await screen.getByRole("option", "contacts.rtf", { exact: false }).tap(); + + // The first document preview loads its worker, which a cold Vite cache optimizes on demand. + await expect(screen.getByText("1 page", { exact: false })).toBeVisible({ timeout: 120_000 }); + const document = browser.frameLocator('iframe[title^="contacts.rtf"]'); + const body = document.locator("body"); + await expect(body).toContainText("Account"); + await app.screenshot("text"); + await expect(body).toHaveText("Account Name Jean‑ Luc"); + }, +); From 0351dbb09204c464bc556c2cfa1b48ceab0d3b21 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 14 Nov 2026 08:28:50 -0600 Subject: [PATCH 131/192] fix(acp): refused and limit-stopped turns no longer show as completed (#28797) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../Adapters/AcpAdapterV2.test.ts | 7 +-- .../Adapters/AcpRegistryAdapterV2.test.ts | 59 +++++++++++++++++++ .../src/server/claudeAcp.ts | 12 ++-- packages/provider-acp/src/server/adapter.ts | 48 +++++++++++++-- 4 files changed, 109 insertions(+), 17 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index b7808ef897ee7..88505e9749d62 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -2456,12 +2456,7 @@ describe("AcpAdapterV2", () => { }, stopReason === "error", ); - const expectedStatus = - stopReason === "error" - ? "failed" - : stopReason === "cancelled" - ? "cancelled" - : "completed"; + const expectedStatus = stopReason === "cancelled" ? "cancelled" : "failed"; yield* reportNextPromptStopped(stopReason); assertReinjectedInstructions(yield* runTurn("Start work.", expectedStatus), "Start work."); assertReinjectedInstructions( diff --git a/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts index 70f45bc8a46c0..d0014aa6574d4 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts @@ -2755,6 +2755,65 @@ describe("AcpRegistryAdapterV2", () => { }).pipe(Effect.provide(layerTest)), ); + it.effect.each([ + { stopReason: "refusal", childUpdate: "state_update" }, + { stopReason: "max_tokens", childUpdate: "subagent_update" }, + ])( + "fails Claude's turn and child and closes its running tool when they stop with $stopReason via $childUpdate", + ({ stopReason, childUpdate }) => + Effect.gen(function* () { + const { update, idle, prompt } = claudeFrames; + const session = yield* openClaudeReplaySession([ + ...claudeFrames.open, + ...prompt(1), + ...claudeChild, + update(CLAUDE_ROOT_SESSION, { + sessionUpdate: "tool_call_update", + toolCallId: "root-tool", + name: "Bash", + title: "Read package", + kind: "execute", + status: "in_progress", + rawInput: { command: "cat package.json" }, + }), + update("child-1", { sessionUpdate: "state_update", state: "running" }), + childUpdate === "state_update" + ? update("child-1", { sessionUpdate: "state_update", state: "idle", stopReason }) + : update(CLAUDE_ROOT_SESSION, { + sessionUpdate: "subagent_update", + sessionId: "child-1", + state: { state: "idle", stopReason }, + }), + idle(stopReason), + ]); + yield* session.runtime.startTurn(session.turnInput(1)); + const events = yield* session.until((event) => event.type === "turn.terminal"); + const terminal = events.at(-1)!; + if (terminal.type !== "turn.terminal") throw new Error("Missing root terminal"); + assert.equal(terminal.status, "failed"); + assert.equal(terminal.failure?.code, stopReason); + const child = events.findLast((event) => event.type === "subagent.updated"); + if (child?.type !== "subagent.updated") throw new Error("Missing child update"); + assert.equal(child.subagent.status, "failed"); + assert.isNotNull(child.subagent.result, "the child shows why it stopped"); + assert.isFalse( + events.some( + (event) => + event.type === "message.updated" && + event.message.threadId === child.subagent.childThreadId && + event.message.role === "assistant", + ), + "the child's conversation has no reply it didn't send", + ); + const tool = events.findLast( + (event) => + event.type === "turn_item.updated" && event.turnItem.type === "command_execution", + ); + if (tool?.type !== "turn_item.updated") throw new Error("Missing tool update"); + assert.equal(tool.turnItem.status, "failed"); + }).pipe(Effect.provide(layerTest), Effect.scoped), + ); + it.effect("keeps Claude activity after a refused Stop and closes it on retry", () => Effect.gen(function* () { const gate = makeProviderReplayGate(["close-response", "queued-close-response"]); diff --git a/packages/provider-acp-registry/src/server/claudeAcp.ts b/packages/provider-acp-registry/src/server/claudeAcp.ts index 4c875ce597f57..7804a323accca 100644 --- a/packages/provider-acp-registry/src/server/claudeAcp.ts +++ b/packages/provider-acp-registry/src/server/claudeAcp.ts @@ -2,9 +2,10 @@ import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import type * as AcpSchema from "effect-acp/compat"; import type { AcpToolCallState } from "@t3tools/provider-acp/server/runtimeModel"; -import type { - AcpAdapterV2SubagentUpdate, - AcpSubagentSessionEvent, +import { + acpUnfinishedStopMessage, + type AcpAdapterV2SubagentUpdate, + type AcpSubagentSessionEvent, } from "@t3tools/provider-acp/server/adapter"; const decodeAgentInput = Schema.decodeUnknownOption( @@ -86,6 +87,7 @@ export function extractClaudeSubagentSessionEvent( const child = decodeChildState(update.raw); if (Option.isNone(child)) return; const { sessionId, state } = child.value; + const unfinishedMessage = acpUnfinishedStopMessage(state.stopReason); return { type: "state", childSessionId: sessionId, @@ -93,12 +95,12 @@ export function extractClaudeSubagentSessionEvent( status: state.state === "running" || state.state === "requires_action" ? "running" - : state.stopReason === "error" + : state.stopReason === "error" || unfinishedMessage !== undefined ? "failed" : state.stopReason === "cancelled" || state.state === "unknown" ? "cancelled" : "completed", - result: state.error?.message ?? null, + result: state.error?.message ?? unfinishedMessage ?? null, }; } if (update.originalSessionUpdate === "session_message") { diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index 332c4bee95ec6..77c68669eea06 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -1021,6 +1021,22 @@ function makeProviderThread(input: { }; } +/** Stop reasons where the agent ended without finishing. ACP asks clients to show refusals. */ +export function acpUnfinishedStopMessage( + stopReason: EffectAcpSchema.StopReason | null | undefined, +): string | undefined { + switch (stopReason) { + case "refusal": + return "The agent refused this request. The agent's next turn won't include this message or its reply."; + case "max_tokens": + return "The agent stopped because its reply reached the output token limit."; + case "max_turn_requests": + return "The agent stopped because it reached its request limit for this turn."; + default: + return undefined; + } +} + function appendToolContentText(content: unknown, text: string): unknown[] { const next: unknown[] = Array.isArray(content) ? [...content] : []; const last = unknownRecord(next.at(-1)); @@ -3441,8 +3457,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ); } + // A failed child's result explains the failure; it is not the child's reply. if ( taskStatus !== "running" && + reported.status !== "failed" && subagent.assistantText.length === 0 && update.result !== null ) { @@ -3812,16 +3830,18 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( const update = notification.update; if (update.sessionUpdate === "state_update") { const child = nativeChild(notification.sessionId); + const unfinishedMessage = + update.state === "idle" ? acpUnfinishedStopMessage(update.stopReason) : undefined; setNativeChildState( child, update.state !== "idle" ? "running" - : update.stopReason === "error" + : update.stopReason === "error" || unfinishedMessage !== undefined ? "failed" : update.stopReason === "cancelled" ? "cancelled" : "completed", - update.state === "idle" ? (update.error?.message ?? null) : null, + update.state === "idle" ? (update.error?.message ?? unfinishedMessage ?? null) : null, ); } if (context !== null) yield* syncNativeChildren(context); @@ -5027,6 +5047,17 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( class: "provider_error", }); + const stopReasonFailure = ( + stopReason: EffectAcpSchema.StopReason | null | undefined, + error: EffectAcpSchema.PromptResponse["error"], + ) => { + if (stopReason === "error") return idleErrorFailure(error); + const message = acpUnfinishedStopMessage(stopReason); + return message === undefined + ? undefined + : makeProviderFailure({ message, code: stopReason, class: "provider_error" }); + }; + const settleIdleWork = Effect.fnUntraced(function* ( context: ActiveAcpTurn, ): Effect.fn.Return { @@ -5043,7 +5074,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( if (work.stopReason === "cancelled" && pendingStopCloseTurn === context) { context.interrupted = true; } - const failure = work.stopReason === "error" ? idleErrorFailure(work.error) : undefined; + const failure = context.interrupted + ? undefined + : stopReasonFailure(work.stopReason, work.error); + if (failure !== undefined) yield* terminalizeOpenForegroundTools(context, "failed"); yield* finalizeTurn( context, failure !== undefined @@ -8671,8 +8705,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( }); } if (context.finalized || followsAgentWorkState) return; - // ACP v2 reports a failed prompt as an idle `error`, not an RPC error. - const idleError = result.stopReason === "error" && !context.interrupted; + // ACP v2 reports a failed prompt through its idle stop reason, not an RPC error. const hiddenFailure = checkPromptOutcome === undefined || context.interrupted || @@ -8684,7 +8717,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( yield* Ref.set(runtimeRestartRequired, true); } const failure = - hiddenFailure ?? (idleError ? idleErrorFailure(result.error) : undefined); + hiddenFailure ?? + (context.interrupted + ? undefined + : stopReasonFailure(result.stopReason, result.error)); const status = failure !== undefined ? "failed" From 6cb47acfb8c1e9913c8760a5b677dbf63361a501 Mon Sep 17 00:00:00 2001 From: preet Date: Sat, 14 Nov 2026 20:00:13 +0530 Subject: [PATCH 132/192] fix(web): Escape that cancels a thread rename no longer also closes the find bar (#36768) Co-authored-by: Julius Marminge --- .../chat/ChatHeader.browser.test.tsx | 23 +++++++++++++++++++ apps/web/src/components/chat/ChatHeader.tsx | 1 + 2 files changed, 24 insertions(+) diff --git a/apps/web/src/components/chat/ChatHeader.browser.test.tsx b/apps/web/src/components/chat/ChatHeader.browser.test.tsx index 8acebdfa0bb41..2f54eef77c8d3 100644 --- a/apps/web/src/components/chat/ChatHeader.browser.test.tsx +++ b/apps/web/src/components/chat/ChatHeader.browser.test.tsx @@ -80,3 +80,26 @@ it("opens the thread menu on a desktop title click without waiting", async () => await screen.getByRole("button", { name: "Thread actions for New thread" }).click(); expect(openMenu).toHaveBeenCalledOnce(); }); + +it("keeps the Escape that cancels a rename from also reaching window shortcuts", async () => { + persisted.title = "New thread"; + await using screen = await render(header(persisted.title)); + await screen.getByRole("button", { name: "Thread actions for New thread" }).dblClick(); + const input = screen.getByRole("textbox", { name: "Thread title" }); + await input.fill("Unsaved title"); + + // The thread find bar closes from a window keydown listener that skips handled events. + const windowEscapes: boolean[] = []; + const recordEscape = (event: KeyboardEvent) => { + if (event.key === "Escape") windowEscapes.push(event.defaultPrevented); + }; + window.addEventListener("keydown", recordEscape); + using _listener = { + [Symbol.dispose]: () => window.removeEventListener("keydown", recordEscape), + }; + await userEvent.keyboard("{Escape}"); + + await expect.element(input).not.toBeInTheDocument(); + expect(persisted.title).toBe("New thread"); + expect(windowEscapes).toEqual([true]); +}); diff --git a/apps/web/src/components/chat/ChatHeader.tsx b/apps/web/src/components/chat/ChatHeader.tsx index e80e8e0fdfdc3..1b5ab41725566 100644 --- a/apps/web/src/components/chat/ChatHeader.tsx +++ b/apps/web/src/components/chat/ChatHeader.tsx @@ -240,6 +240,7 @@ export const ChatHeader = memo(function ChatHeader({ renameCommittedRef.current = true; commitRename(event.currentTarget.value); } else if (event.key === "Escape") { + event.preventDefault(); renameCommittedRef.current = true; setRenaming(null); } From 3908c4eab3ac33eb990e3b961738fbbb3951e0ff Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 06:34:58 -0800 Subject: [PATCH 133/192] fix(terminal): answer OSC color queries so CLIs pick light colors (#36777) --- .../t3-terminal/THIRD_PARTY_NOTICES.md | 2 +- .../main/jniLibs/arm64-v8a/libghostty-vt.so | Bin 1100368 -> 1159760 bytes .../main/jniLibs/armeabi-v7a/libghostty-vt.so | Bin 1249176 -> 1301808 bytes .../src/main/jniLibs/x86/libghostty-vt.so | Bin 1105392 -> 1148772 bytes .../src/main/jniLibs/x86_64/libghostty-vt.so | Bin 1194440 -> 1256144 bytes .../scripts/build-libghostty-android.sh | 2 +- apps/web/src/terminal/ghostty/core.test.ts | 12 + .../terminal/ghostty/vendor/ghostty-vt.wasm | Bin 630932 -> 660897 bytes native/libghostty-vt/VERSION | 2 +- native/libghostty-vt/include/ghostty/vt.h | 3 + .../libghostty-vt/include/ghostty/vt/color.h | 432 +++++++++++++++++- .../include/ghostty/vt/color_scheme.h | 73 +++ .../include/ghostty/vt/kitty_graphics.h | 90 +++- .../libghostty-vt/include/ghostty/vt/render.h | 66 +++ native/libghostty-vt/include/ghostty/vt/sgr.h | 7 +- .../include/ghostty/vt/terminal.h | 36 +- .../include/ghostty/vt/unicode.h | 154 +++++++ 17 files changed, 842 insertions(+), 37 deletions(-) create mode 100644 native/libghostty-vt/include/ghostty/vt/color_scheme.h create mode 100644 native/libghostty-vt/include/ghostty/vt/unicode.h diff --git a/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md b/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md index 0f8a18d50cbd0..91e74e697bab0 100644 --- a/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md +++ b/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md @@ -21,7 +21,7 @@ The Android terminal renderer vendors upstream `libghostty-vt` shared libraries The web terminal vendors a WebAssembly build from the same revision and uses the same C ABI. - Upstream project: https://github.com/ghostty-org/ghostty -- Vendored revision: `9f62873bf195e4d8a762d768a1405a5f2f7b1697` +- Vendored revision: `3ff6d08fad56f2d6b81420a27bdc56ee707b453e` - License: MIT Ghostty's MIT license applies to the vendored Android and web libraries. Keep this notice and both diff --git a/apps/mobile/modules/t3-terminal/android/src/main/jniLibs/arm64-v8a/libghostty-vt.so b/apps/mobile/modules/t3-terminal/android/src/main/jniLibs/arm64-v8a/libghostty-vt.so index e086ca83155655edc2e124eb69056e5c2b24981c..9e3ed70cf88adb0c21aa84defde455c9620de070 100755 GIT binary patch delta 327904 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 A4Ifo5aH(7Glexo^QD8rU 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

X8zBxi3ZoV|8( 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
fnaP}rLv-xeT){}B2_EA)~T`e?{t0-!G@ zh#!MC8UYl7w#8&7Xg|9vCTXGpkByYnuVnF4ueXA{dAy`dpt%jy6Ahkmi7{GI+i;LH z1hCNTkOI#-;hho5W+dd~+pdLN&{B(Kv;YX#KCi0Vd{I8d%}+~hdGbh+0S;E`Yv$6V z7?Gb`BqDz+_!AS9!4U5NxO znzBF`YV`B&Tp@Y3ScITcqfJb}U@B^wZ)-MUQWTxLgoY_P*9h)Ck47({QQQ~g;=URU zB3rx_rx$0wkIZl4>?LrvlyRn&d!aF)jED&rJ?fwo7STniE#1tS_9F*^iTA`%pTiy< z4B~`PL&^+4LrOt^1Hkn9r866HPmE-P|04#DG)+S0LzAGtfKeR9W)3H19n5p`*WSeN z@vwjeHoSO@)<1ljAJzZxr&$Nd<{thuhYs;i!v8{KqW%TMK`Hd$ju_}3-`!!?DfZBWk=SSiK-VDvO0kDu zF#n(jlx_2q^{m4MF|=nTa&y*zV|qjFx9hj5kdu(y5}gPcNmeH%h5#1{7Qf5c89at-YWPm5K|LU5}(mdk8%_GOOu z#g6RiJ+UjZJ1?f#6Vb|;d#V_znSe1ckt{)>Y>n?@Bbkp(vaujpl)~p&{^A$g0^5-+ zZ*g2(2}qU)lI6O3SxFXL)#4^RLb79rF)RYfqEUy9WWT)k2+7#fX3<|P18JI?un7~9 zMguL{zZPhh0f|a0S@PSFB^P8_L8OC&t%QlMgpiXNZ&h$6m~!pmjvWjsV?zz4*?xx7 z=lUB4LbOG^H*;b^SPw+!CaLvg`bE=}^-=hy`J>1KGo84&Ja938`6@%YB+cc!N27zF z+G&AFjJH~~GCPW<86h{Et{8%7sbP{_L**{!+APMfT6<{_8|5T9Kl*i;`e4>zpLunY z5OGm4Veh!VUZhNl4eM_9UC-IivLL_DyR zORzBJAyX`ZQK^kQuoTmmgbjUvgk3X>z8$l1a5IsBiXSGWgB%@(wM%&q#aW^x?RfpD z)ScDZNe2aG54PA|3gkDj)9i)7}$)CZZY=FDwX_?Ze|Eu6#u+SE< zlP1|El(+&|*^eE8j%yVX1y@ES6&?lpVIcJ^Jo_~(xHmnkLSM5s zIk9F9x`I(~4|I~7@_856V{X^g>&-Y%Ggfdx%B6Ev6C|S>p0^;(NQvLwFD+ePiFQ}J zxc1dvQMgu#iAd&h2Zf3gs>NcAtbuX@roOh2*q~3@xtwb|1Tm_^bLw+g_Fl(OJ5sid zlGG`H!>MF8N|BZlrO0&@s|`aW3EM*|*_TQwP&i!isQ!fViEOTuRG`WJH(!1ms=5~+ zZ$-WzW#WDfm7rYZDR@U}us1)h?N{{dU*Z>s1i!c%z7UZcuAkW~2MYxX*+Z46fPN(F?G^qb{>3n(C69YZe0 z=#a4Jf@OF|V+oq^UVvjxN|neH#DYwkt{~3c%t*xMcAv_d+wu4||AVALCvI#Z+#b1J zCp*i3c%7J+lnS>0KGK{?xdFt2(qKU)0tg8ooKObTY~O^To1$-ukeLAcigOYWpWJA%jnTIztdHg=egIe1WUWUd z<4*u+aW;JW#*LVwqKdMSlLN2oiN>ix1cc+LfyV)(@3vs9hV8~Q(9 z{*IHu)m`%B1%(rf@Q}hZL?3T^tc*@Wz8vVa71&S&IuV%Cwz6G$Y_tbD-$Jq|7;4!o zyFpY@SPxV%MgJqIP?}&@DF5G&|5L308|`$VSR5IKiDY$M(IEbnqQP>RvY{%ePE^VN zmk$k9N&m)8OlnPZ7`cj1&v7WU&rCC;E9F6V!+&ves!HU!-dG>6l7ssoaRNFlaXWj>OPYaI zX_vgDg~EwcHp5#A<_EIn-ckgAis`+jg`rzhQ5htZM-My=N|^)YF{0v(+tz)&uda3s zW!XN`Xnq9yz(-o&C8NwTTS(LVyYQblN?X6Z1bWfwYAP%tgGC{&iP8+1!EcW@RMSDA#U^OJu{Wp)niwdS8);% z4g77zv1eda$Z~rX#SB7=9CSy@*la&EG;S?h=_mE^!sk)ESImB_Sl*_2_h>o+dY7HGZ z1%IhoFneDrEuC~jj2w%;DMlU=B#jpM32a`FbX@8VQ%*Z+gtF9*(q6&*8@t~ToWH|5 z21~KtdCpuvr&};>*I;7-J!K!yU{ivn&6@enT)cz?bDFdya}YxsiTgBYV+^!W{ts=8 zfi}wjp^a*2V`bbJis6mIrP| z&vl~hGEbz2zv?p4pG+-ie3!< zUwH;h4jD!=IpjijW!<8retZ>sE($EX#=!jW#G}Qyb_}!$du%Fo3HB)kHlF2zUn2J141@6*q8%fSO`A}#D2=%O7X6BNFXJH zLJ3N(j_mEOl8@hEu0uciPDFVL<>556Cc-|l_q$7B3RF(MVvyZpgxT0r-K0UvFr>@n zzWX;-Wffc5P3plrvk$vTUHsdAvj}d^Pb9d+RL$g(gY4d2^5tFGutw?M$U^ka|FkvLeE_atk@=Mv^9#WtkO=7UF(NdJ%68iUQ zcPT23W++0!;tOozbI!A3;$(S3)U)ah8ypRf!jXyhWL&-8RGa>d@BP95!Zvoi2W-Vh z>=K&l_#HY!lG3o_*gC=iN7lnz4wd^J@2V2kxu+Bwc^zIw9tn(;PP^&8j;jv_o)o#d zO@}kSm!!wmraJa~PbtcU;+{tkYB`F~%V@T>r<5*aUtyPfNSUD@uY9tqN&*$A0bwdW9Og^_DjA9rloBQelYC z9ueC4Z68?6St%?gP8uhSPhrdAq|bz}|7l3;E1ed)eQ)gnC~dzKy0LMS+Efp{rX`|G z?jJdw09w%`bPOBOUn&u*OWDc(QjGBZZg#)FG+=xjV%zO!OEC#^m3oM($7f3|Jp^{J z3DH$PHd`uMmObhy!QnLI{J=^e7vgE|~=?h@ncZopw zgcadEk9UcwJTfTiLM&Y(u-s$o5)Xn(epeLi%u~SffPn=JMVBZSK$nP4_QZoO@u%re zc8MUwiATD`0j}NIl=t1dG>+;9v%M&Ck($+oJ`2nyI4AYT;bP6wswAY@<#|M^V&K`l=sHuCyE3&VYd=3X`+gVQOTD|`bTcWY4mKgv1>*$l zwR{T)!QGA(OwILTVT;yXW$q!EjN9FHjW{q)^Dl4cLGl=txE5=9@sN}>Dhe|s)&d_} z-$x-uXO11V)S7~!0?uzTj^*@_q!ZLYb23**5_M}5uW!+Es>ThRs&o~%4-&N}3PuG4 z$DI2>om!7T z*&#g zLxv2G-IWvC5JAU(pJnn{+=X=TPAOK7T|^>ZTIVAHF^-%j{;ZOln{o$P`nc8=fx~zy z;$?0T7oQtEwvcQ9kLS6t?i6E+HetJFjG-Rf^GiRthnQP5xQ-2lEGI=jg>!SMD;gcH zOTTRHFdV&1f#j^s=0i?@O^dT(1Hh%8)9zf{SPE<)bXT;tddx^tK^p)xn;L1`w9Qen~{t}!+RqY}@m+^J?} z`z{AA#X`{Irkc~p4c8?;Y&dKpBxN=i_c|J;_#gQgOL0ydeY_uyhR)(LU(A1Lk+F}T ze98$38uJ}BIPh6jB{dj0TyBu{c=$~*8k~d%A0Ud|{Va&$EU3CZT;|67Co!Ga4%4E% zc9?p)Kfv@uYB+GT1yijB)8MQ}F&#{p#yvuM-OY@r^uJ+TaGM=j?BF!B&J(Z=urQ&~ z$^<=Gg$a&2Vg{zXvCiKSQ+j2a8oO)b^>Gfdnk3kLxv^qM>|FSdWjG)X zQcUQC&8Ikrr_~}@)coleyIBmvDo``-X$x)m6l*2;NL`AL!J))5E_1;%7OBN>$ZKD( zC!z4{=nGZ3Rbo;Cw4s@4l znmr>`83VZTA|RiMvXApsa{!e7I?>A4BrELTjIU+m!G z`K&(#B<7IB330n&W(gg#L{rEbfBH76x1}eHa=22gcuxT?4xSAGMP zWd1=VoPBo35T~!T(xbhQ6a`8l9j&0 zn?x6+lNz_X>2BWRO}ETEHjc!RxlVR9&3!tk3OaG+2dN=9!Bi^ulO1$7J<(74qMr;z zxq&Emqk}33<#tgynDq<2ADZw`Y}#ru`vDKkfFIJ)ng>pl&Atpwpk_p2e!ISc?EYp{ zwlUCR8|!vkY~%NUI6lboEN749+0TTu6Kz!2{@LIDW)L}(E8Gt?i^eNI;{a|>CH-xPDD$y}zj&#_qn)9vxe z`nD`~e6fQ=#PKlHqq5cmMWT90!V&>P`@Tx^54c`F11$x^)RuzZpr8-T88ynooYBYj z(E0di%d@&|mS@ALob_4QcFQxw;k<5JpB3YotnMHJzq}$K4evI>MUv}Hwo(hhRj&os z#&U*PDiQH)T(T7?^aWIjFO{>BnzsU=rV87YBS8I8&L`E{NSYNW`nCw$^WuHNC>*mJ ziRpv>5RYjZ{86Km`5Ue&FwoPi)^CZWry_ENz`Hn|VyzGgO_g8?rq=>%Bc$Z?ECtVG zHrZM3qrAA$;$VmX{Oa(SM|!@8ER($x&*;cM@g!b{LH$D^4}|G9E)-0MNzu1^>TWK9 zqHCb&C1eonAx~bay>PF#TCIuENr4{5PuJ{b5&B!65O!Y(dmjkiMPWL|oMgvJ#LQ!c4xM3C;L7HYW`kZu5^v5u%O zNHr0V<^obG4@rIq$t3ACb|Ktp=|PRO2xB+w%U;+Q1MJIu*q2kCRr6r;pTXDhz=xQ% z^*O7EqDQKoj$RtfGC~NN|Hk#Tw(tPeK;LIDlV`OCG!OPf-;fnxH5XPBfxIo*M8rR3 zX)RH-w&u>woem2@QvzEtPP18xFO#egU~h7YQSgq=VHNVD)yPrg?)>rSs>?X$ijZl)DAY>2@%19(waY zfO*4CwH;v6B1IV`u)RHW6y9yup!e80vB0+p4Uudq+;rw&AX}|y@Cq7if^2PYQ{4>Z z%4wOdO+{wAapjb0WV<&kKYpPZ;f6C*GU3L-kS08fQ{#r< zIvFku?hglq$Qlks%cN1X$cOYMA=+{S@kF!2;(#=T=o&HFHY*^Iju1#E2xK6fkolpi zm^iL{Jy0MfAbDSQb$Rq!kNe4P1%00sxQuET4&tpb!bR6Dm@8j|nq*E@Ai~n*rEA`4 zF%dVn!#unP(=Z99VZOIY)0r!u)sArO>7WUP6K$iQ-2Y;NLqPxgBW6yFj=U)stT#r8 zI#8aQus9)3 z=3F~e$uP3|#SZS8p|E_m0aqhBKDi%3j>9timk<_<&hf^HX1Q(KU{-uEq#|W*#DHcC zEO7`<_WKf6IRvNv4bQ|CC;V-pEAGi!(|XlyP$xptiupr}EO(+w?5o8N_5;VoKn4qV zQ;HJ_zmv{92bjzUCJTYd4PboAN%d&}SMK#Eqzu1*9;7e_>MKcUn;f7GL3;BUXv2I* zRg@=JE|}Ri7su|NFM+f+lDFqYOgMa5<5#??l4v>IRcAg02)+dbX8^%Rpydr$)lty$ z+4%y@MHkPE3WoAY0j(#|O zfr4q4uGcsEAG{ZJpM*W^=Og24w-Ln;`p5@gOka$_wy}@3gDvsbnfsxG9SA@Y07wa3 z0nY%{o87qbE&xP^S_?ap3_HTCc4RH=$OhPvKCmOFyi~_wfCGtA7^FM`+P@K$i1M@a zp^tYynr|;zYiE$P?VWVZo2|p%3fRW&u#M^Htow9Uy$+K627#74YK%*X40*tpW=9h` za&wPFq3g_(bl4hQ6@p0RHEY>AFRQ(Hg_|p3*}06#ShQ6Vu4^7|r!h5WSsR>$#_C~+ zJtI`ZVTc=1K?=#?UZ7p#t!p+2CXvSDAHlOa1hh+lwx^HkGYVXxES!N8w9lj(OeP@f zd@Y8XF!r)O%ki^3%R#-g>zC1Uuyt5k5~?%%qDKe>)*%2SnE(k>oy3^zkQEai`_GV4 zkSMpOujW_}_-cRB^%U(#*VAh4dXhtQb=37-0i$n#(RaY;8<4nDAu1Ib+KPr`QN@z| ze2{mzySk84mRG$L=#h(D)iPd+!7P(06^T+CEf`-Fw}xxv5l|VNs62e6#3(3=X2L0p z2ch{a=s+T_$})TTx$-$lwE`c@d^k%zlaH>sNianbX@3D}TS3}0)HC_2&P8(NFWbr< zBk4ogudHQHq3pLPyH`Wv=c8JR!gEoW1cSoF5`q)6Y#+N}ygfZi*X)T{f1tfQ>b&I( zLpBhG>_(JoWnUbsY%7}uE(CkN~n7*cY7id(~hv-bn&mj(Qb5cX{e zOzsUybqMuhMXqlM_=6RBzLe1bUTI`zr4k*%FfY6?ZnjEMeUQ#v0SVd;y1WOvjD-Y^ z?Wh`pA+nOneQPbZJy>TxgK}q4t_kIip?I^ba z2N(W`3U4Ei&bZrJaC%3bc`phYQ1BxZT!Ml(I;sj$?pKr}+LEFX*6_w-$cs#aa-OF; zBCUEB5rz#{p=UjyV}0-|fS&CQQ&m*(DlxV3YeS$|J@t-6|Dz;bQtb5;<*UfQC z*f=Kug72?kLyz^8xmX}^9$9%^`#Y6MDoi|UGIv-}X`Uahk}-&gcz^*E(>)Cz#Gr5* zq7rGS1WEGRs60u<@SUuf=`=!w4rC}y&Eu_lFcu`=OC!Vp)rjs~`6`qR!70W?G>XNdYy$Iwq_(Aj>{vUKn!oqVYe$zT>=Xd_aQrF$&Y>A0OhH2X8q6 zmLe1>bP4d7ix59apiWP)uz<*qNjep=zy@$k9{%%DO3A7f56WmBE2QL=F~q|ZFqg2L zYQs{4ZlDQ~))H$EumZ+c3!w-^movYU%C`^vSE8hC;%Pc2n3}8;PYO?6%S?2D1u#Q% z@O*=(iVx$;pGJ9&F#_gB5v?;vBD@z1pQ#5-&2(i=m_M07dyX;Y7%&NUT#Rx zr%*-6xVUvjp*N?aqCVJp0Tum(_pJ6ahak#Acx25s=3%Zzr~2qt(sJeqd=X(?vyD09 z)IV9XjRc_wYqk#{a&r2<9m^Vq`FY(g_V%#$SqSUeXCcsc_%AUGjau005f4sDeRy9VkDEKA3G>Yo53-=2N+G{6|Ji`{)%n&J2jaNB=0 zlFdw!R6>VcY+H)-oc+4}BZG8o&uFQW@M|@@oFj#^;m=8(*rHTvkR#F@PL$ss#6C)u zIy&A&%%gmO3p<-C`D^4iK7;AdYp+Uy>x=uV<-4#J8VJyyJ#yib&brs(QTW>KKU3(u zaZh`m$lo7L(DlVV`9pGf2N$_K?b##W4+zuIl&JiFlS20l%F?(V_jY^AJvg=`P3k4A zu4cQ^WJG?jA(B_khNMf+*sa_DLl9%>(p<-VU`zSPc;+@-D)SBKU_DW}E1yZk1J6#J zKJLzT4@VLQkA1<1OK}!1t$L^JS4ErD)-u_gTOMDS$mcQhL<~H6u^PQZpqAQO(~} zFoz5&*zq)p&*DxjHUnQxf1e$nimzVCkW##EQ+?BdbkCr@JNSQxuu2 zkF>qA6@O9i(qi`SS_w9cp^cg#11I*+c4Y;l(17RrY*)54jQu!Da(1+S$*zo&`U+Qe z5(S5`=SR1DqZuuE`_5QoAzqywFIRXAuenacRYwK91EUh z;430j?N}*0Cr-D?>ZU59bO0+KC-ql7UewmK?Gqa%6z#cry`yd%n>9*`6lPVi?&GB~ zzP4%}`<$`yAn{Nj@P8`Q?^&3Pz$%Ntu6tJM|EJZReHM1)x}HjoiMF+A1659au?ik3 z8K`vi^bCcLvFMQi^@yH*g3lbSpE2Kw0Bwh!jhxu-;d>LMZjNy9)?xQ2c4ne9)bS#2 z(r^OTCtRYmt(PD>|sF@V7~CRtP_UC-7OK+JxdBsoXfUfK9Y zVn!l{+{CMX?f7Ofk`a2={ds))_sNoT>N;!s!CJQLz3i^;=2rJcA%6AVv# zmX5HH=u0C*JF9TKic40-wOzzx=r4LI2W~9n6!095G_Nx+Dum zDNpL`43l?a;+-Jf$LML8I#Y^gN%_)L>SJW}Y~RPUyul9SO9S|qS?6b@bT+6!>Lfm* zn!+1t0XJzGe{?F21&5n!R}6m zDf62Jrr2x_Nwr{nhdQNj7BmGstF*q}Lni5?SyC|>o^_8K9^nnE;SGLNf)9}0w;3K` z!#hMtcpS6Qr1cfMGFu8Ge`$}+U*dC_{{--T@*L^aG+Sfjh}qnwH<_ieI(LNl6wmf2 zRl&)IbL)P-pIHag!@PK%?K}Er8PsVNV=*sC0ReyZUc=bb7o_k<0dckf^2G~(0?1%1 zAc;t>B0vrhAkm0>VHEp#LzL=Cfb?W+(_AUsSr5uYM|Z+fD=#fuG)3ynF3pvmj_|{@ z#^ZZ^N%B&b2YY?7@$;nAKHhjp`NUC@Wr+31C!B4s6cL0OsyC)_uJ+B~F$!>fULdl@>P*+K2%jwh5%Q zKGdX$s|;Pc4DW3@+xA)@bNMsCGX5H1f3EiV6tM_IxIkfDS!YpuWW0wkS?v$E3sb<_uk&k) zVE2wV3|8#f!n1)#9daTzj`%~aVVl8rdf%RF>)NZlY|mhSWI&!&c^ME{<=X=SmEWWK z)=Hdbwy<30lEE@_un2+7|K0CQUz6P~VMpOt_j-ToRN4;BBvB|^>?{rdIoKNX?w>;GpFvh^GP zSA2SsMw?yQ(`fD678<>j{+F2bU#C&4i-ksUe@>$Xy8q`i+U+XSsF3kTAxGc7#WwvN za`ex%zeJ;G7PnH$4PSZ`89uc7bPsYwd2gr1xX;B_ar6v1qwWiivQJh@!OGvF&5WLP zKWezJQi>OZ)r~B8wRAwpYGkKZOGkxJj$x%&oZ<8u2?zSsnGM0~qa4JmL~NpfyYChbbC{GWzCa3XaNB2 zW5joZx1Y@$!&+I1m-56|zEyW12rrseVB0l9w^2;!^ z>j~EW=rA4ykP4ss#&`+Jn?tc1SHCeGG>bw&ejx-@z;P zYr+wWKf%7;+{81@PYx3j2_$Ft|4(7);Vusaup>gwG6=(n`BFwi^IsrM)|*mJ z@X0S|M+BOK^{G0YYf(vBr-Th%Vvc>metuIL@?Rw1c6W;^tz^BH|A{JnvFa~T58L=` zmcnDPb<5R)99|c*HBptDm)jqk0;ff7PjR&@tC#RW3^U$;PyN+ zrzKezn%b<@9-Fl)1Tj=MJS-Br@{I=)Du*=Mo2xIhe@NpKPnfF{kD057FtxW0u%I@W zZ070#OtLl`JlT~B3^lsF!MbljPvQB;M0#YM>!G6#-I#HU^#5`9=W$V${r@$=VETxek^if%Qe&4m`s z7R)>AZn9%8_rS8-n@&3?1+CX6Yqj9N(Mm-U@;_>mMMPmlOXG#B2iI ze{Z?I?ybP*&8Ke;pBC)g|6jsqxsPHxnf0_{I*I>du+gzwUKL}78~bqP$JCczau$Xi-Fo%OP6G$!TQKqTZmO!w0{%%E@U8@DCnd?xFQDtghzH8L-lG5s{-WssU~H})Dc2FN`tlSGZK9Tfut`2W4i0R5 zPiOQ16UvE^K$j;>m^`fml)7>ff|993<=;}0&mSk{-lo{M0Ze8pAGtX2U;b;h{W&os z>OI@My?8;Hw@mzZnl~%%+xFvcrPN0O?C%eXB4*J+C}^L>?)xX(XFNcT9XnOy`#QU~ z1UzW%R`z@e=y0~JJB~SyI(3JE(J55rzlTdIq+TU^Y{b@^_M{}3)s`3qnn<)9;2 zKJt?(uJGwo3S9QacS~(MEU*6uaOvwW!-ank=Zh%fXO|_mZ|xrf%Y^9A<94*J?%Ae0 zwPQ_JnN42%J1gMm>;j%s{-!sRgyVi}>f53Z>t%5X61<*bE(gSwJzoU@q=S^25E@Yy z$~H&zjCa8!a;P7?ERIr4?c}Z^m5RLSgB%7xi_WHfbfgiLVlvzHpiH^`EY5eG-1xqV zA$f~QF(f}c_76_(XK%q?YlmW{?;nR^;M<65afBjSwIKc0=wIo}CcX{KA7Nq}-WC@{ zLg@r&5KLJhRn86r#{-#5M@G(8J z7{6SMQ3*vnt9TpcPfKjKSW@K3VL<|t{6miuH!IL}!_B^XUEyXC|6^_@1I5P8rcCPq zm9FcapkgY?_iic45RQ|9qa-ZGT;gEZ2b5}jr@P7|V7q{GOq@pwd0W_27m0+LN?Ul zh~mwLWKIfPkqX?_ zCGHXaeLW`2 z-}w!jgnx=}2Uen^NI$xoGw<}{DtC$;Vrv7k4G2YlSpFu!BSHQqa9Dg0E>c=|PFt^1 zOR@4xe~;1l$H*!t(#Y67?~6VIXz=!d<-%f?E*I5$MB0j`q39yE2}O;LaN32f&9_le zmH)5+`CmJX4o;$O8ec-DXH`H1_Mu`Jz8(~ozg`tEjLvM9yN_KYj?GeX{5ZL+p9h6= z(=d&Dlrr{t3F2{X_XxS7zQeNi2s!o&s6ad)ki8q3RE@}eJs1Dkz!t)$XAr8t7}!M{ zj7iSih*0h`_i`STWPca#2}EbKoX0`_mh9asI*PzIdoKPL;k$Iiq0={u@J(boBJBBj z8u=rNT#X2%2a(y>v5s81UfG8s3WsBdZy-LZ!$;xx2s!^Mb@-=Tx>OO3%z|g~4qsj; z*F}c1SNbT{(_j*(A1HGPGL}@OuP`^HkD|Aaj^-;11TLo}KYhh|iPM{l(?_MKxE!h} zRfGGa^c9(Dd}T>S`lv?;SMki^Zm7&jIc7=ep57lpo(1=*3R5E4uN64AEv5SeJl^i#Z5S2x+bRdu8P5V?+I z#MEW|qreg;>mM3u6Gi})0QSs#Vi2hxGQr*-${+vPK?O;%sUU7pK?-dj?Scwnw5cG4 zP(kRLW@o)k1)(gT8#}2W)EL3bu7Z3A6~xY1!>_hZm$lJ9L$rzicP84hv{CG8s({=! z+KGs^H_Lom4Dx<-6Y_~6EKiLPk&=ABmcI$Ug@NnX{b!+IfLvY~i!H3NIJ{{k0m>panbu3dl`)0VQ0d=#bE%|93g$<$j8^%`wxPkhbmJ za}+}oZQXN>3+dE7cJL|DQ3=7s)!0O$n$Es+h(tvs5~b&h{toC|9Xgz7hx@I`--@ag^~eHviMcoN#c&h!^MUy|JyjN#tUDk=|qm$V3)S znoAwP?X##T1+phjR*gSt@l4Q{^hX9hGT7B=!Rg@bfPdPO1VVtkYjpmiJe1y}OdCjy zoI1)Bh&?qFcY6ntNX5XEu`7Q^thsmcFBPFZ5TR|>WK+DsVtZtxk zENL(Fq$GcK%Pt68l!PS|KM@-O>rOb)J_F?J<4sD>aPmMP7yTPZN!$0p!9A!q6DI{y zPvf&fMya*jzTLR~8TDr1d44}my)O@44K5nR(mxTChugtM7wKw>|F5hJ1!xf*mX4kFz;1IFHg~VMSrl z$aS`U1_wZQbsl+KsWT7E$hNzn=;Sv(rkMa?V{x)F0lA-xZo-U0w)t~$kvarX>NM5Q z)QIDTe`#}~74%S4m<}k~RUnj6loUa2iGxq!09G)FN|9a_C`*ENqVm8;Vvx|ffj#w+ zqHq+E>_>Eg1yzf;Ot+0qCJ5>aT?j%O`0PqZ2!?96CEC?P8S@0xAk?j_ZKQisIMZ*!1AwLxPE)7=HnRbf?KbV<5g0)@$wRj&{HE4rCC#!~x+OTRw z6R(gF#Zj#p|5!Vs+zfN$E5($dHT2eQ*1!;;fXNzkN2@cOR*jsJu@x$-Z?AGY=H3aS z;(IDs@mQ4P&UAC@cSH2Tx zM*{riM0a&fJ1g69!GUPhI2u%BqVeV_@khb`E%tn^ zIPJeRH*bf^bE8+*?3Yin4Rx5Cw}<~ehPc;@vs@=(N;WnQWJ~L@Q%1fy+`po@UOem+ z|M#l)$P7Co*&{RLwFvhK&1eU?2ksL)EDyqc!VYZ{o$bPpJiB)HN%UI9eZp$BlJjT> z_X!Z9M!hMzi~HoBsE+Ovtc;pA_leV5*?q!pdrfqf;65?qWm{xMB);q%+XkZEVYz%> z>YfBED#lq(3)X?=fI-R&wu=V^Sxw4fz=k$pE(dTad`SpW%=cx(8u-cN9kkpaaGG^F8n!z)@znswrJb0sEbZ_= z2n<$mRt$HYh*O?Ud&aZcE242 zeC!mp3xd*NB_e8fFe`fos`7EG7~{Df^hTE#W@>ub!<>few7Njn_lh`m*lnNQkWbRg zMPizhXqXsCwO5Gi1vq<;x1EfXW4vp}Vs^HntsAaasmtD9DraLs3`CkLKP6F2? z%B3(!<&@0_mn+;l0obK*Y*@`-ZV}UW()M*zq%BpQmq?ri9){R!B z3)ped=3bdR?GGShmtX#ObL6^DG!-wKXg|UQkCdxw-19EqHk7RPpSEEC|n%eyQSRK;Pn3r?< zk^S(W!Rb?ptJG;(l3LOUUp>Z_sHG<-$gB_QY$61p@T|-gjj&Y(U*}Agp)&qEyx~UK}f-#zvOAm_ET(i-;^A{Gi?a z3d}F^y&557evYC^rah*FsSee3B~9jz2GFMuN-2M>eY^G{x4$k@(WK9T%EmDGo=?g1|RN!b4i;?oDo zUL$^!(z^ab=rPNGzxDPIsNrt{2T2|)u?dwOP`;}LK}Pw&qa7>=Mf$2iiUncVO2vX; zUxJPngoo~L7k8;aGCRr(z`+g*3qmRz`elbOnGPN5DjKz|!H#QSu(DbA%N%91?%M-@ zcSio(Fu6L|W>v|#IZWPJ{6B=r!6C{T>6fFd5!L@5CZ0nTm>gNr29qw9Ca3OU(%I6K z%Ffny#*=azt?p>9f!*<_&F%;h+QIHP3S7wdtD#D0@&dM7!upj%lP3=77F#+PQ`&;% zXa&`DT0!j{>9m6E9a;KC`wG&d8~Vs71v1iqfXt-qZn5RR2AOt?s0^83qhvldOSZaU zFIH49z~|`O*!Z-y|L?;`-uQHJg2H}j-^O`3_ z%QjQ(-@uUDqLZgSYKu;m&1nwiwMENw9ehb*wP^B+&%kHtbk~%TG4ee)yA%rg0#7IHH!;i2_!IJ$TH@R~TL={^4J$F^0D^^K&f5>-7A7(H0m*%7yl!Aqc53}b7O0(VNDRn?I*D$GH=!3yPvxx@lX|P3kk`(p` zG;1HqgfM9?f;sq7bhDJ=B}pqvtr^{m4no1%+@?rTUAPE=9O($!p!l684MJ`ZYpz2) zIZkoxvj_nl^o{~LjMv=?Haf(VQxds!#FQUIOnC$jj|@aSc?N<(D8|Z8;xa{UnjFmW zXE0eY;B>@f7;lR7g`(*IT<71gsfBJnZ0HIxi2sls_9ZGDE=`C54ivRpS(LOEYHXOZ zTcxTok2WEqgH=j)L%8UqVTAPMjmRlX@GNLY`f}OcR4mVU5g|4QgtvqO`6qDJl6)Y1 z26r4YUPI<5D^!*-!0A=ZNbUTxya32>#(s06jx$};g zzvjM=|boLmbQg(6R@-`gfJ&s2yM1RnRA4B zv17LYJkKm=E`t<=mp6Q@C;A7vx^xf_{@p+z{FE*T-=WK|50d8kSfE6zmxY)bU2q6r z&b$K=kXY05B3Rg9X|5{{bUmv(r)66POR2<(%LywSWZ5E{NYKTzVVhqDSvJ6@t^44` zHTuZm3SJ6qFvI@mD-W_qhDfu!#mCr%e-IykyyO~Sj6NRB8OlP`hvLG0k%rtSH?A(9 zFSCXsdFyb_s$a|#6j5?Ckj?NF|cVSvXZ9bcNZ!d!c{th3Q%5U4u^>wResHDa2$yd z2lHAG4VanE@1ScA0U~3_BN1%Sd+`YFLmJ(9MA$5bNmO*>qK$MqirULS$$1|%AS)5Q zqs;>~u`yIHoD0>TLfllU zD>Q!9bbUPU*-Ru&!Y=mZSBxhpj6BVY^Jr3e>YMsv$Gex|sg#^dV~Zt`+m{l`?^uNo zuc*Y*&OJSIMai#A!JBe^UF7AZ#;L+=jLdD*tu0b8XYvL#HsSde{?IqC^)}>u#`_F= z4Qap6^gi^>W;`#$wtzRTk3aFb*rV{)piiIutVi#{vsUA{N)Dqqdyda3LNtq;;I^T* zQKkR3yd$+7cH)4WJAJ)Hoyp zE*XXs>1|NU#}OI?wL^Uss2vVapeE*xvx=*otlRK?fe|r9Dd$`FnO!&!8dbcA(|mBG z3Qa>U{>?X1PN+p)q1tMwtl#UYmyZiQA?8?sbd9fg>Z37gBA2ZI>sCPHu5RY~mP@TO z=v-l{zx2k$6PWUxH0RYwq3kmZ^Uvj!ppo@>87YPrQeNehpz3EM0D;BUag`~2_=Zh5 zqjWJK6b@+I59lmJWmNtZKrVtRLq!p&N4%bNVgsJ*P$Q**OH!SWh&oejdvv?qq|RGIxjZM#BEs^GFAB&79a+yab}{mUgd+> zGmn(rmVclI_6q1NiHz z`j{^76s#0ZcDW%nCuTesefiNuBuz&WK3iQGh`=lvtDa6_jU|t(r-vfwS%eWS0^yC# zs6%I5E%hCR4LdDaIs20*Rdo8mXu^Pvu(}!*_&=BP5IjiYH`Hbb^G2JJ$e6P#pB1 zg~a7J1rqj7$Ex8 zr-%sdY{D501hN$8fMJrgfxOUw|66$8Q)j3p+X%g~Ej9B^k%sl1H$g!STj zEV;E#Ryz5E?q$N&+apKR8T;#nsQ&uBwilua1vfca>FAHPf>9`#g>uXK>&4f7Q-gO7&fgQ+1TMT~WGz7fIL1YRmg2t|;v zBN~`&xq3AYy|N)(ivzM<9>zsmfNazlHIR9|bstHKgyD;TFA-Vcqo^U0Knb~bqyAb% zoi7`&bmZ_*w3|O&Z}9;MK4oq?}BG3#NXGe;9087TjeD$ zuRN7kp~QR3OROE4yyaze{sCH6giGG?s``?GGX>vf72=v7MF%_KlCj%mb;)6*6_H%@ zC5)6hXs@go%W6I@E=fQf0;!0Upu_#zj-q1jeNUiI49$$9R4h=yJI2C2=?l zUef%n!BSK6{Y{i)Tg>Z^j4AR5=XV2<#HjgtggK8;i>Z~49Fe{!t|;qX5VzF$MqWKw zSxZnKP4fj$gSFa;+gBS35}suAFbziP`pbJ$bA0@`=mN0gi}xiWwSA(sghL^u?t{FC z^nKo?90fEd8LUTKIMYEv-CRrzWdgVzC}=$I)vTlYc(7POC{zRC(s^}r9u1Bcj#=V4 ztB8TzK(s8?*MQw3txyzeBZ|kH)jbT>A5>bofqD>z5KnF&6toB`Y?+d1t(}-?t(%$1 zmX49UjICq22X}#0?t_3|0J%D)cbnF%`0Q~+mKw}}lycIc8jHcLAh8nMEy(_F!LWn) zCu1RN!|o9X{mDgZSv*%ewd(%lc|F?T6O^Kg~-F<#{j0^r$B z000)sa7fJL>{1W_D9v<~xMd<|eMImsJheuN&Zdor7J!J>p(CZMgM=%yRIJwv5Rv5w zZLm~}nv1@GwQsLObfSn2<0^>B6fjPtltydBeZFAz3Q#Oe zdDtjKP=PvBga9s6B`6z4^t%Ik$8kVL`-r^%+K3>}w~q*QsA%5Zq8) zkI^~%KpHVfO|Ze-gaO7kV^kK53I|hdJN-(cbpdP=ga_e@ZK9~*Wr#S%K$yFdm2=lI z_x5wwTdShkQjK%JewSfVAxxvSaHP3)eHcVHIMxPQL`kZ zqo{d@P1GdK!l(*pHVlDYG{$`gX&8_&D`k}j#GYjB!_b-Rs?hptt|N_VC`y4i=a9p; z2U)s@*jatOHJYS?qv(vcEI#ztdqH}zkZ=vkQGKm-AH5&x%#A!6p`K3X*c)hJ7C)!HlTz+y*EKVd=A2TE=XC^eDU+3m3V@be)<}?Np28&7X zKwqdFHR(tpAq&<>sr^hOjx~iU-BJ_}L(a@-2p;H?Lwlj7Hh~a;Xo9vO3xos&5(ohV z^H}!T`_3Vb{q5Yx_iTNn#YytwAMK|a{j|*N*vHF*+xtlM+2LaArm;`sP5Mc&UGHvq zDgt7E#;;skx)I%6shUsRCDIL;gcEOO8ri*$2+p*Q*~dZUa@ir(0_4V$1g0+3g1*hI=w zBG8^@uroHv$duj{nPx&{yi7A^v{h0C!8Qt&5JjNrBv27$sQwCiWJtAt77pwJsw^_# zUPftw+b5$7iIG?yzd%T~kC49QPPWeQ#VART@TO|iT56gsz_N*?D*@5yLH`+oN1Mj+rBrRYrx3qEL0km)fHkJeBVv3 zhDzunvfyhMiZ7tY>CjPVFZ4+0CwF>nc$P-srSd#>Snvc9@M;%J#TdQb#co_DjLTE=Ky5)`ljU5&5}fjO49I zkWi}IB*H%m}CjVDNCg1em~Zf zid7{GTbM#in97{x*Y1=2D-vU`?gn8K7<(E2#3;f^TC|UXD)>EV5d&z96wD+oHbeZ1 z!9Y#}l_)ZdghVy|wPuR3b)RIe`&RZr$TD8Lgvgd?oVESxfj=Q$n@wVUJ@|v9%PKOhCCYbI76{+LNN)hST{kURlK-mj`I{GL?im z4qV{>!5$cmvECL3XMWit_F!CR?2*U(Pw(F!bKkMjGFRD{H#dfD9V@*NNdvD{9g1rW z<*dQ8s^W|xu4WqioF|2{FJ-peHnxhT@zN$ixKdVeBS9L?3s2owF*-^5kymLh-C&`~ zQnc#&#TylOB}-O8m2vq78}smCO!YmgS(+xTSG|Rct7+0v)jC{!G*K#3-Gz%sCrKZubhwDWRT`w~hl{1RO6S-^ z|8)0a_h{6Cs&(gYFr!B8=bwuUHRh^bFjUqykksx~O0M@+!`MGG>hY?57g4`Py-dY$ zvD8i7$mUFz<_HUCvLlnFg{~UfFmiYl8!|Qf zF3`LtZEhI5dYkmUaVQSi*wj%HeFin ziZxo47Ih1|FdZsxIEfZVArf<()t)L+cTq_n^#7-=(dOL&rWyqtgM@7jomzR?&cZa`I!+Q+~4^j zrsQAUKq0G3Id~?zdA7ZqXXb964Zt%9LLHY~?`{e%>*iTPmuE%lGhI4er2e#*`>E=jQl9AIU4N=f3^DC7In08=F!bhi{H#AUFM zS<-{96G4rTxPiU?l5>KbVnLP!@SwHSSs!Mvi=s^^WdkKjxZ@6mfAL7h^d-DI`;oT6 zc#ouKxwEBlu9Hw79T>Oe=bq#3>FomXwyi4Km6-s`GF2tns>*}mN*=?N0WAF$=UdtP zcS&n8!oVa!+Q@-7O9MbFCEIZZo;SZ|TtCO=E0BbqMx{Z9@+B zcUoZ|xjbT91#R8DF@Sykz1w8=^u5w>c3Xzj4^a#=QdBH$p7d`4{}CBdnvkkuk7P*C zI;SFR>l>DEwa1W(ar31EyzuWi?8XA=-rlHU+=Saxdzf+!OxpgZe zLX2)dUj*@nx{BL!qy)agZ;xiL5V9tn-^QskwNK#x2QYiJVUCtg2+hTRIDRYB*)K0> zyggQ=^L^C4mT2jDEf6nyI`e)}^Ssk=+Rth5?GeO&E_MrYwP0v!p0DtH!A&QUI8mfd z=vR?c=C*)$tp0JdCe_E*nb=Ic-`$u`mhEcvYoHH`Kd~#^6c%` z-3GX-fo%NY2^BxR?zYLv*s9`>BQWhc?6~(|U!^i%ui{MMK|xxf*I+GWx^glEX*pcy z;@KhiG;)K2wPE;Ix4b@0;KQ^AyuZFG$=swdppL}*QJ?9MD*a>-%437-j5TA;Zr$4+pX>=g=e`;Vo8qlEuxpMMxpFKE@I!TZVMhJ`3d?$B; zS`Z*e&xOVPwd0MKThWWLT57Bo(Hg;qU~Ok;ypJ*j4FR1Yh?HK$Js?6*2nRF}2{fb; z5J1C=*8B$u(Dwx87Z9F8|MyeeA%SBH1W&HlEpjL7@G$pKaJO^H^@^3tBoH2;3=g~r z@x3|7gAN60r9%U>+^7hx8V$}t-39m`_e<-H`UQf!AvnaS-GF(}88G*FVE``hfQAa4 z`9YN%M@Yzr16)C&+C&|2K?&b0|sygcr$lWh?YWDQg~6~I5qe~kBJtQTf`oi z3CS8x^^%*C<3vq(WN?snExyx=Y6-6th#k}yrtcn|}Btc&L z9s9lI2D22W>Z?Qe5sg^)u%1TzHi54U(!&#od+S{uP&9Jmf;J&m4WJdN zCmA9fp=#4-mWpRqOW7)^PGtfV$|<5!lP3+Lc)l8)X*7$^lf3w)Z0ZKdhb_*Np767R z?CA3q<90467aa)Ppz}) zDTO$8PO}aHlo#$`-`#{prc37N9>ZsjRd>8r;^OQw5UVbrfLX`|ThW zxLz70EW3kEUXMUgO9Fdjy);(1c!0gRUP|nH6(d+xF&arT{aeD3(o1xh7S9cc?jCvv zo3^1-M+^=fnai9D9Y&2=@M54}n>%-Pjp8TW=@e z__NxLQm(N0b~bO5v|0GEf}P(ag$PNvGw;m+yBrO_W)PdO866D0ojti(n%wsA+sy#q z@gnBEMM`v)1&9}sU~h|*?3x0AtIho?UfBXwPVn8xTujoF!rU2bvq}0w`1nmW?BFBfF(5uCmRmVJvemmUKv@akvB) zj9ZH#X<7D2LCV_-<*jgO3VZxzwEWH<=@yn-Y=50(dtGCD{d2K2NtCPoJkGS89X3nh zyyKVUFWA5IiIbZZp0K?Rcu|_9G+7m^yt&u$!`c_^AMUWd9%Xwy=_UJz%WZEw96#*! zvJ|V-yWG~|>B%&dc}h1u$3jl;T?{$>&}8OOB0a7=-VtZ|m-2WUN|$2b-<3!y$~y!D z*xoI*y?elHf0sSh{w~e-&Z*S?F23!%0rI;=!q4kj6Y35+jl&#hvM2(!81V=s3R4cD zL9QS)KD$@A&y+0zrg@{2uv3R~bjC~#tr z%$KxL2`=1Zgd=E5SU`^fY~!HvWdI|c4sR=~^Wk1qS}y!g>Mmb5^Ma`~;LbwAo$xj@qL zAG7TXq`Q0c5DP;tK7QizBz~LPoe2wJgklsT5X9%RCFRmIr&|&+p7+b8Ag55eve1=3 zUAeu1tKM`q^bM(xYdGEl`n&k8m zUETQ)RP-KQJw{ji>1r2Uy-HW_(^WBDS?KCnx^ml#D-&G}#g(xDSG#&}`$|;E45&I8 zTbJL{RO=~N_cW-E!y6kxmHQMnyOjw_EdA*$*n82ulNRZ45P- zYnyZ9cB#IKt#{3ft6djZX8o48T2a^4Q+WR?Z@qSbw^}aq)`R)IT52%x1NqZd90=h; zqIao|$G)wa)^Zp;IQWnYa!Luu;hv75P(R|wKeKatrBMD5^V%o%S9>N}S7^BXzp#XT z(m0ndoN`+8ap(#HH(1F&$=VOE55TvhR>4b#LDab(ilZ>_T}i6rb@}YQ{gT!wr73p# zxgOk%TGipWI@iNCBWR+vH2=1iYo~fZ4sa@~)!bWlDM`q=1n=qP2|~{0eBi1^D64f% zFFRP}HLBIv8|73ecTZX4bk5q;sxjAU__E#kIb~Oy-OMfPI4hU8DYh1U;PW=da(M-@ zmOxJOuxxeGx9)Y)A7yjjlp@9NQ3E4QA>#<;&snT}<^=FzE}N zUc`}{t#86lRj<;k8=MeU&y}f*o#lT{%#D);2_*93MMGI_zF%4GSGjTO`$Q82GHbBN zm6@UX>VgsTD<+h|b(&-8>14VxoVQ+zo;Qe)icR$v;eo^{sub$;p-d+1rP)-^Q52 z9zP) zPPNn@qSvQKrM@ioLun}ctx5_I{UC4Y(?^){*&Clib{+b@^q4FB&E*IKWqaS3f?Z?r z{d4I7?A!OH0X%l`eFC<*`XecZP5MCcRT?dIwKd9bRo*MA_ruUI&)z!>IkP&M z9r*-bzLc!AyzNs+9(HIXeTFaZu)X=`XOge-`3*qa_IXq?(|(T6VNQ}ee)ey%_!sn9!0pp*McPxhNa*?74Pe}Of~-o zCq>OZI$U#bWBD7hM4*MCnDahC1nCVWELN z-XPt}4%EY!*w0Xb8#F$xn0@nu^qSMFsEchpEk*HD*?Xs@d$kLSL3%=YQy${KNq<{~ zT@G_>&%VYH+&@A{KQe~B_oMWg@Xctp`6nqtkmA`}KS5$XGMfGJ6VUI%Smte!9v7a} zvu7;Q^FseIY-A(KoqC+jYlJ3|r)N(#LZ&Yr!>%++lZ6)_XK786Kkva7G)Xb81?ax| zTpTNIlCFo0i2+;pe-U z@UD*VP9-T$T{gLmGJqPiZ_3gCi8-4ZiUMw(j(q`o<&^71bL2O z&s>%g1(z`u-&~fy;9WH!E!lPb*}?DBw?1=48mc;S=mwj3Kpic7SHl)vflYI~j-_1% zS>CB*i>^w0ge6g|=QYUnAL=VUz9yCOf?EcA1 zx^W6u>BMs$trNIP<5C3dJ3DcGC>rzAU^D>h%ApASV=GLZ5k_Pr+ndiB^bLEl=MLNB zL?qr*BjFxmSaK{e94tAWr7juVHtgl=6Bvw6BBYIWqU@ffbTZ{3rE?UUn6;XVPDOny zdtuKX1_p{`1ZoMT^%n$FFk%r~^Z6XhM_gs=L9X)s_hEF&NYYoY;zn5VolHEWnt6TK zev$v8>5KVmH-5pPJZvE~YrOZJS50~O{0ly>pC1b0?Obs}PRKW(pI7@Ct|v|V`ur2G zR-H#sY=jnTd0y?c^TjtB&cEODxAOytiS`7&EP*zV6=JAdzXEafdFcnpjzsq&?rA$* zld5k7_HQ-kKFZCQxca>vcb|Fegv3jQ2!QSVNHAxb$RE;c_?8$t;tHTYN6$~zbEfCW zYJly?uK_Yy9+)O)Ocz3zL}zYaB-%OV#Eo!?;L~aQ+tiP_0Br0sR8Gp1N;mPs#~XRo z$FHq++wtn^Pj-CDJKr@1&6YN#yFPj6{dCF=?91b>2XCk+|i1}X+2NdiIg}UqGPB>!^)*KIrluwlO zoa4b=|Hk4vvi>R=Ocho4%p7i;;}j6qQ@h5_4cEG3DcHc?UE?%&Lx3X%Ixd=)Q4ZEP zMP<>l87(gj=s~uXk*ZTxMm+D`Og8NCh<%h-Ot`W_T2 zKlF1dpMVuG66HnUBJF~VK$v(sXw4j0?fYWzk+@$3&Nlu1oF&vd&EIE#CZ?@^e{_c#{S)7ayfOXk^8A95`*y-;}+=)pmb5LLlb z4$#0B-olpx43(!ijeLN`ix}-K?0jk#FGi?I`68RlXmEDB=dIND}>MQhdpuW98 zZF@UVUk>*;n;F!%hm(!^ZsEKNPqSMK+`@Q+LVxSl31xNbRAqL$yC*-er8Y?b-3{2$ z5Gv>!2=$V9(+}%-h{MjMr$J{O>27^sE{q>SnI#EPg>?-2yULKTvwmNH3)<{>)}z= z0vAJ;86FFcm_3oXURY#*t^?qsog254x1$2B%s%qURCOY6&z=E!%QjSE1XjY|-RU?c z+F|6sGwSv>jkXaJsAgjI9-PZ4YMW75 z2b7|Qys*4U;|j^iJg^uIlAV%R8#Q+H8uq@UlaFDM#(HxjRziro;>C^Fsq)V@qw;W) zK1q8UqQnqGTrHw~J(dg}agfd?0}l8IpYCKWX%LP>q%G1dh%%bOXF6F^YzBgpWE6O$ zmnr-%Co5!Bayps9)19njFt{C75*?5%D8C>!Jj2NvpN-#rPS%4e-xcB6sZXY@9+Y5&XP+2;NCLG`I5PJ75#3rtDv>%nub_!1)$Ce(J zyhaBz>L!1DcNKSh#11EU4_b+8)(W6`^oZr-@TKx0h3N0x$}fX|=IkmM6_SRrv2Gfz z$EMcA6F-B*m#sd!GNxK<^R9^zWWy++umi^a~lLOXIUZ+u6T zWqn8+=V}fLxhdmvLGjLvD;LsuM3kX2o%hQQ#&rNDoS4`KFfo$g6ebo>O5%u}iCqOE z{00K>R({}tD=MD|J2Shik|IJrR2W(lo?lk^8zbJzPd`9_rm+xbs{@l-@4(&qbXr6H z*y}_Xr!Y53W^V7?+dXr$Tx#7<+mxb#VWgW>`?X^j!OxOmyeW#A!k4#W*f+qi*;VeQ z@JAgmY?XpxZ)3jS!RDAYEHeVb*5Q{AEX(h^B7EIA>o)9n)a8$}Cdhjp9k9%3Y)7+9 zoJl~1Wi&$t&(NmFHx(>Rui~si`t#(-}M#&TU&iGeJrMaZ+3!I7KG7L?G{jV|hWqi42ACdJuA)&=h78SEpC& z$eFZSsxCz)sw@Gg^(BR~)+y>>Bj{Lp4%ibRoul-P-pLXYj0>^=YS&9)Ym!+}I;86$ z^!O+V-M>6`Lf6Clm?rDesAjOGlaM)eb)1Q$a4JVqIPC+H&OL6P2;ML~v2vf4MT1fQ10;)yN?bPZ5G;(6%+cQ4$#y1)Z@pr^@HHSj42CKG~Dg<-Z zaRVUdNKJ`#j{pmgc4XaHHQ`|0*tffnaFPUr2-{vrT0DgGryE*j`*U@D>;3@DY=z+! z$-uj2k^ODiFt+YhDXQ(};H%O)Hh!s#H(Sw1^PKQ~I`j6@#Pjpn9bTG5q2M5U)=TrG zu<#%Y@z#tNW*lUTyfyw#|A^rGu$pIGec3K=%{HNYLB&WPjg}WO7qB~gHP31i7PPWV z2_^-Vo~8S0{3`nRX;!ENuR&~cfM%>}wXi5dYD7LbnX0+Nk8;2pnuFU4-_3&l? z?5BB=-_2HPH4nNz1WjjK;xKkWtI@UHga&G6Ib9k6qj_$iCdkuch~kLLL)!lU1W`U$ z<;C^}YVLP>_k1g3mW%3Jgm2zq(}Fa6d?AuES`^CJH$J6L;C?$YIy`e(ox)}WYi=31 zk+6q5?FPqR`^G3&qXYOUL12mJJHjDaFc5a}?r{aMD z8Yf=(D2qKZ5ZJIki@i5cGlbvFei^7q8&Uw<`uv}G_tQUc?ng1smww`>yo`J9%R_O& zw;Qfcd~M^jjgvM{`xp(UJse$eUzkR(a_s?cS=k$X*~<|cou`>lG$GVP2ZA0$vF!WD zB5j=}N_jCgRL-0se^KYl!Xh>Oloz{0Oe%bVuWNKuz1hr2O)^D6!F3{56h~^JRFUiH z)ht&KCJ?u1$cxm6sjjBLqujDooWT(I0k^CG&(sO76|I9cUkb|i$pZT}O0$2u{A#Yo zL}DeuiyXXaX#I5JDmchl_0tisx2T&Bfp$XdtXw~6)3l>yuRe%nF0-c+s@m2|TqE}1 zn>7s6jC65v(w9h1Y)iPapQl_1V{8OU$=3;?Y~pZDzc2u{W*z|90KgK2UI1WU#Jvju z8wS8uQx68HE4B>R3{uHqS!j#3j?{RuuSaTvlqaZ+p3sfly#1y|J=ON6fI2x>qkLGj z%~>0b#9w#!nl1%!9y5cb2+9aayN|qs)e+IbVuTzjhr|ZY0vK5#3FaBdR28i9W$qW9 z{CYng!EaM64SfnTdveylF;h#A{)10&W=jTaNufAY5V=QclW??|JB}=bIxL^3Nn6-$ zyU!alIOh7K^RS^PPdlG8(D962Bat*k2|TCByz+huj|q0;W`I8a?B>EoV;rzT?4_9SfD zPRFM0k8g}SksY7$$V{c&60>!8N4p)Wc2xp|kr zb?CCwpX+jIr!JS;x_r=~%NLa{5BS|KPezwn3v=!llO!C!zRJaXX{M|Bw<&7#bp!yA z1BJAv+kuVP1YjbRu(P>LP|!;%K7!SL=jP`#JzklCP-XHhAd{32;pqpQgN$>bPlbmg zcLY2jZsdl6wenS1D+^q?=)(zmRvqOW&!mZYl2feOh+(`Ayfda zloS+@b9IFxT?pnr1#%WC+2pE$8J|8n8(bS_S_92Kgr37kwA=ptA>K3(oh35Sh>lPO zwD4KT=>)T!1sx%gs|ocMW93o)CV^j|?1rEQP|D4VlJg`ZPd>vRLTiIl*dvoV_9y)(2X0^h3qXj4n*2so9P$0$vR zw79=*Vcx~Yj?(zV;`Q7$00uTZZU|~+7aL~hc!gm@dBu1u2vSkq-tktBHVq1{Ee9p; z+#KP_*hFlI{IWl{e-JaFhLDC_Q0^YwRDydI$Sm!h%)>mrJ5y=t@I&n5QJQ<YrnYjAkeFnpuM3AvSolCc-$^o74(}KJWv6*(aXBm`(gLE)4&;C$WQ6 zj%b)GD0Z!J>+GD`%SYm@D)-X5t4HGMXyc|cvP4EctZ&Tl&uP3cv9dNaKd$z|k+`|z zf%;+jadp2QiEFvAQ2(1JhrFCl5f^s~x*rQA-RVfKvSBzER#^+{@Y#>tbWO)b=o^dO zDlH8I^|gWcuy20c;hx;SIf({qz)S4PXw5*u<0Td{Ml)Q*)U;Ol=d>oVd&g+}T?>If z*$I&q#xa^(c`-cb-~*H?VH5i@Rx<%CykDHAzY(b84dX;LIZjWs$|-ndK8nq@mQ_6} z!HCLf8?v(hG}_d-*TrfnbhcWOZZYYU+~+1v^<->IlEIXTEY21|v3@ne@>Pjx)?f89 z`2rQb!CIjZ8lzD#8{x{#!dCtnDxYKQP@E<}cq5GcJ5J*-oD++?t+P--A zt+T$h*hz0$&R5nJie+x>)_6^ju~pJpvXR>tGjG9LLdQ!Pg6Uh3$nbRE>5VF%2wFkS zu&+J}>PIU{ohO!3Hm%=0H7lB{1bs@NVQ=GCqPgkiG@WH8XDvVu9$Iad&+(32$kR0| zkXN+Qa>{nU756@d%El9d-q@Ig7rzR63w9yX^0dHfOCQlPtzDN|W^q^PE%%{tgP?C5 zjMmxKYPRKoTAC_QBe z`WZCGGR0s*h?Xfen2WwK1`#q?BY}CGo_GcMkbKlQX&w@D${D1`!O_H`hVv+NPh(gqA88Vt^WqhhY0_lMgz9?uEf z*U*9Iuox1MQPEH2&W#p16Nel%b09|5o#v)&09M<@t5-mY?c&vVh*$U7gK<%$RIFDbe23KSx zYS!>Vel+_jNt56e)Q_9-D+G(rc=zTq-o3C62|<5gapN>ojD0jv=)9Cnm}>^hslDhx zbyy~`|K=nHocmdI&cHu&sG>V}VGgSn7L?(Bc z9+4^D0(wGps1e);LV}ghO*ZD_Y2~DFcDf|fDLXyd;%p_FbO0UnZ4EYE2L1XMv@gq6 zJZH|<)}8q6z%PH2caCLevULOurj0w2t$ZhgX(LN+Gs_!sngf+3d#ME#$oA4_+ihc( zt?a|`nz58;x<-wZXh(^1ggINQNqSG^S>Fj7KjRC>5x(;FcsT_cnTW&TsDmrKrU8ty z9!#=s)vZbjG>qDw;No`loOti;K)+NZ{6n^u%9-$_B)vF6f9V*!*C<=35=`}2P|-LS z*rK{SC$LUf_DLBxqX`ru@Ka&g&mSV#B!@SS2%O6;OL&~00aPo}19av&U{@O)={gJ&&BO?w#_qTW3p#8g;Df(_!~cxMcMIQRLyxS*qwas7Elkmb7Ru~_nhTUj$`cjFX;(L}`gB4lw;no=Ut z6%lu!P>p`4)-)T}KZ9V@ zs>0=O8i0rN&GUP>ZB|vX^@?k<^|EJFT4j?@TIHp_X_c11w919}03=JFr2^SP-tI<7 zGNg4|3Zubwu;jN!vr}mr@7XePdzW6R8mynEf;(Nq>EnL`k{3U$&%*bM@IAtxm!&3% zrIxBC`X=mo9I;j^4aZ{nz51t2)C9-B0wfiWPUx+hisW|-fHSyP$BpZ4ZrwReZH@1( z7P;Q)sYt8`RX-KDN{}On{S(u-?L$3#eWE5}Dxe1wXoMqF36e>GB$cJ-C||Q}SZ`B7 zKBp&0kHo^G8KK|| z1PY#po%lxt4ZhN%03*|#A7R0J617w2@cXJElDq|wFZC9wucK1Ir~qwMrXQXVmuA(qe+^6JxH#8Il(PrrOKcm$j(jDj4>|A zhb*0>$kNS`(fZn7*Th*GoUK}*U)tP6J%Y77n!whZ(1ws_C?igtmKSYmM&J3vM`9Q8 z8=j3s+lh#iPdJGYAJc3~z+8ga zZJM59rizu3-F8C7?vVakA_hED0w84ewxj9d|Dv}uUJ z8ibB;YYk<{WLP$8w?RJ}kxAEPa+S^Zah0=vGFWFJvaB8qxegn?Cu=0*{jVWA8`d)I znIZAx@B(2*OHTyL_r*TNtK$)e;8!a2$(@#gHfm3;i3OjpTNH-{=dS%5B|T!KlWkQeuRJQSExOoP^=-&8fB{Ytct504rRq1&tjl&K(VPEgL^18 z_3`;}^{u#v0#mCZK^pI&$kYzO{RJZmMB)Jy6jD?;=cKaQiZyYZIIgVj?KN?=2iL^$ zyrHbVYE4|-PF!PaLv1mxUq%_{#4<}^T4nt!DEB&^?OhWGMi`ya0P%DS=tH4I8ddL< z2EHM+09hVTo#%vyE$r|VjgOn*B1EE!BXOK>ZXEk=ipE!6`!ep- zxVtt*<2Pd`Zm>b1wiy3Txp4--ibQuu>@p^Cm5%gmj*2UrqvFfbkpkRu7gu>0hy_Mo zqyD?r6ptu{AeM8RCeWA*--iIOlklb{chtpkR*Rb=%z$H_wF@$tFr797?>GoFvQh;c zC4t5=910eufwJ2|kg1`(OTPi0_U>^} zRo(yiIcI>G;hZzbT|l`iFoTywyo`ws;sr4^ku(75WdSP(FY*9MVoe%gSdeK?SpC&nvy_XW0X-P5F3h5skb}`yQ@7&Kd;V{<}8lM}#-4&`#AdJEk zuB{1HYCj#+WIH|xbr9zn>fvJOl5G12HB|%gRTq4fFz{CBC$m$?dq)q;0%3OUc&>!7 zO=l$9>D2IVXC`Lpp2YbT%$veXIvZ8JDGv^68c3nh(GgaKVmmIV+c*TAE5mEK)C?#! z^|jpYYSa^;pd_8pF9^*{?Ir*>w=|E1l&ZVW)llUkm?bsDw3gEZeJ<>6&KQdM#5~c6 zGuuEXkd)dfB^yAex5zf<(MsUONHIz_IZgD-#z1Ja&cQ_6Ag5w5&TlTjEO%4O#St;j z!_Ham)lo^MWlgXM9gY($#eV_I?XW+91#6RBV#qM9CA2$%@ofW4(ggyB-ANTAJyfd$ z)O59DzPbwWl3fmT?Zwg`62oW&PKzK#rN=qMp3wkiQDfGkg>@)*nnmfia}khPl;)aD zqTarCT~*q*kP{4C+%-)1S|~32V5H@(=!Iw{tqrYcfIhZ=0#FIs@M%LE>0e`20-gEJ zkolOVl@3LAYr3GwpelL;TcbD7fnEwX>)+x5(R!-$TA??H=30#q+6upag+dm}^^Y*S$;YAO{0td1{$FA?|JXmnEJQdvX2V-!Ho$?|fIr2o z)E2W%LO|ibe~8)WLn>xJe)tbCyK^%LSG8-0w6bfH@ZT<2kvBF%JbD(>vS zbumtAM8R{R&Hf7tmRhsBKc(O!@BbdpnP^yz@!OiS!KZu=Lfz9Lg(&-+;N!rt&!6I$ z!64e2QThK0kJBxJpPImdRPC*)w=Jj2-(%-g6d%?3-9d0G0^m#HRB!ky;~bpop8)er z8(`Enq4Fk9CBpCb9!}L3nCLh#l~oSa~vr>*`-O^Xn8l7ygqD9LH_%Y>d#x!};HSboMJ9h!d@^@0P&# zcCmeriBYjQ{LcK`q5pIHz5ee)on8Nzj!Q7xNkmEznD->Hw->&nv!?Tajl(s2WD;}{ zIAmV~%yj8YH6fthD!_{E!_K%;1)qMTR*1V*lZh4`?ct36-16LH-T_$hFhk>N(X)N+W>eskh^wsXXQx^XEx(` z4~%Tn(D@d3&Fr`78#m4DYRbWg!4io`#gokwsr-&Gn;eVpzMpDNrS}HZM?5gcdw+Z| z-Aow?>v3WhQ=50!zA?Dz=vY=dSu_o;dKU_42vVy&VG#@yl>NA#RE^d9;>~9NEiepO zUIhR!OBN4lPQ6%El=30~yf}K-oS$)u?!rqe4bWD-^C1hIg0*4M*|k+&VX`ER6_pu2 z;Nlvfu^J7H)qipF-*N~i^H3gmT9~<4UElDQ2=B-o?n{Dn^McY=y8`K^I#G zy-9yq%p7t*+c`z_&F;69i%W-SX%E)v1&7LjIo`7A1YQdh2tSHme>-B9ql2K z*G=dt^$41s>&o54-vMM$0J21C+oBZ{0M7es&6`}vb#F!OH) zhu|awvGW@jyC@QJSJbnefD&XHo0KN{w-xk`EK>K&m1$z+L=^_1oANac6n*!hJX>nt z?eE=4TJ`;^z3-N;)g(|sf7o?f&YeEb&bghR5a(WN=6XA}k)rZ(Udk&DQ2aI6(^OE} z>}jn)nbQUobw@zZa;L!S)(Q=6Vab1{)hA~BwZiBY3+KGIC>2y&fb89U?9|x zIGR9No`6~P=rS$Rd33pZa={MsE37{A9*l=P=ddj}1v;Nb-L_rG<@w1`4x1>Cp zz-4V3$a(emY}a!yI!h|RaR;6GBWC^I(SWhd;8khV|L)-JV~@PYnT|b~ChARqvh^_} zeTE$q&ISk*s+k9pzxcQqOPC}7jXZ}Kr23xF=;3=Z(bK-IRm0>&ZZYNHHRV$UB@n!b z1~UF15LA6fm6-1(sEE4*`KwvnKDiY|;TwWI8ls&OR$E?gbu*~~ygc&zkt2-J0rP>! zZ~(#WC4yUB2*KTy00-QEwh&@#TajPI-vIs>)R#Wt_i$6H2OVW6fxZr|1F74GATccT zb!kJ@NRVY~uL{Z@C`4mniO5fBH@08^=e2(Glrs*gyP-T>Qu=~J?ZX&rXQv;2^u6@+ zck|GC8V0ukUF9zSkbaIN|9&2jHee5NB+zn0`5XO|uYgbcqWpV~)UgdLDn|>aYw5W6%R8&DJkG zg3}OLn8*3+`iv9W^_hbqkb_bk%3=6S)xnvFj(3@GQle86becZvgLHk;QIEiH(FYLd zq$k4O>DYZ5%EyVyS;t(fhnQBoPru3VRaSLBt=7!iHaKuj9O2*rbl3_9ZFip|alfDI z`)MpN$W?|xWPv+TS~zk!U~1M5ja8uN_eqC^5egT(0~M0u5k73d2l=4W*#0< zP4sGu1RPjZg`@L3Lc(+S?ypt`xL{d%O*B%s+PPrcmGMUlqtXFqu#-Ee9fsSv!@`#7 zQg@TKFeU}If)Sq(j5F}bpPBYPzic<{LHuqp?b%W})wK5pcFI5dZfAr2JKrO?@a(h+ zZg+h6FP1P%^yc4U56yx@*kt%Oa5zv#B$beBdNp(e2p(2Sk=Z%ukLf^+qeq$3Qve4! zQ`(JYe|7C1HYu z{@v;$joY7?2-27Fsv4N=4P+)z%>!U1s(GLl+oy9IS^9Obx46}!p!y8k@QfH6T+sVY z+%=9XfA0$X1YpZthu=H#o9w9Ny;$8dVqo?gcqV$N%^PgiXY;+GCpO>F4lAiagJff+ zGkZzTJ8{dF^5q*_)h$`_`?{ZDyP&qim+<99s7t!1s)I&2@bIN%bY7put-W^tLs?(0 z1bbPKTv2->BimdDg;xFL3^Oz%%FTlrX4RSRLWY?XB%8cOEKF5KETr%`D%|#(Gt9LfnP%Hg!D!QDn(Gts8@i+<&j%LP$KabcIiqa_IwMmf z>7^nv&9yM{){aKKp4n7!LJ;g)a&c?ho-XmWp5Dw?`g7RlmSE3fuZ8K-~k_F6E^!rMv?a zt_pk9yrX^b?|fuNMzN3Ph~WdO5;)aIhV(g3KbfZzIMq*P6toUHFpmeu=BHdjTT-pv z*M>9~rHG#A{U-?#B%t2Xp#qY5+CjA`_zY5ZnvRyXxAW#(M>>x_H0RbI`)ZPltD}=tUo_ znPI*XF~eNbbB4JpafZ42-0+GE(`J|(z8_xkL*5MYS~&DR1?1EqF8Q-!_v|h16}IuN zO=MKIZA`9c1cg|k@S|ggYHxuSQQtJUsUVQseG9Da23!bgphn$}I#tkuQn(A%f)#7Y zx`s8D6pdp_0XJI@tx6;GS^<1PU@`QBw_b)i7UlGA(-|)ASC(9nivQXAn&(rp{Bh4t zqn~Lz4V4a}plrwgjGUa)x+P1_5HNbn)~lr2G;)Y zm;CA}tJ8tGFv$@j6QR3>4mk9{fBG9eNA0bO=%v(wD(mtx%{2hrmMDX)ld~(X!>nE# zBUjX7k=23LNq7va#!ZRQdGSd-guTN&o0)Cb#H=F|`Cd?oBFK86Qja>VgC*1^j;zS+ zF=+3_x8RcobG>b>Yg5u?kSomiRik0RjzIe{BP+H*ZIIc6+gtZ0{MKOFuj@HUQ&KC+ z6;;FX;!6d1DS zOHI@xvi#QhVp+&5*k4FJTmzT6#uG!EZk!w11V@tH)DxUvSjfv_AU}hxUVvL!*uJ2* zEpmD(H6l96kn7F<>bn)9vH<`l;2B%oN%PZYReKh1e{L{eHmJ{LXZ<&QlnvOTY{ zxwY*%`|rGZZmnehZFm9iFXN+VKoXtL24vyJU-)viGz*UOk-yp@4{*iq16u<5O4KGp z-UO}PfR_(Jn7^>aHlTWX!X{8e-setFPqGV?VD!Myx{M@mvJcYY(2QNB%vQ zvr23)6t!n_7Kt%kfu9yLJ**woFs+ca!vdUFV+l6G>Os6A-5ajyPqSl-#0W1GuYzK? z9=7BXOckzwu;xXgIn0)ZWs5e*>PfCfq*g*Y1I2`7j|O-yPh=yPhy$&!EEeC@c)=z> zyq-3Kw|{y*rOV(_w&po8m}O;)Blri{p=@!o<^}#S7L_B;7G`y38*;?h{+}cC!cORr zp8_`z!T~A?E|;WGnSz5AwEew)0O$9_d({;a2e0h|?$D0lMIM!`a4RrfG6KuA(M1QGGs%`$LzOuvAmkEbhuQ90w*NO2@Wv7W{(f=K){!2IOi{T@_Zrg@;@fvJsBehayfn z(fb1sMx@hrcIIJ*njyb5LyidrMz{+^#^jh8P#Ov?Ax#Gv?P#HfeTM~#T}R*p-m>-C z5(rF~(_wkpmc*sD$6&L*!z6%P;2ZX79uA;?G!-`Ni^J=2zb=H*Lb?~iNx0tTfo*M- zef86HY+s2uFj(y*24caet6%l;#+0;ni=ydrPD9&Ok5j%+sk+Ly#W?4NriS%gE`|$J zgV_lNwCDS>`OEES4|+umAJ+=)Nx59FETFxwrhQ>ZA$KJaXt#kOLO*jQaRHZ_xp(vF zpRsu9o%;)KU8T!h&Z(%wN~aZH8vMi69hj1!6=IL8UIaMCG)%6KAG+#F;}A?3e0a)7h08xls7f6q5o}CB&=PU2sgLsV)HP z8V|*1F+2&NRA2)OL|>t+FB?~Y9q<3Z#lSaJ`lEiRT~)~$W2eJ>z)2j;2`~4EE69R1$`qgS*Ju2yu*-v$#PK@T<9De~zdXh~&8M^y)- zhDe2Shi?zfLW^$?-aAK4>ZU)Mr6}ht4WXQVA*Jg<%V(FHlpCYHu*M%@3tv`c&(nFEfJ2`>|5k` zAGTrDUEl*-0Z-aKVq(-7K4#6UL?dx7$}-i$x%#|lA4#h@P<^ZJ&cR%D#k83NbtHc_ z2TwRglF-#Y5<2E%Bt_+~o0L7^FV2yWQl}XHAgqgm&HkD<3txSAB!ph)G~`2#)@me* zdfrC@(`m#X{4Gr&3Ohh(oGyx{Yj|}jIMGWk5+pE?Eym;kt{Qr5!b}TrdrZn%EEgwm ztdk;!3j4g7Va;8jCba@8I_wXC>gNCooK{sTd*FZprgaejT*f;+m=-BdSAtQIo11es zO2D+#eMAvsCxmcKQ$bVCZQ+E$VXRnZ-wJARI--yVv|YHvr8E@0tnLxlrg`4Hx^<9z z#_27$X~~itXxr#sQRNLE+YxL~kr)*TA7*;p0hxtpHPdSSwo7xb2@o%91`ct`o$T^U zVz3OMAlxB`R*z@FFGFVnxjh=TL+1hD=SQJ7Ccg}*9>_1FivGhdgU)oM=TPN$hi`@v zTv)Y^QysF7<3Gxa%&?9_F7{FDLiQ)!34WK6oPWXozRV1np4!W%gMmL9ax`9MhA>aE zycgbq%cLO7PSe27+!Y-fCq_e?9x)qYG|3MAJmd`@dJl%v%8@X$W!e4(vB&*xUzqA< zpPy=6gujQNE}4||c7kFdir87W>cxgCcWJ3dYg&R*>URVl>!76|#|EfjE7yp@Ei1S+ z&A0?{O#*wN2$v(<4ijJh{}|@)MtRfqp3Osbr;Y|`VBYTW8BSIYHjvkx2O8P|h|cKb zNVL`sAr|&0@Uux_auJ6tQkJaJgsGQ_c7QI)4ddr+apunx*v*&4@UW_3c4Y^`PO1tM zDxWp%uA4?*7Qa@E5_Sw=)7OgO$x|R0(H*4At@JBDqxwlmhUJkltFOma7Pwvv?^ua%KgWXK9Mz-H+*@&(|Y}VPkyPNMee+s(ZsmbO9i zR?SE&dNwms6WPEhkJR5Of1(j2io_lx0ooQQ!3uj6ZYaTL!>xS9a6TYbpApknkG+k6 zaW7t4Lg&RSqkiM?T?rLNkef~~77pLVSCFC9$RhxN2E)Xu#0ajARCvde>Jfk$zn2M| zyQ(oF-BcJr!~v7M+o)|FZV+aL={MTY7J2QVEptnIx3zr`=CHyRlUPz4W2xvJ1V!FK z&PZ1KAYR+@`p`x13evTQ9>~Z0gZ=5)2ynbE0HM+{dgQpnHDs%=KqwC?yN} z;{ctl9*B3YhV2QBl53dzzlRzFG6t2@0w8@n!hAx>PSzoH+jU$Vv;m1=@x5uMZG9o9 zb)5{HUmFoXgzD5mG$1Z-!$O~i_rx;$q0P&h>=4s<{Gb;T9RKG#{+Fp0LQGd2|8L?y z`K{5GK@IBq-<36<+}4~*H)cnY`~WXJRn~wPUV=l`conB|rzc(_0H6!CJe55P=umjl zX)EiGLbWm!iv;;Zka-`xqu**hhr%${@vee|5r>koE8Y=_Cw1V;NshYAvR)HsdeTX) zcH3O_z^`c1%$wNHo5XCn_LmusR~B;?)jZR&CULHLC%j94hIa7^0<(02I-vSYlsW&g z_FfOM+ndDu+3w9^f1xauUEM79%MNX4=fO_30$B~{OxO;*xM9rgxKKB4j3;>UsybLA zvS#h2dRHRskQ8`Z%+wF{N9|uhKcxAgEoT&zpQE8bqWWFJ!FDh^)b3@}zl{0?5f-ZZ zkXkv>QMnfP$S*>zM3tQtsWB&T)IeXI zH|Tb%4RGxr&dLC{V+UcsJQasE)%I_s5CY;Z@tpB*+kXWj7f zTf0f`MmFKwzff`@&Q#&qT<(3LfOp9>*rX&0f6p~7*DXevc_NF-~XASutKI}jL z7E`sj4_Br;rL#u8AsTs2+NJl|*f+(I+Gs2v9dEY2`KH*F*Cvs#cRJF>Am#V_pvwX(Xh z?qy<_FlHc2DHBbaS0^Kdd>Ir?0|r_*mx+X+k?MGs zJX`KfSZaWGxpl>Cc>O92Q;_Z9I8xutMCkVrL~rj3AbCd$T{UlShvHezLDXwl@K(t!`)$znaUsXc z`AsRFLy@W0S#Jc-iP=~KZ$TiT{yu}Bx$ZGe48umC3#$wa#(!cE^+;q&odEeOPdACuhq=-@y&UaB|5;WzF{R-L3J{8$}t zM5e~5U`s22>9+SuG^hiX)5G*VA(NOWr7i$v-(S~;u_?Pmf7gcdFyOG~c8Oht_uX0P zF0qp@JayrxAs~74|3EW*kfp+Mc`&=MOT6DYbcefyS!k1sEMLm(_s7>pAl5=gw{Ig1JT_@+a29rPK&obT`4~1s_lZORd_mc zNOa|e`&`-i!(yE<`Vc$(DWV=l4q=x*6=w-chp{c_cp<&Jr`MsC=<~%{0;u{pf&M7 zh%Om&4cYM0lM&F$jq}uJEnG;Gt<6GL?+W;)=`9MhIAzO~%J?U6V`l{3Q`uBRu$hVXFS%C6rz4V04C>PR$LAf4r{8bZ z9*lqDfc^JKyniO^U_5=hKC3c*wy{$EP4(xVs#NRmI#d}yuh?Ga)uWXi;um~qFKB+B zuUNRmUO=fB79FT$7rqq3yfW<+5K~s5tNroZ6$Hge-9MbW5?0fksy3D3&Z5qPe$^hcOl-w@%!vB+(ExVvyx7@Z zG9iXtK92yB>Njbg?EY`WF)h_z{ze=b;^HUe3d^ENt8lVz6I-BorhSqgvM|LOB4w?`dHxQKn7DsC2Uhw(|!uL0DvASAP(PMyM70 zN2pQXY6fy^D-m#^?l-WGVQ9p^{nzt|Bu|E5gcz_qu5|F|Rz-}GLCf169ez^cB?C}l? zm%}O3@JG({XA-O{YmwBxow`tw0aKVu(GL+KvBO!h6U%wr;KHU{65H9~i8rtXm&7H) z4+GfsOJcIOI>dN`Vv7j0LqA+#V=s$}P%?o1cv+k;6b@jM8pNeRGa=d3f*g!t{gvpj zMOoV=T)ZLtWEogv17>~j0Ji9g*iYy(fbF>=z99(s@T%zNHRL9@Hak|Ebs3^A1P9ZN zc((j1g8KdxZ#{NZ{75e>xZk?+miW0&t0U~C#aJi1O1vQKTEM1irQTX2R=$ykq@eKJDcGn-PT56$FOS<9Ank|Kq1wbs&3lAnu^KHvIrw1g~XdZ5Ty9LF}Aq|Pl5pPHl| z+Dw#S|6|Q2DM9eN$Ogqo;r7*zmosCeR|PiD>efZtEC}X$_C`0U&hHK`(B-@N6jnnG zK{A+7yZB5~o%N0Gl2#`?7|wR~k$i=YwbsLZB#3%%+1RDN(wu(46}Y{>qVH;XY2cxO zS3Zv6QjdVjVOTR)X)Veiln)B^S71RnIjt&E;kY0rP0I0_WMUi4(!|&&YzWzndR3Yr zL@!vD5{B~T%)WgmB4ZYzUWg0#ZfF375<_G! zO$XC#`@0?qkaR}jcO?2gY!TSTev&!3D_%4{J2K0^vALJMW8YHY*&MawNSpO)KPjGf zIcYrLXYJWv%HV~AFyPfvq1P4TyN`C$+;K{5fQP!S6kwJiTnW%0b-1K6o-Y z%%3$5z!c~p-wR(>vhD+=0O3;;OCBgqB;qL2h23X;YoPR@BpkWGG9Qovg##DZ@&_c7 zu;+sHy$2*Om8!~sRd#%&^n^61J61IUpx8tN0xTLOwP&fLr2B-f-P!U{(t1JLodu7U zW(oD(*y_>JD6fbMYOb_W!b&kzZ;dt9OQWTwyvHlhxA^5)%nbh=b0 zs1!gf4h?ZrB3buYQW$%39H6`}MRL(n`?|D9c7KZ0vF8RnOoZ!t1lA&1fU6KR(<5@6vWm_oi}%TuLE zt-Bp!f0!D{`aC2}Xn9}{Nu5VKhOYckbH~20505h4ski`sOAb!jo8ij!cEDV;wq}5` z4mjT@coWWm|=MX8AY-9T1X(LjRPGZUnT6P-OcTdsm_?Wm*01C#Hzcn#~M ze6_#Y;y8@@3p;9XYtowyeAgpDm{7$cCra~)q>6~7?lu@$y4KNP*aR;17c}%N0@G8l zt^(9G7DuuZ6TxsX8ATv=7W0T?^ozqlowKmV(N(U%Ei=bp@ z_U>CsWq4nM@@cdhh&7^o5dMfgJ|AWk9+TW%yr~0nfS|_L3jr#9grtE!UjB{^NRyD; z4iy~huAe(xh}Cgn!L6Bt4e5h4$>LSp)Z9>uu-Yc{6AEcL`5~)es#Gd?IB}CcVY3!{uLjbFMLed$wI%*QnUGiUc z_6ez<2h}Xy1uP^fU>9_Gd_M3GEw*^5V3dN+UOs-gLcV`P4rJ^gQt`bDt$4fo-Owb!l)Q z$Si&XTXH%Mmc)`KXG%-lX$vKA!XL8!m?>QmeC}!Xo+hV!LrsRYv}SQ}Hsc#Y`5&t^ zzNT5<*bzo(7=ho5&=<(l`sOU@L5)vCo0&v%ngDkBDeMmu&eLoLGRvHMz(oMe7H~14 z4+T{r^y($-I0=a9S%W`Y6LQG{y*!ZCJV!DLCpt3Ivr@124MZ@Nh}Gr|v*545PHzX< zE_fE$;m@=6&q{-YG2_|!XQgi5p#PI=mk&^#2EKHlF=#F-e(^QyH&>d|ZY*f$+0DM{ zGNNsP6(KA?yERejuznuog9cgyw3N`BD#m=R_srHabS!jiT)nMO@BAgy*vP z(!)ZhubKM-skg9T92>MinjfjNls^;lppb=t^<~_HamsJoV4H~Gl;TA0R5QG?wrfIe z_9MvQzcoq(^kt+y>tYBgSnU(^Y8} zxGS0Hr5DdOjN{jD*zyycwh@b&pa3T;dv>Z;34{)PJZ?`DR^M7Ib&Ois0)oE^g6gy} z)xI;g2I2Bzsq?h-ZYo8okTgOIPmnl2AV?Mf-bU=3;HPEwE!tmAU`SfJ!^a(4br&R~ zLA@5JKGxb2ha_xDQJ$!unCF zZdIS#TRa12;`xFHV6?K&aq>Qu~1dSW7J(- zXt*roIjOznPY2h<0SXU6@^1(C;W--IQ1^S2++caCJQ*pN5fwKim zfU_m%*!m?>TKh7JkvR|eAl&IZI}{GbilX$t9!sTw?!|fFF>qTqrYlgk-|ZP3}r&;|8!g&84BEy_*n1iQkwr< z$ke-}yX`>8Rj%AbgmdHp0NXj&rN-kz*^zDLY9gHbIBVk*;MmD|8_wBtZfE23I%mf@Q zE(()xZ1B`)7mgGy71@G3vlw_E38%9ixt5o{7v%aJsFuO7JjwP#4ZiKp#XW@%-h#0? z1&oMLMxJhT6OxiZ(fp7)RS)zG*V{9YQX(gs&!MPK?+buJ#GXV>LL`Dh`$vDwUUcTG zKgk5u9yoJmg6fS10%+dgUrH(KCiC7itD(cDDbIup-UDF>lmV>39@8fs$)IimJ6n-S ziqhCoJmyK$@316T?@1V^>~!vE%cyZbZp`M}t7#tnK?Gbodlp-+9e3NAUzJ9c-_%@w z3eGP&&{*BFNjIX6x{Z2ts7GhyQb`>RMF;+B2kp^8Kq;X&Nh2L61-K4ba%(f;DM>_z zK3a$j{pjYxnp6Wt1N+xjHC9p}K*<_1&S z>&VL41V9@SEm^+}vt)&;DNtF;3V0Y$E?KB_k5alpmb*go_Wcxir^k}^G;!_Kyrvdb zvO?-(*iA)_ETu=?*rgTHaz|#-an&n$WEK@#GK*f=#+$bSw)`;vjWq{3WOVick~r9= z0}$gd$W(SPWbbdtF#5kwxe}-C2qe{1;}7RIw1pMc)*;*t49YHyc{V)*U z(#!0jmH4tZh@@oOqYJZfQ)$|d&8ge*{OjlkOKfA2>v8;OOyDR?zO8fQ9>a`K znI2`%9JyPW=Zl~vD-}6tsn(5+d-2a{sR}ZYotAf(; z1F-dHwDjR_mb_X@5o}l3=G8b!K3pLAvD2$1FP2jvxiH&msT12?AQ@QT8p&PWj_lg$ z8PDwv0nbTbJ=RDeg77#s5X+ulBYECSFLrL?0(z-(&`TBQ#ixZ{Nciz9eVH9yBPGW6 zbkGas_11u1{`I%?!iE+}S6!6Jcl=rC6&EOO6e(SJW-|LofkJWAWH$6AX@d|onbo`` z4ef~yzl`FcC%M~+3PNF#@l>pWb5M~DJ~r4wlv*r9OG%}ZsMwn6?tTHS2>k z#k!3@W!AC*LHgdp+|%sim!*jwD#1apfG|Npx7qhW`i>DBm%`l;@@wmageu9w%2o{v z89XlA`>&EjXRbo!O zPlr29jfn<1#W^;J$h)J_zix-1U@N6@HSIabpRyg~l+ezV@kb_dCHYt|IxLvs7?%+X zm?tp5l1&8AI{dSmnsa&n#$7}-o3OFDBq%C zL=j;fY+p{^xv=@zFF)Qa#kRM1 zBbwaNP3wkOP7i&1CKXGayhfb3R|n{Qf(|)FvddztlDg6vD7g{ zeK?>|)NEh4GEb2zC@j7Gi{7lhSPHY)zi?3~Rgd~R*F_C8tyao4tNz65?*si6N>*mC zuTc#xQv`$}Zt&Fmuqm%gaoR7Smsz{apLKuO-H*Ndx-?!3axbIXV;2sOdU_oY?Bzq_(YA4q=oI)?6w)n2FY80$xMDnF2t>}6+T zon_P2vZ?mhKejZRpf>AxeXN^u&fZdoV{Gb2(tAOTU2v@li~b?Fh$TJPZY+tB=U`HA z9OcHk@0WTB-yCJr_e+!XSlLB7+TeMs3><4MQfc*~lxERvN2T=b=G0Yz`K8ilXLquG8|4>w@2M0 zg`=OccRrOSP8IOgpy#~RlV~--+6MmeKgc@Y&axk{4j^igTIWG;<@s=8Zob$Kyb+%f zdJRXN;!%z|H@uYRAy_)=Y(t%6vsmU4Xf^x+YUAdfY|RnL$S-HbpCKFPXGcKtC^*@8 z*2rRxqF}PUAmb>KdZ`5iBxNt_dK4Mp%}hBd4P$Y~q)>a&J4U6iy~x}C)vL#((OO^h zQ@*|@yL?P~N+_MrCLPC;S3CFX!L}TScIe~7?AUQ>DK*m2xVne(6)Qe2MG5Nph3q&6d}fVjWlziVm?$M{(%>>ZBbF z4ULiAou6bLVy08lbnRJ8_1QZzYj$-t@`KrHUrN(hr_)jgca>a*_Qd^H_-??M*8UKi zb6R>&uf9Lr)w#gF|HQt)o;f3J$HmitFQjc+RsHj32X^oask5-?6IS8!*0VsPTW2A7INX{qcYp6Zj=&j2770cdj~e|YuE!Bz-(OJ9{vUZGx;DZ z{aRW|W<weDJi-Y$^j_-#A@c086W>TL{5fR4e*{?#JMgWv=FdO+!@jqb{|;$ZAnj}H0^k+o zP_*-V3643nd#rn^B!foiyvKU@g4B)oUR=qo-3Mtp$s@3^fPC}P;+ggbX{0c%60QJJ zu<%GFoBsoBd=FN#*XjR|O7=P4`^V$MQqA}qaM#+M_Guv=U8ekaukzKq0BnZmKV+qsrDW|99*U*v5cb<;DZu>*=DSLh6VFXx!3~gyHh*ZH z*dQhI+Gvt-|C`9(x*{ps?EqH*xQSO`uW&wWyDEJc`|Sq~YciIWYE2I1l>M1;eb3^( zhRaW(&7=lpWMa{{Zk-d|m%a3hv^o(8EdwBB07QXe3%7**U`fu2Co#7MTpwa@NfmK- zh_%Bq?7Kr4d~awc+zT|dPmEyGe#I(MK47bVg=}xz#r&>;O>9RY+4#3ylY9dg018-T zdr+nhr&k{RAM*kG{F*c;s#6Q}9(L$09?HO(aeagFo(xIQ$u_`H#AaN_p1XMm+k732 ztj`DR@O7zQzk-TpX3HCvWrOfWR<&x}F~8r@VFEh5^*$QGTogAqv+8YjZbQx-KfcfU zHA=BU?fYz2qqI4t8ubWB`sO42o(Jy=N}WyLo^MrV#qV@zSUyHsGn56l7Yil7K$^du z$0L9b_%Zb*UN{nPDen*wE)ij`_TE4(317L-H_W-qLGb5C9V41ZMY`{m5mn}__B&5C1Mm0Hck6z9-N%4_Y zpneX12t*aXLd&!m;ByEgOjdG|b8&gOwDZYY}g@f?!zYUv1vzCn$6oPMTs z!|)(F9#qTU<;m_r>?MCW*6k=7hxP23zdS(ji`@kmWj9|)dF1ZsUn*Q%dZz0x790pD zz}d>kTct2i?%MLOKTz&PGD=xvO=tF7pgdCe@jaFtB##r)yIWrmlHGN}1D{#niIDdQ z+Lgq?65QF$NV%)fqYK**Dfja#05dp-U56~3WZg`HAum#%#0!UGtV5$^cQ+x?#8SG+ z?+E@TtI%D(>LRS$XT9E2UN3m%W7%_GA_6z&wGl~^_#xKKz2$J;KrbkzGZ5FHf5`fI zANeJnu)ni)szqMv9=_{eaD^BF?Fnu>+D33q@4(x*Y@Gxlx^`{MCYD?B4cr&60Ko@v*m8qsyXtLyr6xJjhZV@O{}HTuZLH(1LqI99Wpx~ z97KIrJ6w&Ltw-j@0;IB0-+3lisFAzyO-RDv*(O*ijUNuT{xVmN=Y`dqSnPax0ZZ$x z_qASKD4!LC@{O?R$kTNjH*#y|269bI`Wa$O>T~i|&rH0^fj0_?=Hz8Q-RinTzMmH= zJF-Db<)Ji&23_?)wq~h3LHKzCt6M715>9Pk6SL*j+H_!Dmk@8goGt&r3)4EVv$=9N zVSESXwoLBE2eMwv!oM@U5noT7Og-f<7f)1rp6KBAUKTFcq@o4FP0Yxr*<&mb@`Zf5kQ(a ztq(i%y1Y*KOktVZ>s2CeEyod(l{jMCZeE<)uPW5Hu-<1o5>mID;UEuR;4>s;S zxklKEE0?>_!cq_R@h*9p@R$c1x?46y6u!u<{dp$T{cys&hF$zJSV|)dAfzsJw*K;N zaPc?~R<|1s`g^cRRY(ZIed$MbmuOWA|}9~#)YT7z$RJc>B15`Tk(F1f%V&q z|A_|n&R(p8C<6VF}#%zb#a%Iv#+n8$AmS-1D)ABEQoncoLc zi!5U0AISSdk_+wdw!jG`TZ0|elZ1(vKg4KJ#c|Z?8Rw ze~>Ak$m5x9zibg+TM4&z(8dsny-_Kz#z)Crg|AB=amdCOOkIQlHD@FTWSv`TRJ}%E;(kF5#3q35mu=_rdr|^09s{i{$ z9vt#m!C!+v?2w#*uGO}`JtPn2L+JMk;S=0QI4o}v&gQESyDxROcbV3gy;CXoV=hPJ zhlL`0q0rZzoj-!%jAXZu0mcuG0>-O)2aJ&fV=U`(Oim9G^8XZ?GD7CAWAbppwu0R` z2FiI+Z(V*|9>DYS*`fc)V~O^tk~QQrc?=(tL~QMVUKxh!-{#kbF3$XFWt@4-hao+ir_4~7!64S$gw*3VcY`K0WdyEDj6NO5-f((8T z;7ugK(h23?&s&qfl3x;{j987v17<|JsEp^}=Y3cH-mjXg9pzTdH}X-QpKJZ>TUp~m zFjUEX_zb46mO~JK%2+Kg9{&XxCmDZq4n2Jd@!Ex#@F8YBjb;`WIKP? z__F}tH!h7}zg1(c1Tb@r{K$yygi%KV)$LIOO7iJh_Ee6-7FLOa2z&reZI2pQlF$u` zZI}m1eXqiV|HRl^Ta3Q1kq>2~fs;DDPRYPVn;yxPWW$9sJCLgwfcxK=K5Q&;ihhE{ zXyW}$rFWX+Ho?`?AC_Cq0n4pG;}D~77%s#fNVr8Q!uW)QTSCI1TS2(b8q6n{HLeNf zd|VqGj^tK_xJo#JY}v4LORy-_V^W@!xvp`uVB}l_BWKrKed%f#H$7n7Y|@CPZZK$$ zhKaojzh_n5y1J7tTc`d{jthDb*6Yi7`!;V_Np~>sgGKvun8<51GTZV$IVk(dy#sl1 zCgwA>5Qgt3d3h!EMpaJ&l5kwtq|d1nYWx%o;rC-XY==udLHSlF7e^3w!EHLa-9UFb z==+_#G{O@Hn;e&%(=LMNM!GNr(kgZ3+Z8V7+Z}&1{X*YQxf^|^WxH_7^HrSkJ*QDk znAEr%4BUq}m%=x5Chu+L{AQ-%wDa%ENjC~{Dyzp8p#7X!Ujn)4IO_Zm#Wg)uk!{RM zn7o%VLADb#g&KJ3y9geI4Qi)?$>mmauSm4pw2P~_Wc4wA@51$ZAK`uoP)~T1nVPpD zJ7cq#F$6vu*{wCzKUopn(uxqg{KgwY}5y>$W>oO}<6En>oDs({CObj&tr-TasrtC9%5KzHSd_&9N!LoQ=axe0MtW7Ffo~vBCzV0ZAt*7z78) zcx?<{K|T;*l8{70lt&?`Z~84tu|-KG`7kUxt>f1;8f7Cq!6tgUG+FX;`rD$|sCr1} zUD=X)Ia6vNZz+`t!>IX7z1%r_bQf;z5EwNHkSD+qoiQQ78H*C(^j1>{qKZi%vtJX< zurq{;x(wt9MUT#iN!mbkSrY|gIvC{wV1+PBkr`eDm9&n`Q1I#9$P7AdPDn}tX4v#^ zgl0%|F{i`*WaYtxie!*jS5L8U%moUFK|0l z3?ei_0U{yj5edQQ$|-tBBm`OsKX-@A2cjXIcjNlJ&yLp09em-!gWTeA`O}A26kJcq zdQ0#$)$7@vTDiBFhm8j5Iyv}}cH8bSjK7Ss zd(!PJ8h@4-SK)zii(rQ`I9n**7xof2W?>U}y6a$L6EUCtUdAcMntQ3Z>oq{B1I479 zxRMNfJO4Yu*nF@v^LX7o_+jAPowro%xZd20ZZgRD-5S`L*PG)CGly5Kx|)(@;im-` z<8P+^KY?E&@VC4EXrQMD7ni)mH|VJ)oP{yAEQjA@Z>U=J1tdmxrz0ec`%u_OmqAT~m5 zA|aRa*pUSGkb9w-{cp{n9hjBgm*h#=99_}{;oy$!%C|Gq`#uRi=Je{?z;S)@u}xE4 zvuPf8SUN4WFFPwGn1SnXObTs3C73Xl;265xYUnjTAS*bAuE)%l!bggDh7K`Ah{A~| zOY%DQt^Hoxqc=x51@o@O=>7X!UWx|Q7C`6GcCAOBY%V`b-cS!T%8v>kEoJa@i_`)q zC+T|CxkfolTY*i+oEOE$-;j+$&QiAQhCI=jh&6BbGo{<$lta;X^9 z+I}u}^Po3t-y}Z}===tIglaUQi)xLDoO#P^>(VB9B`bLK5_;+`7v68_+Hi4@-s`nj9I9gGEe4pA!$tYM`3N8eZ9mWr52k z%6bG7E5{(pg=n_sw(Rdaydv9;>rssTJl{7bh3B|0rex{a`?uv+1#t-*cSo*qcf#83 z88)a{HVKEGW3!s&IS+1s&Y`Kkz}t1bRXpxJnftzu_f^|?r3UXwOPxpf!R3IZA2qY4 zK8UzAaLQF4n&(<5*Z?yWJi*Fa;Fas{GYstB@T~#V)>HZYyL$Lv*E_iO;kA`qz)^p6AHzeqs zEnCO(hFn*nev$R8o8cxeJh{kvT4z|s3*sW{ICq1=O<1weI=!8tMi4$kOHI{08h{3~DJlKs7N4K*T9S!sOPs6NzE z6Yll6E0S9G!m5Kcde-SnI03pHe}NJ~(%LDbCBuseXg$L-5b;shky z|ACvYcWdZWi}SS3Y~X+0;x+d_%dzbLx(#99b~d!vs-Qj5f&JFmV07qnU06({AuJmk zMwu=nS{V-c)CkH73FBrHiBQHJ;=NcG4LajMwLA>v+vzq6ee;xADZn@6E2P$$*BKuE zi0FuCwR}Q%r2rg;d^+)5q+8;pK9gctE$`u~==q!}Ddb0_zA%~*3uh&~R(B;4^cWU^Ha*+yiPpLL}t- z&xvD;(GYMCaZJD*nAZS!BY+PE@Fv6jc28n~wM1H-#-C!L!LI$anRtu%s_cx(&?^S~ zu!#5}x$cnlliYXuU{TXr4|B6y;Z(%@63KeT7>vTv*=$nGA0Xz_7(>U1hK_d;qYlZr zDq2mLwu@nSxJmUX3{gV4N*Du$(#bjm`H$jDh$WCw7A#;+7sEJV)@O4i7 zn9#`J+)?fgdQQWFq4@O?w2=duM8nGfr0H>NBo56@EBY;Jt-0ArOya?vI#;%1JzV=L zx*J05Mh0vNTLk--FqBTQw8*d}{L;&8C${@ z!M(ofcU!`{_ZdczCayvER7a-iVd!d_f+299_$vnhb`vH9XTQ>__nH|D0_AF1Wq^`~ zvTxpF6MGm2kg5(Y4E@-a9_ZQG(zzanUcxiw%nz@7!3@up-MJEjjn7iXn+p)v^)pE8&SEbiU_6=HS)A9n={Af{?!os73|myJ6K}v87U8>y zFMDS~45Kg{E$JdQtq0jFN-`U4bit`#8$Ol}LtVtvJ}@E6!Q|`K56H@{V!C(<&K zR1Wx(lRx@2CZfqFMCgsPW(TEoNe-(FNb=jh@*&@;K~+tUku^=z3+*BH6%OKXK@YK= zQ0hfU53#AG747YFI>Cg7xiA#=ucm#wAN8-=pUv zaC$!KrcI6)AA&4mjZWe*!Kz!2du?oK^A z)YUN;&{I3O8Mi6xwtLQlXO}Bp?o((<;%8;F2EmK-lR?0%@WOmpF$BSj^Lxt#FU+S2 zaD^_EfD85Z?fbPGlf`kODQNy(sVg-x*!!+`R{>wc-GHwBOy&YFf3h=9%(VzOQ^sXx z{=vNwCUP5N+WEjhRewLOt4BV4Jl$xOd=Bt0rs~&~RYv(z!xj8*M0rPj&&e9oMnll; zjq~}F-5{Q~HZ`+_U0V_djf%>!Z5KwD##m9ImmVOVxHX&8nCyv1chLHAay?#r$g-}V zY%5)E9LlJWcWHIMI~P~=yCW~rAh8Prh89O5L3OD{E_c4;KPq>sBtL|C>=m5VUBvlR zqTfpM!C2O&p}G5j8?Me4EX`cQxtO%oA~!P^Gj6I3-^2>orHNiPE(2193c6_U+7yRQ zuVVOwSsa7`tFp_YQ29|{f+i3K0iYF2g&>0OBiC8DTL>cDLJ$ricmRxi_gcR@mzVUz z!e+xwt7~(qGod19wWJlf5zQ||A0Ga zV^Jh6NqmssLJE__NK3PB+_DAJ%(FIzVbK_QrJL^lShP+E$+(^ojsrNCxRW=Y%VxG)@siL%~XUYKJzq0q8GNXJzif z<-795Z%I3i9qLfhZwsXHV9{Ft#v2O#j}0CD>SBABHW$`ekzHa*3 zPY)5J4H6oB5gjg~3Wm-~T{DzOX<|cKQAG^^+N7bP>gdvVNmG1n!PCn~<8h=xuT^3b zXA?}1l`l?iXrG@Zb}$U5wEJ!yI_nddDqZvlcF~%0+aXnhFG0E}=cJBwpF<5c|<KzzZSBzuUcJ-S z9y(6EB818)JlZw{p{~w)eAi5b>M)_HEexT!I??u)6U3%M-GN@1x4+hB*2h>Im|`FL zh*)1}*#RLsERD8hAZCZzS-Uo1Y?ig>uvWIZoPT8oC~;vScIzbZb)Mfy!l#H~4Q8&- z`qi*t~i#>$r#*j($`}i2L_Ia_RHvakN#Yw`JQqp&>xLoW0!(4HyFs+mfn5Ek^N&gMv{d~KVtp?T>g^x)xWjNxA<*s%3# zYLhvodn&AKuZ7f``Oq`4Rhbvw3Jbu%^pJx8Kv?@g8x8mBX`A47tE0NLjrO>CD{jv4 zA|Kmns0@hX3~%L2{Vg&f*8^@sMOB3EDsFgS&1g$<6ThjteHrK>?o{2r4)lbpRNZbH zJ>h;(-15R<&hG=2i3crBJOR$D0iKGLjSSEnex(wM@%6ZUriS~pmnZHCHC$1Uhlx+r zaG&+^#66^jo8o5Hf<#KxfLXDg$a~a)MNK@BcdBj=H}Qnqs=8GKdcwV@xRr;$5#n*% zpt$9Q$HaT$u7#ULu8X$59>=#l0pmQmdqXAoREQ_sYpUC>o}NTrRosfgJM*4!3l%qK z_>`)WenE|!7H#oFwy2KVdwL?zR^0N!ABgjWds=ZT3h&N)+_DrmXZZ6~vNBDj_dt~( zPEzT$jdy3xQo3=x>R8^(lkmfeTTyrn?O9@yWo)zLD7^}EB!*}_3!gW^M^1_3A$xAj=Sld599IuLt zIvdf1jfvER8Qr&1PN`Fkz4im{c^s}d(52)6qsS?>JUzP(Pl{=F4`DCn=Tb;uk(C3K%vWH%afjdZv?t$-sYA2M}r! z^ac^1@8N1KJ))sGc%H>hv-lYC=OmS|HB*T_tT>5+@Ilr7P zgp4FDh}c3X9!Ulf3icZ($v*#R#(UE%8-B7(rUU4Sp)q$V>G5SsJ-WzkAb2uhnAD zh8>#PD(d4AR~+5Av95N(6|7$L*iBwt4U)?cbhVfhP##4Ct;2fTM9v1wPz6_T^yaH# zGCN;fDm+q5#5Ll`kjt0_7)v=DU9xuxftC3axs&v*j*B=Y-!_fpuEBnd90tc|H?I*B z1^+bSSR=wPXE9m676o);m;Lx!=t{zz;iT_77&NC3C$rxXpYiSt;a;|~zWvra;-kFx zZESJ`Uh*ds*FzckVHjDtUQBNU;g#2&mA0kq3AgG?d^d<4X`)VJtQYCML5%kv30h~4 z2P=&rf0)t;?iHXB+}nV944# zjC{Na;)`aKXY61g-kZgy3K$bY+5iN|KKBG$0U}PpwRGs*MXA`X8Mj&N5CYF4A|*LJQ0uMa_CTcE3zXozb2YU)@;~A2!2m&Mtc`|A^+RuwffV> zqul8uwHkU#SKGVn?n4f|C$8@Xtt(HR#X@Z^QfIMrMmrf31MDHoyXDxfK(B`z`N&r4 z&=C9TLUFvo8~f&Y=>vj^_f|~$5{8hjTg4R1UhBtzB!G-WOPf9050( zvAaENqi6=;Hbf3*K~AspwlCmFyto|$l*r|0YIl%}31?+@P-{POSUk?xF;RLAZ5O!>vuQ5oGRJ?0R%m=?NxVw|caQu}?rx5z2`;V#u@G z#16t+gJINN?Ay~kniUY)ESeBdNd<&1eKn$W^;-g76-Bmw_cVdLTW7Haz8$mwCkK;Z z+r_@HugMaUB~7f7G?)nL918pQ91I!JmE02g#TqmcC`UuLW#d#us6Tcj!nzPI-S&x4@z+of4T|np%zOf*57~gnq zX8u`x6XEVFq=hDf0Y7~E1J}WKAi~wfcPQeE_>O>kH+=C5fw?=rW8l6S-|^hcif{3K z6!9Av(#%1ytB$4<-oV1!E}?IvdIRk@bJ33GB)N5w0S39P(cCBAcBSR`Vci*FHa2l@ zdZfDj>H7AqD*t@dzrWjGQ(C3JxzgwoK@dtWeNHo58acU7Y$@CxMBd(m@iV^<5$$5* z+Ii^{gH^xJ+F>9+eWpY$8brp{k`T2gdE1WJ(~?1C-hSMhetxTH z43MP)l^KZ_B^LBH$M#2;5QSbcVHftXHO|6Grf892#dk z3R!GI{gKZ^xXZokut6>}Wl*7`&4{@E?e3cJX(d}v(*G;f3#F8)Ft_F@P zH;#YV{B(*#M$E-mFAsN(bi-nOT3-C-xx1^+?hRI%9CP}0&sn(P*11?NDyunhT; z2C{m$*f^MO`OpomoCtSvkO#F7CeBl0GvUZU^6KYeUnx6;X*Xo>99HEC_w9j7 z^=4q#NngFQ-F_7f+#gtIa@E2wIV*{Bl{s;}*fYjYDb`rAh zliGVBc{5=1Fa3$|^S zTMaKNK?+?6@}odp7sOcp7m~Oa3t;9;3jdR_1}<;eUzGV54iAtku}jAJzu}!WiN!VX zF1#g^ND;oOk#Awv0MhP5aPV1;JPRWRkn^SRE_d_Wney9@|5RajOMt>Ie@tPwu98-# zBY(p!y6D8G6>ghAnC75UA|=PfR{VEl%|VbqK#m_48+pf}{NBVpzskd66kkF*d<3t( zvKQ@;%F`z{b@x~Nn@YT5yt#o>26M>x17b=~Zimo?zFwv6kS^ma9+pv^(;f|$&pG>+ znqw@%&jjtyM`ln z_N+2)8f~Kn0b1Rij$GA3R}3Pe;#p*bF;I$q6`W_pBLKD4h~qdPmy6cfv~x90^nK>&&?v&ksdDa z0X86s_LK0;No*yHNPJ%`Q8onQvqs3EV zJMTIWESxAJOMb?JhA#kJ-3T4y8yf<}L|)L)+5l?XQ>nS}SaheoY6;=Xzz^+J$u7!# z)@iY=@OFya7uU)djn+^2lVW{enJb6C?Ll`<%~m45dtU5hs7MpYx$_uaMyHTLS5Z|% z$lcGxpe8;LpQxvM*}zqKY8SzV(}YjRatRfp{pVuS;5om#2b6(|9Hl`XAxq`750{5a z7y^^|tGh?+)TyGs(if>A)ZSm6)KE1s{Pz8{yBN9~Bk9;V&4~9I41;(2lG)#geM3w7 zC{nl^mm~_Bh|$@eL6Z0GP5a&%_i1*H9o&el14-xtN94BxbL5S1LA96v%*Y#Av=HO(VqsC7c}m1_e<| zCSS{3`_BYSxX_2R0|x@{maURL?y|i{#(pa{qBHm+Bf9>dps1um$mqm*S{FR*Vv?HI zzvxubN6kWm3%2Fl<;jTym>)0fL)EX8&cE@p0-^d>6Q$-D*517@cWyV3z_QuRU_FV} zkC4&Ft!rN+(=R~RE?PDrSoMoj{0w2g@QP+3*X`P9cijqsedrJjW&1L@e*rD|7U9p+ zQu&UT)yIIDCS?#Fe~Fiq?o4|Zx3jukTM~Ub{wUU6Y6SHe7QNAm=_AOE!vJ*W|#;4Uvm9JOjZ>Ts@RkG#F zCJAcEJCfWg@HHSyC9qW@ehBF4urZw(0Re$gXG2i_YC@ zcU9zO&4<-1Kv74pZTyO6iI3;D(+tU0~;IW6T)OmuSMir6qQXY~Cv5 z@;9-IyIo?kOxJ{IjrP1zxEk@N&4udNvYdLF0aQv))Yf6E|2is4 zF>l$~I=1KBDhPdtN+5*{P7hB^_`Gp_c;wo{=nXqKs&p3CDv5}s( z^^A@3+8iAlX{RL@jCO2X(OD@Reu+}hxasD}N3z+<(6?3VF|hPd+T*@wmcO>HQfoO2 z-6f4B37-7&i`ZVcZzdrQv2Xi~7i%DiwkeNim~AV=r-2TiMX8y=5(OYQPOD zs%}8FICvea6Gi~z)<|1{Zb3l$S;=w+HYo%@tRxt?E^^p~0kS%*Rdc=EVL2R5yXA`) zyHR|`N9)O|u^cD9nLMG;#<^7~)9?NS?P`8fBONu^GSR(g1OB1}d?Nf|UM@DYA$etD z)vp-RUr!(heigAY@t~4bxxC#g6Z9UZ+|M8PwKe9+yg#rqF*AWozm36iI8SEOkz$1* zN>K^AubN!)A1IuUxh8fd{53IvZjg9H zlYiBdug*d@MLMYhoVTn+PqoPTsbuI4jNKI--9_HNm#q7CZC8VEdYfYd5j}8oGn=fK zz8`4Yh$A=)4CYw10%qJ18}`fX;1*8EPKHmlTJkJ0KIaa1p*1gSyKdQ$=U*e}{43)w z4$S-YB0t>``$lptd3o|ItG#X^`U8_^swc~hHN7Wk<1IxAKg5%HcjuNtuX#Mv$tcEOyN+sEHqXU{+pOGfD6!wwzez58f@vuRdp`7 zjeO)ZbXO_XW_kF#n}MgR;UA`S`w9N~j)w0pCuqp8qwQyQc5wGIm`)*H1M-c7s=r31 zui<|s*U+BEa9%ak=0tSOxUsJ`5W1#I_n&-xxK8wX6VJmIQ6wK5qvo4ebO}g+Gf*>0 z=1I;Tv59anPF{oz;1g0f}%Y2^Oj}gFrKUAAtP<$$Rr-?#bK3X z3orHH{bU9N?nly5kdiveV-{@DCZRxDWh&M-sg+G?T@wZ7VI zR!!AMPBo~WZ8nMLrItcrPcpAk3?k1Pq_%?j9NA=$dI*c*h_O!y!^0q6|04Ch?UXF% zoK{jFrqHQXae_@gslDwind!n{)UKzzOm4_H#*x)_47+vZC@GdBH57b(b1|B^Gj_7F zFm(ruQW#m~Z4g{PPm_0W`}eSUqaT+4zlW6*lowscmhqK_)rO+hKPnuT9#o#Cp%zJB zrxr=yG)lod*75}@MF=Y)qPNsYsj#u_l}26ELvGYU{vPtSH!5t25;ogTB2e2&3IobU zC|y~XaR-BZbL_7eD*QoVD15sjHKW{Qi%3W#sS&NcqGh#v+EyrId8ZyE)ko@EmwQhs zE_pm7{(Hst(C#VkhKda+SS+FYEv~KT9i{6za`h^vY?ZOH;?ag%r-`fWFtACJIKtB* z6nS)ykCcc)*xSVMhqAEZ^k^UXZqNJ`vZvC$=QdEgcj5V1I-)|fUMJJ*px7QF7ixh` z&Ze*#{&_8h%?nF7d8+m&t-DjKyLB&CnAcC(j~(7EWg|t=cMeb0J$1@4Yge6n-w5DoH`Ua@}Al zXS7>+c%wVnDbX#U=g55t$v&$j+dliL>ax9XKCGfat9H-4^XSaG5^Sh5Z@(4aDQT~n z>8`${?uy=6%(Jt0Og()d=!{pA<-!C(1oFbqk9rRPp>8K9qOo zGEFs|?j~0gd902Uhe17Dl2VAXj?~s4^-*3!Z@Z|QTpuK_E|QUnxeK44Og^tG&FA|n zH1b8MbI_laucb9{h*C#7a!As|YUO#gD=p7l;docM`bf4(3JmzmLUUeJ3N0s2DKvfs zsp~IgG{AOaPA5(s>S%GVRr*_BI8#H%8E)jhg(DcTuYO zy+%@;5aA9P6ClA*cxNI8N`1+vcvnXFN(Sd)qQ2ymv4j13pfrz~s4qF?F;U;aJ}*eB zFGP2kvb5D0^EUK&ciblGH~log9D4<3>gj?w8;9%*mfG_jh$C1E#Yb!^^)P(AG@lG; zDy{K`pcMt4z^?eizES)L(lkVx<{b>TH*aB{6C%ZWUqSPrdu%5{qz)e79V#{T#egVJ zLT9uf?LyJV2qZI9n&^E7GVVD8^G!$l{ZJ`{H!Z-nhA}5WwkIZA^*2RG4eg2H(l*}P z?MHr(ke=?gNpqUy7`oxVJ57QGw7k?<+#4(u_u3z6COu~8l!o)c)HEwKOOS(!(v28y znGxn2eB!x!3E1Av#xc9lVt%F(oA(8`F|`PcLS#_K4yyO%r=w-adq*qsIHvgu*xT{2Dj>_Hc>D{H-LPMMVOm}HH z~1BmBuT3<)<*S~ZV8`7k%&H04=OfAMn0BI>>~~2 zx09`Xq#67&5}qtsXcgfWSuEL*EOiln3nJ%}rS*y-1(e3x6CRLa_&Vjk;*e9n z23);tA(_k>C^h5DN&Y}7Hn3Lm9b6PUiHC+3QBAvZ&hnMiV#zXcZJ^ZNyQn3skP5X( z%Ry3eel!_4NE(M>u5b_che%OkXE;(iiJ)_+1$ldj)S@em zO=Hrs$cI4@oQ9GOitq@V4`U(=1kMUG7sqHp(4%!c3gxF3@yQ*Dz_ArxM68^ha`ypr}p^lV;YRP@=S6GnzF7Adj}*;lrh9K`J4+ z!=2d(lI0Jh@#0CQF(78;I9zfGCEdnI(O8arbc{47 zSY`?xHQhj!|A+lUHM=CQRjokMV5~GGasUj)FKCfe5XCn-Ch!f9Ej%L>EX3nbmq!T& zmpNQ@4G$$RkCi_6z5;;Tg7%5yr2c~UHuTm{MzphU8!x>mDg{u=+Y_>+6}2hCj(1n` z?K4tSw^tovcvgC){!HXA1HJ|rQ)@Q0B)-;X2g$l;rIR!iELJ9vInPO>mH52Te(pI5 zH)ng0g|nqO0iR$rN*&q~&vKe$p%KhH3LG`QC22B8S|PoLvF*uW?QL)18~AHS`@uQV z>xP~mBgmD_$puFL)Z%_To=|FoTYUc9tX4dCXCE%kUxq~;nGDEMGrQEv2#sR|+1D)S z8WgU2kv$fS131T4eoF|qx5<`#43U2l&(;cx%29-*_bNxtmkzmU#FEGtq!*P(caHWa z@4p~5rzW4vqlUJ(pL+qaM%vL}SjT$-_vR=DI_cs2;lL!Uq;~M|n|RZeUM8h>fi_ZY5$|N7edM8k zg`p$7B~<|9-G;`uHy-3q`nWB-P|Gj$04=rbf>|*7f5o!PbBG`4Es}8?G4h3wJ8Pwf zB-tNVT~WzJo~y3Ou)(A^If6E!wf!Ssb^XtCM*bfJ*mMUL&V7&;C%rKaRFYjV+s*(vC;J-xe(p2)~SU^kJCScLOJt84;#JE?Jh`S ze;57L7;^7j=~4H?b#lpQEZ)qB@NGl39FhY5$4Rg3$t3N)0ZC&xp}geA~)N~nvK#}#TFriCeI@@=kV514MLNrzaLFmlg&d=kw#bG zq$1rPP3KQU`tMnbOdzX{K%|FL*05dssgxqg{3~)@#p3LLz#`s&OZKn`ZChcnv}X-0 zDwY5sFe-~M7@|CRs4Kmqz|B!)@@6T~a9!fasyBVcM}GMaSZV*Blsrl;Eu-2292FE+ zRM{;?2T&!uDl482K+6g>_k>QMJn)I}Dx?p6Xv zzM}UXj-$4063(QR_xyap)QsEx7IQer&)f9=)WF6cO+}KhMQYqq_PmV9W+gHrcBkie zFFSZ`=5k%4Km%qd7o_5UN^je%KE=OsoLAnHLJQL5 zeW__1eX5l0Td0OVWBs9j!#ZV$_CGyPSLuGe|>c~2s;!22GXh$csuuIOuXRIB}scP}eG zqm&CxcoxFM8eowhkAw$dJ1@=9P=1EH=T@mH9`+i&RZ6j3p2>B&5ox|N1sc@rLPP8R z2=1BzrCxwv)wC|+2LE9-oh+R!_+%q3Ypwqir+L8__8E#8y1*jZ^C3RJwqsm-56;rMFv(NEDl3n>qq;`(cU(% zK=a-H_&;i>x2>~3d19Ls1d1TWLtW4I;BR}kr+J%h3PCYIbT&swz zZ9~({3omJ9yW45ON@|y3Kr_;Dr_?p_msT$F`=#EqELU-sAJ2fxB8bP{EqL5rE=4|) zSa(VTg>ZLTPmy~&rQX8MB_zp?v^Fdu^X$^>@F{y&WvwxIo!q^yYr#g;ei+`#r0?4A zpC530qrr6BKpO9o=1U--N8geMX-s`0$>v?q!S}#%&hTh*VwV&zG>sx+u@qh3nFMYx zLDs*6)`X|CHx)c&A5<*W;w5KPX)9YeuApy@>|7AricHvzNqQ?(Y8L^!j+j2)b;bn?P|_1 zSQ$p;A-@AmTm)^J2=}-s6d}|%O9J-wd)OlpmS?p>!ptoSF%yBayK~Pirh~$*TfvD{-r&{!lYjW1O^Ak1#ZnADa(1 zG$Nr-)oIK>P1fv{BD|bvw&ciOsqF)p8?><9L<2Y0HQ$l%m^{Hr^#Nm=SMAs+>w(XFCbbh5_}bThCha!VTY=XQDh#-h*t?ER zn2DomQ4Qe?Xp#jLE&I`cULi zwqR^gGk=itterm~zeS85xXxF$Ni*7u>|2Wk)M7IJD0HqnrHal~B%7#4ZaivX#rEoI zAxf%Trxx}bRjy$(&&_7lG)Kecm3!#-s;jFkn{E6Q%VBOdap0G-Irre7v1$1SY|7RQ z_f>k=_8J2rBqfJy2S1GRRTx(^#d4#fo)vjyN~4}tXS_#&wtVfdA0^MxhWp!GFzH9i zZ~JsM-1NrAbGA=1i8~}Ui{y4zSIXq#tYHGuG)-oWYL2qF|0~w;bc3eq-I`-w;P3vj z=7L zQ{j-#21xd%8aSLlIn)`}tVuG5{3j)|DDYrYWr};YHl5;*t5$IQKV)mT+hBUef8p@M9~d#haH}_}SM7K;i$iVIAu)^ zcd{&zvz&Cm1jX?#;wOud*cxc%$D0oN(N4CFmB^hOn8TeM1bXegxRdl0$TV7t4Y_M(tgTZ9axo=Z;O`?d z5MvRzu382-ygY9L1O8wjRPdk1!^WQp3Hu{q;L02@WJ1FJNSOLZfRPw5emP)ukQod0 z9ZcDeAmU{36-=2fvda%Ml5T=2|Y|hxiWj1 z3p>>2PEyar?EUXu!C8skS?1V2G3Hnc=OvfMrTgApeQ41c&3F2sc;Q|d4kmgBnv473 zRX!T-7nDY*FRaAj=57pfA1d&b7ja9s9BEyHWnLP809{&bdK zfhXTJ*G(SU+Lh8GG0{shO8B#z0>ENorw#>|5y(KHy_IWG?Jq zOs`whb4Mk>RCE2Y0#g%=E!ehgLHsUY-);xqQls(eT$J)^@hRl3AEn8@SU}EekJ(lQp=c6@RUX?=q`Tr z2_sA%XW;cB8(uHUcyaM5amT!ZC3wNeA1@f?2fTm1Jzg*(=8IATiwS9O#WOS|;*IOn z7&eF5MJm1%@xaS>u*7oFuOuF}B@5|OAQQR99S=^8=ni=un)6)5t_!G%M6aS%7n_*x zgn4m=5ytHJh(PYn8q~-*o}ZSF-EZ2eSEIq0@qbRNX_b)qbN4WaS%k49J~GhU?#`N7 zkL|h$8>|)HT;U~5;&bo2x<+L4o4&7SF7D0wf0D#WpY(a&uqx^KgR5|6{h}1w@X@=j zu1Q{;e-h`l=+M-Q-QRV&x*aEvS4d5|T!t-99~3}FM{@yv0)!vWousp|*0?iG-$_qJ zF{k?YSW{7~sVLHjsLiVu691p2HWnvV?TauU_80oxF$rO|Fd_5|UEflFWxv|+i%|Sz z>ZK zL4!L2=W1|Q;I$gu1NZ>NmQaO(UU0am36KOVL{u@*7dS$LQ-PB-co6Ue4IT!ZtHC3I z*J^M&@Bv^8id)X%SU6nN1egFUL{>2{2{=N7rvfKw@O0n_8vF!st_D8?yjFu}10RUA zS!jLB49tVWMNNQQU;*}FRsWZOBQ$ssaFPbU3OqrBmjdT%@N(d_8oUztK(ktM0%T|q zwopxiR~X4}g;!C-J~4S|{wumFg^_>z$3)7nO8@((S{cNU3R;~1`{#elpk_J9Snia} z|Kc};n4J))|G)mk5R=`6&f)*cKN(IbgJe0SA^)X6X5!p#iun)ysc@pd{D1Gy5aNWu z{+mB05_1hP{^=icG2}1n8f5(6{)r)Z*HET^`X_(8DeSc&p(gd02e%^f{MHUqo6C;eS_$ zZ#o!)PF?()F1AYb*y*aOVwyhZ)YZ8j|L_A#(B))hpc?)TTN0WdmmXI!Kt>1f4K3{l z57q?dxPOZ?Gc%d^{}Nlctn567z`w=ME`S|6&CkEXsDyp{_GzO3=W%v+HbbAmR0tS{ z8u01Urx^nO7B_C3#l+WQQtF2-wj+K96a6Dv9(ny2CafO!i^mB^&5s^WO=la79?zM= zHXt3ga4%$4wGf()9&;R+$=Jsw$!Bb6N*E<>M#ikLQmL z_dEHi$BX`>$L5N1#vi@zpJ6$Du0j_;kH@{8&k)e#NoAWDe?7LiK4bj3M^vb!&s8}n z9wVl;S3}5OJznx5Y+P!7^thdMl*X6i^f+$YOvPVK-{atp7WM&~BRziO_cxdX^q6z0 zl-z(rVLQA_iq$J1Vg9Yf8J9`ChJ)A(|n9y@i7$(w)`9w1 zlk0l|)P(b~A*1~%doe)^x%0bTX7J6M8Wj&dH_vZV{pIwz8}DSO4)RxzPi@!M8ONQ< z3}M255W&jKyRBO@{#TaI{u8XE&)v;_f(hWtXCqX@?=rzT7t3FDY#G8taH{%;JAKaM z;11@Rn8HMGB{Pt#hFNdUm8luQ;8zVdcukI>rq8+D4k{g2$uad``Rm$Q`j_}zmO@Ys zq{l@u8LF53b>n&-M^6Ax=VY=RY51bYpZ=rAd!}7pcdN+^S)g(*Gv^8~Ray zEZo_wGb)h3yGNYv~NpR>+o2NeAT*m0~-jUa!^-hZ9J z)@L7OuuC@~cAQwl_?K<7y0MZzXPq^c380?W;6b&&}5n(9f_P72h)n zl$9Q0uyy`R4A#%U9iOv9e3@Ndqwu5;8JNQm(5@L!j|z>xPH|}&Tcq=?YMoMFj+;Nj zrTpXmJ9{$Q5oM01@vGA3QsP(w{hnb7^q$TV=rN9Eprx6OUtKw5@ z1;4}+2z-iVz%+=BKXDhEfrsB?;}2d|rp-TWUOmDRs5S@ufF%IMk||srAc-JEiZF1}^Cj=&@x zfj4flIe7JZHV3m0u^AZt5}N@|7oTI}TR2WffYTGu<$%-WfYW7w(`A6@;=jVgNBXmL z1cvDd=%>xkI1V#Xub*ZoTc2Y5OI>VNz1^YF8<9S@s%#Y#A=8@4;56521`l#9V{q@X zYzFJ6*(+V_d^68crP9&#x#!ECX9%QO(->TB8tr(7^)H*mVrw#sT`c6PL`Y6wznG=( z2s!->`k5*MGUZ9E406dj87y)2VmR35=;6lh^z}33Ox+Cm6Bj$af9ROP5L{c9&fw|R zcm}(EW9N&GA6``a-RW~>ho593SYH~%VAs!VmK6MP$WF;Y%5hYt=SiO{D`XR}KFwlRX>-QGv7McemaS^a_**j@ zFxXWrG1yTk0#~KamAzhv31EHH$Y56~+m0RE*%B*T#WrKBZi~P*bt*&8F>xZ3K36tk z1QWp8LzPm+*_F#qSRJ#HnDAv&Vi{~rX~AIEX_i69A)7Y;XoZ{RG7+p#vJAO?+|Bqq zzTCp#vfaxWY@NsU3$AwuGyaa1{WaweFa0WqHN6cJ!DVU2V8=7z3@)3blL6f-iz_CE z3GZlYY0Eg2wP1_P8WhC%yUb<=J9_qHa9O8L47NtHD`~F9Y}f9v2d;{!e8-DQhkXZ)?>9%ZmAce9F>_V1XrT6K`WWmDJ^ zvBCnS_Q%zEn-+487+ncO=}I72R|37%j!H?NbM#ou5G?D+PRFeg!x;Z+t6VxMDAP%S zRX4SB&A+SAbEnTaW?x|vD9b#`U~39nBCb6y#^14p6~nUStV(2^#EN0XBqhF@K6i2) zOJMsjmcZ<8EP?&KSOz}m!NygE7Q&GtaGgNNN2RgU@%n0!hd>Pt5z+>UyqGOR(#4T6Q+dpxIqI}bz(89EFd6&VL*?Abi<||2L}f; z@%1<~G?ejYFcq2baCYF=<7UlRML~~S=*D?Hj*5z67|>#xKG&vA8zz7rw`)R zohG8kopg$X2FvMlUAuN=642xB-McgXdfZc|x#)3xd^{7s3M=VzX0w?IpvOr`?6|DQ z$;rt~d_7J{VYOWkcBjv!rlv9k^myRFfsDT%57r$4(&J&n*gjvyYWiGS8mp`6@u*Rw z7zXrs^ytxyzaBrV*+irP-Pn^pH+C$$YOTlP$B$2rT~ z>S@}vX&Ukzr^nN$PiOq~I4diQ@t3hCeeTI8*=e&LKm9a2i`3)io@2*(J)S*#Hp2kL z+Vr`(bJ;1A9$PF-a88eNb90#l^!UXWUu67&S%NRW{4x_jj~6Xk#Q5v+;>E1yqsL2h zJKal`Se7sZZ8qCq;$_R0G5&hIe7UNNDZ%u3#flY7{1q0R!<%ou$pp~jx8G*9Sv_8} zW(^ZxkJqhR$M{?H4(r#iX9DQ)#*G^pe?8v3c{Ag$$AyK3T7L_tbKr`KinIZ$;ceSk zby<&h?AXD?*W+EgwDSYVAM3zzyLazq2I{6KjW{*2M!#l@~868H~@e2 z(MMGQYQRU19AW(R_^576RgXXY^ixlKx&3J!sLzQLCp-aa!Y5BE>uqWvJ^t*o&r~n@ ztH)=~oKgMd^f`@#?D5rCU#S6V#OKbPWBm2_{Q2{YzaCdqRJi?>^tmbr#r@KyOAG-$ z{{H*#8Gk*#eEBltug8^@m5RSRea_?Hj`qthzc2*!*y(gK{(5}<`gO)%kJWSMYWke& zpvJm&>z4Yj{MF;Tx`Xd}{Kp@EF!A-+rR-#Q(#L^97ZahNpx}RmtF621X06KBN>%!F zm6%CTKh3RhAH3#{wL(vDg@PH5> z+fD0C#yXVBgY>>1ZllmEO1Q@thWkh{8p6~`4{-rWrcz)rx($@kzL;p<%I#NSN|BeA-yC(D@rt5;qzPQx%ogjQ%pFHuAshz)C#OfE1k$q%p z?^_h8)CEfXr;kis8>>~Oejll&_zSNO$OUmXOo#nOzr)$zE@z@yC$;vh^b5ctmgho3hyc&Z*aWAoatke$qzy;`x@8#lQtil zy7&dU9RlGnjXLaVsf5_I!5=U9n!5O-LCH~^8x+F*$hD7AqTQnu=k5iH^T&;p5*>8Z uRL{SAChM6;W*#-gnB1-(yQAcjtw&AWg#v$4dDPU)xJe;&nec*F=>GwOr4i8p delta 269861 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
@uFuJ zxErri*Q}?9YQvp&y?SUp!(7yvG}Y_Xd+X^Va&X6OR3mO?xUa??dat_TW@OrMCvH{` z-Ap_&@yz=+t9>_LOVqIqa*BTw z?y?e88K!m)F-}t}Zjr-%O^{hna2-H7ZXt%!TV#wc;w*9s7gPgolcx?q42lmhUQjpQMw(5y+h0^q+$QI_qfkXswK{Yg z@ucF;cv+3U9Rn!FedJZO>UQ{g+-bGy!P|+)I}^TE?YmuG3%%SId%cVZL zORo0h;Awc9IE&ad>iLbkvGdet>C;mQ zsC&<)uD_cxWdoknk6r4qyICvHghV++6~w>qCieY!qCRn{UH8)cho6J>;raev-b=(2 z^-tTAji<%(j0#C2k`jpWW|z8lGh^6(Jk8(WX{7J@Y_lBhiAE;jTbJtA!8v%^zjLYc z?jzGBc)aa2)<2zW3{nT0rAHmCmtzLx5@`!zjX%(f5f-H`cvmhoGpujuWd55%-$#Zw z<0AN-g$J2KarI!B%F#TvXjDXVIfp#d%n@0Dm>wY47r~u z+wjEUIsbmS$dh;;#{9EOy>mbHRChn|XTcn?m{Fe1c)Y*36tSEZ5=)--tsh|#<2~)R z=(1ut*{AOT#p+u#dYBxlx|MXR%3>Mm$s!=_SCS|ukCk|i+!~_J-zq1E(8@&{MCk1% zG1V76AJzTl5_M|jE*Yh^Zit!6vbiK| zgQ&erie@X$GAmAZ+#(w1J?grWV zf~h@=9&>e|3!jW=;+s-EUn-}1HsOhWORDdQ#NUJ`@?DC>=3XW%e}8H}Z+ageY0+Jq*S!+HlA_R@7b^7u zQeoNhEFzYo6Ylj$HTfa(8!gS1{D3SX$RJ33Cq<23FO5*@A=*bh zTmt>mcB=99Z7eSI{jwv5I<}3GO5ZWdbE_$j=s^N^qeor#h+OKa$6e_)OnVKZO$+|w zyQueZkI18IDq0PDRE}_`Kt`XeWlS~=UUfjj!8ftbnrH;6mZK-+cy{Fmw2tLjBNIFFOd0KL| z8)x)-nDP|0_!;Ih+i*85>Uj-M`(i8Mz$L_&Y6oQE zNnTMu5$yDR+$S_p*l`PG3uO#c8 zFy0kblnIwIAIdb;8MT;LKAxP+)@ltZt z*qxX}9`54nte0zWH(XEYUzIZq-0gYln&;)&#wyBnquTjAb&E6kCOi6CJURK+tKxE0 zzur)fRkK_T;@R*AOUhq}r)&fFpImD3({hwjRdT4i9w=h zzl?21uAzXBs4Y*+v)wr3A2rk^FEcmVh9~{0p2YCPJ!2@LgQjytu+a>rgn*Sgzs=in}V#g@li`>HLEJ9DKv zg!Ou2uf^(Wt%mH$!BhWNLtR@dC%en>Y$@f>Xir}u4}*Y_l2-?3iE z#huk?2j~lvMekZ>*@?UEJu5kVakBM&yT4+a#&AP)ePF1Y>M)uLJRKkQ83I}%ynDHK zDXk%3z#e(35t~PAQ|l__*#ePr17(RoDZJJ`e4Y!#p*Rn-3W3)jjNawc`nV zVH(!zSl)=c0dr|F^ksc*2$_9bqN|HZTBhCf2#)5-L_Ud2mYXpNHg0+i$CXc`wO7@; zl@=ja!6nQrYuWPk8*+%J0j}|1R*}s0g}f0Be1n?)#vC98PvId$-Ss!Q-QR_$<7b0G zS#5lQDet9svV^#lC4@iaCVDb1UzSPr+8So>&(zD4LuehMh=ABGL!I;{i>ppNjd=d> zCWjdr`PA6&hWhGFjHColnbY01wqL5^W*bTG>zgt~$kDOzk6Xt6oL@j@dw`*{>gs6w!Vv^a4yC~Gn zKK4q3U5i6+Q6aIn(DR46^=xOVIcdm&s5*&zo+qovoZt`QZ^wURl)9!-&hp1?WEL>SEkegy6Z(CPOepdp1M}5yjdDr|GiXtbzh#MA zee{t$H$=xE3+S|#xz$Pg=`)h?crS3PW&7p1o)SDMcplqNf4>(`DV{I(GgHzQ)&)^< z0jYVd)P}vs#(YvC!v(h~tMumSP<7#Z3|&QV&6lc;?{PlVhFh#q2i}u&eQ~!UyU{ID z^~`XLN_k&KdGg`%Zg;C|-^cLv9n^+9-0I2qnFY7uiN2HcwI}*EhLF45>eMC{cWHRi zi`;5q6E(06PtDz2YILXr^zdqT6Vvck$hdpm>a!+!fj@XVrpA5Cbf>zkm93p)3WG}j zfaO6UA+d|qqurWD`P#)aQk<;)xY=3TRoX~=_;{Sp{Ct!gJ^gayQ3{(cWU3qa)hT1Qr5Zk zP;+{CR1rn5bDKwxbPx8)bLuCu+|x=>-TQ9!pHJj!(=^iV#;iZW3a-+oF-8q&mP0}s z$m})(nr3>`?QhFH{%nZsdnkNRO=^}`xcB0&nxh_SW+El-#Y*R@zcSTA~U%>tBqlV3Ta_9PAsNOtJLBad7-ZqH~pk2QCmlGLe$VABmH~f zGOqB5j5Ac&Z=8-s9F&Qkq^)#)fAXk{4$7xI9e5J|>`|^S**l7QfN9M`9(Bf-oDpZ@ zN!rG3U#GgJn(egBU&=gBCuI909_9H8^AG-!1D&TlYS~x3n}eqePk|1p!PE4NN4@hE zO$E#D7MPbk7^DUiezC%;91__o$D)mbZBHh3C2hsPAVEyw?4T0crzGC<)*H zFL{Aa-+zwh5^lYkQ-px$8#%}yu6PA5%Bwbp6X}9)Xb=Cnq4bnRm(td(EZpARS2qM9mN`3HOU+ zOq8qne@7C>I}r~N(Wz?Ew;WO?<4&WW%~Bt%;i4hb!R-yU5C5*RJHG0 z+IGal)a-CI={ttkG~6X4)HUC6)TYnC+$9k40rkrF1n$M%I$HhqJu57IVZ7-yHLG1d zF(8q|>+zS3^NLb+a6TJ8G5Z*LjR~@vu3FSKge0C=_Et?S|A#O+%r~> z+|00%bhnK57x7ZuV;J-t_4h;aPIoQt(s^p>zsWLF6OnzMdhp-!3SSQH-1%OPB+Yfj zunu{Oe;eF>xM-*Pt%EEs>5vQkp^vlBTj&)z9dP;#i=VI=IJJo>(s^Z9Ddc{J>m<%t6CRc|U!_~AO z6I)Cw5U2%c=K;eMCJQV2V@vGPN*!7xiVjU?R`A8GRaia^h3W zfk>dpiSo@Y`Z*smdHk(Y4qLT}u!vOdgc2lj!s2?)7@d{e#4=A4sV{;mk!waS$z{fI z{4CNHj=}ewJF~`=XT)`PXwVUGIyIQQ5z88=w=e z>;`quuX0LwYz1Ay4PGu#nwc#$?^YfBRgQD#6SDtC6?Q~^=u6#B@0pJjd+`HR*AW@; zZ-$Dz*(>5At$}aBQTpG#km3QaT6>fUns*0z`J-1oc9a1m4^Ql)UKR2i+v*K?ir?|7 z6~ECl2cBj4eaE|N*KhJPj`AvLXa6+K&9?!!x0&1jmc_(&$$+mGZWCOB?yaNLmh;(1 zzp0CbsCbTo?(-_uC8zs?crseNLip675e!>Dcgb<)1ihGm&~K62CzrU}@U$#chxW;N zAwe>pu#;v@ASV~oRp#$l;0Bn??Q zbfs{|Uaf1yyfU3eq`Mw&+hVoRX^i%@<1Sso-TXd9ZqP!p)pYPn)xVs^G=Dbk&@`Wj z2{m)%jQ5$lz$)`7RZvA>`wE{LcL-C*owU-YE;%H}dS7CQy@H<&sKY6AaOH=XnCQ+s z6+V??375ecBJQ>;)ehXACftEFK3%yp-4QQiMOUdw(wOVdz#Wyt&kO9c_eZ5M%v}x{ znX6ut#yn34?ylQ?D#S1rd6IT97~SDhD-2_fXEUC_T|V`UVO-#D!?QVAIo-x+cX$nJ z)cIo(lr@nC;OFYrLQSD|_>oKN#vhbwb z?^6XHW2#U85~PGXmwDC-`ExE0d0OBa9`LC%zGLkbc!gI>eClkkG2NGmCs^Xc#%S-{ z=y{6e%VS<+sHdEO)(3s+b+2)*uLDm?sZU?p&_n7(pE1ZE`zrbTGxu^ldY3MsuJRcp zJb94Y%6#g6pYgdTy_U(y<34jeYHpnROJU@b{Hle$hAX-K5uqNtlD4>C3qE6KFY2$z zY`5MDOQnV~AX0YlgOFrv`dA)fL=A|cQB@JJ_eUT5G4F;LSj^vLvP!Ixp=v;=k*K|` zeY{sU$TK3ch;IMu427LOQK*|`N?4O>(#N!;!;HC}q&lkM7oS=iW=!=I;;HzJ$_g_s z@bAad=;5at4!v)1nwrRUJ9o?;`a-W-e3CKCpMyKX?-$#+EUMRRyH7Gsbyq`XhpBxh z8T7z!(20ht3E@>8Nf5Q?B{OS)U8{^%Z@T8rqcARXC4zJYidEEVXQbaai)TkfX zKs>g)((UHE1^^~qf3 zw*-q-3sZsdlDTBDBfWPnsc+{bljTOFi|+G_CbyY?4!-Byg4vxq$QbU4{0B-Y@vGT` zC_pBjmOt5^3Ov=yuin=o9e9fV>{mku8}mKE1}y$zzq(R;it&_h^Q&Fj(}E}Ps9$-8 zP)_e#7zCa(v?mTvCZ3D96ynLilZVIb$jXLrV{4Ng{Ok}Kc{84xGQa8`VqD;ff18H$ zq+eYUNy90`lk~J-MC$%-lzKZ7#ni(^KI2y_&gJO79ZzVFC*mE-+v7>Z)A_V5mW8Jc z&u2q1AKcBj?;T1utMN4Sc=qF|(Nf&(Re@pjx3Uo6J-e-F=depL{K zCMxkHzvx#VMWMB3Jp1uawDGX z1AgH}6ME4T4z$2!^x@LsI^j~`GMQmSs6`9uwh~4d37*s@thj}Ijx^S&mJ!rcJAmN3`*T z!@(K+SYx7NzIt%1agE0cOH?7_sIVdgy`yHTv&R`PdA1+xt{Q*GQ1$CLW4m+TU)2*a z#zyBuyLXKqZ=B*7HnUblT*&CKiwmwTu0cEg<8tgc=o-9Z=>oNQf-%f_^M&fF3C4xa zrGMS^#suRc$I$tXk~+7T)-uq!_283^tv`R}-rhZ19Q9tProZYfm?3I#sROdAhODGqJn7W9@Sub><{2 zY&@Rvc>XZSh?P~J%nen~PBNyt7SuY`?3)J+Rl$7z+dY}tY0Mc$uvh%rGmPYcRhI`R zZ5{3KzB9++y|=|RaL3%|)Z1qmlUxPgI@Ev8FisnJ!?%u-$La7dh!AHL6@)Lld$u^` zSJ%iL&BwZ%BG-;kv*SqX>sPtr`SD3p)b==IVQ=pK6UXR%Q+IdOujFajWMi`P@mJNI zlhOVJvb=yS|7Wss!!$epTfIK;@` zPvO!&=@pZndOhA4bk6?=!G8Mt=~+j~$V747<1cil?WO8U-iTB~rW(0t=_t33ayYhX zEqjfqDWf@_t#FM@XtdwhQ`w#Nvuosp$Ouv0;u?ZgMeexqJoVaCW3clv&TXd}V+T$r z+tZ)-Y^@#f^^XzV-NhwyC#jRB8KZ_(*pbYs=uZ2@Ew+~Z&GEtGW1Jt{wrtmeX~r*( zbNiO#TM8TVlPf>wxPr~xIUPRiz-QV)WKV<-_LgJoRJHX0J_fYq>ZAv-$J*(}wjrWMM6?YI z?%vVWJ)~k-!AEu`)wMH>NK0XTO>@f*OSO(Wy3=;MPEEir%v?N9$~`%`V~2We28yCZ zH}&)wH1#Ik4g5O8xWMs0LsldjlO4;Zx<&E!UxG*ZHE8wszjqgpr({R4^vqFvW`6sm z*!}Go=hp4Fk2tdZa`6EcRkdK$$y>#(g^9`BXY`&tVC%kP-5tw+I%zAi+uL;dbNuS7 znZ_dLjWxTb%rcygW$~nNHz~~jy}Me+PId3-iOEcXR0BFSQu>70K6yI4H^sgweLF_> zrf}D6V~#VtM!h}T$eJHK>h+43D!aRf#9%32416xhc-P;Q|C4Lzj<%mX`8`Ujn=9yV z2JVR3ZYgySXV9Hw=bt3|>&@Znrl2w2xo?+xB515mFx9Nz81=g_W7IMIP~j-~`uyNg z<4|{X1%+z(fkIKhFYiy@e(=fQ?!XU0HG7WngYIh4f1)~bu5q>QYN?o+yPlbA>~?y- zCAIM{B&aLTHU8{*&@6F`^30>B$a`7Mm}lG|t8}~l%RD0$BU*lj@|PQkUzYnE=)GoSuebkY-Cz5oH3uU=Lnl`e45_jT^X`9lxwOHe08FTJjdL` zN8K3E9_*?Nn`(uG(+;X3O|F{;j&g7NF7WagOb6KRI5c zlA1nw@nm(^0xBVu>-h_etU~d&#_c-&O#$TzRK)#)@SD*^aPLU zs`-jC>yAO&Z`<|m{KZdO)iEbWq?Hh+`>|&i8bz{dGkUmxi7|H)ztT2shRWr)>>nf( z+jnF+dCmCzWcumZw~So3YLQ4wri=AH9cd4`hOE*f$%@5B zveemA&n`BmkL!`|+x~8R%Ff#oJ8wHZ=rPo zdpcU3<^Pq)ujN)EE9dkoVCyvX+(IK(Oi5>(SN<9u0cSJ|A9`2);4E7m!_HqAI) z=eS8%d*ABtma7-jjIDiNqfJuP=4*evz&L%z|9b89CvAld6KOr+2T^WMkNESGV*1Pc z>f{3Bg7BsVhy7b;JG^T#jwbKqP_^Ph<8$YkFRED=8EfL3bn6&^F4{JRFB^#&-NyXV zlzcp7s|Vh66GQjhT|^9fUr?{48&BxIm8N9P(`fbPJnhYV?||UZRyg69{mC(AR@n~)caIz;)~)30 zf7x?`(^&?`XU_;8^%>rKNB_L>qbK-5sE#9ZY;gNkQ>=JKI>cd>yUK`+)G_db@oQUL z5sah*cQ^;#y7Y1yoZ`+>wD-+0OI{@|H`a|GV`REPcLEViUsR({= zZNGL;Z6nOeS>e*1dQ{Nwy(!kU3>Il4-xBL?{iQoC85t(?R-B}B@aJysxD+mB;bUkMR)nC3@C^6dy`;nKREm-Z0(5Q^_8T*^L4Z$z8yi0VRZNK(MHXB)3ABh78RK zw=$I7PKJ;%GXyT}$q=%92+h<~CH=Q9OpDe48f?)T)fz;@ZL)A_ZcqGZCCHb0%=ioF zKy;mEf7?AZ88zz!g5V4i(2)$#86lE7UfYFTWAaNnU=I7a(Nzt+CJaD)4A1Z>51B$pT5kPlCF&| zp73N;I7LTDhRSq=WN6R9?ldpqu|2%bIJ_B-CLiYue7c|wE!|VSc0mRB^RezBs%V9g zK1{P-{Wg_XhO{o$N;;|-seW5wTv(v>-;P?en!W=oz&2eE;J?5kuoY|rzXZLMrUlFf z4}cBeelUjI?*o(ZH-i=6C!iO8uMWrH8g6q=?>v9r_Fu;YkN$Gex!ZsL!#V2xOe4g2 zZF}eWte-NCMI&_=Hp=e8+W2T~-!5#GL#P$2Sd(O~G$yF9Cqf24xY9`e|MAM)dEQf0 z$tq*EY+A5;p!(-3WBU1ZUpUj&teP`@Jc+HLpGz=;N2k)adIyP9yhFv3k@typgr#*3 z77=IkISi7sBX&11f_b+l zs)sK(UgCc7k}TtF=cq8XCCgaq$WRBfjM=9=>~mDNoHJ_cmKH}z+cT-_y*@Q%wK2i@ zI2V6c8e_qO`5ofmtW;&r(^1u z6N5*;Le(vQ8@;t_?aJxD{3Uh$LD%T5!}L4XFIoTDt7_U6#sX(@y}IUlgI~2?VZ=GT zZ}6i@V}jo&MMO4g$*wa4opY{NTk?!agJWKEPHnq9>8STucXcsSLoHMES4(E)IxTa? zpjVHJ@Bvg)q~aAz#9@j|IwQO0xXeZ^!-&5oY{rlaBu88e`_*O^*%Uzzgm>*n*wG^LQbnL3wkpLusvvH$0!yDN=R zsyxRCIM-CFzvmb^EM8(h>eln-s;{n0I{GDba`3Zcy^J{eEeUd&aI2mg6qDeNV@!(n zoW1pX5?ru;ea(vD;_$*-?|rC|Vr{;aD{EJ?@$fymTRX?O>!YiU;f@h??86+zc8)A# zR>Atbrwn!V-D&46P+`{^GtS%be~=Mxbf=xZz)=!L(kDI_g`y7{=X>w{cC%ae#b179@$6fLrtas@mVz@*SKMksr09}SxVQ5 z+`*K`PUL6|?*E&vbb)&cs#e&R)uHv438cucEEd&_OfX0Nmx zofYTcm=(ucONSj_(Peg{8*VkajN>8)ZIOygY!Oa3X}bm47ay1Tv?p8X&iG87tx&SH z(OK=4!J~dhaQX^{xzpbjX)72m@B1EPxXir8k+wpQP6Z&N(7_GPwAqF@Tn2iL;B<5B z#eI7Taq1#}E&&jrpjUF;pAaLm%*_=u3!Ma%`qb-{~A=NCGvXWxMio>2$Z8N*J! z>RWM^-w|@}9HVaL=*@v^=2paAvp|d;G2yUK1=ksamXG5*s-{wxIL%Hw2Bl^%aaNBf zvI#o(qa5o}o+j5m$GY~q*UY@HS61%@YCu%z1Yck_YKHt*V zo`L@lZ*LwK#j*8`clXde3jzwVi7+gR%-|AtMj;H~0%$Y}>WxMbye2FfMPuT)4M-Lc zqf8`LqDcfzB7;Fm0ztAV@g_l&D8^`Rl6xcIy*DgbG;tfZdB4@&11QMz{NCsJy?@N7 zd#bBWee2YzQ>Us%PAltwa z=DHJ1@)qQf)dw?WJY%6b(HXm}Pg%oX1d+LdKg{@Lm?p}|Wh{GGO?$gST^@4Bp8T_un73ZFfvut%Pzw@@*O}zSKeIDi zp--AJTO;q{+&{>owb1dmp5|5V5g?Yrs@hvIWk!kpgf+LP_yA5NmM(ydd72-{UGGO; zN1&@UimURavPyHcA9^8q4k~>fHUVhc+Yp7wG0ULv=>vZrcA6gcjDAAgDUC^49Xvhjl!0J#+ zwUV)v3(RRbk|mQ|0zE(M;1TH@zYm?6XP@Y#6ZC3#IwzLn@&iv}1Wx5WZAK;L*zL*R zJBXr3>Ji*nByxaDeWg25J!vxW9_%r4#l%Nz?!1D|cl0#Vhoi`Y>-YieR`SR?K04^% zV(=u>?h)MBK&7#wB~N7uM&Hn=>{rpeBPZ%fQj2I4zoyatoNU@Xlt!Cc(W2;M367)D zd@7p1%!zswN4--{w%&&WW7nj0f-QZaJJ`(B?i^T3EbDm>t%KPPvnC^!Gh|?Dv>shF zHEoWq=j+%k^6Uma&NXHlQx$yEUe&lH$nx8AB=G+IhUOPr4-SXqZ?xlv7pxlzsAbE8&~Iz$cnQj9v;w4e`-8i%MFOYjg% zv42JLp`57sB*@Hr_}$Bp=Fy^^Co*HEnSWupFLdmU9gHnS;n^MpO96VZycxe67;LBD zoP+tyfI|8m_HOl?ED>@rxMhhUhu_0}_&xa{hfj6Fv;#|`b7AQ%{ec$qVbjEalP7Zd z0Upo8sHW^~Quu-B!1BdYSh72p57Nw}5n6^#*@d%pTtscmTJl@l=FJwIDpFD-N25835@w*aP~R=1&xA!pq2X-xr&HvIw^gg z_tyOJcPV(m2{Pe%1TU6?o8;h$^<)zbo+kx2$iaS}lTT^zOey$?9DMT_5%Li{RSG^R z2me?{5@~R>6#SYT{NWL@jRubwgT1%P!Mi_Z$F?k(WJ`cHN<5lNPCbu#)e%zC^>Uo` z9}=$uBpo6JXUf3~YRP;An=w1pU$lu|V=a3)Rr_Kzp?uC*o4IYrtT5h9);HhOwAgb$i-8sgGnc+9CsCS+KAyhN8dS zI+q#O^lxV70nBQrVf0`em`W~e;eE{?LrhY}!itE16)~92K3b3GNH&`-XOp!&bE(eC zCZ{7Kdt9Kw(LK;1V*Ru~SEg?gtY-B0@h(h%+9`*NAT;ft&Ou*~j+)JAy$i%I$vuM@ zUkMO&X+<5!(D}&rw7F6#nli6<5YVF(TjodvtSCjWe;o-==l$KQk@yA@$0M;8EQrk@ zu7y}7&PeS<1sqYwPFQ>=C-*)$}KnZ%3ZmYzui4Ik0)T6(E(1& zYiv~u?ZYvaa^A}O4mmEb3AAJ>Y%Otq797!G3&VPW2IDJvZJ_epixbJTtymjqeS>V< z%BL+@x|6M<)WTk3t1EFVqTIeZhSn`{0hZ?9bE6u6h7xbjjrtundAB%utfe_52YsZX z;^)rqWZDb7uk%k`0#+a(>1<~}mt3@#W;=4ZhFosuMt#*|oB@{RLx{5o9^X_3$!%5ZAN)EM>RQbj@pAbDVT

0e}%+Y}46dOC`GmJ+8E-jq1Tt92HMcn+S9 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 z

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 H$oqc)=Jxx% diff --git a/apps/mobile/modules/t3-terminal/android/src/main/jniLibs/x86/libghostty-vt.so b/apps/mobile/modules/t3-terminal/android/src/main/jniLibs/x86/libghostty-vt.so index bacab7908336afa52b3b898d8d32c62455f458c8..db7667ea3559ba6c650125ef04fd1ed6ea099f80 100755 GIT binary patch delta 329183 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

|enDt406)NPnhM5j`8LNrCO=}FN@ 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 z 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 HDF1%|k?yI? delta 286773 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 LV!Pxvh$#ENs{0SX diff --git a/apps/mobile/modules/t3-terminal/android/src/main/jniLibs/x86_64/libghostty-vt.so b/apps/mobile/modules/t3-terminal/android/src/main/jniLibs/x86_64/libghostty-vt.so index f6e0b646a2fb2fb73d55ec3aa4326a801e022f42..0a1f011f8c1339530011e87acd03b030b49cb1e4 100755 GIT binary patch delta 368566 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 z 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%0DvO^7& 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 delta 309380 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 z zv7AzyL|aKNldE&L@Knm)f2Gz+?p2wLB2^Buyyolaf)IOlp&oS(=Ot7SB~oVim)(xXhbWy4BWOo+_=qx2}ON%u9=zxc^nq&e&y z&ttM8CNq*X!DLh_)4Zc;7S==Yn>o)UXsVKW1)rd$d1!xA6S>coo@_2RrESb2We=`N zUYZ5om`~N0CNcepH9eK@?@Q*Y{DS_AVAoy8@%;r2Lk0Rp{auTONt2Kb{ZQOQ%cM#A zGeW~&EHj5BdTl?~Z#%)o>ry&yf~=FCyZ;;T{*Gjn`>)3P`_Vpv{(iKNK>Cr)|8l&a zKuwdE#pJwv+n(-N)RuC3&}INJo|3y>vMvOSG}98LWSFIvOoq zN>(fy($c)0-d9L|vSyfeV85CEB$-M-m;NR%iOkit+qlZENKUqfbDP#U%Vx2Bd}T;1 zn>n0Y!->?~aL%BdA!J@Lb9?gA)>)Q|D><~L3lke~gywW3t3vWgmsOl8IVwlg^haH$ zQj>jdwG(y0ck0?fTnIBajp`|~he#*+Q! zQ^dQbf7IlAe~7xs3Q+1F!BQOJpY1 zr5~j|*Iyx-zT+GM+>L&Jjm+eoZ%4w%EQHaHVJ2j7}g|OS+=7`HGIG@hs`g z%0Y|yH^Ew?vL`mROuk2OcICQAPQOXsnU?Pu!Eol-H$-LDO7aP(@(x3Kc#?Of6EAX-ytQv`sAa95m3S3m z?DPJWrIwH)uo=)e(m*M^%K$G#<%%?E$YmfeaVB7gw@OzzUqm@%5FF?1vL%a-XWlk? zod3*3&L=PCO{O88N4QckJ=?xUaqf^_CtD~>I$v<~GAq9&ok_WtH@(ihvvD@%XrG(| zc?}ciqRLrjB%Aj+W_m<~#F>{lTJlVhxIEb1o9><>Nn}KY`rn(lETn7nlVV-IKa}p# z&pOM}L;6W)RQiaJX2~;ef1F|IB~-E@hsG+jmXQqRrq?W&MjVOSxp#cG=*{IQW$3^@lGr8 z^2(Ky1~d2P@dWQo%2mB=NE5WP%YMiBKS5jLW*a2UGR3|{>u;>$33$=5&azw`$S`+o zqW03nIm~tC}&SDh2*m*p`0zb8l!xNpv{A-M+$~b(;wlZ-AmntF? zu1liKCdLt|A~{aUjOa46Q?$H_l;adFPCJ^M`UP^H;{0o0Yf`>`3E9Y=DRKMKFOVZM zGnrhYN{#HRKRGT7nedN!wTN*p>tqo(4QVyypbRPd7&*&8#(8OpQPT#lgV;*hcZZmk zdqTMkkxr5-A_I8|NI6XgsUotG{kkzqHmM@pyvcjS=D2+wX`48CgXmsTx>inH9Xi%E zbmGd;vGxd_oTO)Smsym&8#gmNA5F@+$&IN?3T?H7 zl6BVvt0h+iX;d9IsyK-=f(cfOvL>jwd=({G3P-7IuxjJ?rRo+7MWJl4S`+rA>J|${ zDQvJ>6ZfU+7DKWBkmHa9t5zmlDpj{wC<e5nqj)wrJzSp7p(h*#w881&^LRRmBBb5#UA zoZV{EVVQJIC&vBhT)b{^iI6f4ncx(0e>#Vra1_u6r-%E~IrM}x`+js8{w8DGpU$BN zHz6)lrU_0F_uI7a!l5V}g-aYTj6vL=&Y>q91+>B0!~N+Tdcsja330i}GX`;gg0vip zaIz1u!Rg`tbPhe?D4-2a5BH~Y=n2Pu(*~m#Upx-?r<2SX!cjUKtif@(Kb=Dlj%CUP zr-%E~IrN01fHpWi++SA8`0poC6p|9!;1qFxI)|Qc6wn5zhx^kx^x*6_imbJ4O2pIS zaDS4E8xln!DWMHc5%;Ha=m|#wZE$+HKb=EQI0|Tk)5HDgT-{>6KQI^YX5S!ZdnsXQ z(Y_q>vv9HjHNokzJf?H#2`B5K2~Ll-GMz&Yjzu7D_V9KQAY;}x$P^Am;n*A6;0&^4 zr*r5DN8xO6dgJ%y&=Zcr*&sjvDm9*vN(zS}oSa^4aC#H><WGHICG~f+fuiHjmQ1z9D2gZ%PbR|J=wmHj`x!&3P}lV zaEh|sNaN5GP7ZJ;I6d5-&Y=e$w8834!2RhQI}hR5UE1LEaDO_7o^TXy zd|du<%j^W)Z^$HvBAonSn+?t&?oa2?6OIDf;Ph~RI)|Qc6wn6lAEftB!2PMD^nSxp zkO?L@gSg+Og%=JzIN9jh;Ph~RI)|Qc6wn5zSH}1E=_HD@6IWL*I7Qr_&Y>q9dvhDC z-bCD=&ausdljFP%P7n9nH1EIUn;ogiL?Pv@Zh}+9{plQf!cjmQoF48^=g<>QE)7g@ zdbmH0&2N#zM3mcpw?oa1X zEaS8&?}JTndXx6$&=XG1Lnc_gvZ8%Cw$#GOd(w&g{F{6!D4noOj--}!9E$dsYJU#R zZB)wSRK)8!8{}E^;8+h_aQ4RS%b_P6g|k8QIRBJ!CQBz#6pq5!;0%u6mqSlD3S)!Q zo3Jm39vtg~3r=q$Ts)0LQAkQ?gHy!)=^T2(Q9v7<9_~-)&=ZaV+F&CmaQ|!Rg`tbgnMGcoHNFf(uR&_os6h6pq5! z;Ph~RI)|Qc6wU^xhx^kx^n{zp`(M5rCOAdhZ_^15ML4-=vBBx#{&WsK;V7UDP7n8| zbLa_20Zowa|D^Zx0ayy_4%k8#%e|uwiJzvSwbE(1A36OW*jiYAib{U?Kh=g+mLKuv zIx8yoU*;~vXwTrIAj>Cc(Uwnz42xolvV38ZPK)v>!iu7p@3-u(%+e+3$FDfseP*9{ z#@tny(s0q7)Hkfy=W3I6oDm|M4Y^v3nV$uw3(Jqqr3%b7DQcJ+sy!%qs$ZeWvzSvic$WqE`Ao}61si(@$ zw=#Qk#4O65&<1(O7hA}$t1v#XIdGp}$IqSU2kN~eJGRt&(=&9{cfINITgUi)Z1Q{3 z)=6^Xc3&Xwsi9RTwD9YUqDqB`4{sTI>4f8y3i8XIGStw^N{>7Bf>WYHFD--fsiBvS zo1KAV4q@n}rNs^3#;EAbe(6Knzd zz*aCfo~KkBk&$30SO#{1m0;y|rJBJOa0S>3t_Rz|tzbL28|(xJ58*km6zm44g1ulf z*axlvBk%J(m=A6R%fa|=BGp9l4<#e89Bc)v!9H*)Shz!})nGf=2gd%0{!qdPN=*hk zzgB8FSon=n8^CIC8yExkfGuENKF@7&X*_mqX z_^lag6Il2L1^*W5Zaf2)gH>QP*a*hJ#jA zL|VWVV0*Dob&5i%PwkNOc%RB0MS6lym4V%-`_v+^0sA6KDAu%i)DKDAZwDW8fQNzFU(09gJi1q1W9;4$HUgU7(W*Qt3S8UB_E zfSp_M6xhCvjKRwH$ynq&eQFOF`3n_}A4SbR@u{g`H`okTe@acjuD?+Lu<{F^@{T6` zcb_T(E5E{}g5Tg$;rHTFFrUe1C)k$ZS9!-!P@i8-0XzMEwMf#Leziu@0l(TN>5yN= zLq!-E{p$VKE$tjB|XHi@{7?Qiea$wFbqrjaKBm&mPh@n z3(OzxSG&PBaBvAY(yu0iEu;Oa8SE?Yt5slmgFmi9<4|bus{*jS(XSf8{44!xCD^&Z zue|YiXrW&fg6-G%)l{%N?pI5}wk3YG0jzHIs~uqER`e&(cW$FXU~Gk7)q~x4`c)ek zd5i*rg^&AHA6U5-L(z#uHu_a1*!3DU2V1xJ)jBZ$b-(Hc3*YdoNEs#nonMuM-Ea9- z3@rRT1qD07ZD4gbhQVHNx7&_EbrC|P0PfZ28!9`$YzNc1!E#OA59qbW2%u|EQ z$?$NV1KYq_FkTt;)G{KyBRsVpEF9^nK2ZQi9#4TrQy{PtTnSbdP#~}i+$lKLQ-vpB z_()Gxf|XzlEIb;0!DBqt3APtu7mSVb)RYtP)Fdi+BICc4$VQ0RvA6{6J&ueks6n}> zCWDdVJ+&Il2RDJ$;0~}2^iHB+;7G9W1PTE5f-PWqg{RhlEnpYe3ig6+VB};9dZwqU zPiFiVp2Y(Y)sR`#wzqcDK-m zVCOYFa2oQsr^>*>C7zlNR)WjGHn1J+z0OnHM1H-e_JA=k?{qQ*E5LTJ9*ivYRJ@f) z^$$F?0gT;D+f2cAx6tNb*Gg&#w%_Ba&62*?Q@g>w&A9vw(gQNpRIu})Otlp3&dXHm z!M>rHYKNpJWU9eu^8BPsRRLC(XQ~+3b$q631FKKUjH}H=Vkc*+Jz(UtOjU3e=`%9b zG_d;8Otnm~I#acSm6v6zZD3z>rmC+b|Gs2;HQuz<=vAN^qgRRs1Pjy@P0jy~8oBA_;c zts?_!7Z@2GP|*u0*bxC$1?GFCoLr&;z@^gC1Bt9lc8!|J^kd03v?|hQaok z82&bVT|i9*t7iw)Qn2^C0ks}%iD4M*n~UK~DOe+h!B`WX0DI@-39xSgo&Z~~3aCCX zvN)g$tI@lfCIegIG+CTTCW|xr$r+|e&peexW8)*u#ds#qjk@U?0 zrM`o_6~kcXtpQaHMsB0PVC(G^80@`=rUUcuqv&*TXz8otmuS0kGv!3Ieu0K@GwDC#j*NpQ476UQa=4NdKIIfcYH()hg)?0o5z% zX99Q`?4)8~+p_`o|3W+;P-}$P7*N|J{jUKPno0Tv`U05$EBXT1{vrhh3pWSUX0Y=m zIvH603Z1N$0(S+}G_dm30Dlx1PrVjU8^G${1k_GR|CT1JLvJe;0xRF74H}7b{+=2M z@eVZt+qYvFtbU)qFbj|Dpf7-JJL$b(>xVQ2nEw&_!hehhz{=0@z-;X8#RFh(Mo_H+ zW8t9c0t>T(YL{SkQ03R7ACCmpWFplAgK81jdq7YXeHVj+f~p#f9TZf{z}`cGYCYI_ zXi)6|yM_i;*%cHdKd7dGg@*-I3m7>(sM^8KXi#;7`6WRWi6I|{KG<6tRP|uX?4Vi! z#(NurY7>#}D}$;J?3^D|1#@|@IjE+BF>oo^2CfFX<3Y7m_$5K*&4a&|0)w$ff@(V0 zyEdp=!NRA4YJtZ6UQ4Y*`*sdxZZ{NEI}b;hiB>4R(P`CA~7F z)`Puwhtw|N?+K~=1w7Y=J+Sb;kXi)hKNeDJgl`Y2&0zTxA+;N9e=?+Q-$QUeK zA5z=E*v~^MbQK1{Qm}JFNY#RszYM81F!C&hz}62#N-aYEQAibmT_1H*un4lxOlu?nklu>8odiq{i~l!nzxuy1@=Z5F}Aup0b544oKOlfmwa zuxbViPYJ8lVE(CLwF7KDEvzEn$Kcf%1Y_5P*|d<+jbXJOj4TVQ9!Z0t7ScC`RR!1z zE&{u63dhwNA}zOs)iyA8D~eZ>(H&t`3by|+tZKp5yTWR@q;**3T|@fru&M&f?+vRZ zVD)`r&IV+#Dy+7GoezcS+VHExst}C)B+O7FpNGS$4Qzdcih`YM;$gL0h-bnox`dKG z8&=g|^~SJT1{VHHSak~jVp#10%QuJB$ZK)!%V9MY>;_xF&aSXp1Lkj|#$d}^c;-4h z`gT~AfrY;htLc({C#+h**!Hm609MD}$CX5S!QAU9$j6irtlmWdmXiLAn*4x_KM$({ zu=)!!23z+~b1?E%SZxB^!CtWK>#)kdfdYY(!M?pT9oWKeE?)(tLaJ=k54t#*K|CuFO^x8u=@Y&99IJ~>-8gZZbDUwk<=J3U)ffw40v z8Q4-u$-u&MvQ;10R+Y_&z(eO#6R-_j0=8e6t=56n7iX&;u>2Bg{zEeUPPVE5+b+*m z^z*s#M_z?y8ZnlaS5vl%Nwwexhy`8OgtsvufvelG3QFxD%f`y-D ztJRYJTejK?wtSwgyt^>?MYbveV|^41?D{5KEd$%Vh}sNRUlmbNP5S#0H61K(iKrc5 zBpy+@E74yPiK}uVh1a40MsAF#C1CYU5!EgVH%F9rH|g7vgRzwn#y&2+C!#ih?QIwY zJMYKfJ@Bg{svN9-D5C1Y!qpMA0&D?0!FI3*>;}DiX}Z;vWF(QUH4#+>_O(+0Fn?V{ ztph8cjL_l880-UE!TdH1f#qNqSPk}qEnwuSh-w21!A`Ie>;~I^7EzJ=un!i3mFpv_ z5^RYRX(kfuh^WM1N-(wR2SI#RYdIq%m`@>{gMK9i$>ndWgPjNPx07q= z0|#P{WxjVXe{Z>w@!vWIgH0$NIY31hqFBgZdIn=h4^S(=kBh(!S0gVX1F*B0zmLrf z85=i%lPv`u&tHQFyC(3rlEIcT>@jl|9y>ry1G|so55$9g<^26@u)TsmjVu$>=>t^z z?G-3Y8KAmkJXT@=tUPCcT0`&c1b2gdRs11mdTr(T6a;LW%HP?hbGA(54|CtGZ@AtY z5nu8W1yuIGT%d^10+*4(O(wnwE;EIjM!Xp=l)}Y`H^Lp9!Yv^#_7ZuFB+WoI_P)no z!uioMZ;+m})EnsA@{&GtsW;5m^OF9qAnBV0H@>7F6ZF|98+@k>2%Ot5Uuxv~)f>Ho2S}Y(lP*BNUf;2dKdo@}jb3)33yzZ9h7Nz5!wdN+ z;`eUwE*iB4#b1iyY0j|k0W80$#89nYsVOfj^_c#bCwMOVMsI*`%gg%E8@)3#M{ZBe zV4yFyU5pR&wQSdG1xZt&VZQ3^`eT>IRVID3VC8mwj$rwAJx{QZ=a+e-eJi%>r%7k7 z#{O#k={&bmlpl{@#bl@iBm4DJlgPD{6q|{S7W2=`p_`VF|ivmi#n|`rvC;d_% zb|X*TcN2{nQF`r7-mo*vkWUHlGKLQI9&vS=tMO^qXWr)B?)QBcGcUaO>T4%O0=d^` z-rzH2ov~erItNKrmXV=4GWFV9yhHS1H+$I^MiFpeQ=0=B>Sx50?Q`<=eqXzJR*B9w zbZUeDzK*nWA37?Op(6T9YMdi^axhW5c&zpYJ?uvBkb|b2jY%mVFS`&|A}+d3zkahf zBEZptl~jL8JlsY+dRvBiKtK64ZkT(EH!#f3UoF2qL-pFmy!RF_9NrB#ZFz<&)7J(w z^9~s8HwBJjIZEe?{Ybxgi#IGhnfT@(WvDCkr+0dJdB+6P%Fv87bT4ms?kvt6tZ!|l zv$1zq%kS0qw|b)kn~9hHRKMKHFxg3b{ZI9WtrRgg%=Gos3>DBP9hiAwmQ=O~s{5z9 z3{jvL2FE_jkvhA@9THGjxZ}Sce zM6;O59@8Uk^G1xBLVRg^hB|-@l8sYo8%A7M@8k`3uuNJ``IpP{?fT5+-sm73BBdVB z&<{M}9b}rM+Y;&;Papj&dY^}_7rK7^>T55SqZwgWQ1zsKak+PFfE|d6KBWV9c%x5W zM104F3^fFuWHF}NVo2Msbn-(}@J^m9L?FMR1fpX9au_@ybzpp77I&caPThub@!Nhz~wO3jO3O-XVcCBu5^h zKM=KT#7hf|sUt!IDQAICm6Dyh+?D4%O8cUneG8DUFYxKxZ}bkzk{&Y!VpD<1BhW~^ zuRyQ8lX5I49vP!wC7!j8c>Wll3WoI2oph1__Uy1N#6pieRpe^IP_8c4=iKET8ZIIp zE(?)DDJnhH1N zMxR=oY3%98@1{2{foyBl&)rQq*Aee}NPm1c(@(b;SgjAehYsPffaX2yQ-gKQ2I_j* zJzjRO2(I%TTaUnW65S8$`|rWXa^m@q=vO7ak$CwU{mnhn3yDYC^{{)Hq;d~pL-Djv z9i-1=ojT~t{;5EktpaK6m--qs1C7MHpVe#crATeWxBQEK`(E$RU?=hE|LW5X_u@shM`!JGIRl7|dq_v+Lh{`n3nVF~Ld_6)*Yp-Uqzl*^=M2Z+!an`@G@7A`*opp6{ZB#9LnX=?5rCa5;%y z60b>OBZ=~@K7H=5tyb$BJ{^CZcpg=H)2E+s@xpD4k^6aI34+$`n7!XSEV!D)HWE_7 zpnPvpu)`%hrou&kbcs?Dn?JDC4A+v#;tsZa=4ZcogX%m==zKC!>C{{ ziRi~ZedH>tG?@9bp2P)`kgqVJpZN6mS9wFi@+rm=-VJ|gyrb8zV(n;!tNN=?y``_b z$ICnTY1v2Esaz)2PNZtL^>$7z@tqJ;`55KI)Aap#_`okwKb5t(-t}kyk27@jgI?YR zGOvq3W)Z=Q9LkJVH58|p`_%=+lT)ql3^ZO;lc5@s$B?t=_b(K_EA?+5^oqk95SAYA zr-#VS?tp1Lg;qVPJxE%P?qvA!SW@Dk#c(|A*>4|tW< z`DnE_Ft84;?=tQC3C4C1FT32YjylM0S=J7wl>Nhf@ZpG?^{k&zhRMVW7V5ix;tdPb z6CZh%ePkQK!DEYlzSgr#e%=&#hHA4X>n@vSTMnGd5gG75i>UidI}rV`J)Pd_a2 zrNlQppx-2(wVrtWgML+IHVlUxaI)Pf$|}@@sP!RT@`!ir*ywPo|Bzq(5MG_>Rsh+* zajzUY<%Jhz;7mqm+K=^JkKo1_@r^&$&+%-aop{wc{mmoZ*kCvDU7P&+m^G}?eI$B* z<=5w~q2m{hKB`}OZdZ3Br-uljZNPZ>GgB%-fj z?Wag0-(rD%*RL-}5-%dr`mSG1l=7bM%3EW4kdlsDg}fd4??isFlbg-xi<9Y%H~3`x z<5ss8bf^EpudWu|#M#2P2nBM7YD2ye`E&j9Lk9$WRVdgt73XY3sk+CnX4yT|m`53~ zJKXc()ZhjZ zn|9*Vc5isNheSS~6I`onK4u4bR=YPK7#YpX@FBi!$LmETx=CE$?j3(dGl`YGepMlr zKhHT*4g}75E0AwMUYshI!H!%SVmZW5QSr7rSe z&yp|cb!#yYBeC%-e6kitw~{F8^XosX^)3nal9*2732CXw82S&1%by@2U;H$Zh)bf9 z#BvgBgogxUF7f;mICTYyZGC?B>gTQDd~kQN7RCvikk)_gS7(@2_o9h*3`VJ-+2vDbV6x5Mr_W;rN~!))~&4NZYv1 zD;l#JZfDR_e@Hdrd)ru{6RAy-Nyt-2+Fo(YlU_K`3%4$;Pk53!CI3j)-7G!tN#>m? z#8+qOd!A&!CLb`Z&-OU7Un}eXSLy4=O3@skhdt$u9=(Zp0bilaPAkC-Q-Ubm4&;mS z^vzGPca##zzHdjKe(GmTH$}wDhv=O3-qC?t;(J_NK8IR=sPJQRXhPLRx^bwd9?mrr z@8COfjd>Btmr}cr^weUz^Lci?oDYs-8+DX^bv>1;BHnV0{&GEAl1Ac%MV@~CEpOBS zo>QwJI!iqDdwm{T#JsUmOFQ<(^cJMCQjaCcY^t8!>jlk`qz``G_%zbv2_sT<$7!f#lrp8AoUmk)b}d3inDwog6r)0p565(S@m z`f_&n!Q62S_|H9k3zBdJiP#sOy4Rjr-*|>IqkOj3{iUbQH>KB^zoZFP!9~9D^w3{= zV**=9bPd#J{!%&u@in=6-Y=yilw#}vea|mhhO3BI{;S>62bYlOq5;i0WgUsuf=u}R6+gZU-9o}NgpWjU0#L8?e zU-ebokSXud`pa#KOs|7#N4^gEb)LShldQjfj@y_X$jB}F;OEI`FmwH)TlAUFGi2lw zz*T&6G}xZ)ZhfA{md^o~-PhT)-APuGn7oyI#K2}zd&8;i5w$m6qJX_Z={EPwG!iS`auWF> zamU+EvVp|p?o55|%UIhfYVTy~@4v{-px}7C^FhJ@iS>UXQ#o%gBjNoyv;Vxg9%9;G zoazn|8+xs3c<>1`nr z(p8&1FSr?|^wUgz>1MVsy(CtVFiy`u5p$nq>L;WHDoIrB&eVU{%nou1iQd0CwRI#a zzi`FxAH`T<~`2x0~TS+L-CIX$Jm1(pB^3~+JfPVEQ*-xB|Qb@0T zg{7*Tcr;{nV#Jq(^*66zwN2vL&QKSLEfM`4IjHv$FUqx97oI`^4{%D=BzgwP)1$}9 zB-1MC!F>DUBazr+nn}k0X<}KB23n8ao`bEC@D37F_(8WwNyOZOnh5@KwVabw_}7g7dn+*Inq|9 zjQHwNrW~WpdyIOB-J=8QN_(sr@)`@ya=6MP0_r+*i#@PO`k7?30e<}v)^$52KE_rk zn0Fc_I5J>5ba)DhseG&Vig|IMzs6#q8E(%}I%f;MT}^!2G5Ulp+;DXfUvi9nFwjS0 zOOd{73sY^u=~yhcSxuAp2`;N;B&MEdD`56I@;T`03Vr@>*oO2FkDa8yC-Kk}_>)Xu z7+pqu_sIdpx6u8qjEXMt1daa8_F4yn6X!UfR^INOXUSm5# z56Y35rT}9zE15lN_5Cl=hD1B-Y`HQUNksH-{>?i=uX}-+V-@LGeLx>`zh|E5oMUaN zvnkFMw#dOE60umo%x75z_`4cn<2?QOn_TqEr>#pHuppb04J5YmN#acB*xz|U^NL~z zeBMGaJ=!$D;B#2d_%!hXqj$gwnaOpi0!e#|e)D&@Cq}&J>i(zNNH4!8pdQgz_IP=v zcbmpG$C=GY245FYA7@ay#8rg9M~3T*QsAA)cU&JxyBZsOE^T~6z^s`;`L1^5jRE}$ z3uv&JMBeQIefV1>R+E^vGN8|W%R4^2lf-1cXk2JEk^1qsykK?|`-7`^{6QXni$ixA ziPDDxycy-Ne2^adwwDvEg$w;Spig|8b9O5UwK||@zs)?nkwo{yfsT9M_MXfLFFK#D z`ba?i#GDEBTsDir)o^8N0y^I9jS6oe(aooj&1UB~Xofu-S75LYNzvm0{jHyQld@T; zRK*1}{1XBF{!`xgBl98T+uPQ40rf0#H79Yw(JD?-QK(hO3!e<=FS@D4CK7A-#{{B1OaC0770@2Gi zlusFhcixwotv9|)7hDJ5vN@n%d6$dqZW29T2Gl&Jd0rMC`~&G3cAH*`(a43&`u?Ck z_kHiEY{_LZ#5OOeKlu}DRU?VkKu{0eNkYB{-bmu~o!*$S@=+67UC___@vYB>3^`D9MmQBxl4oUdAm~lT(}~*%FBYP&|DMb=1aN7t<&JA zekW-5);Sdr%OJL19#k{!6=-2oVh`FH#8jj zHUsjip!z^R`5~L>?)SaCa0Oh~;-GrSo;42IL2qt^tL`v|)G@(TB=WBb>PvUvhAon~ zKB#Zm;S~o%mtt;ZP`|LlJKMa;oDR`@AAY&Y+-D!WAXy?cozg5v+;&h%Kk|-uiCK?# zLo6B+QWx24lIQ=(HA#Lo+nPi5tAFHWO$G6m{1BfDn0xyHA9&f>MHp&??;94fJOLxhHh^z5H`hnXSO1yM0Pr0%n|KmG%}-3PbsTOoFo_8s;I-XUhnExU{bb7Y9~ ziQG2ncm6~lsfAlJDx@AXop|sM`#bS+B!x%t38!@VgY?@wy+PrPaP4rtcV=Yf=>dP{ zRIv*xHa?^-mw7j_SMq<`*pZHz`yC#cz&EevCQo1XXSz^1Tm=^qe`fKhC%$97J9Dli zQTwz>jM9zocwTrj%*f+HYP;DT5B@EV#;6$Hjbta1+f3ga+|pn2=;idb<3nnktN(}W z#H!$`PtezW$Vh7;zWfBe_Cxk9?Zm52((iugm4tT?Uvo-GJ!>o;^q93MlTPk*Emnw+YD|RYn&wGYaOC4zVrPT`~LH5EQfpGV$X!sAKjS#h}?2#(AJ$Heac5% zi&m3}J{!`vf5bRwB~kRNkbYhgog}8e7}B3eqL;*y%^`jG$INPlGf{gvq%Z&28=f_l z#G+S1{A8QG4Vh7#93E1rrHJc&Vf71r(vx1^kb4o@S4Z~d7wI0PBka-jsNArrO&$;S zAnM|awlMKzS0gG;=tI_%t&jeMH@72&tEJyW^sGRWY>XVyC-TVQE^aMy@|(O(%8Q;4tsmc9-?J0HDf(gmZ!`t}bv+N~h5fy5IZc&D1( z-wudvqxD*z4CmHU>?6W*1C@eO z^HA_ze0q`2`J3G95U;;DteWghGu=Ka-duvDb6Qw^%YH(0_1~D>*1&C=rf-ucx5|^3 zhWV01?zaYiNA^dyzp5*k7B3HT7b&Oy170@*(~}F4G)~upKBraWM=Q2Y*C!AUE+xKb zt(z#y6zv9tw1&Oxrhgk@&e45?fabNPTD+X6|O<14%CFa^l?7D~2N}`Lz zlKU7PUwRh?3mU25PwDetaTc0JV#kg!XN6)W6n)88Y_{ZQNlO11)>nvv{6NY2Pt*EO zum`U5)3AQwD?Aao5>Nh>%1fe*#E#FxdT1ZH){|KO_pm;z&l{B;rSdBwCi}DW-VeOP zvN}l=(Lx*sj_zemIpI%UmYL&v5v=pF65qGo+2`ekn#d!Nr9L%1r{WK`=M-Ra3X z-~XE_;(#0M*omSbKR2^8S5Nqcee?h0?M=X&D!RDwB<-YYxCIK7y(|UF+5!a%6k4E= zLdvEfia=ScAP5Cg0Toh60*y6_OC1$dTtM7Gfue%62xU=L7w|;^5wX`Q7Fk5L{(ooY zCb=!}>id1)^XGZ!y?5r!ncti_bLPw%Iy>`5ZGO)Uq^9#TrxW;Ncn=#x@44ZIr-cQr z<&?!Gn@6W=#>~UL9oVoYjNlv919oTuV*6NoBsq-$>%4FS7pfGq(65f79Yp6sTj%2d zgJ=iQCfN>1vZ;t!`AE2K?I$8+2EtB08qNM)n!wWmHW??yapMhf$ zLqyd?Crg1)=~g0U`P1Qn@f!9*A}AJTCw>#eh5H!XO$41R3^()^n{NuQGbOzJJk%Tx zPOOn`Jnvux-Mt9R`5+vhYGL zEGo=~u$-(2Lm%Ps){XO-4yv5_h;!k1$eXf%EDZFIZy+WV2S$K|wk^CD$|B4M5!QPu z|16X>2&usPO6bLjP*y)Y$^nQE;xx(}(St|7EG9Qa6YPzsVi>|MQZbrI#psQ^ry`~+ zT*kY?SaR4}d<-}pfx(2>ul+$7vxIz&u&EzM^3TJdIfgv=U7uiBjE}JP_!tiAF9g%* zj|18^1~C`TL}0Wdc1cVKXO^}ugyok-(6~6zCeyJ=Mz;}tEogR>QPJuO$8tBmU1uX; z4rxGGt7jmG3y4`;5y5{72ddb`=qo;rJ7^^wbuup1{piP?ttgJHaG|0;|5eDYj;$Aw~@1V&3#=rxe9y#7>EhG{g(^j(W0O)K|20 zK{@Im9p3$hmUgY-li~+x@%8obBLneM{vqCVi}*Tb9fLPCf;#-w4}xnyNO|tig6it? z)L}Y(t9YFf2D@dMs-)NANs=wK_P5m)5DkB)!W1)|EG(|i|D&*u=Gdh$0@%9| z1($U=-mf*`eWF;CFgj)UeA7tuVy|LHB3~HALc&%etQcX5vUgb>g%SvEUh<4`sYN(twME0;)z{*dwwv zQ@%Yw^XypixJBQZ{455j6h0N<(c|*V%vbt2ZSBJ+!)GK_oAz&HA;d{1ji0Er>K^cT}1}HSy2twWt*8Lg<(H$4_=*^}-Nh z^QQ1EkvuAzwTfATZ>6R|iDK`Hx6~+Wfn`0c;LvDx&6M1L+v>1~!~1+9QXPTKK3Io& zOwas>kFCp2MdI2fgY(A_Ws@s(B+sbF<^{Pn!^YQRsX-I*Vvb?Ynr0UAl`-t1X>KHc zrarTqHjXI@Yrwi2P1h##ehpc7(^t*-BMsSH%{}uHRRje6v^kW28Ou5ZeT0{5 zv20Li&~cGp8$P=OYig=BgU@Kj?7{UuxKa6BQ`UvoYtEVmolz=za&u%eolj`ao;I1^ z=Lh0gJs#cySbl84y)D?o_3vwd%OsRd8!K*9R$9GVFXJ)5bEa&3V;Ja{WOV-Qu`s2K99Ghh7`#66hj`cOQeTSHLGnE$bkXCG^>CU(L>Q?M-)8B7B z*P6vQjj+k|V_y$3WV?$M*JV_`Iw6!afUSRvTS3&|^C6O`wnpT}1Su3D?v8JHL2LF@ zR1D%jF_P9mIFnxs;qBY7drbqj@W8J`#@yv-Pi`i{u~lt4l$&8cB$$g zBpQqN4L+kiYtvvAkpe&krh};{ZrqYZC=(9x_uI3Ac30DNCBQnpn}+h;omj{48mmNOcym?crUcf?7=9cFR?(#9y%Nb25?K?|ek&iI$X+nD z*v88f+59H?>ny$yt#S1+&%uPtQ4a5vTv62#hqDFjnOZJK=SF!QDf!9Eqr0^ zh}f|7#M4%9<4tAP`R|=sYvVnx!l~hVSpn?l)~;!Mi81#?4QrP^lL_Z}VI#YhS2I{xj$TY8eQI zow?2*N@9JCckzu$Y)YGkApZoChI%c0zU;ndLkzY8Zvx7c>E5Hb7G4L00`~s97M|7( z?Q8D4mZBNmSVv>f+s{Yxm%6hLgX-X==7Tlr@~J)8eWp7$7ai!qdYeo;dvZ%JhLbXz zc(-0`uxZ0azN8mxAC&T@g|F|$>X@EbM#XHyD|)fIHO$L}#PU$w6@x51x;OIr`owkK zw>L{Nj^Mey*#j2K8jHb7Eh4fux~R085lw;)ueNYY3VY9Vst@0i!upu@&*j%rP>sWQ zi#{yhWP6&w*N3H>j?Lj0`mhA!P#)J81~20&zOFCpXbLXiC;PI_#!Oy2mHk=&1e&%w zH4+u;8Mn^nN#C8Gc**ANQ0n1L(pXEQt*Cz*3o@G4Kf$g2Sfd696QsVy=4(P;jJgE< zuvvUjKV*K+%R~D^B+Jeg&Faq*ji$RE=R4C`o7xX_lnFm^YY7`h@F*Lr&oiwojIZj) zI`NL_EYb9Q0iT!-%h9z!q-!v!gG|R=S~}b`K<#Pobjq+kj~&P!G%4NqlLJ|D-GPyY zKb7{yC`Go?CVgGDQm43V-9UbPAZupY^8hyv0-}Mu%OK`5rF!_sgJ8NtJbdk7wA%*l zWIhip(en8sgz`yxVH)phMZtRI^Uthol4EU%YhFq|ObJMjgDzJ7}D8;Ue@UgKp$*;Zo;Uo(v5 zg&%iFLz4$XGn~2V*hg7CKd0T5Hu{d?R?k&iL3dk$Ba~kl&DwT%e)>EbXlU+$yhox8 z_CD5vAvsoWuoq#FQ*^4;eb^pvE$C$}=pSlz@3Dti3+85(M|{qQj$w6q$GcdEmKg7Yo)pPvgAWH7s5jr+)eA#TsdE*W8rAPXCFo`E zRqFe6Z}BB}od6;4Gm^EbyAoj;o(Zv;o_TFDyu%Vu^)D=pBUU}ZCg{MQ(K_M!6;F2JwT+&rc&qS z&bbX_`UdJb6o&FqTnYYYl-Bf>k)IpEVuD_IG?GUWpjU{HQWqa|UXDMPwJ?~|b zRPbV}ad+iO$y8D2N~Q^ldlKGcXrww$&ml}%Hc)c7?`94rHBoZtthip1sT_cvq_~#h z%Xt|9!|@`kqXP1e`rT_uM7p!kNu$* zb%USwbzi8%&`y0>2KP|hq=Axg4He!mG486k4$_+taIf5%65Lb30&og}QxL5_h<+s? z#(9GvLi0;Ei7>W22`sxPE{s1U%SM-bj7{O{4~bt;jduce;zzt>cM_P*5*W>Dsjpu3<=0YO;!hu^KIDIIp+4Y$ zZ?2B_zc*9icxzZ&sc`eO_tt7p|9fJ*ihfRuXrngAyR{&Fb7>yoGOD%k4T~)YH)Q(m z7|zd)W*xfuj9q_W>{4!J?Aqvzomm*WaKEifFollgB}YT+@;^tj_Wy6VC9vAuK87`N z^r%U`si-1PiSx(4xYh857p_)jOpeUA8A~dEv=$75c)bTvHYF-ViFX&{%>_T|oXueH zjN0wlI2HAs=NMzK*8nX{^0ZJ}L0U|CIQ)iI-`=Q9Pb`K(b)M|&EWYBr+(KEl841xi z{y;9)$qr#)Y#w(OxKy6BdIx2}F-*Ywu4U9iP`kH!7H%$Gwj6J;^Y8UQ9O#87xl6c^ z6jACPj__U>CfdE%D*=_K}F4E`+mFbz)MzfwlWbQ+4kSAqe8t@ zLKHcQ9tGar@@pL)8D1+ztV2tOAC;{fV<($YC+%>rLQu<)eD?%^$V@Js+a)l{UEV+M z(Ii8!-1-^LQe$RvMeZL!PoA&MQ=E9ok$AW~)8^Tz1&~u~_=Ns|i@mGO^XJ?+{0VSV znx-1k@*b^Y=w*+zI=34$k`EHY?L)j>JKEy^w0SD5?o0M)tMQ=Ezjj~Bt)(vOXfPyR zN<556#q}^gY{|uQJAqLsGoX`2k@%FcEY?9VGCiefTL?nhHu?)nd*=+#uDLDnCjcay z%9TPeGMu|muFJW9WO{ZD^|ox25x*?x{BaSs5?eD`=1 zJKx=r{z^W%55UW*JNHIqM)D4ONLpU+mVI;Y0;potGaM^YAZtOPrXi#nWtq6RZGcj= zdN(Mt(@_q0FqWjO1@2<#SY>Hys8QLJc_cp+e|P0ujkW@z@#>p`;a2#I(1$ZLVGwtT zQIXpt1Vvtrc^wSVx3p-klvp^u}0-D)@n7x&#~>l=6Eq$aP$dkZdH!!Fy$z^Hy3TQmS|tZ zo3#L*kX!29pisP$-r2nKvZSSi7i>7M%wjK$yk2=;alnr58kB=DgX^$TSPZ8lM0vI( zwj_Vpe5>%B=Z89M1yf_vHW(9+p^=iwwRslqCSwaLOGdrCzO?`!1v0KcgxLyKlci2~ zp`l&?b6aw`;@pI4vwDZbT0KW?$?9Ay21@7Clv%+c*~%=-oO;yw>?PUC5wtwT{W6Ft zc0fqaD1A8jt5&Zy*6J)ywI!EXJ-g?8YV{6}vL*kjIA5W{X)Nt5wjO_CJgck32-YX| zk|ln(@eN*arN#jM*LW5)o-k6`VOJ9OsJQV1oyc~pcW@~1DbB|vw?)DA01$&oyTuzr zAdi%|itTe{hhS%S*QGFupH*k8h?hB)#PHNftm~xRo|3#DL-M`U5}YSPtj6tD&moo0 zqvM&8yw4sDyP@=wa4Yzu5W%G{Q0Tv#%^8ze9I)>+@{}SEMvj(R4gIxv(0y2OqqdWmJ4vM89Kiuz0tb$8^D>#%*kuRbG1 zRvOT2`(qipKkIjv{!u8Nz+kyf}38OaxORh#Dvj3Yat6w0RH zsFoq6?vrqTjTrR9QmeJ#DOsH$Z$Ttu;hTw0N-Z?`@GF~-s-&Cdd`>SBjGOR!A4s>8%d>McINZNGgu>I4!2EVO&!Dx zb9ICOb9FYFGg!FWo`C7P@sEm`GJ8vOy8?58mn?k2Mk zEcy1W!grgVZ;YNVfReP;kD*k~%S`)JoZHAec!viC^7y^JANy+*>F}l;Lr?(xhx$w+ z{F#)$2-v|s#;*G%LG<=M+p$P;?i`xi9hM|VaMkPv%({cvUh-KD9}CDv8jdq6W&#!+((9?6WI|w$c~T=$(s)wqPUh{glkvLYIwx@ht6t{(dhL* zSdD=b^;Y9(jIbIZw5;$~`E_odh83&ky!|xRFqB%M-ayQl#^N3DkblM4@%a#}-;|FR z6Z=(-1(q>{L0}!`34@`ydW!E`8jZgn(isi4CI}M`Cl;4CPb^L~RHQ0Tmnu(-qNcho zDQ=n)aL6IoH})QB%BHT-aH=NQe@o3PJ%~5Vd$j}!JcyFiqVUv9zbMOPVrBWRR9B__ zJ(VF^T5HWf!^g43SJ($+8qXviUQR6Qf>CTPn#^{i6(hUf%G==aP(z#XM>r6bdHmT2 zSRKcXjQFo}bCpe^-jq#Vs<(XiDpX>=_f`BYb+5%=lh1L()M&v*m@OKytfPn5OSEO< zPGO+oE|$hpSV_edb6(Ur=2I~J+2}ORMte?ZW>~%Ur&P7n?W`nx>D57G2@n$n zh^hFCu1NDNZw5iM>O8dC4CA*L)5PKv3p_T@sj@qN7DJ$x#6WRPpoZ*B8Ja7!vi9GT zZdS{?#5!3uwfrBnG8(D`hg$2)u#XLWS^J3kDy9RxU1QR=cI1oKu|{#tu)Dx!d=MR4 zIXbkCb9Ua`p}ptt(0ad%*_DpUlG!LB7NYWG*`U1l2Sa&3!I>FdPp5U*vcpr{9258I zcKpF;m*j#-sM`vk(x}hk zj~rj@?FJc>=N}m3p-QNutv1F}+;wn7c_wS`$na>@Re2D@Rc*L6hWKb>KAWe~{+hZk z*f)krr8vwGSur~#7QH(8Go2kOIyC2>O(^;0U}Z_8I1B_GEqrO|cXp}{K^xq9463-l z39d3+%ghcSvaeQ``{67~s?PFu92ZEAnOYfl-B*Pp?)1P32$1~&vd~W<97$dcNfHW7;SX|=nLjyM#1bX zKH$6@sw`s^<3Cu+ym!{ z^@jC3-%ptp8H%q%aQzQv4^WP5Pj?qPU|Zwiz$4NYiqS;XSjD{-Qy`h%5wRK0lGKdk zJ(-?EbBfga!C8uCWB`Ixt27ai0lCz4Zr|p-qA1HcQjj;P{$i3#^mA~!D^JN$GbDUB zx<2x7jqt7~Y@yY)yJ+k@6@`b$3Moo$gat5}9bMFMQzhTJrL5rV=dfPB>ivBTNky9u zS*?zSclK{)M9e^;(&9)J;!MxkjDk$rl$@81iu>~hnB*y4x(d{ohz3SKIghntPjQV_ zl>24WiK8J+1yYrq4&y`ZtX`vX)E8zBY>5HkD@P8~DDS0qe6gL?c634-KSP?np=ocL zy0I3FfzXdBu20ZjF`a;Rd=+`n@AF|Wi9Al3Q0*AF+hEasHLD!iOA`U#Q??>XpRb5o zgDSUqb7E;Oz?yv2foaV-pQwGRqqxl^5+rLVZ&L;DZ74AggH(#)=457Nc+#h;?@DQu-7zyS!?`)K+1f?pm zdCtxGRQ&@LR|TpRtuDj4B12&!k>MTw3?K4kSfhG|<9w2>p_i;ffXRR*wRtB7K>P?0 zCNK@mB!xgKa=yLxznE{IpQE2|zej$h!q325d~f-cfUiuq1H${L7G;zYhw!w#g$6A5 z{f-ctpD|=4BmNnDVOE+ZrM;;}d%3z_B=F5~1(4PK7syV`(jzNz$WIO0>+(xNL(YW~ z+2aV-kR7OkEVnwcb+Z`I&xX?^HoHqB^q2Z$but zu5=mQ{%;7s9XcO4bo)ook;k&JSo(^z-tq}J;uqnD;oL2zxI^Ix%DLrVu*+GAa#I2& z%3CHLg_r3OUs!yW9^Q50;a+vN(gCLQtW@cS+N$a%01`e7u>SiB5AZz?C$$>BaKg7@ zsKmD$@e-d^;-dsgl*7ALJPS9|0{|-|K=E*|!h`VPp{Cs)ABM00h_4O!SHl+xe2%hf znG)a_5J-S;p;LeWDFp=BLp%zt^oTD^mG}}QzF3Km9x8rtpql(-bFKGZ!8zpm*ufe4 zR`H?y(i+9H^2^^S9*Pppbs>VaMzIbdf#y0IUsamxQwX@-8lFR2#Ebl|C?&}DRY2CL z0yxbD^2z7YsBV zs$<Di$z*4a;I7{9i8R?FqVpTzRrEY!Ym0Ph1JIN4?6azfD~iD_kc;qZ9= zPfMZW3BV@I#?GY4wPJMt$5_is2UXTXLe~D3IFh&GrpXa)4Nk1^sbi^l{^gB;>5u*w z`g3}kUdc=4S1NLFpjTQeKmA(HY6vZV3c;F|_eDrR%YTMBsG{YuB7weB^4l-WoBdb& z;6TT=wE_mTnt?zThO(o$zJUyEtlvJk?-cfd62LwfqLRQKRh66{pCjTQ0201cH+KNF316~??;zr({W^1pz()yyZ=HA)7SjXvi>fu=n;;%y zatZd;5sOdt1Zwj(05O60Uj<0`vLJwJ@{1sRFIuJimiy&5NaCXez?X9#osbe$m?s68 zCk2=z9_}nW0FWMPD)8OTjA>TP_)j>YXp`^PYhgNlIpW=r_lLj6{R&Ve-@m?+2Xw<7%ivJ;`BO23le z--^(kG*#P*Aa^2AiE``vcG2*>#Vq>&@veksfn5pZ4R5s@;e>xoY;+U z?g9O7gqeuab|cV!gKX#J5M|ljA$-KWtezw8iZpalRM}$7K~1q-*wZ_K?4mmsiG5b` z=A8#EGXG)cfu<1DWtH9YVD+5`H8UN;R2L)qwmT1YA`%6o4no^`(D|T#=K+4K@E>;` z=wW<4L)T$A#14o88w}_FzVo0xkln^QU^(RH0E3O+WE>#PLz&-ytvO7X zFG>OPuv2G=Wvli-*ZaOHR@j=GgLu# zYNEiYA=@Fp{Ky>jB((Psu0b22L6gY-f_7Iy*7+u6{ysD8pTMC`qfjZjI(49te}bKL zhO!H3vQkq)AS?A%v@WEF%l#SBx%}2b%XOj3R0q~6xkHV?+K%Xx0C5VSR77ySP56=nJ z6A&TnkJ!2=Yq!?T0ITAI-nGio>s_e)(rWkD2_nv4yPGj@1HJnS!CK}1iI9N9_B?*+ zxoYixB~sk1cQyY7GOx^iI9c*2HcNhK$VOB{_M$}A(-DC7bron2?D9&|XEbCbW;LAiKLdvS$&12}|N<$jyX~Q+EOtB97HI zzn#vm)&)7-D+c|Dc!*^j zKtG5tHT~b^J?o~tldVGRjZXp{jnXJ@>?g09H$V9pc10dUH})^ecV$ZT^6eNT^3Z1y zUOfvcoK*58I86N5=WbODDc{^^(JLBfOYu_sGSS(#Zw6r*kyv2?YuXw2c zp=}YUR>wcoYw%5Z@0TjAFE_SER~0RldpT)#yf;rg+&Orl-1JZ7y5ylGp<=ZV}9ZZ*4R3 z6!#*Sv9!gDOcUqWQxta&zOWn9gGYMe;SA5^Oz+?*tmrAup70!K{ZDKrWYfxM>~!R^ z8(Y^}AO_ei_ldBuSqEtv>F=}Gd9$aOb?C0te6g^q6k!v=71aWd27BDSbFkB}RB_^h zOj_3pu1OmW?>HM^ny4{7@C$`Wo|7fdJNr$;^cr^eXLtvK%TdL72XVoE#Q{kGVrMBL zjo8ZS>ZS2X*74c*yTr!ZJ;NgVm#SUh1qyaU!LCGc-lMS#?oQm+oDryK@1zUfzbNW! zB21#{=HqtbDORIjC+s!G*fhgyYblvc@-b^g%xdVE;nGjREL_Lz<=-@DU>5FBLr_rc zt0=}Vjv8<#8#6pu(yy{da8DCj4gEjgR!P8}ts-b^&b{Ya(KQAlj$s531}SBs-Zh4AhkeeJB2&Lz2&+tA_QoGW=sNQtof< z7AW_9Gp9hE=aY?#uTb2_X9-U!B^Jpc$pbyGey-nYIZhne)Qb_EZ@oK(I)4~4@$m1o zRJ-fN_{blTJZ&9oUH=XtyR8-ISZI?59@u)>ByDRW+N@GLv58NZzu{L81G`ol&G}OO zygLb}>P}3^v+Mh;rNe_bY)=wg#Wt5l%8~`JIcg3iqETP;3Cxo(Fw#*yy!lK6N`wXX zwEWRIuni{IhU*!gp;%NMI2Gq!vQ#6Q0ai@I)3(u4jZNuy8LIbsrV!XjaP|odYn@gv zJBgiCRTb}llh}-IdWqfmT}Z4|WZdf|*GWi?(zfD;An0Gv!oMQ;56`g%+HNy#_f9Pm zrr6d(I|4D8UZ)%Hz*+qV_Iyt}g0#InSITO2#gW=G*hhjr3D}{4&B%KdrvttXY}uk< z+UQ{E`x7;AJEp;p{GIB8VopZ1Z%4A}M`3>~4+tV^@h7k|;uym>UpPLhtK;wh&ym!; zUqWE!NM+NUr$RD~hcZ2T)gnxs(auKi=um6&cEuS&xgyg<$~3Zirq|$i_%b~qErMm& zDWCC^nac0-?U<}TcorytgnP$FHRH!Pi=0B)BX=vdYI*)tzoTh8PX(6fQRd@{^GWbW zZ?b5BSmn1|5#ouwOj5DJ})a z&GRD`$&uR*#rdIDxL{gmw`_s8u2rbWjV_z;i8k-JEL)kN3DhUP1Gy#mMTy+LR3C?h z$?!&ZliLaC7ZeA3d*f?Yl-#6oq0_~FoWx3%hMlO%@)~))Yo;i!JJ5yuHXZ&> zJ0cP;SJcio6TKpHFMYNPrbQ|g}LJ}Ikz)+ z;}BQH+{J3n4dn`U{&z>YL$Sgj%UyxZ#y=!b?3W_86=Lb0nGEL+NaZAAw~5%x zP!r36V?rg^5&5L%ZJ91q{{)n=obPU7?vtP_IzWyLYz-m)xR z@(VxmZ7s}$c+P8##Zw}s=rDFPsb6Sme)^BhvRpnzmJTgVV~+BVVFFGmQIHBibV+`s+KHQ>A>0oSr!uKE zLb&u4o1rtEIm=5=$!b(@r z3CT@}K`P5W5hh)+iMYFioKAjr6^m$g4D8XEND9Lx5gQ|)LJGef7gBJ@nynC(R5{ce zt)M01c)(yI8hI3LUbn^X$?)K$1Nx5~9D>UuH=9bA;YrQP@K~p2c;@G1daU!S9^y*N z&(8yq8VRuxgh*+4*t17Og$M|35XMVdnA1s7YV!fFv($tU)atz{Z`HhBSow6xX$@{TyzL(mr zvT2WZ#9z)U4d>MI?17UsXC!a)2J6`55lyRZU^gn&llj{Kt{1-8`i~F4RGa^rEST@a;B_K6*h)``orfZP&wFJI1BTCWMXpTrHP@V>fjUd zqh@2A8_6BL*~ZkaW@G06s<$zHZ?(s@Ry?nvJ15zJ_^wccc@vR?mNI z9ymo;qvpiYi|0Q)s|_x%c>jGCW@?1p!_KLGm+XfLYHmTJ3nZJu8+K}Q`- z+1D>H+=ygH0b!k;HTVY~u=r^W`v^e}gIKEwvMG+Y{dYmi$rI4r(qx}*r8n6R;rEWo zWGn2WD&pkx*0EMWzx)!xSFO{#T<|-a^EHJyv=%qV74YN5xt~9J3kTlbQajvpOXs~H zWi!a+xgUAkfRXOG!CC0VJLr7-*tC1o#)&IIKBv|){gBkkx$-GmO9(YyzP*Kc05z9L z2Q)IAG}2lyYV)oo)GE;*pramd9O8}aPQzd9+@}$+)iXH9>gk75W`7jp*k!-0{-wd) zp|n>+=Z2vFtbm=sN38g`2h9R!B(2VqC*_{O$FYOE+yZW~bTrP@2JvLFqLn(piT+{H z;Y-`Gc{db#l2#h5@Vi#C=0=4-yqYz|<3;)S1dlcfu6_SCl&IZ7pcY0^iON=X9%h0? zm|!tL;l?#0Z8P~uTf>^0lqetLd>Oh{K6cATnS6x4DH67k59^yasiE+hZ?afCo|J(f z$j1RZ!tm1&5(NM2P1Yn-iGZQndDzUazsX`k6^r-^5no6@dM&Q6Qfl&9lEF)BMOIto z;|KY;E+38G5^*VbII#al{o*PbLh|Q|>y35>!==w-6qg4tR!@l(G%WNiX8cN2xaGV3 z;#udZ1LE2EZuuQILVkCsFP}*z`$T-o-%t=yJ@BznJ?+5we7bNu)9EN#tpViN4^S5E zg^2A;)X>?tSThtT>TT8#kM?g1@np)cY4Yna`FQ7T*0i~T0R-KPL26mCSLDhr%coGw z#}7$k+2tKpZ_+zSqV?X>v|8(3iF;A1w%%%^C76v{Hrq#<=$ozOTWy$>68(-CCN)jl ziii;x}G&?k+6q^A?JYpgt?;LxV;FIy>amY(Hq;usZFKmlGS)#wW8yMgMcQ`07obM z`@69SM%@f`7-KOfYxRhu(Noc!F=7=_`Ga!Q9%K{6l@sV?kc8&*a8R#K8);{qt4mkw5mR zQ(XBNJ$$N$5KvFALM@0Xs=S?^I12@0AntQBF-7Iyg)#kQqrHI~^>4$uFl~%~!+M`* zSQaEY6}}Aol^g1N6zMr`^)5o2*`+vXT8YM`ov8wQhzb;K=157p3v&tdv-7^u)0I-8 z|CUMqYL)|2e9O_(_a8BfJf-^!#%i#<=hZmOh+$DK+6c#_rgJ$&hObKo@s%gPj*74T zIdnFD3*KR*`)7$`4ErHwc{<_|G^xBrG^H^cZ}J!fDXu&Q0XGyNg?aDqz|nyIIS9a7 z8csqy?q`uK69y?uI{BH!;SGl_OybZ*u(GTU(ReNA`{|BnppS@;6?wwYUqGG`$FF!x zyj0e8pGjUI_GV+y6!eebzieQ&YNKe>U`X$zBKP#5~? zAM(@Bko22;^znke9SzddK7RV{e)V?7ZE(p7j0s7hn^5geD@Ai z(7gkn02S^XIHx$D0b@T8JKKg$Ge}OS)g4f1%<9al^982ie5Z*g?B&fiV~EhUh}CGY z7L3LH1Br*#_b3zeNth(ECI5*Vby=NGM{!jMm{qxRAXsD|8P6B*@-l!&cV6ddlUSE~ zc6+wxoeIepGnnGi0rYcwRij|Pm*ywyV0F2Up{L^dog^o2i1{|RoNyH5A{q5KLVd^| z+a5sPWRF1JoQ#7~bO@Ijlt1XBi`CI>*ey%=)n-;Z8uc*P8`;hp{~ug2Addd*Z^QrI z%o>^g+QsXA%tkhUeU~UBrtrB;rpC{o1qc`H z6oiiLQmSG!eZ=ayqSl3B$LQmQc+u@fU%{>y&(GCQ#PXo(op1GNj{ojD-;0C$=)C7w zhEku%&hCliPiT7pakV^w zD8}A_jyN|OH+N7ssHK$W5(CaQ4tZvJenUr1Z3=ezGSC#TJKJc}=U8 zfAU^^od`WQtX}^5Ru+>uktzh|yvW)I>a>@rXsP8*bye14o2b*IF#n_{Z~GZbsgblL zP+NPpi`ojN+Tx#m#_HAB^=Tk>tcc4ruit?}EjX~}*swoYRk#Nd_aBV06V zs%%Qe$y>u`36Qh%atFmV1C7afxt-$PLaj=ip8J-(=e+sG35x3r@%m^KPEWhG(fh}s z!eJ-16+WOX^;%GFKE@bbaF4lsD&kyzn zx3>05Up`M;3OH4!9?+|gWzJ6Z0Q&dpuAHVpL&z2cGzfR&fwqbkS(GIYkZWTnCPGkj zKcFV4sp3@%>Rx`OnDuShNEZ}^cbbY6$E^n#4yGwjZ>Qtdd|U}@J8(ZrK_{uxJ_=8} z^pkdyI!#&rdAjSE{k_kS)}X#Es zxoBg-sz%eaEn?=#XDeFo(^q!ATRH7oO}DRJ({vGLm71<6op$)kQT*l4S)*2?KfGR9 z_Ewm72kB9&3Y@7(`I*a+1I`0vI4=ZAqc!PEJ&>R|`^)dXjmizK3M6-&4}>@6K5&+{ z3oh1N$QQC~ip{H#Wd($cxe!{bp=m@!6kos27w^CbF@JJ63gWv^#9AO~##V6OZpHl} zES2iP)*t7tmCnlA^M;k2je-xH-B~zipWqr8Tt^2FB{e_i5p^PM5t1Jdt9Rlf^-?LW z?wAa}J2meN)@S4$+LB^{+L_3mDQ`Jqg=VzGXC|g*q+;M+kN7IHFRxtrzf*}Rs0N5( zz7)!(;=nR1e{l+efu@6}gJ4fG{94eB)gXjipb6t|_ie$-HwJc#TE z4V*1Za?hMU5IFmZ*a7=rVU#SpxcoXRE3p%k5<;bv0^ zfyV7_Es<9~MWSXhQHYj^AKJ~D4x~M&a3&W8(~PtwdIRMl7Rk=h1rx5{#AGgAMrdb8 z)(ZfZzJe&Ql7w$D7Q)2xAnnuF(G z8J?W^>b@UD`&@{taWFgD9{E=h$tUOjNhFy+5x zb867<#UP2ip`yl|V%11}RDqlL=r3M+Z$7GvPd~t#_g35`qB)BS&=M+3?neTZhA^0k zM&BMbFDh?uLjJRsS;Po;k(5U%MZu$a-=k5@J9J$ez?ugeEU*qoT7h-<<5cPZ)(7^Z z@Uhz?_{#k(HiDWnwz~S^aCJXx-G1Vm*L_#GOA)NSA=u_VYV|fgmspiy?*lA0Oqc~& zmmA#Jutse@{{TyLXjj9CiDY}-e>IVu0y3IN4&3UR_ZYN%_>RTi_eQbY9 z-i`a1oGA^E!eHN5MS+xGNr49Zmn|Q*+f2!wML7euJP*H)Di%LqDG)`jAYtkEdbF*! zFmY}hsetc>lD1fw5Jw+xo~sjkJ)R(T`n?`)wS|dXMBuC;uBTI6yGaMg25-YlUZ0+d zd+}GK0;SjhcG;x3YLUI6SvS`-gsYMCNzTz&e9_I0BJG1{xY`wuH2lx-Ive;ULKyk({L=*7)6G>b@Wo^A8KMb}gp)eXMC3Y@XRPi)HnUm}>RR#=L&oeD#BG zQChj(SKB{dcDi{e+LZrlM3u}EW;(nd$f*}sU8sm}7tw=3NY8&c%$nXi7;kb$3G|v5 zimgS=HG$li{xq{R6%iPb2*t_opCjg&>Jk9w6}Ua0BQkH13DZ<`r!{g6(sFb^#3y~l znmYj3zE!LL02`srfBCZOlU&LPScpW=W!79V^ga5M7 zjRv0kH5;aA7pQmrP$QF_{afcqG7a38~b(jRr{*Q6qt ze#3UffeGSotoSQ8FshRg&j)|Q+J|65t_yuE{D!rq4-T0nQ(Z~=UR47QHH8LvPG}b=x9pqm-b_u# znbJB+bScqDs$Hr{mxYX%)&Xik-f0i4%%Y7`w+(-YghINfTg|&wk4q`2heZ0HFY9f@ zymi7x)aHwd!z>}f3qR!M$G&B8ahE?PYv8+pFZxy^0PO<4p=3VwW4*1=B;4I3434Xd*Wv_*`VixXpSO*^Cn0v=4DL^;}{OAZ;^1qt+po+&rsSTq76|X>MEJ zSVm0UnCF1RvhgJ;M4RU<7B*?>rR>TDvwy_maPQHX8GAR($n>_zZmjKpTMreR8BL%> zPX;B8V&iM#(1%iUU@YGIY46xO%v#BJvwvH$`rc;pZ^fOai~sH$)f&fY=#SE`MA!!N zLG1nO(^qkg`GVBFSXg+?u)~YODrI`kWc4zZU&!a+hreST9mmzR2VnTbA}X1Q>|On` zz_Ez^*apjM9d8k_mm=x}B0yV&plnY++(Fb2cM#3VQtt%Mv{LsfFf^(I@BweoUJHG6 zNY7kaih~2@EH;*_*yoQ~W@m90bcSY`GrWVM@Jb`}L^3Of(SgLRS|vJ?4n5#@;5*5H zXL(p=@&(2DI<}qIlK)biFXF|Dg?aC=W|$g&$ZYdw8Ozf#Q(P0qXx0D)J5cP{JV%qm zikn9J6pS5eJ#@jVNv*vTW6^~rfC+Y)J@h%Y4ONI8y~GvA$A2u(5!a)(<~@(G8FeOx z0>(q3v{lT7vVtkLz41?vvDg}4yh>yF7aY7y9tB)0B&A8m>K z34dGTOB5Go(`mmNE+-WFDlU#4Xblnqyqlw%0CwNp*Mr4sss3gC)jj{+bpd6Axs5Bht@$vu6Kd;e&zfmi;9@LfGh{yP1jH^(cZaE z@A);vXnqd7*u1Jfzqo9*nz1GQAMv8pe^s`Fr1f~h@?e%kXpr7TY^W?5fvO_yMLUN6JgmQ6;UoSJ{M#HhFKxG*0%gS-ytoj6ib{`uif~dx*`75sX zFcCy^-ND*i_ePI@h$zil1?4cR^$-E8{L|rU!dfTbeHY$4`AqjaT0|7BWj=uqa)Zq3 z=~5KM|Eqf0%X!5qHX~?0el}G3nKcOiw4D05q_e+X=Uskg9fQWc7s03e%oYUg`Xhp$ z{F$W%t#~(rw>-nR>6L80^$dP+jv|l6vSn_&S+yty6+5-FYV*?ROSj=iGvF-tAIWoF9%fCvhiXX z9eYlc9r{^I@e~9oe)BD<#eN?3E9(-pU~L2+_$ynUawY=~gLmOcU;m7qt^X;T{gTO$ zx3J7$55^313v|VoUk69;!h>m>VJgv!3#uV6@!I8Va=qu?BzSfKzp;tI zJzG8_nuui!L;&TkrMKu*1sh|GTe2#`ATBic zOM^8V0U5^QHV*>M(hmw`t%~3i{=_0%^hJp9!&4DF{UUz<@JYPnUj&w`W`67<>k+hk z$2DH}5^EXs=gSd19miVf7S{l#VndDDDR zD-m^>jR~rS-OkTnW(7e{rD{>9Rz&d3zme+l@$3A ztV?^ny>Sh<@ei&8^P5-j18ZOZ07bdN@*6C5Na}CD)u;_L+Voo3XMFxu6y?LM{EMs1 z8Fcse8v9v`WP)wqXbDu*2x{l-7XIWlHYaG!vFkkeI(r}}885T11JE%1J{)P*D_?6V z_MpQ9p#7f`pxHso{&StTxWQ%y)x`@Hp<(++Wr|0?(o(FU`7CeT(NC}OuWqo%8Z3K6 z5*#_K5f~*wmrwb;O5|O7RC0>={$WYj{;;{QwmHufeYCL9yfCQnh=%PQGWx)F^`OQm z4!s$2y6aUKb| z%AEo>&lO$c#Xm%hE}9->PB(Tu{WJO=_a$_oou4uptjd5=tB0LHU}EvO&#j)MErHjP zpCkG4U~|Ku^1rSWT?;l(GzJx2y27)}<~>0T@zOrT{8Z387q9T*5c3-mp3C{>{N^QP zQTawb%Tcp2|8lcAB8--%yeUNoFh$tI+@wZ!C){&hGnKs5A)Rg%t?Xg$VKlWV;NK>j z$2RDB5`$aCH67jndR=&%uout+_Urq6NKbRG8s&=8@=!XNdcWP4JENlx>ci4RO8sE~} z+}#wG&oA~iPiZ;!LH|4zQmoz~eRlxD_2pvP>Cf+xeTund(5HBLBgJg1J>!onxP-Fn z5*($dJOkamx7XF8pg!g<#Zbs!-4NBF{!m6&(KG$cp~i4yBIbs?b=oFg z<*NsnKQQJMjY>CnFov5-sC{=>Q4+x)A83BeRCflC8D#Eg8vOvb4l=9ZLrSqy=e0!R z7wvml&8bRQEVchJp>TB?TVih%J!UmGFb2hb7Qx@lF!v5J;^j<+d9La8hxypS=5fk^ zha+H^tlrGsm?7Tq3g0!@JRzuGlN&sKh`FWdg9Uup5c4wAt6TWdA?Bw;2akuR`qD-- zpJOv8n(m&@SKG|*g%0kA(2X0+{GLptF}%c$&zkG=nM2L>__|E^>g{p!_0GPt+u0#7_22Vd+{2)u@0?YsxVYo>;S_(!A6y#8J>qtQ4I`w`X}txEcL!awZr+tYmVXmf&T+Du+J+B|Mt zO}O8X)u(sBtMuU3^|G*cG^swDMwUExU1|Ed&Y*Y!6yhf=>7u*SZwn$@!Gpz$d%7UB z%-0cM<6&9x+h#uOE^|V&!j)vl8xO(IX00iJr0A(@aHQzRO?b{|a|=`QgM8;*K=fk` zfv8}l57BpT`RX~;iu*S-SJ2id#QKS(4Qbq5W#cba=D zTO!FMqA)>fsY?XZMbhLH5RG4pU-7=%oY1rjgaa9=SX*Bk8bCDa$!qwrQyl*g68i1| z9(NBAEshk3+70(1TJWZZD7a4mQD^+vr6mM3encZBnMCwm3XxjQ46BN$$A*_+Fsfoi}UCZBe)%g!~ioN1Kzc<0hkvaCzzVt7EI;t zdx7#2mNby$i%cELx<2Nw_VT0b0-`l~qH}Ku1QO+ZL1=O72ngjqNrM@uUIJz7OWRZZ zC|`pVfpQG)d0(3vK)IvfS|zt+nOht0=NrcXVJX&6kogLm4&j;Cwc^k2=|@QF|B#-j zL=s7aRzYYPr6VAOFZmd?=}8D<_|xOf_2Z_AJqeAQV)$cKtVgAl@IHo?pdZK%pJ;Ar z%;p!z1LRW7$szx-Lv$e5yrzLnO!k9J0#P?T(PBv?L52!KOC22nfgI;!RMvw)Hs#+< zFh@4(1i?XmKVvNR7X36?K!ig*eDNgsY*Tnr7O)LmBd|3btizW5s)p@ccR#jnz;-D^ zN0cUsB(~jx(DIFrfUq_8FIewi53gv9rgoFq?w$l}A3$J` z+kF_zzGZ-;6~MNCDK};T+a$hm5==^|7<5}!4ANmc^RmWrHV#&6*m?llLwceTNu(*B zAhe9q5eUV5na`eV?%HS;LC)_-3BG63a7bgSF($|Ef#tzh>S@V*H zCb6p@O)_bio@lWo(lksETI%QsgoeGu*G~bOP7n&|)Z>IEN{?o!OX}1Fo^+pi+yetK zyNiq(rt8pTuhh_->nzX&`hW`qK<4WM`bsj1r&N$yz9Ir$AJEXpYE5TZa}Bf2k@3HK z1-jP@2wkimU8GarHkb2*W6(6m^W1DeHPgJF*K(|XAd9kAD>SI<69Z5?Q{&t~WIm{s zFA6dVb*Uh=Jfq`7qB|yO6nA!_ggW+qKz$DqgditBMo_8e^;heci>pG7$^z7JeB=Fq zS}OL{T2{c*^GT!Z%nKUS+1LXLjT2JsO8uCf$b3+@Niqp_pdhu}q2ohPSNK@9N+75) z{Mo7IMop(coPgTEgNNVP<>ygfk%wMc_WSqpi&Ft`sc@hyW8rH00j_yo1DHss$$S7w zmR*U=2hbtOBtWwuwba(}A%J6jtj=^KfU*4hY37Ld4iF*${7{rOi=QmdnRM zmiO?a2S`JOhh=F9AJ-2xn`=SNWaorJpbK|p;F;sn$dSPq2( zU~Stk)yE&H3RQH;ckzo4lCEN+8yLo7@ZhVsvNg|WP!rqgppwb#N@PB$4oRlzsvx!0 z*6|U#>SJ{#Uenc?q^l4o>FN>zHA)B7_Hb3GLRUxeq*>;1lLiWh&C;-!4r=z(8q{-b z1l0F-rN5V$kU$Opd{5A!=MpY;A4w;%?i93^!$hD{)&@`W3$x5!Tm9w~=w4ea(8cP| zMJx!Q$TN&@a3u?)kURLs*}zsRJRr-89y)Ai zp470-Zl%XY?(1Bl^I`i;(n)OTg4Qxp$B3}K_$0T_0k#>C7qB(Q{4TKN>97?%q{k*k zNF({hIl#7*d`Dv`+RAsZo_zPE~%Sv&Me2e1~`_rdR&9HzPTP6 z*}{*A&L_4T1%ggfH$iK8TE|GJTLJI-5UCsFMe6p5gr*ztelaHiO;fj_eB(o;Zo)aT ztmvY{cIGh++w5j~Y-IfA5}gm*XOd1+H$iI|sbeJ6?J;hjN9qQ7k-9xBvFV1s1+(?o zgt}$&i}Og`;6EbIv7L3;)_62*iLp9tiBzRsh|Y&?k)+eqP0(6u=@uVFh!N9}wjB$4X$e1Z;JA4#XF zo1nEECIY>>PLQA+dc#bUv}&SSsi=brZCfr*({kx-I2hA0c&vyhz<1lF(8E(7t#u08LZ348HLZ zQa9oDSXOk@VLRj2u+7FVOa-u!*ya+Q58G#wPE$8QYZ<9yB-G8#?T?y!Hp_;*fUQw3 z9)ADtu^E7+-E%HIKr6raC@C7biBPl-I#g?18mdJ6QcnOCb*o*7&WCD|q|+2l&{}Hg z7zssl@eK>j{TeiZsDSG0xr8cR8-n+neywsj7d_`dp0tQ`OE_GXhV6B5vYi^7bMEAgi~cX);7SU ztkn_?&U)+t_e-ZMnTU^w&L^E4iv^vgPlDF+w2qO`r^UQ$9^kx8V(~VKF+X%H#IM zfHR)N;%ywAqldHPeu5)Bvi|(yV$vNraHKnJba2)aWgu!MAn!~)%!sfW`q+pjxmJjqGABfKF?!`3=D*^4wd=dc+}gVQ-7fx77P zt#okuNIFe-1g+&T5$JVi5x?Lh-I*oeJo}&?j_E$X?xgZu7wL}hfGo%20$j>kE!5zw z$5C`YoX(=_BRZdSZagaJG~E%jmZx=$gzh}byShntNG#Bu8G1O|C;N4$FW=}U-4Tw3 zWkpLJoHLJTaOhW`0&qlcM|3_opGi7RcLc3vq>ho$okzHRDd`T01-dg`4`<0FzwY$m z7nhRm(Be9Dr-csAnuj$wi8!U^htq{7RJ#zJ56&V}Tww^1_^ zi?{KSX?i&6_{~sEIm=$HH&4nZ?Get6rD1a&pzH-2pmT~2kZ4=yo9O`ck#w5&2wKZw zBG7Bk0)8Ri+&$>yMG-vj!?5~%kjGrx^ja@|uLmm{`)h4_%p7Jk{(p>J3w%t+_s_kX z?7ew#;}PPWkPz<#A>LUMHYB9p>QP!!rByUrv~-tb$y!_S9F$fcT2PZ6gsV0D-4t<4%W<=f$|874Pjcl?R4CL6~;KrM6G#Z2r16fM`EZzwgt~7M=Ch_N^dm%{`D}frJ|ZNzk%na2 z95<3%`~*Z3HkQ5!?VQETSLFKa^a}k1ao{}GDN{d1ESbx*HoVe!HtT1&38we1u~Q80mJMgNb@(!t z(1Gn5tZ^5&bG0B@RdROgPk+l!va2 z?}R>&X8JSV4f@xGShn(AY=&&vpwHILe8r!Q-3a9IYnXMTzKhuQ6?Q#Us>^=Zs1Fhv zuqzw&t#oH!_NR-NbyKb9HO{=`&muvCcsY{|dsp8_=t(5~T(8MA`feJYO^y;* zj3aIG{_Eas;d}bffJeXFuh?6RG3Xi*&bGg&?_l1B9mSDG+ZDrT!zjZ@!-(O=MSrdb z*Npb2iT2?zJf6L{E=B+@Zlp%x&W0&A!OqX+8g14P9MUSZM&K9AUffP-M?Tzmpm;os ze6)0Gf_+yRl8kn};v6C?&QX4)&*0qkuG+wu;8?r`Hod((54YIxJT=bvW-LZpO6%|E z{^Ks8^~n^Qg9=G0w6@`8Jz0|d^?*2gf1}|`AQFB*cd43>mSwFxp?vb7A8Rfp~jbgs+!ZzNlo3SV`||3}CO+miDw@c<@*-H76atu{MXNz#4{M+(F^dr7#$i76LH(kZs2x zu`N1_%tptaV(xrJt-sd`b$8`8_Xq9=kUd_hJ?*g3r{OmLH*(`1zNBJ9>9rlzWu z0**AgdGlmy>dTgW0Ytfy9s?<9UoCU$*K+Ew1!k)<+G`ul)?=xiE>Ubd4; z!Vx9mAkOjTrP!7gAu3-D%vg2`o)p{SA1MqzEdCiDqiu5;#w^Vi366!vB)cg&$#y%b z^#jFvim0Q5s*YKK#yw=YnEvpd=onmVO0xZvIwiqA+L&M;nryT=%)wxQ(N<{8{{zq3 zuayx>bhPV{DEwn|1bmBvP*N_xk>#jch^Pbr6`^2$b#twY`%dBjax7$223!M1%~`~j zM&qgj;nq+%IHb)%Srcq0P%xE5<#XJW*XE#nMn{eNzupH8KC1(1(#<9Q8Ew10;P9Ey zRo+9S`$p3cLH2lFrP<^FN_kL0XHlFEJhqJk;{2#L9(Wsr5MA0o2YVukww;+o+StlD zA;pYYfm^zVd2(C;2&BxlyuK3LB_!Gx=Hb2>Wy2k83%{qYA$1qwE|4fG#pHy6yqnIB z7Fc0~slw}yH5masycNGK(Uz2x`kKKpN=UKsUu;~){nl4Hq9^;4D2VQ z#M_4UG1^V*73Vs)$Qs=%1;%aq8SU#dSv9Q8xSuNIg#&lS(3?hw6Mk=F_nXR^gQZOj zjv*d`c*$mWg-rpa8BwPU8KYx~We|e6N_fnef5|(^78e8-+ERmXqv7WiLt$JHhPT0% z7!(k1&m@VLq)QIO<7P0wQ-UJ=Mt)?9G+1)dks_GtC0Q@0`Wgz-!%`8382zra4nDUV z?!?{n!TrOmhbW&4gcSyh!7}Bh&m%}z3GYAT;hsJ^0gaM=xEq8WUT((_Eru6DC=m~l z6p_=KpFBs7Noaz>j>ZYdMWYNCBw)60!CAWXWy*qt&P*MP)+8vL>V4n~eP=F4k{M5~ zXM&+w)ELw#359WCBqT|MWTX_vje+ni^d3VoIAUcwgQI|UD~)EQmiJj>Vcc2XdlGfK zx#+O%rV#`P8q56(yk#NY-tK0SZdxEFCyNC7c6KEK(y1kkw5nuVwYyE!pTPO>k6b$Z-fvRT(7Qb|9LOi3^R zl$2oa5;n+D_fdi-9j{Xs;Y?`)HCd7^HB3&h_kx|rE*HsqME+&(B;hu_Ep5FH1YZfp z1DfdFzFjRrM8e}B9$^6igw1Gn^g0V#^T>~zis*1g;XY(+4v%mYljEp+0@+cGLYLea zs+fH(k0w~VmQ%a-=DOC1Qr(%nVcxWEEXiqmE@7jWUycHR*OqD*o-pG~7c*565X7uK z#4N~6ViuZ^7*vsrXQXq9D+#tiO#ums!aajMd4b~0OTeJSbd-m&vsejC zTpR#JB{6`0y#t^QZqu1+k`2=&bN4f*PgQS}kXEdNewo^vUlFc{o;uj!4W!<&i9(s7 zAkI5LwAaj6MMFlSw@f}i^7)dlHu?NiA}z_D92jr6kR_S^X$t}$KpXIeKe6@CNdj&N zT{q?zhZu$HWLRr&JIA~ptC;a6-;U;ZyRppRNPxL?RZtd_RL6kLGDaN?JsGcL*Od+X zsM`Do_F|Ou

$`uXT^J9YJ`01B2y~IWFduDbQe_cRNui?A~RCl1{d3U{@wC^eD%X z<`QC+RfT|09b5$;OuVO>_m%c}vbI8HZ4(N;F=Wtz3*)@`XeTM?9SnmutI#pxqs~R0 zOR}Czgbh9ZP6D+bRxZ~Ql(=&=YV%!vl(kkqLsSz27zHigOSKWe|IjRQfVe_k$x~Mca!swa zv8VNzVl9CkLRGl~`IBZvGHG`1>k#=J_$_VIVd!v12&rixJ;9zd22(|RGW%nvzM~nN zY+%Y->?p&@#V!pTM+SFxXTpJ0T@MF0Ot-*+%*~|*LQ;3!AX`QD4;`BcDNgF+8i^|- zQlx9xLQ?m;FbzHj=`=kNClVc7sB0T62aceUUP+F*^OQAte(~mywn536y3K?qf$3I< zYu#GGYnzZ-_s${VD}gj8?E<(u7|cn;iZ&5bc!GT$DJ$p!Wt_sRE=x2DejMv^$Nt(? z#4XLGR3ert*-J7(K|%ZxOh;DAh^a3YRv@u_wNefQlM%{@FTzAgKG2^)0 zG&}x?A%k1agtrLZEx_$Y2MMusn-(N=6wE;|!Lfp3LF^bJ%gz;2FpGCqqZ!Jb>+&*c zwXNfk4rs~*Xd^*;z|c=!`vHX;U%}`G=ts3s7ced}$#yj%|L+iFPvjZ4lGseh{}cBv z<1@Triz16hyGn;czVo5vTpLKWab8!P20t_GI~jd{wqvS7h4V{76lY?TABImxfe3xG z--8s}AY3Dl&%kSxG>5{FH&@Ov*JV=WY~RxBJgxa7#~|9m*{12-)SEHXToUNETQevn z?N1p%LB!P$V#%^OE!$C*iGacMpc+HU72V-W7^?JOKuD@pvtc!36GH0 zM(7TI>>1WiTk|*v?C)2o1L4=y?x_|&*sSzq5;_tL1*RRK0p?}$cMmmi4-@s}RCA?@ zZWtniw@JaLKVZxNX|3bV`){Sr2hmUzJjzTV2B;$6S1R9ExMcO z@IDx4yU%B4n5!9?gUQB>$RiL!SyAW6+U_AUtastgDqkMjCT!zU4_CXv6r;V1fGa7f zQCexiiS}tSIzRN3QDuqtKDgRnhEd07-Xz;~L&eBo%+r{iO&5%IAaZz*1C}hfsEv`7 z6mU5&)nT+p8fTpBl3o28?s zxFZc#SJ-Yq6|DCb-R=l@+bG_c76at~d48i{zjIVAro`T^{3+f%#A>T?uHEdBz38qTe(ey-jS`oiM~deu;I4m29= zLj&)|<%_hig`sUWKUd$Z<3=Dzdn78>b@15bMI~UH*M92Z>T(b63UEWi{Vyxb)sGE% zhuA}nw-KCJF$PhF^*TIS^}@_@df{3;@Boz&@U*a3Zq=X(-d{@*3wP-hej*(kCfPamEJQ^3zI$jHDdAXPBt=!mzl_6PM{!$*Tm?C&T`H-Kk$pSVv!=KJts59iN24=PG)qxu%M^q@Xj z?01v>a!}veOu9(vuAF9oT6E9LtCrVmdb`h%ZMZ24Lzfa4N~LSD$%TieD31ffvV z$o@59J;x2_!Q^nDnu7GlpXB>y>yv~nPRC)rNod>DXn)OUoKf@~=2ASjd)Gb1lo={U zgawy+Q*44gE-ZL2w&rIR=tFdL3m~&V|6GWvKh$V#6)(|3uzaas4Cn zPk%muQ9_vMcK9@G22Ih1_wF1%eKl_|ydyMk2)r9KZv?y-LOCq9P<&Q5=U#l*_AzSS zSa>IBUL(9WHP~b@4EDjtV0v^7c~G&JB#29=zwhVlenS6>Al_-jwtcUk(TwVsXv3F+ z3+}aZJk+81BbbF#3~3J!!7&DfWs?wkkM|Z9WbZ{4vOH5ecH3d{8?Z%*u-NFPKRb7;O`<;bh85vS-KA3TGd!2itNmf`Zl_@@Ob~EZ?7A7!;f|ONgp2YI}l)GZ^Q6x zKM;jR z9d(WSn0@t&{vF+t&+oHmeg#|6m$H7<4=Zjx8fH& zsp(;4Xc(U5VPS0FZ~9bxk&HaA|5U8-X6Mf9UlPwou>lwK3Bn1Mc|mUy&%`*Ti~9b8 zP@4@d)-Mp&vLnU%J(1e9gNXQKcWfk++Lq+gn?@LhieD> zvP8eE8!koiKiYzAP5x3NJ_E|a$x@%z#}wx=eDg{VBOrK|YlH%IYqsc`zD`Y^0ftpH zAh!CFKGMJYvu-kyb?m}5eS21ZN&lSCoQ?ZkKU`zN_NKi4iMChp0>{TF@<|@dy$k_e zi~V(3Zx=suus5#gjpAKzcKC|ES9oIwB<1KlwC@1gtkR=PrH742lR_&w>;v7W(Dmut zEc~i|ppeC0xvC!|Y-KrD!KH1^=s$1=Uc3~^7P$1U3bWatF8!|d7jQv=bM1Z9v65>L zoYDQiBd#sXarFRaiDj$UaaI3T%+oAOKn+I!|9DovI?qsO5-E$j*``bS5Ijq7vQSB_ z*^k%sn}h&1yA&5|Lzl9LrTUiLKh5y-QoqJT0@LhveC;Nid^^c5t-EQvqb}+uMA#0| z{$&7qGN(V3jsH_WqKQ0!H21nIvE^BDDc@CBjRo8A{lVx6`X!E?_){M$t{T9u|EX^x zwi@VE{?ao+clCic`}A*pT>GE)k`$1cGnz1Pzg!$Y=!k_|Y>P`K6LCiYrta)2MQxFz!`Q={|TDU+j-)?zOf)4^JAsu`VRhs!*I+b zutpa+Ec2>w6J~gz?-+K>&nsT|mv&KT!$tT5vrdtF?{z;^GB1}u@~?TIA15C5V>ce) zI0oE-4{_W8?)-=Pu-a#OaJ+fk3HlTc3v~H5bRK=E&l82OcQ2D*Vc=Uo=Vn2AAc%+j zobTu)vybREnKky6P6&0_HD8If_Al{XX;1P zwvRe$$YHOp3<&BO7h16BQO>^pUa1XWG)q7QOfKUdAgC}CmQd{Eh$(3YxRgz8u4J#E z(+$v@8rhe!DT)-;_j2k>WbdrPxR)~FubDCQ80|m9#^s#e+^!e4uG6kQNN@z)rR5f0 zB+ZM9!gs55;xqd}k(vpAuzwV(WxLO@nWd&+CE@PVEtIs6DGHaTJyGbv`w&vSorXZE zpQxMc|G>GVwlvyDC+Q!s;`&l&S@y=)O(p$#)?u<#Un#WCs1ZH1 z#9G5PkVIU#T+@r{KJsR58%x8*cds!^V=1L~*K1hc&~&^LHH3~IN2BbfYiNDZs}$qZ z6Yh&J2V0_n(O4^2oh_P3ZS;Xk3M4jmRH^E?zEI32v;d>>oC{k>T}AQeO13*(8Z0hY z$;6h@Fx~pQ-pMWYu2PG@Q6d+UeYq19Avz~^l?rtJiMz;V#KIz%a`KaoJtRS_@fC~eDFuo8 z7-sA#{Uoj~W6gSjMDH^8c`xatui@@Je3ETHZY;~_Ew!sxwo8EAjJJR_rWA0Cl(G>$ z*|FYIbLmKVd4+4=E_SQ86e{U)=GwJ-m$OkHDL@duWf6U)xw@Yq#6RdO^%S4`gI(+^ zJu7y+!+OR_;o|Mv?D<%!t@z;`wjx%F6vOX2^JAqHo#=a;wT_duAj*(fWo11{S zGb4p9F-il)xHw z*q8(h`{_f1+8l!D#ngqu7GM(oEa~Egg(zE`6ERNtC(- zyxiCWc&-~8o+#}Smww8mB#gfPpR&P8(kIApD@kgptFywJH69|h(y4EIvjIb-=e+%) zl`+eGCCG=qRTi^DL!{{N5m2yc!A?*7q;CcXuq)QCi{Z}F0#K)z&SFqbZONJpmEIIx zE!oDQ=@_SYOnAKg0>`{}Oyl+;CTWbJ0Q(;A3oHp(U107$!Bq5yH8{ zq$qQ`;Nzv9gG6%m=nd$TTsAGtbB&Kd=#V?ax13yiD2hHs(06xJOIul5v4SS~`^x_! z6qJbI&jOW)XEI?OcgztSj#bj_ zY-Xku%zhs()e>)QW_N}|z}EVb3)uB&%a5nAl2KBqtl4w3UuBa=VDvSb!&Z-wb~Fti z%_Zp;SL)}`a!6* z!IISxS;b0|0K@d}b?^_<9s3s6(MCx<8g2TPQ+k%RJgpIx^Fi`Em@{PGrYvWa)VRhp zkbqf%h<-Sgh;Av~{GIuZmU@UUy~^T8LzqlH&fXu5KK9E@_S0x-rdW84#gCEtiXVQ- zmW+{li&MU2-;R;m$*&EEXe!w5!|shi?)k5<=&{la@!S&j!B{Cu{N-i#(^zSe?hlyH zc_8_Q>9XK??pdjAOIjVlR^?EbW4F-`Mv5(imK^RFI(vwimMrU8DcF1)A*Ggu7RNk2 z+x9%X85y)3c1KL+5wz^Eui)nlRhjlZGun7S0vBm3Wg9J!m0B5fxnm(MXTl-vJ~M<^ zZ&x1Y-)x|Jd>)ncm@L(-KD}Dk@#(eJho5TGYoY_6z#KkZCO@8HWj!_QfQ#((ICO$f zr!$}N(%5=$caW(Kr&Vdg&kp#rNrir4x>R`H885vk^kBLP(sPXlVgK1^f0Gtzx)t0w zQO=kL6{6CtgG zJ|<$_?`=%5fZh)$_>-OX=4|3~l3thT_mE}1Erp4pFEYn-((qXQA#_6<>_B2i91MZn z^%)JLFNivVC>V32C_0#Y(KY*#taa*Lyi=3Ovg08vWD=&M7gJc~3aJmXPQtM1caW8= zkb)zp;50EzBzeP5k{Q_CSh62-_9ScUf$4TWW_|kZ!-tzmQbY^yeg3cwtS&)x1gHZ|6Zzm!)&%lbYKR_vL=raq5`dpF{# zhTFHt-Eisfls_-EX*V}pQ*+Y$Qm;$vJqm`TAvU5AvK=b4PO5B@H#6BYFQ7@9eDJtQ z(1oZ;J{!xMgqy{#?H#^R|B#({0pqjv3o!P8HmP~wsW#bx3*??Q!8F9%1Pemf{QK@U z+0EO8s(89F%a{Vy;3XKcm?91BABSne)56r8it~_WjrzqDZ1-|&)LQEhzJ2#N52$B( z%)}X;IQ`uZowpV|Gp9=7a#{9&x5ILGJG@uD9Y$BS10OV}e`TFhFlhEnVpCIy_c)67 z#Gu)?+rxW!$_aS(F4q1<>753LzNRs7nU8@g3@^14!~53V{FvWH2Ok2BMpp7$k*MGL ziSA+W&O{mpeOb^7scx)=wg#$?g()z}FwS-dNs)GT;T81YjL$i}ul^6VhNo-v_Gi`T{c%Voy%G(x&rg*z@3H`m|E2|$WFYB3C<$N0DGBVqrWz$bvVT&g#{3MO)zpUl!QhV`eG8_52w7tp0gM8(<%@yWw zD}yjvq&+)hHL=EXrEcQUp={(_WbJ*B?U{>(QO}|5_qoz!ari1WV4gHr{Bgd_A;-dPu3mOGsvN8|a6f9sobn1*e}R<1lIBYt#YQXHTk|E0|IeM=@Zrmt?;BDx zaYrZC<_#=r8qJ3F$RynQYWJ0|FH3zx`ax7PSp1s+8N8IGyeZ8V^WNrV`k#(;<7l42 z0v1S1Men!S+6B@CajSy^G|K7d29TDziAv)FvPrj@tlL6qmTv7{Z?=7*bW6C%-hE4& zDrao|&sGh8%{}OrRBP2Adsnt9@91hiYw;R7dcr`hRXg}U)vAMG>sRYvoBq3hA?d&R z*KBwHO0P!Ah+a=nV$EYM7SWK4XCoI;|MGo`lH(gaLvG`LQj&qB|DYs%mYXMO)hPL~ z#}kyaJD^dL5T{YnL4Jyo!5ch0+3=r~$VmDRO192)^JGyqN=9^lf|Ap@Zl3gW^F;p? zCC8a(%)wKdE=AV<*sV_u()6jxh;MI|>WfQeYU)#jg$mU^glpt&X2 z#VV$?foo5Onw!xttIy&gFx26h#UX(!wI>{!=gYJz+SC1VW}70l5nqaRt52IM(N$NU z;;PT&N^`CaG!g` z(g2<3*cWeOHDrvbR4Rkouc@qpdx5on4(qc*s#kCQ4^JtPH*b`)&`DU7yxmRHB41jq zX_5Ax)hkR| zX@{dVYv10#MqsMe=TJRb=ccx+>~nlZjry{jP3eXiHJWW*DRp9J+WXgHu9Z^lARLG) zz~-ARzv_aGRg92goBrF7YJbrcKj*29jaAFs8yn-`S+h!tYFGc=>T9c(%Q)r#V{J8f zGP|(~sy?t*@1SqCscaN1t)`!Oz$PsAZzXiry5|L04}GG0<_+Ufe6?G3WtDLJsqXpV zU*)Vpntxkh<14GWRj^g-o!z=sZ!8iG_G3@=&fpAp?<`sB&uXrfItfte*Gk>%v_nTR z;5&pX?Ouzycvddxm5-QXtz>I6g@&%fXW#aF6@y*~*kTm&agx*Cmo!UUY_HTTRR@b* zCp7`SiR+|9v(d4lm`0w>k&NvoTfU`4u^mjiqzra+xr~=OUdJUoCoD!M3B?J*b+l%e zWLzXzkc{JE{IwF>P0J;`JllTt&~kjmD$N*qBnDT67Z#a@1GZ4x8GQ4@Dm#O?m%5d% z?G;$*4PGyny>n!W)7k8c`XCmwL28RSZ4SAPuWF9=9}OwTu?P<#8$YN*$p7R`FFFTLCXx9L zDL6`hY9!w~tr81bywrv~7SHlf@%+n;o7HB+c*6w4M8k6vXwSAUUvE6!46DAu`UJ15 zKGix(2`B=w@JZAi83i6M3==^J|z!aZdOpaoq!n`z&U(y=7t1r_kCVS@T54+ry-52Iw@ z+nWe|IqhD3W$AMwTfbRq!*VxDF~Tl(XQMQ<&1b+LXS*Kus25)OYk+{8YN^>Q_|_nJ8l)ybOftbM&VJ#RT{nHwIPUihtsY$ zgBX~IFg`QjGxK5GK=3Va{m$NhPpWI)Dr3VU(Y7;?JGsH!v4+5j!(Dnf8IE@NRk8=; ziILr~_EV8DL{yxE!&7WEjdmQiv<<1F1d2EWqq93=kqrK|M*T9~|8}Cxh z>aN52eq7EyQgI1qO_lT?!9q9Yow%Z-EvLxV>hm6P(vG_a34w)+Xg8exRL7ICG-g#8fWqxW?al#2EQeWG zRB=)r$GtJyrmRI;iU@fomh!oDZe;jc0s;Z1na!XV3R&n?&1Ix-Esi%4&Upk70CX(#^8p zw*t7a8`!$9HFdEV*G@bmr|LO12+s?2eFveX{);0b949^Vq4kBUP#f^LTUZOldJbO7 z8?_I4kGVmuJKY#damGab8nMV1k4`iRGa}eo*@s3W;;BBw8!63GEWN~xs;qZc{jwS6*dfa-xxtV! zG%euTWujv%he02qi(|WEFl;q=G=uBo>RC)DRU8>0PNxQGgf|AiIpK=67QFnhS$}{; zoimp<#2o92?h1ryhj4gg!VxY=)59tG6xC?WLEMB3dk$nS&;*;S6}}`|a^FJ8D>WZ? z`6-?c(hj&^P!)|SS3Qr@@4U^UP_Vntw7QDUkjE3u5Gdnoco=Mf&rRnd=wzHu2sF@H z+T3k3fR|H$^poVy1(b)(SsIymeZY)wpqAV@M5yTi)w9p@)iavR$JO2*3U@bRBk-!XkW$-{XXajq-fh_pA%L>p%!CGg5U zz}c=RnD`D5aVEx8lK3sj-nTpnLn;$Cy^VyaNFZ!(Nl^AV;+}B>TkAkrx+Pre!bL{6 ztSwlS2%n&K^x(|oEw(iRLYGGV!;ht?#^JwUxLn|0IM8y>Xb^NXg&+off&rC@F-Npo zSU=~QwzVd zJxe12&qP1?MCv%>5^@`HuMj+Sb0Fgr9r&F{>{ikbLa5jsluDf)(~fOfDrPlxH!hZP z+V6xvK|3dB9gI$YU{+?%a>)QJ(03s^T)5QBSI>r!REzfcaEyOqvw=>>T7tZu4X^ z=hq!Pfy);ne5^RZ;RIYBSJFSxe7K<1E&^jpaVq#9Evg%QArAQ*hzzN?dJbT#6P*>w zxEmJZ`r3|(bThrLOOs_k;&sVI)Mi<=sQu>Oi~aXmfTRcXHX7&!lMc+cb7RK{_AQ-=Pa@q&|R; zlbTl6M@jDlD$>gOD%LshfM`uHry?8VVYpUe48-Eg=s>Q)Q=XJQg8}Ffi39=_7b0;>{PU=QJHxYay#fQNA|KdWX>+tn^l3T3T6iCH)erOf*_&BL)X6 zA!q(@bswxlM=w@SqpG}OJGtt>bx=n$)HvInU{0U1V~NX^6&S&EaA-4J_kb*1Gzr6>{?uzwvUrU) za`8hekqsWvVD8l$%G`?q*v@hU&GLt%#GUNb2irw6C=N&XR1;u%c07&YQ4{Lbrnd6n z#p5%cgq=01t+a&HsT%!MtTwpw4&yYSvUIm*Z89kHUWNJb88)q2$4~=QtG-qXR0g-2 z)9y>kk}i!%I6q6`)mXS_7`gaU14w~{fc*Ks;H6gl?XD=U;Lywy2q_JQ2XiMkgge#< zIOqzIa0;wl*!(Oh)SP4j)gL1_2o_Bf-I`c!U@=(mR4^E?hhL+x6Nra&!OAo@^|5eS z-ky{iy1R}^_U#5x$ep_>)gvKZj@2Z@0F

*_Z?oJP{M{6J&`T=ZAg;!CN!vb9X> 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%0AY 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�LX8|iQhlQ=}q8<_ZKROrJ?sgGJpPpQ8zTj2VS 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 diff --git a/apps/mobile/modules/t3-terminal/scripts/build-libghostty-android.sh b/apps/mobile/modules/t3-terminal/scripts/build-libghostty-android.sh index 55d994d3b363c..b9e225eff4bcd 100755 --- a/apps/mobile/modules/t3-terminal/scripts/build-libghostty-android.sh +++ b/apps/mobile/modules/t3-terminal/scripts/build-libghostty-android.sh @@ -7,7 +7,7 @@ MODULE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" VENDOR_DIR="${MODULE_DIR}/../../../../native/libghostty-vt" PATCH_DIR="${SCRIPT_DIR}/libghostty-android-patches" -GHOSTTY_REVISION="${GHOSTTY_REVISION:-9f62873bf195e4d8a762d768a1405a5f2f7b1697}" +GHOSTTY_REVISION="${GHOSTTY_REVISION:-3ff6d08fad56f2d6b81420a27bdc56ee707b453e}" GHOSTTY_SOURCE_DIR="${GHOSTTY_SOURCE_DIR:-${HOME}/.cache/t3code/ghostty-${GHOSTTY_REVISION:0:8}}" GHOSTTY_ZIG_VERSION="${GHOSTTY_ZIG_VERSION:-0.15.2}" GHOSTTY_ZIG="${GHOSTTY_ZIG:-}" diff --git a/apps/web/src/terminal/ghostty/core.test.ts b/apps/web/src/terminal/ghostty/core.test.ts index 9feeed716a616..27a10f2fd9f72 100644 --- a/apps/web/src/terminal/ghostty/core.test.ts +++ b/apps/web/src/terminal/ghostty/core.test.ts @@ -296,6 +296,18 @@ describe("GhosttyTerminalCore snapshots", () => { expect(core.snapshot()).toEqual(reference.snapshot()); }); + it("answers an OSC 11 background query with the theme background", async () => { + const replies: string[] = []; + const core = await createCore((data) => replies.push(data)); + core.setTheme({ + foreground: { r: 0, g: 0, b: 0 }, + background: { r: 255, g: 255, b: 255 }, + cursor: { r: 0, g: 0, b: 0 }, + }); + core.write("\x1b]11;?\x1b\\"); + expect(replies.join("")).toMatch(/^\x1b\]11;rgb:ffff\/ffff\/ffff(\x07|\x1b\\)$/); + }); + it("answers a live VT query when batched reads cross a chunk compaction", async () => { const replies: string[] = []; const core = await createCore((data) => replies.push(data)); diff --git a/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm b/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm index a8e2405adefd37612dfea6ebd77fbcacbd9134be..80b269e8699a1474a460a626623af93f2ff669b2 100644 GIT binary patch delta 167333 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

_4yOyw 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 #include #include +#include #include #include #include @@ -145,6 +147,7 @@ extern "C" { #include #include #include +#include #include #ifdef __cplusplus diff --git a/native/libghostty-vt/include/ghostty/vt/color.h b/native/libghostty-vt/include/ghostty/vt/color.h index 9dc21864eb9a8..f5f7f9c4ec5e9 100644 --- a/native/libghostty-vt/include/ghostty/vt/color.h +++ b/native/libghostty-vt/include/ghostty/vt/color.h @@ -7,6 +7,114 @@ #ifndef GHOSTTY_VT_COLOR_H #define GHOSTTY_VT_COLOR_H +/** @defgroup color Color Utilities + * + * Color parsing, palette generation, color math, and X11 color name + * utilities shared by libghostty-vt. + * + * These APIs expose Ghostty's color semantics directly to embedders. Use + * them when an application needs to parse the same color strings as Ghostty + * config and theme files, generate the same 256-color palette used by the + * terminal, list supported X11 color names, or make UI decisions from + * luminance and contrast values. + * + * ## Parsing Colors + * + * ghostty_color_parse() accepts the flexible syntax used by Ghostty for + * terminal colors: + * + * - X11 color names, matched ASCII case-insensitively. + * - 3- or 6-digit hex colors, with or without a leading `#`. + * - 9- or 12-digit hex colors, with a leading `#`. + * - XParseColor-style `rgb://` values. + * - XParseColor-style `rgbi://` values. + * + * Leading and trailing spaces and tabs are ignored. Use + * ghostty_color_parse_x11() when only X11 names should be accepted. + * + * @code{.c} + * GhosttyColorRgb color; + * + * if (ghostty_color_parse( + * "ForestGreen", + * sizeof("ForestGreen") - 1, + * &color) != GHOSTTY_SUCCESS) { + * // Handle invalid color input. + * } + * + * ghostty_color_parse("#abc", sizeof("#abc") - 1, &color); + * ghostty_color_parse("rgb:12/34/56", sizeof("rgb:12/34/56") - 1, &color); + * @endcode + * + * ## Palette Entries + * + * ghostty_color_parse_palette_entry() parses a single Ghostty palette + * override in `INDEX=COLOR` form. The index may be decimal or use a `0x`, + * `0o`, or `0b` prefix. The color side uses ghostty_color_parse(). + * + * @code{.c} + * GhosttyColorRgb palette[256]; + * ghostty_color_palette_default(palette); + * + * uint8_t index; + * GhosttyColorRgb rgb; + * + * if (ghostty_color_parse_palette_entry( + * "0x10=#282c34", + * sizeof("0x10=#282c34") - 1, + * &index, + * &rgb) == GHOSTTY_SUCCESS) { + * palette[index] = rgb; + * } + * @endcode + * + * ## Palette Generation + * + * ghostty_color_palette_generate() derives the 216-color cube and grayscale + * ramp from a base palette, background, and foreground. Set bits in + * GhosttyColorPaletteMask preserve specific indices from the base palette. + * The output may alias the base input. + * + * @code{.c} + * GhosttyColorRgb palette[256]; + * ghostty_color_palette_default(palette); + * + * GhosttyColorPaletteMask skip = {0}; + * GHOSTTY_COLOR_PALETTE_MASK_SET(&skip, 16); + * + * GhosttyColorRgb background = {40, 44, 52}; + * GhosttyColorRgb foreground = {220, 223, 228}; + * + * ghostty_color_palette_generate( + * palette, + * &skip, + * &background, + * &foreground, + * true, + * palette); + * @endcode + * + * ## X11 Color Names + * + * The X11 name table is static program-lifetime memory. Entries are in + * rgb.txt order and are terminated by an entry with `name == NULL`. + * ghostty_color_x11_name_count() returns the number of non-terminator + * entries. + * + * @code{.c} + * const GhosttyColorX11Entry* names = ghostty_color_x11_names(); + * size_t count = ghostty_color_x11_name_count(); + * + * for (size_t i = 0; i < count; i++) { + * // names[i].name and names[i].color are valid here. + * } + * @endcode + * + * @{ + */ + +#include +#include #include #include @@ -17,7 +125,7 @@ extern "C" { /** * RGB color value. * - * @ingroup sgr + * @ingroup color */ typedef struct { uint8_t r; /**< Red component (0-255) */ @@ -28,49 +136,128 @@ typedef struct { /** * Palette color index (0-255). * - * @ingroup sgr + * @ingroup color */ typedef uint8_t GhosttyColorPaletteIndex; -/** @addtogroup sgr - * @{ +/** + * A 256-bit mask of palette indices. + * + * Index i is set iff `(bits[i >> 6] >> (i & 63)) & 1` is 1. + * The mask is typically initialized to zero and then populated with + * GHOSTTY_COLOR_PALETTE_MASK_SET(). + * + * @code{.c} + * GhosttyColorPaletteMask mask = {0}; + * GHOSTTY_COLOR_PALETTE_MASK_SET(&mask, 20); + * if (GHOSTTY_COLOR_PALETTE_MASK_IS_SET(&mask, 20)) { + * // Index 20 will be preserved. + * } + * @endcode + * + * @ingroup color */ +typedef struct { + uint64_t bits[4]; +} GhosttyColorPaletteMask; + +/** + * An entry in Ghostty's X11 color name table. + * + * @ingroup color + */ +typedef struct { + /** Null-terminated color name. NULL marks the end of the table. */ + const char* name; + /** The RGB value of the color. */ + GhosttyColorRgb color; +} GhosttyColorX11Entry; + +/** + * Return the storage word for a palette mask index. + * + * @param index The palette index (0-255) + * + * @ingroup color + */ +#define GHOSTTY_COLOR_PALETTE_MASK_WORD(index) ((index) >> 6) + +/** + * Return the storage bit for a palette mask index. + * + * @param index The palette index (0-255) + * + * @ingroup color + */ +#define GHOSTTY_COLOR_PALETTE_MASK_BIT(index) (UINT64_C(1) << ((index) & 63)) -/** Black color (0) @ingroup sgr */ +/** + * Set a palette mask index. + * + * @param mask Pointer to a GhosttyColorPaletteMask + * @param index The palette index (0-255) + * + * @ingroup color + */ +#define GHOSTTY_COLOR_PALETTE_MASK_SET(mask, index) \ + ((mask)->bits[GHOSTTY_COLOR_PALETTE_MASK_WORD(index)] |= GHOSTTY_COLOR_PALETTE_MASK_BIT(index)) + +/** + * Clear a palette mask index. + * + * @param mask Pointer to a GhosttyColorPaletteMask + * @param index The palette index (0-255) + * + * @ingroup color + */ +#define GHOSTTY_COLOR_PALETTE_MASK_UNSET(mask, index) \ + ((mask)->bits[GHOSTTY_COLOR_PALETTE_MASK_WORD(index)] &= ~GHOSTTY_COLOR_PALETTE_MASK_BIT(index)) + +/** + * Test whether a palette mask index is set. + * + * @param mask Pointer to a GhosttyColorPaletteMask + * @param index The palette index (0-255) + * @return true if the palette index is set, false otherwise + * + * @ingroup color + */ +#define GHOSTTY_COLOR_PALETTE_MASK_IS_SET(mask, index) \ + (((mask)->bits[GHOSTTY_COLOR_PALETTE_MASK_WORD(index)] & GHOSTTY_COLOR_PALETTE_MASK_BIT(index)) != 0) + +/** Black color (0) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BLACK 0 -/** Red color (1) @ingroup sgr */ +/** Red color (1) @ingroup color */ #define GHOSTTY_COLOR_NAMED_RED 1 -/** Green color (2) @ingroup sgr */ +/** Green color (2) @ingroup color */ #define GHOSTTY_COLOR_NAMED_GREEN 2 -/** Yellow color (3) @ingroup sgr */ +/** Yellow color (3) @ingroup color */ #define GHOSTTY_COLOR_NAMED_YELLOW 3 -/** Blue color (4) @ingroup sgr */ +/** Blue color (4) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BLUE 4 -/** Magenta color (5) @ingroup sgr */ +/** Magenta color (5) @ingroup color */ #define GHOSTTY_COLOR_NAMED_MAGENTA 5 -/** Cyan color (6) @ingroup sgr */ +/** Cyan color (6) @ingroup color */ #define GHOSTTY_COLOR_NAMED_CYAN 6 -/** White color (7) @ingroup sgr */ +/** White color (7) @ingroup color */ #define GHOSTTY_COLOR_NAMED_WHITE 7 -/** Bright black color (8) @ingroup sgr */ +/** Bright black color (8) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_BLACK 8 -/** Bright red color (9) @ingroup sgr */ +/** Bright red color (9) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_RED 9 -/** Bright green color (10) @ingroup sgr */ +/** Bright green color (10) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_GREEN 10 -/** Bright yellow color (11) @ingroup sgr */ +/** Bright yellow color (11) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_YELLOW 11 -/** Bright blue color (12) @ingroup sgr */ +/** Bright blue color (12) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_BLUE 12 -/** Bright magenta color (13) @ingroup sgr */ +/** Bright magenta color (13) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_MAGENTA 13 -/** Bright cyan color (14) @ingroup sgr */ +/** Bright cyan color (14) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_CYAN 14 -/** Bright white color (15) @ingroup sgr */ +/** Bright white color (15) @ingroup color */ #define GHOSTTY_COLOR_NAMED_BRIGHT_WHITE 15 -/** @} */ - /** * Get the RGB color components. * @@ -78,20 +265,215 @@ typedef uint8_t GhosttyColorPaletteIndex; * from a GhosttyColorRgb value. Primarily useful in WebAssembly environments * where accessing struct fields directly is difficult. * - * @param color The RGB color value + * @param color Pointer to the RGB color value * @param r Pointer to store the red component (0-255) * @param g Pointer to store the green component (0-255) * @param b Pointer to store the blue component (0-255) * - * @ingroup sgr + * @ingroup color */ -GHOSTTY_API void ghostty_color_rgb_get(GhosttyColorRgb color, +GHOSTTY_API void ghostty_color_rgb_get(const GhosttyColorRgb* color, uint8_t* r, uint8_t* g, uint8_t* b); +/** + * Parse an X11 color name. + * + * The color name is resolved from Ghostty's embedded rgb.txt table. + * Leading and trailing spaces and tabs are trimmed, and matching is + * ASCII case-insensitive. Hex values are not accepted by this function. + * + * @param name The color name bytes (must not be NULL) + * @param len The length of @p name in bytes + * @param[out] out The parsed RGB color + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if no color + * matches or @p name is NULL + * + * @ingroup color + */ +GHOSTTY_API GhosttyResult ghostty_color_parse_x11( + const char* name, + size_t len, + GhosttyColorRgb* out); + +/** + * Parse a flexible Ghostty color value. + * + * Accepts Ghostty's terminal color syntax: X11 color names, hex colors + * in 3-, 6-, 9-, or 12-digit form (the leading # is optional for 3- and + * 6-digit values), and rgb:// or + * rgbi:// specifications. Leading and trailing spaces + * and tabs are trimmed. + * + * @param value The color value bytes (must not be NULL) + * @param len The length of @p value in bytes + * @param[out] out The parsed RGB color + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if parsing + * fails or @p value is NULL + * + * @ingroup color + */ +GHOSTTY_API GhosttyResult ghostty_color_parse( + const char* value, + size_t len, + GhosttyColorRgb* out); + +/** + * Parse a Ghostty palette entry. + * + * Accepts Ghostty palette config syntax: N=COLOR. N is a palette index + * from 0 to 255 in decimal or in 0x, 0o, or 0b-prefixed form. Spaces and + * tabs around N and COLOR are ignored. COLOR accepts the same syntax as + * ghostty_color_parse(). + * + * @param value The palette entry bytes (must not be NULL) + * @param len The length of @p value in bytes + * @param[out] out_index The parsed palette index + * @param[out] out_rgb The parsed RGB color + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE on any + * failure, including index overflow + * + * @ingroup color + */ +GHOSTTY_API GhosttyResult ghostty_color_parse_palette_entry( + const char* value, + size_t len, + uint8_t* out_index, + GhosttyColorRgb* out_rgb); + +/** + * Get Ghostty's built-in default 256-color palette. + * + * Writes exactly 256 entries: Ghostty's base16 defaults, the xterm + * 6x6x6 color cube, and the grayscale ramp. + * + * @param[out] out The output palette, an array of exactly 256 + * GhosttyColorRgb values + * + * @ingroup color + */ +GHOSTTY_API void ghostty_color_palette_default(GhosttyColorRgb* out); + +/** + * Generate a 256-color palette from base colors. + * + * The base palette supplies indices 0-15, which are always preserved. + * If @p base is NULL, Ghostty's default palette is used. If @p skip is + * NULL, no extra indices are skipped. Set bits in @p skip preserve those + * indices from @p base. The 216-color cube at indices 16-231 is generated + * with trilinear CIELAB interpolation, and the grayscale ramp at indices + * 232-255 is interpolated from the background to the foreground. + * + * For light themes, @p harmonious controls whether the generated palette + * keeps the background-to-foreground orientation. When false, Ghostty + * swaps the light background and dark foreground so the cube and ramp run + * dark-to-light. The output palette may be the same pointer as @p base. + * + * @param base The base palette, an array of exactly 256 GhosttyColorRgb + * values, or NULL to use Ghostty's default palette + * @param skip The palette indices to preserve from @p base, or NULL for + * an empty mask + * @param bg The terminal background color (must not be NULL) + * @param fg The terminal foreground color (must not be NULL) + * @param harmonious Whether light themes keep background-to-foreground + * orientation + * @param[out] out The output palette, an array of exactly 256 + * GhosttyColorRgb values + * + * @ingroup color + */ +GHOSTTY_API void ghostty_color_palette_generate( + const GhosttyColorRgb* base, + const GhosttyColorPaletteMask* skip, + const GhosttyColorRgb* bg, + const GhosttyColorRgb* fg, + bool harmonious, + GhosttyColorRgb* out); + +/** + * Calculate W3C relative luminance for an RGB color. + * + * Returns a normalized value from 0.0 for black to 1.0 for white. + * See https://www.w3.org/TR/WCAG20/#relativeluminancedef. + * + * @param color The RGB color (must not be NULL) + * @return Relative luminance in the range 0.0 to 1.0 + * + * @ingroup color + */ +GHOSTTY_API double ghostty_color_luminance(const GhosttyColorRgb* color); + +/** + * Calculate perceived luminance for an RGB color. + * + * Returns a normalized value from 0.0 for black to 1.0 for white. + * Ghostty treats a background color as light when this exceeds 0.5. + * This is not the metric used internally by + * ghostty_color_palette_generate(), which uses CIELAB lightness. + * + * @param color The RGB color (must not be NULL) + * @return Perceived luminance in the range 0.0 to 1.0 + * + * @ingroup color + */ +GHOSTTY_API double ghostty_color_perceived_luminance(const GhosttyColorRgb* color); + +/** + * Calculate the WCAG contrast ratio between two RGB colors. + * + * The contrast ratio is symmetric and ranges from 1.0 for identical + * colors to 21.0 for black and white. + * + * @param a The first RGB color (must not be NULL) + * @param b The second RGB color (must not be NULL) + * @return WCAG contrast ratio in the range 1.0 to 21.0 + * + * @ingroup color + */ +GHOSTTY_API double ghostty_color_contrast(const GhosttyColorRgb* a, + const GhosttyColorRgb* b); + +/** + * Get Ghostty's X11 color name table. + * + * The returned pointer references static memory valid for the program + * lifetime and is never NULL. Entries are in rgb.txt order and are + * terminated by an entry with name == NULL. Aliases are separate entries, + * such as "medium spring green" and "MediumSpringGreen". Names are the + * exact supported spellings from rgb.txt; ghostty_color_parse_x11() also + * matches them case-insensitively. + * + * @code{.c} + * for (const GhosttyColorX11Entry* e = ghostty_color_x11_names(); + * e->name != NULL; + * e++) { + * // e->name and e->color are valid here. + * } + * @endcode + * + * @return Pointer to the first X11 color entry + * + * @ingroup color + */ +GHOSTTY_API const GhosttyColorX11Entry* ghostty_color_x11_names(void); + +/** + * Get the number of X11 color name entries. + * + * The returned count excludes the NULL terminator and is provided so + * bindings can preallocate storage before reading ghostty_color_x11_names(). + * + * @return Number of X11 color name entries + * + * @ingroup color + */ +GHOSTTY_API size_t ghostty_color_x11_name_count(void); + #ifdef __cplusplus } #endif +/** @} */ + #endif /* GHOSTTY_VT_COLOR_H */ diff --git a/native/libghostty-vt/include/ghostty/vt/color_scheme.h b/native/libghostty-vt/include/ghostty/vt/color_scheme.h new file mode 100644 index 0000000000000..d028ec080a9be --- /dev/null +++ b/native/libghostty-vt/include/ghostty/vt/color_scheme.h @@ -0,0 +1,73 @@ +/** + * @file color_scheme.h + * + * Color scheme report encoding - encode terminal color scheme reports into + * escape sequences. + */ + +#ifndef GHOSTTY_VT_COLOR_SCHEME_H +#define GHOSTTY_VT_COLOR_SCHEME_H + +/** @defgroup color_scheme Color Scheme Report Encoding + * + * Utilities for encoding color scheme reports into terminal escape + * sequences for color scheme reporting mode (mode 2031). + * + * ## Basic Usage + * + * Use ghostty_color_scheme_report_encode() to encode a color scheme report + * into a caller-provided buffer. If the buffer is too small, the function + * returns GHOSTTY_OUT_OF_SPACE and sets the required size in the output + * parameter. + * + * ## Example + * + * @snippet c-vt-color-scheme/src/main.c color-scheme-report-encode + * + * @{ + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Encode a color scheme report into an escape sequence. + * + * Encodes a color scheme report into the provided buffer. Dark color schemes + * emit ESC [ ? 997 ; 1 n, and light color schemes emit ESC [ ? 997 ; 2 n. + * The encoded bytes are identical to the terminal's internal CSI ? 996 n + * query response. + * + * Hosts should gate unsolicited sends on GHOSTTY_MODE_COLOR_SCHEME_REPORT + * (mode 2031) being set, which can be checked via the mode getters. + * + * If the buffer is too small, the function returns GHOSTTY_OUT_OF_SPACE + * and writes the required buffer size to @p out_written. The caller can + * then retry with a sufficiently sized buffer. + * + * @param scheme The color scheme to encode + * @param buf Output buffer to write the encoded sequence into (may be NULL) + * @param buf_len Size of the output buffer in bytes + * @param[out] out_written On success, the number of bytes written. On + * GHOSTTY_OUT_OF_SPACE, the required buffer size. + * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if the buffer + * is too small + */ +GHOSTTY_API GhosttyResult ghostty_color_scheme_report_encode( + GhosttyColorScheme scheme, + char* buf, + size_t buf_len, + size_t* out_written); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* GHOSTTY_VT_COLOR_SCHEME_H */ diff --git a/native/libghostty-vt/include/ghostty/vt/kitty_graphics.h b/native/libghostty-vt/include/ghostty/vt/kitty_graphics.h index 9bace3a3ccf95..dda8b836e1e7b 100644 --- a/native/libghostty-vt/include/ghostty/vt/kitty_graphics.h +++ b/native/libghostty-vt/include/ghostty/vt/kitty_graphics.h @@ -84,6 +84,28 @@ extern "C" { * - ghostty_kitty_graphics_placement_rect() — bounding rectangle as a * @ref GhosttySelection. * + * ## Change Detection + * + * Generation stamps allow renderers to cheaply detect whether Kitty + * graphics state changed between frames: + * + * - @ref GHOSTTY_KITTY_GRAPHICS_DATA_GENERATION is a storage-wide stamp + * updated on any transmit, placement, or delete. If unchanged, the + * placement set and all image data are identical and both placement + * snapshots and per-image staleness checks can be skipped. Placement + * geometry can still change independently (scrolling moves + * placements), so ghostty_kitty_graphics_placement_render_info() + * should still be recomputed on dirty frames. + * - @ref GHOSTTY_KITTY_IMAGE_DATA_GENERATION is a per-image stamp + * changed on every add/replace of that image ID. Texture caches + * should treat a cached texture as stale when this differs from the + * cached value; dimension/length heuristics cannot detect a + * same-sized retransmission. + * + * Stamps are unique and monotonically increasing process-wide, so + * caches keyed on a generation value never alias across screens + * (main/alternate), resets, or terminals. + * * ## Lifetime and Thread Safety * * All handles borrowed from the terminal (GhosttyKittyGraphics, @@ -119,6 +141,29 @@ typedef enum GHOSTTY_ENUM_TYPED { * Output type: GhosttyKittyGraphicsPlacementIterator * */ GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR = 1, + + /** + * Generation stamp of the last content mutation to this storage: + * any image transmit/replace, placement add, or delete. Zero means + * the storage has never been mutated (and is therefore empty). + * + * If the generation is unchanged since a previous query, the set of + * placements and all image data are identical, so placement iteration + * and image staleness checks can be skipped entirely. Note that + * placement *geometry* may still have changed (scrolling and resizing + * move placements without changing the storage contents), so rendering + * geometry such as ghostty_kitty_graphics_placement_render_info() + * must still be recomputed for frames marked dirty. + * + * Stamps are unique and monotonically increasing process-wide: a + * value observed from any storage never recurs for different content, + * even across screen switches (main vs. alternate screen have + * independent storages) or terminal resets. It is therefore safe to + * key caches on this value alone. + * + * Output type: uint64_t * + */ + GHOSTTY_KITTY_GRAPHICS_DATA_GENERATION = 2, GHOSTTY_KITTY_GRAPHICS_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyKittyGraphicsData; @@ -255,6 +300,12 @@ typedef enum GHOSTTY_ENUM_TYPED { /** * Pixel format of a Kitty graphics image. * + * Note that stored images are always fully decoded: + * GHOSTTY_KITTY_IMAGE_FORMAT_PNG is never returned by + * ghostty_kitty_graphics_image_get() because PNG payloads are decoded + * to GHOSTTY_KITTY_IMAGE_FORMAT_RGBA before storage. The PNG value + * exists only for protocol-level completeness. + * * @ingroup kitty_graphics */ typedef enum GHOSTTY_ENUM_TYPED { @@ -269,6 +320,12 @@ typedef enum GHOSTTY_ENUM_TYPED { /** * Compression of a Kitty graphics image. * + * Note that stored images are always decompressed: + * GHOSTTY_KITTY_IMAGE_COMPRESSION_ZLIB_DEFLATE payloads are inflated + * before storage, so ghostty_kitty_graphics_image_get() always reports + * GHOSTTY_KITTY_IMAGE_COMPRESSION_NONE. Consumers never need to + * inflate image data themselves. + * * @ingroup kitty_graphics */ typedef enum GHOSTTY_ENUM_TYPED { @@ -315,14 +372,17 @@ typedef enum GHOSTTY_ENUM_TYPED { GHOSTTY_KITTY_IMAGE_DATA_HEIGHT = 4, /** - * Pixel format of the image. + * Pixel format of the image. Never GHOSTTY_KITTY_IMAGE_FORMAT_PNG; + * PNG payloads are decoded to RGBA before storage. * * Output type: GhosttyKittyImageFormat * */ GHOSTTY_KITTY_IMAGE_DATA_FORMAT = 5, /** - * Compression of the image. + * Compression of the image. Always + * GHOSTTY_KITTY_IMAGE_COMPRESSION_NONE; compressed payloads are + * inflated before storage. * * Output type: GhosttyKittyImageCompression * */ @@ -332,17 +392,41 @@ typedef enum GHOSTTY_ENUM_TYPED { * Borrowed pointer to the raw pixel data. Valid as long as the * underlying terminal is not mutated. * + * The data is always fully decoded, uncompressed pixels in the + * format reported by GHOSTTY_KITTY_IMAGE_DATA_FORMAT: zlib payloads + * are inflated and PNG payloads are decoded to RGBA at transmission + * time, before the image is stored. Consumers can upload this + * directly to the GPU without any decode step. + * * Output type: const uint8_t ** */ GHOSTTY_KITTY_IMAGE_DATA_DATA_PTR = 7, /** - * Length of the raw pixel data in bytes. + * Length of the raw pixel data in bytes. Always equal to + * width * height * bytes-per-pixel for the reported format. * * Output type: size_t * */ GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8, + /** + * Generation stamp assigned when this image was added to (or + * replaced in) the storage. A changed generation for a given image + * ID means the pixel contents may have changed even when the + * dimensions, format, and data length are identical (e.g. a + * retransmission of the same image ID), so texture caches must key + * staleness on this value rather than on size heuristics. + * + * Stamps are unique and monotonically increasing process-wide and + * are drawn from the same sequence as + * GHOSTTY_KITTY_GRAPHICS_DATA_GENERATION. Never zero for a stored + * image, so zero can be used as an "empty" sentinel by callers. + * + * Output type: uint64_t * + */ + GHOSTTY_KITTY_IMAGE_DATA_GENERATION = 9, + GHOSTTY_KITTY_IMAGE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyKittyGraphicsImageData; diff --git a/native/libghostty-vt/include/ghostty/vt/render.h b/native/libghostty-vt/include/ghostty/vt/render.h index c5b1d0d4fc2ba..2e3f2f71e41ac 100644 --- a/native/libghostty-vt/include/ghostty/vt/render.h +++ b/native/libghostty-vt/include/ghostty/vt/render.h @@ -39,6 +39,18 @@ extern "C" { * 2. Update it from a terminal instance whenever you need. * 3. Read from the render state to get the data needed to draw your frame. * + * ## Two-Phase Updates + * + * For callers that synchronize terminal access (e.g. a renderer thread + * sharing a lock with an IO thread), the update can be split into two + * phases to minimize the time the terminal must be held exclusively: + * ghostty_render_state_begin_update requires terminal access, while + * ghostty_render_state_end_update completes any deferred work using only + * memory owned by the render state. A typical renderer would lock, begin + * the update, unlock, and then end the update while the IO thread is free + * to continue modifying the terminal. ghostty_render_state_update is a + * convenience that performs both phases in one call. + * * ## Dirty Tracking * * Dirty tracking is a key feature of the render state that allows renderers @@ -331,6 +343,12 @@ GHOSTTY_API void ghostty_render_state_free(GhosttyRenderState state); * This consumes terminal/screen dirty state in the same way as the internal * render state update path. * + * This is a convenience function that performs a full update in one call, + * equivalent to ghostty_render_state_begin_update immediately followed by + * ghostty_render_state_end_update. Callers that hold a lock over the + * terminal state should prefer calling the two phases directly so that the + * lock is only held for the begin phase. + * * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE) * @param terminal The terminal handle to read from (NULL returns GHOSTTY_INVALID_VALUE) * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or @@ -342,6 +360,54 @@ GHOSTTY_API void ghostty_render_state_free(GhosttyRenderState state); GHOSTTY_API GhosttyResult ghostty_render_state_update(GhosttyRenderState state, GhosttyTerminal terminal); +/** + * Begin an update of a render state instance from a terminal. + * + * Every begin must be completed with a ghostty_render_state_end_update call + * before the render state is read. + * + * This two-phase structure exists for callers that synchronize access to the + * terminal state (e.g. with a lock shared with an IO thread): only this + * function requires terminal access, so a caller can hold its lock for this + * call only and then call ghostty_render_state_end_update after releasing + * it. The end phase exclusively reads and writes memory owned by the render + * state, so it is safe to call while the terminal is being modified. + * + * Work that doesn't require terminal access may be deferred to the end phase + * to keep this call (and therefore lock hold time) as short as possible. + * Callers must treat the render state as incomplete until + * ghostty_render_state_end_update is called. + * + * This consumes terminal/screen dirty state in the same way as the internal + * render state update path. + * + * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE) + * @param terminal The terminal handle to read from (NULL returns GHOSTTY_INVALID_VALUE) + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or + * `terminal` is NULL, GHOSTTY_OUT_OF_MEMORY if updating the state requires + * allocation and that allocation fails + * + * @ingroup render + */ +GHOSTTY_API GhosttyResult ghostty_render_state_begin_update(GhosttyRenderState state, + GhosttyTerminal terminal); + +/** + * Complete a prior ghostty_render_state_begin_update call by performing any + * deferred work. + * + * This only reads and writes memory owned by the render state, so it is safe + * to call while the terminal is being modified (no terminal synchronization + * is required). Calling this without a prior begin is a safe no-op. + * + * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE) + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` is + * NULL + * + * @ingroup render + */ +GHOSTTY_API GhosttyResult ghostty_render_state_end_update(GhosttyRenderState state); + /** * Get a value from a render state. * diff --git a/native/libghostty-vt/include/ghostty/vt/sgr.h b/native/libghostty-vt/include/ghostty/vt/sgr.h index 8eec11dc97061..757c5aeda9306 100644 --- a/native/libghostty-vt/include/ghostty/vt/sgr.h +++ b/native/libghostty-vt/include/ghostty/vt/sgr.h @@ -19,8 +19,11 @@ * The parser processes SGR parameters from CSI sequences (e.g., `ESC[1;31m`) * and returns individual text attributes like bold, italic, colors, etc. * It supports both semicolon (`;`) and colon (`:`) separators, possibly mixed, - * and handles various color formats including 8-color, 16-color, 256-color, - * X11 named colors, and RGB in multiple formats. + * and handles SGR color attributes including 8-color, 16-color, 256-color, + * direct RGB, underline color, and reset forms. Color values are returned + * using the shared @ref color types; applications that need to parse Ghostty + * config/theme color strings, generate palettes, inspect X11 color names, or + * calculate luminance and contrast should use the @ref color APIs directly. * * ## Basic Usage * diff --git a/native/libghostty-vt/include/ghostty/vt/terminal.h b/native/libghostty-vt/include/ghostty/vt/terminal.h index b22e8aedc8967..b03653129247e 100644 --- a/native/libghostty-vt/include/ghostty/vt/terminal.h +++ b/native/libghostty-vt/include/ghostty/vt/terminal.h @@ -191,6 +191,21 @@ typedef enum GHOSTTY_ENUM_TYPED { /** Scroll by a delta amount (up is negative). */ GHOSTTY_SCROLL_VIEWPORT_DELTA, + + /** + * Scroll to an absolute row offset from the top of the scrollable + * area. Row 0 is the top of the scrollback and the requested row + * becomes the first visible row of the viewport. The value is + * clamped so the viewport never scrolls beyond the top of the + * active area. If the terminal has no scrollback (e.g. the + * alternate screen is active), the viewport always remains on the + * active area. + * + * This is the same row space as the offset field of + * GhosttyTerminalScrollbar, so a scrollbar position obtained from + * GHOSTTY_TERMINAL_DATA_SCROLLBAR round-trips cleanly. + */ + GHOSTTY_SCROLL_VIEWPORT_ROW, GHOSTTY_SCROLL_VIEWPORT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyTerminalScrollViewportTag; @@ -203,6 +218,9 @@ typedef union { /** Scroll delta (only used with GHOSTTY_SCROLL_VIEWPORT_DELTA). Up is negative. */ intptr_t delta; + /** Absolute row offset (only used with GHOSTTY_SCROLL_VIEWPORT_ROW). */ + size_t row; + /** Padding for ABI compatibility. Do not use. */ uint64_t _padding[2]; } GhosttyTerminalScrollViewportValue; @@ -767,9 +785,16 @@ typedef enum GHOSTTY_ENUM_TYPED { /** * Scrollbar state for the terminal viewport. * - * This may be expensive to calculate depending on where the viewport - * is (arbitrary pins are expensive). The caller should take care to only - * call this as needed and not too frequently. + * This is amortized O(1): the total is maintained incrementally as + * the terminal is modified and the viewport offset is cached. The + * first read after the viewport moves to an arbitrary position that + * isn't an absolute row (e.g. scrolling to a selection) may cost + * O(pages) to compute the offset, after which it is cached again. + * + * There is intentionally no change notification for scroll state. + * Callers building scrollbars should poll this once per frame or + * per write batch and diff the result to detect changes; this is + * what Ghostty's own renderer does. * * Output type: GhosttyTerminalScrollbar * */ @@ -1120,7 +1145,10 @@ GHOSTTY_API void ghostty_terminal_vt_write(GhosttyTerminal terminal, * Scrolls the terminal's viewport according to the given behavior. * When using GHOSTTY_SCROLL_VIEWPORT_DELTA, set the delta field in * the value union to specify the number of rows to scroll (negative - * for up, positive for down). For other behaviors, the value is ignored. + * for up, positive for down). When using GHOSTTY_SCROLL_VIEWPORT_ROW, + * set the row field to the absolute row offset from the top of the + * scrollable area (the same row space as the offset field of + * GhosttyTerminalScrollbar). For other behaviors, the value is ignored. * * @param terminal The terminal handle (may be NULL, in which case this is a no-op) * @param behavior The scroll behavior as a tagged union diff --git a/native/libghostty-vt/include/ghostty/vt/unicode.h b/native/libghostty-vt/include/ghostty/vt/unicode.h new file mode 100644 index 0000000000000..e4d62c8181df3 --- /dev/null +++ b/native/libghostty-vt/include/ghostty/vt/unicode.h @@ -0,0 +1,154 @@ +/** + * @file unicode.h + * + * Unicode utilities - codepoint properties matching the terminal's + * text layout semantics. + */ + +#ifndef GHOSTTY_VT_UNICODE_H +#define GHOSTTY_VT_UNICODE_H + +/** @defgroup unicode Unicode Utilities + * + * Unicode codepoint properties matching the terminal's text layout + * semantics. + * + * ## Basic Usage + * + * Use ghostty_unicode_codepoint_width() to determine how many terminal + * grid cells a codepoint occupies, using the exact same width table the + * terminal itself uses when laying out printed text. Use + * ghostty_unicode_grapheme_width() to segment and measure full grapheme + * clusters with the same rules the terminal uses when mode 2027 is + * enabled. These functions are useful for predicting column layout of + * text that has not yet been written to the terminal, such as IME + * preedit (composition) overlays. + * + * @{ + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returns the terminal display width of a Unicode codepoint in + * terminal grid cells: 0, 1, or 2. + * + * This is the same width table the terminal itself uses when laying + * out printed text, so callers can predict column layout (e.g. IME + * preedit overlays) that exactly matches what the terminal will do + * when the text is actually written to it. + * + * Semantics: + * - Returns 0 for zero-width codepoints: C0/C1 control characters, + * nonspacing and enclosing combining marks, default-ignorable + * codepoints (ZWJ, ZWNJ, variation selectors, etc.), and + * surrogate codepoints. + * - Returns 2 for wide codepoints: East Asian Wide/Fullwidth + * (including emoji with default emoji presentation) and regional + * indicators. Width is clamped to 2 (e.g. the three-em dash). + * - Returns 1 for everything else, including invalid codepoints + * beyond U+10FFFF (this function is total; it never fails). + * + * This operates on a single codepoint only and therefore cannot account + * for grapheme-cluster-level width rules (VS16 emoji presentation, + * combining sequences, etc.). For cluster-accurate widths, use + * ghostty_unicode_grapheme_width(). Summing per-codepoint widths is only + * correct when mode 2027 (grapheme clustering) is disabled. + * + * This function is pure, allocates nothing, and is thread-safe. + * + * @param cp The Unicode codepoint to measure + * @return Display width in cells: 0, 1, or 2 + */ +GHOSTTY_API uint8_t ghostty_unicode_codepoint_width(uint32_t cp); + +/** + * Measures the terminal display width of the first grapheme cluster in a + * sequence of Unicode codepoints. + * + * This uses the exact same grapheme segmentation and cluster width rules + * the terminal itself uses when printing text with grapheme clustering + * enabled (mode 2027), so callers can predict column layout (e.g. IME + * preedit overlays) that exactly matches what the terminal will do when + * the text is actually written to it. Unlike + * ghostty_unicode_codepoint_width(), this accounts for cluster-level + * rules: emoji variation selectors, ZWJ sequences, combining marks, and + * skin tone modifiers. + * + * Reads codepoints from cps until the terminal would consider the + * grapheme cluster complete, stores the cluster's total width in cells + * (0, 1, or 2) into width (which may be NULL if only segmentation is + * desired), and returns the number of codepoints consumed. Returns 0 if + * and only if len is 0; otherwise consumes at least one codepoint. Measure + * a whole string by calling in a loop: + * + * @code + * size_t total = 0; + * for (size_t i = 0; i < len;) { + * uint8_t width; + * i += ghostty_unicode_grapheme_width(cps + i, len - i, &width); + * total += width; + * } + * @endcode + * + * This is not a streaming API. The provided sequence must contain a + * complete first grapheme cluster, or the logical end of the string. If + * input arrives in chunks, keep buffering while this function consumes all + * available codepoints (return value == len) and the stream may still + * continue; a later codepoint could still extend the cluster and change + * its width. + * + * Width semantics, matching the terminal with mode 2027 enabled: + * - The cluster starts at the width of its first codepoint, as returned by + * ghostty_unicode_codepoint_width(). + * - VS16 (U+FE0F) forces the cluster wide (2) and VS15 (U+FE0E) forces it + * narrow (1), but only when the immediately preceding codepoint in the + * cluster is a valid emoji variation sequence base (per Unicode + * emoji-variation-sequences.txt). Invalid variation selectors are + * ignored entirely. + * - Any other continuation codepoint that contributes to grapheme width + * forces the cluster wide (2). Note this means cluster width is NOT the + * maximum of per-codepoint widths: some continuation marks have narrow + * codepoint width yet still widen the cluster. + * + * Mode dependence: this models mode 2027 (grapheme clustering) enabled, + * which is Ghostty's recommended configuration. When mode 2027 is + * disabled, clusters never combine and variation selectors never change + * width; predict layout in that case by summing + * ghostty_unicode_codepoint_width() over each codepoint instead. + * + * Edge cases: + * - Codepoints beyond U+10FFFF consume one codepoint, have width 1, and + * are always cluster boundaries. This function is total; it never fails. + * - Control characters (C0/C1, CR, LF) are never printed through the + * terminal's text path; passing them here returns an unspecified (but + * stable and bounded) result. + * - A cluster whose first codepoint is zero-width (e.g. a lone combining + * mark) is malformed at a cell start; the terminal may attach it to + * earlier screen content. This function reports the fold result for the + * sequence in isolation (typically 0). + * + * This function is pure, allocates nothing, and is thread-safe. + * + * @param cps Pointer to codepoints (may be NULL only when len is 0) + * @param len Number of codepoints available + * @param width Out: cluster display width in cells (0-2); may be NULL + * @return Number of codepoints in the first grapheme cluster + */ +GHOSTTY_API size_t ghostty_unicode_grapheme_width(const uint32_t *cps, + size_t len, + uint8_t *width); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* GHOSTTY_VT_UNICODE_H */ From f52e5c19198b744229d5aa193f55d4802b04cbfd Mon Sep 17 00:00:00 2001 From: luke <97411838+luke2x@users.noreply.github.com> Date: Sat, 14 Nov 2026 14:38:02 +0000 Subject: [PATCH 134/192] fix(server): expand Windows home-relative file paths consistently (#29378) Co-authored-by: Julius Marminge --- apps/server/src/assets/AssetAccess.test.ts | 89 ++++++++++--------- apps/server/src/assets/AssetAccess.ts | 7 +- .../src/workspace/WorkspaceFileSystem.test.ts | 36 ++++---- .../src/workspace/WorkspaceFileSystem.ts | 7 +- 4 files changed, 73 insertions(+), 66 deletions(-) diff --git a/apps/server/src/assets/AssetAccess.test.ts b/apps/server/src/assets/AssetAccess.test.ts index 582e828842ede..8f050c3748c56 100644 --- a/apps/server/src/assets/AssetAccess.test.ts +++ b/apps/server/src/assets/AssetAccess.test.ts @@ -525,50 +525,53 @@ describe("AssetAccess", () => { }).pipe(Effect.provide(layerTest)), ); - it.effect("resolves home-relative media paths independently of the workspace", () => - Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-media-home-" }); - const home = path.join(directory, "var", "home", "alice"); - const filePath = path.join(home, "Downloads", "repro.mp4"); - yield* fs.makeDirectory(path.dirname(filePath), { recursive: true }); - yield* fs.writeFileString(filePath, "recording bytes"); - const canonicalFile = yield* fs.realPath(filePath); - yield* Effect.gen(function* () { - for (const workspaceRoot of [ - path.join(home, "project"), - path.join(directory, "srv", "project"), - undefined, - ]) { - if (workspaceRoot) yield* fs.makeDirectory(workspaceRoot, { recursive: true }); - for (const requestedPath of ["~/Downloads/repro.mp4", "~\\Downloads/repro.mp4"]) { - const result = yield* issueAssetUrl({ - resource: { - _tag: "media-file", - threadId: ThreadId.make("thread-1"), - path: requestedPath, - }, - ...(workspaceRoot ? { workspaceRoot } : {}), - }); - const suffix = result.relativeUrl.slice(`${ASSET_ROUTE_PREFIX}/`.length); - const separator = suffix.indexOf("/"); - const asset = yield* resolveAsset( - suffix.slice(0, separator), - suffix.slice(separator + 1), - ); - expect(asset).toMatchObject({ - kind: "file", - path: canonicalFile, - mimeType: "video/mp4", - }); - if (asset?.kind !== "file") throw new Error("Expected a resolved home media file"); - const response = HttpServerResponse.toWeb(yield* assetFileResponse(asset)); - expect(yield* Effect.promise(() => response.text())).toBe("recording bytes"); + it.effect.each(["media-file", "draft-workspace-file"] as const)( + "resolves home-relative %s paths independently of the workspace", + (tag) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-media-home-" }); + const home = path.join(directory, "var", "home", "alice"); + const filePath = path.join(home, "Downloads", "repro.mp4"); + yield* fs.makeDirectory(path.dirname(filePath), { recursive: true }); + yield* fs.writeFileString(filePath, "recording bytes"); + const canonicalFile = yield* fs.realPath(filePath); + yield* Effect.gen(function* () { + for (const workspaceRoot of [ + path.join(home, "project"), + path.join(directory, "srv", "project"), + undefined, + ]) { + if (workspaceRoot) yield* fs.makeDirectory(workspaceRoot, { recursive: true }); + for (const requestedPath of ["~/Downloads/repro.mp4", "~\\Downloads/repro.mp4"]) { + const result = yield* issueAssetUrl({ + resource: { + ...(tag === "media-file" + ? { _tag: tag, threadId: ThreadId.make("thread-1") } + : { _tag: tag, cwd: workspaceRoot ?? directory }), + path: requestedPath, + }, + ...(workspaceRoot ? { workspaceRoot } : {}), + }); + const suffix = result.relativeUrl.slice(`${ASSET_ROUTE_PREFIX}/`.length); + const separator = suffix.indexOf("/"); + const asset = yield* resolveAsset( + suffix.slice(0, separator), + suffix.slice(separator + 1), + ); + expect(asset).toMatchObject({ + kind: "file", + path: canonicalFile, + mimeType: "video/mp4", + }); + if (asset?.kind !== "file") throw new Error("Expected a resolved home media file"); + const response = HttpServerResponse.toWeb(yield* assetFileResponse(asset)); + expect(yield* Effect.promise(() => response.text())).toBe("recording bytes"); + } } - } - }).pipe(Effect.provideService(HostProcess.HomeDirectory, home)); - }).pipe(Effect.provide(layerTest)), + }).pipe(Effect.provideService(HostProcess.HomeDirectory, home)); + }).pipe(Effect.provide(layerTest)), ); it.effect.skipIf(HostProcess.Platform.defaultValue() === "win32")( diff --git a/apps/server/src/assets/AssetAccess.ts b/apps/server/src/assets/AssetAccess.ts index c841f8eecff2e..b1c1b28b626cd 100644 --- a/apps/server/src/assets/AssetAccess.ts +++ b/apps/server/src/assets/AssetAccess.ts @@ -607,9 +607,10 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i } case "draft-workspace-file": { const draftWorkspaceRoot = input.workspaceRoot ?? input.resource.cwd; - const hostPath = input.resource.path.startsWith("~/") - ? expandHomePath(input.resource.path, yield* HostProcess.HomeDirectory) - : yield* toWslDrivePath(input.resource.path); + const hostPath = + input.resource.path.startsWith("~/") || input.resource.path.startsWith("~\\") + ? expandHomePath(input.resource.path, yield* HostProcess.HomeDirectory) + : yield* toWslDrivePath(input.resource.path); if (path.isAbsolute(hostPath)) { const finalized = yield* finalizeAbsoluteMediaFileAsset({ requestedPath: hostPath, diff --git a/apps/server/src/workspace/WorkspaceFileSystem.test.ts b/apps/server/src/workspace/WorkspaceFileSystem.test.ts index 34ddeaa872ad4..bc22117ba74ab 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.test.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.test.ts @@ -320,25 +320,27 @@ it.layer(layerTest, { excludeTestServices: true })("WorkspaceFileSystemLive", (i }), ); - it.effect("reads ~/ paths from the server home outside the workspace", () => - Effect.gen(function* () { - const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; - const cwd = yield* makeTempDir; - const home = yield* makeTempDir; - yield* writeTextFile(home, ".claude/CLAUDE.md", "# Notes\n"); - yield* writeTextFile(cwd, "~/.claude/CLAUDE.md", "Workspace decoy\n"); + it.effect.each(["~/.claude/CLAUDE.md", "~\\.claude/CLAUDE.md"])( + "reads %s from the server home outside the workspace", + (relativePath) => + Effect.gen(function* () { + const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; + const cwd = yield* makeTempDir; + const home = yield* makeTempDir; + yield* writeTextFile(home, ".claude/CLAUDE.md", "# Notes\n"); + yield* writeTextFile(cwd, "~/.claude/CLAUDE.md", "Workspace decoy\n"); - const result = yield* workspaceFileSystem - .readFile({ cwd, relativePath: "~/.claude/CLAUDE.md" }) - .pipe(Effect.provideService(HostProcess.HomeDirectory, home)); + const result = yield* workspaceFileSystem + .readFile({ cwd, relativePath }) + .pipe(Effect.provideService(HostProcess.HomeDirectory, home)); - expect(result).toEqual({ - relativePath: "~/.claude/CLAUDE.md", - contents: "# Notes\n", - byteLength: 8, - truncated: false, - }); - }), + expect(result).toEqual({ + relativePath, + contents: "# Notes\n", + byteLength: 8, + truncated: false, + }); + }), ); it.effect("does not read server state files", () => diff --git a/apps/server/src/workspace/WorkspaceFileSystem.ts b/apps/server/src/workspace/WorkspaceFileSystem.ts index 7f3fb67b15000..494472e4ce68d 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.ts @@ -260,9 +260,10 @@ export const make = Effect.gen(function* () { input: ProjectReadFileInput, ) { const requestedPath = input.relativePath; - const hostPath = requestedPath.startsWith("~/") - ? expandHomePath(requestedPath, yield* HostProcess.HomeDirectory) - : yield* toHostPath(requestedPath); + const hostPath = + requestedPath.startsWith("~/") || requestedPath.startsWith("~\\") + ? expandHomePath(requestedPath, yield* HostProcess.HomeDirectory) + : yield* toHostPath(requestedPath); if ( isNetworkPath(hostPath) && !KnownWorkspaces.isWithinRoot(path, path.resolve(hostPath), path.resolve(input.cwd)) From 081a5d92bf1287f7bcf0f79a52e1c0b2b18e793e Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 01:43:21 +1100 Subject: [PATCH 135/192] fix(clients): the folder picker finds folders with accented names on macOS (#29617) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../components/CommandPalette.logic.test.ts | 20 +++++++++++++++++ .../src/components/CommandPalette.logic.ts | 7 +++++- .../src/state/filesystem.test.ts | 22 +++++++++++++++++++ .../client-runtime/src/state/filesystem.ts | 14 ++++++++++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/CommandPalette.logic.test.ts b/apps/web/src/components/CommandPalette.logic.test.ts index cc23a79ddddad..afb6dfbea255d 100644 --- a/apps/web/src/components/CommandPalette.logic.test.ts +++ b/apps/web/src/components/CommandPalette.logic.test.ts @@ -795,6 +795,26 @@ describe("filterPinnedBrowseEntries", () => { visibleEntries: windowsEntries, exactEntry: windowsEntries[0], }); + const decomposed = { name: "De\u0301veloppement", fullPath: "/Users/me/De\u0301veloppement" }; + expect( + filterPinnedBrowseEntries({ + browseEntries: [decomposed], + filterQuery: "D\u00e9veloppement", + pinnedDirectoryName: "repo", + caseSensitive: true, + }).exactEntry, + ).toBe(decomposed); + expect( + filterPinnedBrowseEntries({ + browseEntries: [ + { name: "\u212B", fullPath: "/home/me/\u212B" }, + { name: "A\u030A", fullPath: "/home/me/A\u030A" }, + ], + filterQuery: "\u00C5", + pinnedDirectoryName: "repo", + caseSensitive: true, + }).exactEntry, + ).toBeNull(); }); }); diff --git a/apps/web/src/components/CommandPalette.logic.ts b/apps/web/src/components/CommandPalette.logic.ts index 895995c03e487..407e9a5e35981 100644 --- a/apps/web/src/components/CommandPalette.logic.ts +++ b/apps/web/src/components/CommandPalette.logic.ts @@ -749,9 +749,14 @@ export function filterPinnedBrowseEntries(input: { ? "" : input.filterQuery; const { visibleEntries } = filterFilesystemBrowseEntries(input.browseEntries, visibleFilterQuery); + const normalizedFilterQuery = input.filterQuery.normalize("NFC"); + const normalizedMatches = input.browseEntries.filter((entry) => + namesMatch(entry.name.normalize("NFC"), normalizedFilterQuery), + ); const exactEntry = input.filterQuery.length > 0 - ? (input.browseEntries.find((entry) => namesMatch(entry.name, input.filterQuery)) ?? null) + ? (input.browseEntries.find((entry) => namesMatch(entry.name, input.filterQuery)) ?? + (normalizedMatches.length === 1 ? normalizedMatches[0] : null)) : null; return { visibleEntries, exactEntry }; } diff --git a/packages/client-runtime/src/state/filesystem.test.ts b/packages/client-runtime/src/state/filesystem.test.ts index 6c21c5d926919..038fc23cb9858 100644 --- a/packages/client-runtime/src/state/filesystem.test.ts +++ b/packages/client-runtime/src/state/filesystem.test.ts @@ -3,6 +3,7 @@ import { AuthFilesystemReadScope, AuthOrchestrationReadScope } from "@t3tools/co import { createBrowseNavigationCoordinator, + filterFilesystemBrowseEntries, getFilesystemBrowsePath, getFilesystemBrowseTabCompletion, resolveFilesystemReadAccess, @@ -107,6 +108,27 @@ describe("filesystem browse model", () => { }); expect(getFilesystemBrowseTabCompletion([], "x")).toBeNull(); }); + + it("matches folder names stored decomposed, as macOS Finder names them", () => { + const entry = { name: "De\u0301veloppement", fullPath: "/Users/me/De\u0301veloppement" }; + expect(filterFilesystemBrowseEntries([entry], "d\u00e9v")).toEqual({ + visibleEntries: [entry], + exactEntry: null, + }); + expect(filterFilesystemBrowseEntries([entry], "De").visibleEntries).toEqual([entry]); + expect(filterFilesystemBrowseEntries([entry], "D\u00e9veloppement").exactEntry).toBe(entry); + // Linux keeps both spellings as separate folders; the one typed exactly wins. + const composed = { name: "D\u00e9veloppement", fullPath: "/home/me/D\u00e9veloppement" }; + expect(filterFilesystemBrowseEntries([entry, composed], "D\u00e9veloppement").exactEntry).toBe( + composed, + ); + // Two spellings that both normalize to the query are ambiguous; neither is picked. + const angstroms = [ + { name: "\u212B", fullPath: "/home/me/\u212B" }, + { name: "A\u030A", fullPath: "/home/me/A\u030A" }, + ]; + expect(filterFilesystemBrowseEntries(angstroms, "\u00C5").exactEntry).toBeNull(); + }); }); describe("browse navigation", () => { diff --git a/packages/client-runtime/src/state/filesystem.ts b/packages/client-runtime/src/state/filesystem.ts index 0b380101a99de..4f369d2ffde41 100644 --- a/packages/client-runtime/src/state/filesystem.ts +++ b/packages/client-runtime/src/state/filesystem.ts @@ -74,15 +74,25 @@ export function filterFilesystemBrowseEntries( entries: ReadonlyArray, query: string, ) { + // macOS Finder stores names decomposed (NFD), while typed text is composed (NFC). + const normalizedQuery = query.normalize("NFC"); const lowerQuery = query.toLowerCase(); + const lowerNormalizedQuery = normalizedQuery.toLowerCase(); const showHidden = query.startsWith("."); const visibleEntries = entries.filter( (entry) => - entry.name.toLowerCase().startsWith(lowerQuery) && + (entry.name.toLowerCase().startsWith(lowerQuery) || + entry.name.normalize("NFC").toLowerCase().startsWith(lowerNormalizedQuery)) && (showHidden || !entry.name.startsWith(".")), ); + const normalizedMatches = visibleEntries.filter( + (entry) => entry.name.normalize("NFC") === normalizedQuery, + ); const exactEntry = - query.length > 0 ? (visibleEntries.find((entry) => entry.name === query) ?? null) : null; + query.length > 0 + ? (visibleEntries.find((entry) => entry.name === query) ?? + (normalizedMatches.length === 1 ? normalizedMatches[0] : null)) + : null; return { visibleEntries, exactEntry }; } From cebdf399842f227c0952b31499e3ed25643cfa5c Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 01:43:34 +1100 Subject: [PATCH 136/192] fix(web): cancelled queued-message edits release their staged uploads (#29570) Co-authored-by: maria-rcks Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Julius Marminge --- .../components/chat/queuedMessageEdit.test.ts | 7 ++ .../src/components/chat/queuedMessageEdit.ts | 18 ++++- .../chat/useQueuedRunEdit.browser.test.tsx | 74 +++++++++++++++++++ .../src/components/chat/useQueuedRunEdit.ts | 39 ++++++++-- 4 files changed, 130 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/chat/queuedMessageEdit.test.ts b/apps/web/src/components/chat/queuedMessageEdit.test.ts index 0f839f414f74f..f67d1843c38be 100644 --- a/apps/web/src/components/chat/queuedMessageEdit.test.ts +++ b/apps/web/src/components/chat/queuedMessageEdit.test.ts @@ -8,6 +8,7 @@ import { type ComposerFileAttachment, type ComposerImageAttachment, } from "../../composerDraftStore"; +import { readAttachmentUpload, useAttachmentUploadStore } from "../../lib/attachmentUploadQueue"; import { environmentContextRecord, responseContextRecord } from "../../lib/composerContextRecords"; import { prepareQueuedEditAttachments, recoverQueuedMessageEdit } from "./queuedMessageEdit"; @@ -166,10 +167,16 @@ describe("queued message file edits", () => { const store = useComposerDraftStore.getState(); store.setPrompt(threadTarget, "Separate draft"); store.addFiles(editTarget, [file]); + useAttachmentUploadStore.setState({ + uploadsByImageId: { + [file.id]: { status: "ready", environmentId, attachmentId: uploadedFile.id }, + }, + }); expect(recoverQueuedMessageEdit({ editTarget, threadTarget, originalText: "" })).toBe( "discarded", ); expect(store.getComposerDraft(threadTarget)?.prompt).toBe("Separate draft"); expect(store.getComposerDraft(editTarget)).toBeNull(); + expect(readAttachmentUpload(file.id)).toBeUndefined(); }); }); diff --git a/apps/web/src/components/chat/queuedMessageEdit.ts b/apps/web/src/components/chat/queuedMessageEdit.ts index b5e576a36bff7..48f7ee5d1ae10 100644 --- a/apps/web/src/components/chat/queuedMessageEdit.ts +++ b/apps/web/src/components/chat/queuedMessageEdit.ts @@ -7,6 +7,20 @@ import { type ComposerImageAttachment, type ComposerThreadTarget, } from "../../composerDraftStore"; +import { releaseDraftAttachments } from "../../lib/attachmentUploadQueue"; + +/** Staged attachments upload as soon as they are added, so a dropped edit must release them. */ +export function discardQueuedEditDraft(target: ComposerThreadTarget): void { + const store = useComposerDraftStore.getState(); + const draft = store.getComposerDraft(target); + if (draft) { + releaseDraftAttachments([...draft.images, ...draft.files]); + for (const image of draft.images) { + if (image.previewUrl.startsWith("blob:")) URL.revokeObjectURL(image.previewUrl); + } + } + store.clearComposerContent(target); +} export function recoverQueuedMessageEdit(input: { readonly editTarget: ComposerThreadTarget; @@ -28,8 +42,10 @@ export function recoverQueuedMessageEdit(input: { }); if (recovery === "kept") { store.moveComposerContent(input.editTarget, input.threadTarget); + store.clearComposerContent(input.editTarget); + } else { + discardQueuedEditDraft(input.editTarget); } - store.clearComposerContent(input.editTarget); return recovery; } diff --git a/apps/web/src/components/chat/useQueuedRunEdit.browser.test.tsx b/apps/web/src/components/chat/useQueuedRunEdit.browser.test.tsx index 3118cd3ec7100..28785ecd768af 100644 --- a/apps/web/src/components/chat/useQueuedRunEdit.browser.test.tsx +++ b/apps/web/src/components/chat/useQueuedRunEdit.browser.test.tsx @@ -28,6 +28,7 @@ import { deriveComposerSendState } from "../ChatView.logic"; import { ComposerPrimaryActions } from "./ComposerPrimaryActions"; import { AppAtomRegistryProvider, appAtomRegistry } from "../../rpc/atomRegistry"; import { useQueuedRunEdit } from "./useQueuedRunEdit"; +import { readAttachmentUpload, useAttachmentUploadStore } from "../../lib/attachmentUploadQueue"; interface RunStatus { readonly id: RunId; @@ -150,6 +151,7 @@ function view( function setup() { mocks.editQueuedRun.mockReset(); + useAttachmentUploadStore.setState({ uploadsByImageId: {} }); useComposerDraftStore.setState({ draftsByThreadKey: {}, draftThreadsByThreadKey: {} }); setRuns(THREAD_A, "queued"); setRuns(THREAD_B, "queued"); @@ -406,6 +408,78 @@ describe("useQueuedRunEdit", () => { expect(promptOf(THREAD_A)).toBe(""); }); + it.each([ + { transition: "cancel", result: "Success" }, + { transition: "recover", result: "Failure" }, + { transition: "replace", result: "Success" }, + ] as const)( + "settles saved attachments after $transition and $result", + async ({ transition, result }) => { + const { hook } = setup(); + let finishSave!: (result: { readonly _tag: "Success" | "Failure" }) => void; + let started!: () => void; + const saving = new Promise<{ readonly _tag: "Success" | "Failure" }>((resolve) => { + finishSave = resolve; + }); + const requested = new Promise((resolve) => { + started = resolve; + }); + mocks.editQueuedRun.mockImplementation(() => { + started(); + return saving; + }); + await using _screen = await render(view(THREAD_A, "queued", hook)); + await beginChangedEdit(hook); + const image = { + type: "image" as const, + id: "saved-image", + name: "image.png", + mimeType: "image/png", + sizeBytes: 5, + file: new File(["image"], "image.png", { type: "image/png" }), + previewUrl: "blob:saved-image", + }; + using revoke = vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => {}); + await act(() => + useComposerDraftStore.getState().addImage(requireHook(hook).draftTarget, image), + ); + useAttachmentUploadStore.setState({ + uploadsByImageId: { + [image.id]: { status: "ready", environmentId: ENV, attachmentId: "saved-upload" }, + }, + }); + const target = requireHook(hook).draftTarget; + const saved = requireHook(hook).saveEdit( + { ...savedContent("changed"), images: [image] }, + () => {}, + ); + await act(() => requested); + expect(requireHook(hook).isSaving).toBe(true); + if (transition === "cancel") await act(() => requireHook(hook).cancelEdit()); + if (transition === "recover") await act(() => setRuns(THREAD_A, "running")); + if (transition === "replace") { + await beginChangedEdit(hook); + await act(() => + useComposerDraftStore.getState().addImage(requireHook(hook).draftTarget, image), + ); + } + expect(readAttachmentUpload(image.id)).toBeDefined(); + expect(revoke).not.toHaveBeenCalled(); + finishSave({ _tag: result }); + await act(() => saved); + if (transition === "cancel") { + expect(readAttachmentUpload(image.id)).toBeUndefined(); + expect(revoke).toHaveBeenCalledWith(image.previewUrl); + expect(useComposerDraftStore.getState().getComposerDraft(target)).toBeNull(); + } else { + expect(readAttachmentUpload(image.id)).toBeDefined(); + expect(revoke).not.toHaveBeenCalled(); + const owner = transition === "recover" ? THREAD_A : target; + expect(useComposerDraftStore.getState().getComposerDraft(owner)?.images).toEqual([image]); + } + }, + ); + it("leaves a newer edit alone when a save from before navigation finishes", async () => { const { hook } = setup(); let finishSave: (result: { readonly _tag: "Success" }) => void = () => {}; diff --git a/apps/web/src/components/chat/useQueuedRunEdit.ts b/apps/web/src/components/chat/useQueuedRunEdit.ts index 0ed3f9228940f..e7f321bec9dc2 100644 --- a/apps/web/src/components/chat/useQueuedRunEdit.ts +++ b/apps/web/src/components/chat/useQueuedRunEdit.ts @@ -60,7 +60,11 @@ import { useAtomCommand } from "../../state/use-atom-command"; import { stackedThreadToast, toastManager } from "../ui/toast"; import type { ChatComposerHandle } from "./ChatComposer"; import { fileAttachmentCapabilityBlockReason } from "./composerAttachmentFiles"; -import { prepareQueuedEditAttachments, recoverQueuedMessageEdit } from "./queuedMessageEdit"; +import { + discardQueuedEditDraft, + prepareQueuedEditAttachments, + recoverQueuedMessageEdit, +} from "./queuedMessageEdit"; import type { EditQueuedRunRequest } from "./QueuedRunsControl"; interface EditingQueuedRun { @@ -232,10 +236,12 @@ export function useQueuedRunEdit(input: { const focusComposerSoon = () => { window.requestAnimationFrame(() => composerRef.current?.focusAtEnd()); }; - const clearEditDraft = (runId: RunId) => - useComposerDraftStore - .getState() - .clearComposerContent(queuedRunEditDraftId(routeThreadRef, runId)); + const clearEditDraft = (runId: RunId) => { + const target = queuedRunEditDraftId(routeThreadRef, runId); + // A save in flight still sends these uploads. + if (isSaving) useComposerDraftStore.getState().clearComposerContent(target); + else discardQueuedEditDraft(target); + }; const beginEdit = useStableCallback((request: EditQueuedRunRequest) => { if (activeThreadId === null) return; @@ -366,10 +372,29 @@ export function useQueuedRunEdit(input: { }); setThreadError(editing.threadId, error); const current = finishSave(error === null); + if (!current) { + // A failed save can recover into the thread draft; a newer edit can also retain resources. + const drafts = Object.values(useComposerDraftStore.getState().draftsByThreadKey); + const retainedIds = new Set( + drafts.flatMap((draft) => + [...draft.images, ...draft.files].map((attachment) => attachment.id), + ), + ); + releaseDraftAttachments( + newAttachments.filter((attachment) => !retainedIds.has(attachment.id)), + ); + const retainedPreviews = new Set( + drafts.flatMap((draft) => draft.images.map((image) => image.previewUrl)), + ); + for (const image of content.images) { + if (image.previewUrl.startsWith("blob:") && !retainedPreviews.has(image.previewUrl)) { + URL.revokeObjectURL(image.previewUrl); + } + } + return; + } if (error !== null) return; releaseDraftAttachments(newAttachments); - // The edit was cancelled or begun again while this save ran; its drafts are the newer edit's. - if (!current) return; promptRef.current = ""; clearEditDraft(editing.runId); composerRef.current?.resetCursorState(); From 0c6f421f3c145ad5c7ff75ebbb4d4996ad7537d7 Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Sat, 14 Nov 2026 20:19:35 +0530 Subject: [PATCH 137/192] perf(desktop): stop rebroadcasting preview ownership on every key (#29312) Co-authored-by: Julius Marminge --- apps/desktop/src/preview/Manager.test.ts | 110 +++++++++++++++++++++++ apps/desktop/src/preview/Manager.ts | 82 ++++++++++++----- 2 files changed, 172 insertions(+), 20 deletions(-) diff --git a/apps/desktop/src/preview/Manager.test.ts b/apps/desktop/src/preview/Manager.test.ts index b6387a06c22d8..64b584fbed7f6 100644 --- a/apps/desktop/src/preview/Manager.test.ts +++ b/apps/desktop/src/preview/Manager.test.ts @@ -2756,6 +2756,116 @@ describe("PreviewManager", () => { ), ); + effectIt.effect( + "publishes human ownership only on transitions while retaining every recording press", + () => + withManager((manager) => + Effect.gen(function* () { + let humanInput: ((input: unknown) => void) | undefined; + const contents = makeTestPreviewWebContents(async () => ({ + toJPEG: () => Buffer.from("frame"), + getSize: () => ({ width: 800, height: 600 }), + })); + contents.ipc.on = (channel, callback) => { + if (channel === "preview:human-input") + humanInput = (input) => callback({} as Electron.IpcMainEvent, input); + return contents.ipc; + }; + fromId.mockReturnValue(contents); + yield* manager.createTab("tab_human"); + yield* manager.registerWebview("tab_human", 42); + yield* manager.startRecording("tab_human", { + showKeyPresses: true, + showMousePresses: true, + }); + const controllers: string[] = []; + const feedback = yield* Queue.unbounded(); + webviewSend.mockImplementation((channel) => { + if (channel === "preview:recording-controller") Queue.offerUnsafe(feedback, undefined); + }); + yield* manager.subscribeStateChanges((_tabId, state) => + Effect.sync(() => { + controllers.push(state.controller); + }), + ); + for (const press of [ + { kind: "pointer", x: 10, y: 20, button: 0 }, + { kind: "pointer", x: 30, y: 40, button: 0 }, + { kind: "key", key: "a", code: "KeyA" }, + ]) { + humanInput?.(press); + yield* Queue.take(feedback); + } + expect(controllers).toEqual(["human"]); + expect( + webviewSend.mock.calls.filter( + ([channel]) => channel === "preview:recording-controller", + ), + ).toEqual([ + ["preview:recording-controller", "human", { x: 10, y: 20 }], + ["preview:recording-controller", "human", { x: 30, y: 40 }], + ["preview:recording-controller", "human", undefined], + ]); + yield* TestClock.adjust(750); + expect(controllers).toEqual(["human", "none"]); + }), + ), + ); + + effectIt.effect("keeps human ownership until the last input's idle deadline", () => + withManager((manager) => + Effect.gen(function* () { + let humanInput: ((input: unknown) => void) | undefined; + const contents = makeTestPreviewWebContents(async () => ({ + toJPEG: () => Buffer.from("frame"), + getSize: () => ({ width: 800, height: 600 }), + })); + contents.ipc.on = (channel, callback) => { + if (channel === "preview:human-input") + humanInput = (input) => callback({} as Electron.IpcMainEvent, input); + return contents.ipc; + }; + fromId.mockReturnValue(contents); + yield* manager.createTab("tab_idle"); + yield* manager.registerWebview("tab_idle", 42); + const controllers: string[] = []; + yield* manager.subscribeStateChanges((_tabId, state) => + Effect.sync(() => { + controllers.push(state.controller); + }), + ); + const press = () => humanInput?.({ kind: "key", key: "a", code: "KeyA" }); + press(); + yield* TestClock.adjust(500); + press(); + yield* TestClock.adjust(749); + expect(controllers).toEqual(["human"]); + yield* TestClock.adjust(1); + expect(controllers).toEqual(["human", "none"]); + + press(); + yield* TestClock.adjust(500); + const staleInput = humanInput; + const replacement = makeTestPreviewWebContents( + async () => ({ + toJPEG: () => Buffer.from("frame"), + getSize: () => ({ width: 800, height: 600 }), + }), + 43, + ); + replacement.ipc.on = contents.ipc.on; + contents.isDestroyed = () => true; + fromId.mockImplementation((id) => (id === 43 ? replacement : null)); + yield* manager.registerWebview("tab_idle", 43); + expect(controllers.at(-1)).toBe("none"); + const count = controllers.length; + staleInput?.({ kind: "key", key: "b", code: "KeyB" }); + yield* TestClock.adjust(750); + expect(controllers).toHaveLength(count); + }), + ), + ); + effectIt.effect("gates recording decorations and isolates failed subscribers", () => withManager((manager) => Effect.gen(function* () { diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index 2c199e358fa5b..b30e39366a047 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -889,7 +889,6 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function const update = Effect.fn("PreviewManager.update")(function* ( tabId: string, patch: Partial, - humanPoint?: { readonly x: number; readonly y: number }, ) { const updatedAt = yield* currentIso; const next = yield* SynchronizedRef.modify(tabsRef, (tabs) => { @@ -905,17 +904,6 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function }); // syncTabAudible can commit after this update; emitIfCurrent prevents publishing a stale snapshot. if (Option.isSome(next)) { - if (patch.controller !== undefined && next.value.webContentsId != null) { - const capture = (yield* SynchronizedRef.get(frameCaptureSessionsRef)).get(tabId); - const webContentsId = next.value.webContentsId; - if (capture?.consumers.has("recording")) { - yield* attempt({ operation: "recording.controller", tabId }, () => { - const contents = webContents.fromId(webContentsId); - if (contents && !contents.isDestroyed()) - contents.send(RECORDING_CONTROLLER_CHANNEL, patch.controller, humanPoint); - }).pipe(Effect.ignore); - } - } yield* emitIfCurrent(tabId, next.value); } }); @@ -1444,21 +1432,75 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function }), ); }; + let humanInputWorker = false; + let humanInputDeadline = 0; + let listening = true; + const publishHumanController = Effect.fn("PreviewManager.publishHumanController")(function* ( + controller: "human" | "none", + humanPoint?: { readonly x: number; readonly y: number }, + ) { + const now = yield* currentMillis; + const updatedAt = yield* currentIso; + const result = yield* SynchronizedRef.modify(tabsRef, (tabs) => { + const current = tabs.get(tabId); + if ( + !current || + current.webContentsId !== wc.id || + (controller === "human" + ? !listening || webContents.fromId(wc.id) !== wc + : current.controller !== "human" || humanInputDeadline > now) + ) + return [undefined, tabs] as const; + const changed = current.controller !== controller; + const state = changed ? { ...current, controller, updatedAt } : current; + return [ + { state, changed }, + changed + ? replaceMap(tabs, (copy) => { + copy.set(tabId, state); + }) + : tabs, + ] as const; + }); + if (!result) return; + const capture = (yield* SynchronizedRef.get(frameCaptureSessionsRef)).get(tabId); + if (capture?.consumers.has("recording")) { + yield* attempt({ operation: "recording.controller", tabId }, () => { + if (!wc.isDestroyed()) wc.send(RECORDING_CONTROLLER_CHANNEL, controller, humanPoint); + }).pipe(Effect.ignore); + } + if (result.changed) yield* emitIfCurrent(tabId, result.state); + }); + yield* Scope.addFinalizer( + scope, + Effect.gen(function* () { + listening = false; + humanInputDeadline = 0; + yield* publishHumanController("none"); + }), + ); const handleHumanInput = Effect.fn("PreviewManager.handleHumanInput")(function* ( rawSignal?: unknown, ) { - yield* update( - tabId, - { controller: "human" }, + if (!listening) return; + humanInputDeadline = (yield* currentMillis) + 750; + if (!humanInputWorker) { + humanInputWorker = true; + yield* Effect.gen(function* () { + let remaining: number; + while ((remaining = humanInputDeadline - (yield* currentMillis)) > 0) { + yield* Effect.sleep(remaining); + } + humanInputWorker = false; + yield* publishHumanController("none"); + }).pipe(Effect.forkIn(scope)); + } + yield* publishHumanController( + "human", isPreviewInputSignal(rawSignal) && rawSignal.kind === "pointer" ? { x: rawSignal.x, y: rawSignal.y } : undefined, ); - yield* Effect.sleep(750); - const tabs = yield* SynchronizedRef.get(tabsRef); - if (tabs.get(tabId)?.controller === "human") { - yield* update(tabId, { controller: "none" }); - } }); const recordingInput = (_event: unknown, input: unknown) => { if (!isRecordingInput(input)) return; From c666345bb9a78704679aa93e57d6ff19dcf851b7 Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 01:49:46 +1100 Subject: [PATCH 138/192] fix(web): subagent previews drop image marks and full link URLs (#29637) Co-authored-by: Julius Marminge --- .../src/features/threads/SubagentRow.tsx | 6 ++++-- .../subagent-card-presentation.test.ts | 11 +---------- .../threads/subagent-card-presentation.ts | 12 ------------ .../threads/threadAgentsPresentation.ts | 3 +-- .../src/components/chat/V2LifecycleRow.tsx | 19 +++++-------------- .../src/state/subagentDisplay.test.ts | 13 ++++++++++++- .../src/state/subagentDisplay.ts | 12 ++++++++++++ 7 files changed, 35 insertions(+), 41 deletions(-) diff --git a/apps/mobile/src/features/threads/SubagentRow.tsx b/apps/mobile/src/features/threads/SubagentRow.tsx index 7c71ac2680de1..96f6f659aa5a1 100644 --- a/apps/mobile/src/features/threads/SubagentRow.tsx +++ b/apps/mobile/src/features/threads/SubagentRow.tsx @@ -4,7 +4,10 @@ import { resolveProviderInstanceDisplayName, shouldShowInstanceBadge, } from "@t3tools/client-runtime/state/provider-instance-display"; -import { resolveSubagentMetadata } from "@t3tools/client-runtime/state/subagent-display"; +import { + resolveSubagentMetadata, + subagentCardDetail, +} from "@t3tools/client-runtime/state/subagent-display"; import { formatSubagentEffort } from "@t3tools/client-runtime/state/thread-execution"; import type { EnvironmentId, OrchestrationV2Subagent } from "@t3tools/contracts"; import type { ReactNode } from "react"; @@ -15,7 +18,6 @@ import { SymbolView } from "../../components/ui/AppSymbol"; import { ProviderIcon, ProviderInstanceIcon } from "../../components/ProviderIcon"; import { cn } from "../../lib/cn"; import { useEnvironmentServerConfig, useProject, useThreadShell } from "../../state/entities"; -import { subagentCardDetail } from "./subagent-card-presentation"; import { SUBAGENT_TONE_TEXT_CLASS, SubagentStatusDot } from "./SubagentStatusDot"; import { resolveSubagentRowPresentation } from "./threadAgentsPresentation"; diff --git a/apps/mobile/src/features/threads/subagent-card-presentation.test.ts b/apps/mobile/src/features/threads/subagent-card-presentation.test.ts index 2b32f84fcb9cc..14a8e6ce99dfe 100644 --- a/apps/mobile/src/features/threads/subagent-card-presentation.test.ts +++ b/apps/mobile/src/features/threads/subagent-card-presentation.test.ts @@ -1,17 +1,8 @@ import * as DateTime from "effect/DateTime"; import { describe, expect, it } from "vite-plus/test"; -import { subagentCardDetail, subagentCardElapsed } from "./subagent-card-presentation"; +import { subagentCardElapsed } from "./subagent-card-presentation"; describe("subagent card", () => { - it.each([ - ["![screenshot](screenshots/shot.png) Verified", "screenshot Verified"], - ["[docs](https://en.wikipedia.org/wiki/T3_(protocol))", "docs"], - ["[report](outputs/report%20(final).md)", "report"], - ["[report](outputs/report\\(final.md)", "report"], - ])("strips image and link markup from %s", (detail, expected) => { - expect(subagentCardDetail(detail)).toBe(expected); - }); - const start = DateTime.makeUnsafe("2026-09-21T12:00:00Z"); const end = DateTime.makeUnsafe("2026-09-21T12:01:00Z"); const later = DateTime.makeUnsafe("2026-09-21T12:02:00Z"); diff --git a/apps/mobile/src/features/threads/subagent-card-presentation.ts b/apps/mobile/src/features/threads/subagent-card-presentation.ts index 42e75c5cf214e..239f48cf441cc 100644 --- a/apps/mobile/src/features/threads/subagent-card-presentation.ts +++ b/apps/mobile/src/features/threads/subagent-card-presentation.ts @@ -3,18 +3,6 @@ import type { OrchestrationV2Subagent } from "@t3tools/contracts"; import { formatDuration } from "@t3tools/shared/orchestrationTiming"; import * as DateTime from "effect/DateTime"; -export function subagentCardDetail(detail: string | null): string | null { - if (!detail || /^Child task ended with status\b/i.test(detail)) return null; - return ( - detail - .replace(/!?\[([^\]]+)\]\((?:\\.|[^\\()]|\((?:\\.|[^\\()])*\))*\)/g, "$1") - .replace(/`/g, "") - .replace(/^[ \t]*[-*][ \t]+/gm, "") - .replace(/\s+/g, " ") - .trim() || null - ); -} - export function subagentCardElapsed( agents: ReadonlyArray>, nowMs: number, diff --git a/apps/mobile/src/features/threads/threadAgentsPresentation.ts b/apps/mobile/src/features/threads/threadAgentsPresentation.ts index 7f560497da254..64c9b232ed424 100644 --- a/apps/mobile/src/features/threads/threadAgentsPresentation.ts +++ b/apps/mobile/src/features/threads/threadAgentsPresentation.ts @@ -1,6 +1,7 @@ import { formatSubagentDisplayTitle, resolveSubagentMetadata, + subagentCardDetail, subagentDetailPreview, } from "@t3tools/client-runtime/state/subagent-display"; import { @@ -17,8 +18,6 @@ import type { } from "@t3tools/contracts"; import { resolveSelectableModel } from "@t3tools/shared/model"; -import { subagentCardDetail } from "./subagent-card-presentation"; - const PROMPT_TITLE_LIMIT = 80; export type SubagentRowTone = "working" | "completed" | "failed" | "stopped"; diff --git a/apps/web/src/components/chat/V2LifecycleRow.tsx b/apps/web/src/components/chat/V2LifecycleRow.tsx index 40fffaf608412..1854bc74181d0 100644 --- a/apps/web/src/components/chat/V2LifecycleRow.tsx +++ b/apps/web/src/components/chat/V2LifecycleRow.tsx @@ -20,7 +20,10 @@ import { type HandoffTimelineRun, resolveModelSwitchChange, } from "@t3tools/client-runtime/handoff"; -import { formatSubagentDisplayTitle } from "@t3tools/client-runtime/state/subagent-display"; +import { + formatSubagentDisplayTitle, + subagentCardDetail, +} from "@t3tools/client-runtime/state/subagent-display"; import { ProviderDriverKind, type ModelSelection, @@ -193,17 +196,6 @@ const SETTLED_SUBAGENT_STATUSES = new Set([ "interrupted", ]); -const GENERIC_CHILD_END = /^Child task ended with status\b/i; - -function plainDetail(text: string): string { - return text - .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1") - .replace(/`/g, "") - .replace(/^[ \t]*[-*][ \t]+/gm, "") - .replace(/\s+/g, " ") - .trim(); -} - function isoOrNull(value: DateTime.Utc | null | undefined): string | null { return value ? DateTime.formatIso(value) : null; } @@ -442,8 +434,7 @@ function SubagentTimelineLink(props: { const progress = (agent?.progress ?? props.progress)?.trim(); const settled = SETTLED_SUBAGENT_STATUSES.has(status ?? liveStatus); const rawDetail = settled ? result || progress : progress || result; - const detail = - rawDetail && !GENERIC_CHILD_END.test(rawDetail) ? plainDetail(rawDetail) || null : null; + const detail = subagentCardDetail(rawDetail); const failed = status === "failed"; const timing = { status: liveStatus, diff --git a/packages/client-runtime/src/state/subagentDisplay.test.ts b/packages/client-runtime/src/state/subagentDisplay.test.ts index 9b1536a7aacb1..631723736dbe5 100644 --- a/packages/client-runtime/src/state/subagentDisplay.test.ts +++ b/packages/client-runtime/src/state/subagentDisplay.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vite-plus/test"; -import { subagentDetailPreview } from "./subagentDisplay.js"; +import { subagentCardDetail, subagentDetailPreview } from "./subagentDisplay.js"; describe("subagentDetailPreview", () => { it("prefers progress for live work and results for settled work", () => { @@ -39,3 +39,14 @@ it("normalizes Unicode whitespace and ignores a large hidden suffix", () => { "x".repeat(279) + "…", ); }); + +describe("subagentCardDetail", () => { + it.each([ + ["![screenshot](screenshots/shot.png) Verified", "screenshot Verified"], + ["[docs](https://en.wikipedia.org/wiki/T3_(protocol))", "docs"], + ["[report](outputs/report%20(final).md)", "report"], + ["[report](outputs/report\\(final.md)", "report"], + ])("strips image and link markup from %s", (detail, expected) => { + expect(subagentCardDetail(detail)).toBe(expected); + }); +}); diff --git a/packages/client-runtime/src/state/subagentDisplay.ts b/packages/client-runtime/src/state/subagentDisplay.ts index 1968f48b90a1d..22d0892f2f5bc 100644 --- a/packages/client-runtime/src/state/subagentDisplay.ts +++ b/packages/client-runtime/src/state/subagentDisplay.ts @@ -146,3 +146,15 @@ export function subagentDetailPreview(input: { } return compact || null; } + +export function subagentCardDetail(detail: string | null | undefined): string | null { + if (!detail || /^Child task ended with status\b/i.test(detail)) return null; + return ( + detail + .replace(/!?\[([^\]]+)\]\((?:\\.|[^\\()]|\((?:\\.|[^\\()])*\))*\)/g, "$1") + .replace(/`/g, "") + .replace(/^[ \t]*[-*][ \t]+/gm, "") + .replace(/\s+/g, " ") + .trim() || null + ); +} From c0ee5a1f5f2e74d36be41d80d86b416a47fafdea Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 01:51:07 +1100 Subject: [PATCH 139/192] fix(server): pushing a branch whose remote branch was deleted publishes it again (#29619) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 187 ++++++++++++++++--- apps/server/src/vcs/GitVcsDriverCore.ts | 73 ++++++-- 2 files changed, 224 insertions(+), 36 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 100ce5fe48671..bc30d6cb0ac5f 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -4659,7 +4659,7 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }), ); - it.effect("reports remote status on unborn HEAD without failing", () => + it.effect("reports status on unborn HEAD without failing or dropping its upstream", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* GitVcsDriver.GitVcsDriver; @@ -4673,6 +4673,17 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { assert.equal(status.hasUpstream, false); assert.equal(status.aheadCount, 0); assert.equal(status.behindCount, 0); + + const seed = yield* makeTmpDir(); + const remote = yield* makeTmpDir("git-remote-"); + yield* initRepoWithCommit(seed); + yield* git(remote, ["init", "--bare"]); + yield* git(seed, ["push", remote, `HEAD:refs/heads/${initialBranch}`]); + yield* git(cwd, ["remote", "add", "origin", remote]); + yield* git(cwd, ["fetch", "origin"]); + yield* git(cwd, ["config", `branch.${initialBranch}.remote`, "origin"]); + yield* git(cwd, ["config", `branch.${initialBranch}.merge`, `refs/heads/${initialBranch}`]); + assert.equal((yield* driver.statusDetailsLocal(cwd)).hasUpstream, true); }), ); @@ -8035,6 +8046,92 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }), ); + it.effect("republishes a branch whose upstream was deleted", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const remote = yield* makeTmpDir("git-remote-"); + yield* initRepoWithCommit(cwd); + yield* git(remote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "origin", remote]); + yield* (yield* GitVcsDriver.GitVcsDriver).createRef({ + cwd, + refName: "feature/push", + }); + yield* (yield* GitVcsDriver.GitVcsDriver).switchRef({ + cwd, + refName: "feature/push", + }); + yield* writeTextFile(cwd, "feature.txt", "feature\n"); + yield* git(cwd, ["add", "-A"]); + yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add feature", ""); + + yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null); + + yield* git(cwd, ["push", "origin", "--delete", "feature/push"]); + yield* writeTextFile(cwd, "followup.txt", "followup\n"); + yield* git(cwd, ["add", "-A"]); + yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add followup", ""); + const republished = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null); + assert.deepInclude(republished, { status: "pushed", branch: "feature/push" }); + assert.equal( + yield* git(remote, ["--git-dir=.", "rev-parse", "refs/heads/feature/push"]), + yield* git(cwd, ["rev-parse", "HEAD"]), + ); + + const fork = yield* makeTmpDir("git-fork-"); + yield* git(fork, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "fork", fork]); + yield* git(cwd, ["remote", "add", "feature", fork]); + yield* git(cwd, ["push", "-u", "fork", "feature/push"]); + yield* git(cwd, ["push", "fork", "--delete", "feature/push"]); + yield* writeTextFile(cwd, "fork.txt", "fork\n"); + yield* git(cwd, ["add", "-A"]); + yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add fork change", ""); + assert.deepInclude( + yield* (yield* GitVcsDriver.GitVcsDriver).resolvePushTarget(cwd, { + branch: "feature/push", + hasUpstream: false, + }), + { remoteName: "fork", branchName: "feature/push" }, + ); + const forkRepublished = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch( + cwd, + null, + ); + assert.deepInclude(forkRepublished, { upstreamBranch: "fork/feature/push" }); + assert.equal( + yield* git(fork, ["--git-dir=.", "rev-parse", "refs/heads/feature/push"]), + yield* git(cwd, ["rev-parse", "HEAD"]), + ); + + yield* git(cwd, ["config", "branch.feature/push.remote", "."]); + yield* git(cwd, ["config", "branch.feature/push.merge", "refs/heads/push"]); + yield* writeTextFile(cwd, "local.txt", "local\n"); + yield* git(cwd, ["add", "-A"]); + yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add local change", ""); + const localUpstreamGone = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch( + cwd, + null, + ); + assert.deepInclude(localUpstreamGone, { upstreamBranch: "origin/feature/push" }); + + yield* git(cwd, ["push", "fork", "HEAD:refs/heads/release"]); + yield* git(cwd, ["branch", "-u", "fork/release"]); + yield* git(cwd, ["push", "fork", "--delete", "release"]); + yield* writeTextFile(cwd, "release.txt", "release\n"); + yield* git(cwd, ["add", "-A"]); + yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add release change", ""); + yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null); + assert.equal( + yield* (yield* GitVcsDriver.GitVcsDriver).readConfigValue( + cwd, + "branch.feature/push.t3-merge-base", + ), + "release", + ); + }), + ); + it.effect("rebases a pushed branch's pulls when branch.autoSetupRebase asks for it", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); @@ -8191,14 +8288,15 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { ); it.effect.each([ - { baseBranch: "dev", local: false }, - { baseBranch: "dev", local: true }, - { baseBranch: "feature/base", local: true }, - { baseBranch: "upstream", local: true }, - { baseBranch: "upstream/feature/x", local: true }, + { baseBranch: "dev", local: false, branch: "feature/x", deleted: false }, + { baseBranch: "dev", local: true, branch: "feature/x", deleted: false }, + { baseBranch: "feature/base", local: true, branch: "feature/x", deleted: false }, + { baseBranch: "upstream", local: true, branch: "feature/x", deleted: false }, + { baseBranch: "upstream/feature/x", local: true, branch: "feature/x", deleted: false }, + { baseBranch: "dev", local: false, branch: "origin/dev", deleted: true }, ])( - "publishes a branch tracking $baseBranch (local=$local) under its own name, not the base", - ({ baseBranch, local }) => + "publishes $branch tracking $baseBranch (local=$local, deleted=$deleted) without updating the base", + ({ baseBranch, local, branch, deleted }) => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const remote = yield* makeTmpDir("git-remote-"); @@ -8223,51 +8321,68 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { "checkout", "--track", "-b", - "feature/x", + branch, local ? `refs/heads/${baseBranch}` : `refs/remotes/upstream/${baseBranch}`, ]); assert.equal( - yield* driver.readConfigValue(cwd, "branch.feature/x.remote"), + yield* driver.readConfigValue(cwd, `branch.${branch}.remote`), local ? "." : "upstream", ); yield* writeTextFile(cwd, "feature.txt", "feature\n"); yield* git(cwd, ["add", "-A"]); yield* driver.commit(cwd, "Add feature", ""); + if (deleted) { + yield* git(cwd, ["push", "upstream", "--delete", baseBranch]); + } + const publishBranch = branch === "origin/dev" ? "dev" : branch; + assert.deepInclude( + yield* driver.resolvePushTarget(cwd, { branch, hasUpstream: !deleted }), + { remoteName: "origin", branchName: publishBranch }, + ); const pushed = yield* driver.pushCurrentBranch(cwd, null); assert.deepInclude(pushed, { status: "pushed", - branch: "feature/x", - upstreamBranch: "origin/feature/x", + branch, + upstreamBranch: `origin/${publishBranch}`, setUpstream: true, }); assert.equal( - yield* git(remote, ["--git-dir=.", "log", "-1", "--pretty=%s", "feature/x"]), + yield* git(remote, ["--git-dir=.", "log", "-1", "--pretty=%s", publishBranch]), "Add feature", ); + if (publishBranch !== baseBranch) { + assert.equal( + yield* git(remote, ["--git-dir=.", "rev-parse", `refs/heads/${baseBranch}`]), + devSha, + ); + } assert.equal( - yield* git(remote, ["--git-dir=.", "rev-parse", `refs/heads/${baseBranch}`]), - devSha, - ); - assert.equal( - yield* git(upstream, ["--git-dir=.", "rev-parse", `refs/heads/${baseBranch}`]), - upstreamDevSha, + yield* git(upstream, [ + "--git-dir=.", + "for-each-ref", + "--format=%(objectname)", + `refs/heads/${baseBranch}`, + ]), + deleted ? "" : upstreamDevSha, ); assert.equal( - yield* git(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"]), - "origin/feature/x", + yield* git(cwd, ["rev-parse", "--symbolic-full-name", "@{upstream}"]), + `refs/remotes/origin/${publishBranch}`, ); assert.equal( - yield* driver.readConfigValue(cwd, "branch.feature/x.t3-merge-base"), + yield* driver.readConfigValue(cwd, `branch.${branch}.t3-merge-base`), baseBranch, ); assert.equal( - yield* driver.readConfigValue(cwd, "branch.feature/x.t3-merge-base-ref"), + yield* driver.readConfigValue(cwd, `branch.${branch}.t3-merge-base-ref`), local ? `refs/heads/${baseBranch}` : `refs/remotes/upstream/${baseBranch}`, ); assert.equal(yield* git(cwd, ["rev-parse", `refs/heads/${baseBranch}`]), upstreamDevSha); + if (deleted) return; + const preview = yield* driver.getReviewDiffPreview({ cwd }); const changes = preview.sources.find((source) => source.kind === "branch-range")!; assert.deepStrictEqual(changes.files, [ @@ -8416,6 +8531,32 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { yield* git(remote, ["--git-dir=.", "log", "-1", "--pretty=%s", "effect-atom"]), "Add alias update", ); + + yield* git(cwd, ["push", "my-org/upstream", "--delete", "effect-atom"]); + assert.deepInclude( + yield* driver.resolvePushTarget(cwd, { + branch: "upstream/effect-atom", + hasUpstream: false, + }), + { remoteName: "my-org/upstream", branchName: "effect-atom" }, + ); + assert.deepInclude(yield* driver.pushCurrentBranch(cwd, null), { + upstreamBranch: "my-org/upstream/effect-atom", + setUpstream: true, + }); + assert.equal( + yield* git(remote, ["--git-dir=.", "rev-parse", "refs/heads/effect-atom"]), + yield* git(cwd, ["rev-parse", "HEAD"]), + ); + assert.equal( + yield* git(remote, [ + "--git-dir=.", + "for-each-ref", + "--format=%(refname)", + "refs/heads/upstream/", + ]), + "", + ); }), ); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index a2b985efed2a5..d04102533a65c 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -69,6 +69,10 @@ const branchBaseConfigKey = (branch: string) => `branch.${branch}.t3-merge-base` * CLI's key, which branches cut before then still carry instead. */ const legacyBranchBaseConfigKey = (branch: string) => `branch.${branch}.gh-merge-base`; +// Differently named upstreams are bases, not publish targets; pushing HEAD there would write features to shared branches. +const isAliasOfUpstreamHead = (branch: string, upstreamBranch: string, upstreamRef: string) => + branch === upstreamBranch || + (branch.endsWith(`/${upstreamBranch}`) && upstreamRef.endsWith(`/${branch}`)); const gitProcesses = Semaphore.makeUnsafe(8); const backgroundGitProcesses = Semaphore.makeUnsafe(2); // The fetch before `git worktree add` downloads everything the remote gained since @@ -1857,12 +1861,17 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const resolvePushRemoteName = Effect.fn("resolvePushRemoteName")(function* ( cwd: string, refName: string, + fallbackRemoteName: string | null = null, ) { const configuredRemote = yield* resolveConfiguredPushRemoteName(cwd, refName); if (configuredRemote) { return configuredRemote; } + if (fallbackRemoteName) { + return fallbackRemoteName; + } + return yield* resolvePrimaryRemoteName(cwd).pipe(Effect.orElseSucceed(() => null)); }); @@ -2362,6 +2371,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* let refName: string | null = null; let upstreamRef: string | null = null; + // Git omits branch.ab when the upstream ref is gone, e.g. deleted after its PR merged, + // and also for an unborn HEAD, which can still track a fetched upstream. + let upstreamRefExists = false; + let isUnbornHead = false; let aheadCount = 0; let behindCount = 0; let aheadOfDefaultCount = 0; @@ -2372,6 +2385,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const statusRecords = statusStdout.split("\0"); for (let index = 0; index < statusRecords.length; index++) { const line = statusRecords[index]!; + if (line === "# branch.oid (initial)") { + isUnbornHead = true; + continue; + } if (line.startsWith("# branch.head ")) { const value = line.slice("# branch.head ".length).trim(); refName = value === "(detached)" ? null : value; @@ -2382,11 +2399,13 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* upstreamRef = value.length > 0 ? value : null; continue; } - if (includeDivergence && line.startsWith("# branch.ab ")) { - const value = line.slice("# branch.ab ".length).trim(); - const parsed = parseBranchAb(value); - aheadCount = parsed.ahead; - behindCount = parsed.behind; + if (line.startsWith("# branch.ab ")) { + upstreamRefExists = true; + if (includeDivergence) { + const parsed = parseBranchAb(line.slice("# branch.ab ".length).trim()); + aheadCount = parsed.ahead; + behindCount = parsed.behind; + } continue; } if (line.length > 0 && !line.startsWith("#")) { @@ -2405,6 +2424,8 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* } } + if (!upstreamRefExists && !isUnbornHead) upstreamRef = null; + const isDefaultBranch = refName !== null && (refName === defaultBranch || @@ -2928,10 +2949,40 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* hasUpstream: boolean, ) { if (!hasUpstream) { - const remoteName = yield* resolvePushRemoteName(cwd, branch); + // A deleted upstream keeps its config: republish to it when it was this branch's own, + // otherwise it was the base, so keep that before publishing. + let goneUpstream: { remoteName: string; branchName: string } | null = null; + let goneBaseUpstream: { remoteName: string; branchName: string; upstreamRef: string } | null = + null; + const mergeRef = yield* readConfigValue(cwd, `branch.${branch}.merge`); + const mergeBranch = mergeRef?.startsWith("refs/heads/") + ? mergeRef.slice("refs/heads/".length) + : null; + const configuredRemoteName = yield* readConfigValue(cwd, `branch.${branch}.remote`); + if (mergeBranch && configuredRemoteName && configuredRemoteName !== ".") { + if (isAliasOfUpstreamHead(branch, mergeBranch, `${configuredRemoteName}/${mergeBranch}`)) { + goneUpstream = { remoteName: configuredRemoteName, branchName: mergeBranch }; + } else { + goneBaseUpstream = { + remoteName: configuredRemoteName, + branchName: mergeBranch, + upstreamRef: `${configuredRemoteName}/${mergeBranch}`, + }; + } + } + const remoteName = yield* resolvePushRemoteName(cwd, branch, goneUpstream?.remoteName); if (!remoteName) return null; - const branchName = yield* resolvePublishBranchName(cwd, branch); - return { remoteName, branchName, publishes: true, upstream: null, pushRemote: null }; + // A gone local upstream still gets the full local name, as below. + const branchName = + goneUpstream?.branchName ?? + (configuredRemoteName === "." ? branch : yield* resolvePublishBranchName(cwd, branch)); + return { + remoteName, + branchName, + publishes: true, + upstream: goneBaseUpstream, + pushRemote: null, + }; } if ((yield* readConfigValue(cwd, `branch.${branch}.remote`)) === ".") { @@ -2955,11 +3006,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const upstream = yield* resolveCurrentUpstream(cwd).pipe(Effect.orElseSucceed(() => null)); if (!upstream) return null; - // Differently named upstreams are bases, not publish targets; pushing HEAD there would write features to shared branches. - const isAliasOfUpstreamHead = - branch === upstream.branchName || - (branch.endsWith(`/${upstream.branchName}`) && upstream.upstreamRef.endsWith(`/${branch}`)); - if (isAliasOfUpstreamHead) { + if (isAliasOfUpstreamHead(branch, upstream.branchName, upstream.upstreamRef)) { // A fork workflow pushes to the configured push remote and keeps the upstream for pulls, as plain `git push` does. const configuredPushRemote = yield* resolveConfiguredPushRemoteName(cwd, branch).pipe( Effect.orElseSucceed(() => null), From 4ce60c162b400b374b12be0450777ab316bb0046 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 06:53:26 -0800 Subject: [PATCH 140/192] fix(mobile): @path search no longer offers the previous worktree's files (#36778) --- .../src/state/queries.debounce.test.tsx | 28 +++++++++++++++++-- apps/mobile/src/state/queries.ts | 7 ++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/mobile/src/state/queries.debounce.test.tsx b/apps/mobile/src/state/queries.debounce.test.tsx index e2eccb4d4125c..8e67b617b6ee2 100644 --- a/apps/mobile/src/state/queries.debounce.test.tsx +++ b/apps/mobile/src/state/queries.debounce.test.tsx @@ -8,7 +8,8 @@ import { expect, it, vi } from "vite-plus/test"; const mocks = vi.hoisted(() => ({ sessionAtom: {}, - searchEntries: vi.fn(() => null), + searchEntriesAtom: {}, + searchEntries: vi.fn((): unknown => mocks.searchEntriesAtom), listRefs: vi.fn(), refresh: vi.fn(), })); @@ -35,7 +36,9 @@ vi.mock("./query", () => ({ data: atom === mocks.sessionAtom ? { authenticated: true, scopes: [AuthFilesystemReadScope] } - : null, + : atom === mocks.searchEntriesAtom + ? { entries: [{ path: "src/only-in-original.ts", kind: "file" }] } + : null, error: null, isPending: false, refresh: () => {}, @@ -53,12 +56,16 @@ function setup() { vi.useFakeTimers(); mocks.searchEntries.mockClear(); const root = createRoot(document.createElement("div")); + let latest: ReturnType | null = null; function Probe({ target }: { readonly target: ComposerPathSearchTarget }) { - useComposerPathSearch(target); + latest = useComposerPathSearch(target); return null; } return { + get result() { + return latest; + }, render(target: ComposerPathSearchTarget) { act(() => root.render()); }, @@ -128,6 +135,21 @@ it.each([ }); }); +it.each([ + { field: "environment", changed: { ...target, environmentId: EnvironmentId.make("other") } }, + { field: "project directory", changed: { ...target, cwd: "/other" } }, +])("reports settled results as pending when the $field changes", ({ changed }) => { + using harness = setup(); + harness.render(target); + act(() => vi.advanceTimersByTime(200)); + expect(harness.result?.isPending).toBe(false); + expect(harness.result?.entries).toHaveLength(1); + + harness.render(changed); + expect(harness.result?.isPending).toBe(true); + expect(harness.result?.entries).toEqual([]); +}); + it("keeps compiled branch results stable and resets pagination for a new target or refresh", () => { (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const registry = AtomRegistry.make(); diff --git a/apps/mobile/src/state/queries.ts b/apps/mobile/src/state/queries.ts index 38890d7d57506..a84059d2e3606 100644 --- a/apps/mobile/src/state/queries.ts +++ b/apps/mobile/src/state/queries.ts @@ -440,9 +440,13 @@ export function useComposerPathSearch(target: ComposerPathSearchTarget) { canReadFiles && searchTarget !== null ? projectEnvironment.searchEntries(searchTarget) : null, ); const hasTarget = searchTarget !== null; + const isSameCheckout = + normalizedTarget.environmentId === debouncedTarget.environmentId && + normalizedTarget.cwd === debouncedTarget.cwd; return { - entries: result.data?.entries ?? [], + // Entries from another checkout must not be offered while the new one is debounced. + entries: isSameCheckout ? (result.data?.entries ?? []) : [], error: !hasTarget || fileAccess.isPending ? null @@ -450,6 +454,7 @@ export function useComposerPathSearch(target: ComposerPathSearchTarget) { ? result.error : (fileAccess.error ?? "This connection cannot search host files."), isPending: + !isSameCheckout || normalizedTarget.query !== debouncedTarget.query || (hasTarget && (fileAccess.isPending || result.isPending)), refresh: result.refresh, From e3305931412d82f8cf09474b23c7e5409ee360b7 Mon Sep 17 00:00:00 2001 From: nasser <63818147+newer97@users.noreply.github.com> Date: Sat, 14 Nov 2026 17:53:38 +0300 Subject: [PATCH 141/192] fix(web): bare URLs no longer swallow Arabic commas and question marks (#36779) Co-authored-by: Julius Marminge --- packages/shared/src/markdownPipeline.test.ts | 10 ++++- packages/shared/src/markdownPipeline.ts | 45 ++++++++++---------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/packages/shared/src/markdownPipeline.test.ts b/packages/shared/src/markdownPipeline.test.ts index 2319fa4475987..9671225731e22 100644 --- a/packages/shared/src/markdownPipeline.test.ts +++ b/packages/shared/src/markdownPipeline.test.ts @@ -139,6 +139,14 @@ it.each([ ["[記事](https://example.com/a,b)", "[記事](https://example.com/a,b)"], ["[http://example.com/a,b](http://example.com/a,b)", link("http://example.com/a,b")], ["`https://example.com/a。次`", "`https://example.com/a。次`"], + [ + "منشور على https://example.com/report، ونسخته", + `منشور على ${link("https://example.com/report")}، ونسخته`, + ], + [ + "هل يعمل **http://localhost:3000**؟ نعم؛ www.example.com۔", + `هل يعمل **${link("http://localhost:3000")}**؟ نعم؛ ${link("www.example.com")}۔`, + ], [ "https://x.com/a_ https://x.com/b_", `${link("https://x.com/a_")} ${link("https://x.com/b_")}`, @@ -169,7 +177,7 @@ it.each([ } }); -it("ends bare URLs at CJK punctuation with source positions", () => { +it("ends bare URLs at CJK and Arabic punctuation with source positions", () => { const processor = unified().use(remarkParse).use(CHAT_MARKDOWN_REMARK_PLUGINS); const parse = (markdown: string) => processor.runSync(processor.parse(markdown), markdown); const at = (line: number, column: number, offset: number) => ({ line, column, offset }); diff --git a/packages/shared/src/markdownPipeline.ts b/packages/shared/src/markdownPipeline.ts index 2b0953fbadba5..ad77b6d928aee 100644 --- a/packages/shared/src/markdownPipeline.ts +++ b/packages/shared/src/markdownPipeline.ts @@ -246,7 +246,7 @@ const CHAT_MARKDOWN_SANITIZE_SCHEMA = { export const CHAT_MARKDOWN_REMARK_PLUGINS: PluggableList = [ remarkGfm, - remarkEndAutolinksAtCjkPunctuation, + remarkEndAutolinksAtScriptPunctuation, remarkKeepAutolinkSuffixes, remarkLinkAbsoluteFilePaths, remarkKeepWindowsPaths, @@ -261,7 +261,7 @@ export const CHAT_MARKDOWN_REMARK_PLUGINS: PluggableList = [ export const CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS: PluggableList = [ remarkGfm, - remarkEndAutolinksAtCjkPunctuation, + remarkEndAutolinksAtScriptPunctuation, remarkKeepAutolinkSuffixes, remarkLinkAbsoluteFilePaths, remarkKeepWindowsPaths, @@ -471,20 +471,21 @@ function remarkKeepAutolinkSuffixes(): Transformer { }; } -// Punctuation and symbols from CJK Symbols and Punctuation and from Halfwidth and Fullwidth Forms. -const CJK_PUNCTUATION = /(?=[ -〿＀-￯])[\p{P}\p{S}]/u; +// Punctuation and symbols from CJK Symbols and Punctuation and from Halfwidth and Fullwidth Forms, +// plus the Arabic-script comma, semicolon, question mark, and full stop. +const SCRIPT_PUNCTUATION = /(?=[ -〿＀-￯،؛؟۔])[\p{P}\p{S}]/u; /** - * GFM runs a bare URL on until whitespace, so Chinese and Japanese punctuation joins it, along with - * the rest of the sentence and any `**` that should have closed around it. A URL that is ASCII up to - * such a mark ends there. Unicode earlier in the URL means the mark may belong to it, so it stays. + * GFM runs a bare URL on until whitespace, so Chinese, Japanese, and Arabic punctuation joins it, + * along with the rest of the sentence and any `**` that should have closed around it. A URL that is + * ASCII up to such a mark ends there. Unicode earlier in the URL means the mark may belong to it, so it stays. */ -function cjkPunctuationIndex(link: Link): number { +function scriptPunctuationIndex(link: Link): number { const label = link.children.length === 1 ? link.children[0] : undefined; if (label?.type !== "text" || !/^https?:\/\//i.test(link.url)) return -1; if (link.url !== label.value && link.url !== `http://${label.value}`) return -1; const index = label.value.search(/[^!-~]/u); - return index > 0 && CJK_PUNCTUATION.test(label.value.charAt(index)) ? index : -1; + return index > 0 && SCRIPT_PUNCTUATION.test(label.value.charAt(index)) ? index : -1; } /** GFM's text pass links bare URLs without positions; its tokenizer's links span their URL. */ @@ -502,14 +503,14 @@ function isBareLink(link: Link, source: string): boolean { } /** Source offsets of the marks that tokenized bare URLs run past. */ -function cjkPunctuationOffsets(node: RootContent, source: string): number[] { +function scriptPunctuationOffsets(node: RootContent, source: string): number[] { if (node.type === "link") { - const index = cjkPunctuationIndex(node); + const index = scriptPunctuationIndex(node); const start = node.position?.start.offset; return index > 0 && start !== undefined && isBareLink(node, source) ? [start + index] : []; } return "children" in node && node.type !== "linkReference" - ? (node.children as RootContent[]).flatMap((child) => cjkPunctuationOffsets(child, source)) + ? (node.children as RootContent[]).flatMap((child) => scriptPunctuationOffsets(child, source)) : []; } @@ -537,7 +538,7 @@ function countBelow(sorted: readonly number[], value: number): number { * dropped and positions mapped back to the source. Returns false and leaves the block alone when its * text would parse differently on its own, as a quote's continuation lines do. */ -function reparseAtCjkPunctuation( +function reparseAtScriptPunctuation( processor: Processor, block: Paragraph | Heading | TableCell, source: string, @@ -570,7 +571,7 @@ function reparseAtCjkPunctuation( .join("\t"); const [paragraph, ...rest] = (processor.parse(marked) as Root).children; if (paragraph?.type !== "paragraph" || rest.length > 0) return false; - const more = cjkPunctuationOffsets(paragraph, marked); + const more = scriptPunctuationOffsets(paragraph, marked); if (more.length > 0) { marks.push(...more.map(unmarked)); continue; @@ -613,16 +614,16 @@ function reparseAtCjkPunctuation( } /** Ends the bare URLs a reparse could not, and those GFM's text pass linked after emphasis. */ -function splitAtCjkPunctuation(node: RootContent, source: string) { +function splitAtScriptPunctuation(node: RootContent, source: string) { if (!("children" in node) || node.type === "link" || node.type === "linkReference") return; const children = node.children as RootContent[]; for (let index = 0; index < children.length; index++) { const child = children[index]!; if (child.type !== "link") { - splitAtCjkPunctuation(child, source); + splitAtScriptPunctuation(child, source); continue; } - const at = isBareLink(child, source) ? cjkPunctuationIndex(child) : -1; + const at = isBareLink(child, source) ? scriptPunctuationIndex(child) : -1; const label = child.children[0]; if (at < 0 || label?.type !== "text") continue; // GFM drops this punctuation from the end of a URL; remarkKeepAutolinkSuffixes restores `_` and `~`. @@ -646,15 +647,15 @@ function splitAtCjkPunctuation(node: RootContent, source: string) { } } -function remarkEndAutolinksAtCjkPunctuation(this: Processor): Transformer { +function remarkEndAutolinksAtScriptPunctuation(this: Processor): Transformer { return (tree, file) => { const source = String(file); - if (!CJK_PUNCTUATION.test(source)) return; + if (!SCRIPT_PUNCTUATION.test(source)) return; const visit = (node: RootContent) => { if (node.type === "paragraph" || node.type === "heading" || node.type === "tableCell") { - const offsets = cjkPunctuationOffsets(node, source); - if (offsets.length > 0) reparseAtCjkPunctuation(this, node, source, offsets); - splitAtCjkPunctuation(node, source); + const offsets = scriptPunctuationOffsets(node, source); + if (offsets.length > 0) reparseAtScriptPunctuation(this, node, source, offsets); + splitAtScriptPunctuation(node, source); } else if ("children" in node) { (node.children as RootContent[]).forEach(visit); } From e5d4322c10df7f68b590f653f2b5c2a162a007b9 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 15:53:50 +0100 Subject: [PATCH 142/192] fix(server): expand unchanged lines beside Git ident keywords (#36774) Co-authored-by: Julius Marminge --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 71 +++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 40 ++++++++++- .../ident-diff-expands-unchanged-lines.e2e.ts | 66 +++++++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 scripts/scenarios/ident-diff-expands-unchanged-lines.e2e.ts diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index bc30d6cb0ac5f..76b1fb4f898b5 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -3712,6 +3712,77 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }), ); + it.effect("loads working-tree contents as Git diffs them under the ident attribute", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + const fileSystem = yield* FileSystem.FileSystem; + const filePath = (yield* Path.Path).join(cwd, "README.md"); + const committed = "$Id$\nfirst\nsecond\n"; + yield* writeTextFile(cwd, ".gitattributes", "README.md ident\n"); + yield* writeTextFile(cwd, "README.md", committed); + yield* writeTextFile(cwd, "PLAIN.md", "plain\n"); + yield* git(cwd, ["add", "."]); + yield* git(cwd, ["commit", "-m", "add ident keyword"]); + // Checkout smudges the keyword to `$Id: $`. + yield* fileSystem.remove(filePath); + yield* git(cwd, ["checkout-index", "--force", "README.md"]); + const smudged = yield* fileSystem.readFileString(filePath); + assert.match(smudged, /^\$Id: [0-9a-f]{40} \$\n/); + yield* writeTextFile(cwd, "README.md", smudged.replace("first", "edited")); + yield* writeTextFile(cwd, "PLAIN.md", "$Id: literal $\n"); + const disk = yield* fileSystem.readFile(filePath); + + const contents = yield* driver.getReviewDiffFileContents( + makeReviewDiffFileContentsInput(cwd), + ); + const plain = yield* driver.getReviewDiffFileContents( + makeReviewDiffFileContentsInput(cwd, { oldPath: "PLAIN.md", newPath: "PLAIN.md" }), + ); + + assert.deepStrictEqual(contents, { + oldContents: committed, + newContents: "$Id$\nedited\nsecond\n", + }); + assert.strictEqual(plain.newContents, "$Id: literal $\n"); + yield* writeTextFile(cwd, "README.md", "$Id: x $Id: y $\n"); + const sharedDollar = yield* driver.getReviewDiffFileContents( + makeReviewDiffFileContentsInput(cwd), + ); + assert.strictEqual(sharedDollar.newContents, "$Id$Id: y $\n"); + yield* git(cwd, ["config", "filter.unrelated.clean", "touch filter-ran; cat"]); + yield* writeTextFile(cwd, ".gitattributes", "README.md ident text eol=lf\n"); + yield* writeTextFile( + cwd, + "README.md", + smudged.replace("first", "edited").replaceAll("\n", "\r\n"), + ); + const crlf = yield* driver.getReviewDiffFileContents(makeReviewDiffFileContentsInput(cwd)); + assert.strictEqual(crlf.newContents, "$Id$\r\nedited\r\nsecond\r\n"); + yield* fileSystem.writeFile(filePath, disk); + yield* writeTextFile(cwd, ".gitattributes", "README.md ident=set\n"); + const literal = yield* driver.getReviewDiffFileContents( + makeReviewDiffFileContentsInput(cwd), + ); + assert.strictEqual(literal.newContents, smudged.replace("first", "edited")); + yield* writeTextFile(cwd, ".gitattributes", "README.md ident filter=probe\n"); + yield* git(cwd, ["config", "filter.probe.clean", "touch filter-ran; cat"]); + const filtered = yield* driver.getReviewDiffFileContents( + makeReviewDiffFileContentsInput(cwd), + ); + assert.strictEqual(filtered.newContents, literal.newContents); + yield* writeTextFile(cwd, ".gitattributes", "README.md ident filter=unspecified\n"); + yield* git(cwd, ["config", "filter.unspecified.clean", "touch filter-ran; cat"]); + const reservedFilter = yield* driver.getReviewDiffFileContents( + makeReviewDiffFileContentsInput(cwd), + ); + assert.strictEqual(reservedFilter.newContents, literal.newContents); + assert.isFalse(yield* fileSystem.exists((yield* Path.Path).join(cwd, "filter-ran"))); + assert.deepStrictEqual(yield* fileSystem.readFile(filePath), disk); + }), + ); + it.effect("Changes combines commits, uncommitted edits, and untracked files", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index d04102533a65c..280664cf8a136 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -97,6 +97,8 @@ const REVIEW_UNTRACKED_MAX_BYTES = 256 * 1024 * 1024; const UNTRACKED_TOTALS_RETRY_MIN_MS = 60_000; const UNTRACKED_TOTALS_RETRY_MAX_MS = 30 * 60_000; const REVIEW_DIFF_FILE_MAX_OUTPUT_BYTES = 1024 * 1024; +// Git's ident clean step (convert.c ident_to_git): `$Id:` up to the next `$` on the same line. +const GIT_IDENT_KEYWORD = /\$Id:[^$\n]*\$/g; const REVIEW_BRANCH_COMMIT_LIMIT = 100; const REVIEW_BRANCH_SUBJECT_LIMIT = 300; const REVIEW_BRANCH_COMMIT_MAX_OUTPUT_BYTES = 128 * 1024; @@ -4216,7 +4218,43 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* if (bytes.includes(0)) { return yield* fileError("fs.readFile", `Cannot expand binary file '${input.newPath}'.`); } - return new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes); + const contents = new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes); + if (!contents.includes("$Id:")) return contents; + const collapsed = contents.replace(GIT_IDENT_KEYWORD, () => "$Id$"); + if (collapsed === contents) return contents; + // check-attr reports both boolean ident and literal ident=set as "set". + const attribute = yield* runGitStdout( + "GitVcsDriver.getReviewDiffFileContents.workingTree.ident", + repositoryRoot, + ["check-attr", "-z", "ident", "filter", "--", input.newPath], + ); + const attributes = attribute.split("\0"); + if (attributes[2] !== "set" || attributes[5] !== "unspecified") return contents; + + // Literal filter=unspecified is indistinguishable from an absent filter in check-attr. + const filters = yield* executeGit( + "GitVcsDriver.getReviewDiffFileContents.workingTree.filters", + repositoryRoot, + ["config", "--name-only", "--get-regexp", "^filter\\.unspecified\\."], + { allowNonZeroExit: true }, + ); + if (filters.exitCode !== 1 || filters.stdout.length > 0) return contents; + // Probe a nonoverlapping keyword so ident conversion runs once on the actual file. + const [canonicalHash, collapsedHash] = yield* Effect.all([ + runGitStdoutWithOptions( + "GitVcsDriver.getReviewDiffFileContents.workingTree.canonicalHash", + repositoryRoot, + ["hash-object", "--stdin", `--path=${input.newPath}`], + { stdin: "$Id: probe $" }, + ), + runGitStdoutWithOptions( + "GitVcsDriver.getReviewDiffFileContents.workingTree.collapsedHash", + repositoryRoot, + ["hash-object", "--stdin", `--path=${input.newPath}`], + { stdin: "$Id$" }, + ), + ]); + return canonicalHash === collapsedHash ? collapsed : contents; }); const getReviewDiffFileContents = Effect.fn("getReviewDiffFileContents")(function* ( diff --git a/scripts/scenarios/ident-diff-expands-unchanged-lines.e2e.ts b/scripts/scenarios/ident-diff-expands-unchanged-lines.e2e.ts new file mode 100644 index 0000000000000..8b1ce16bd3cb5 --- /dev/null +++ b/scripts/scenarios/ident-diff-expands-unchanged-lines.e2e.ts @@ -0,0 +1,66 @@ +/// +// The DOM lib types the functions this scenario runs in the page. +import { expect } from "e2e"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; + +import { run } from "./shell.ts"; +import { runPromise, test, WEB } from "./t3.ts"; + +const LINES = Array.from({ length: 40 }, (_, index) => `line ${index + 1}`); + +test( + "The diff expands unchanged lines beside a Git ident keyword", + WEB, + async ({ t3, app, browser, screen }) => { + const project = await t3.project("demo", { + ".gitattributes": "notes.txt ident\n", + "notes.txt": `$Id$\n${LINES.join("\n")}\n`, + }); + const notes = `${project}/notes.txt`; + // Checkout smudges `$Id$` to `$Id: $`; then edit a line inside its hunk. + await runPromise( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + yield* fs.remove(notes); + yield* run("git", ["checkout-index", "--force", "notes.txt"], { cwd: project }); + const smudged = yield* fs.readFileString(notes); + yield* fs.writeFileString(notes, smudged.replace("line 2\n", "edited 2\n")); + }), + ); + const thread = await t3.thread({ project, title: "Ident", messages: ["first"] }); + + // The diff renders in shadow roots that locators do not reach. + const diffLines = () => + browser.evaluate(() => + Array.from(document.querySelectorAll("*")).flatMap((host) => + Array.from(host.shadowRoot?.querySelectorAll("[data-line]") ?? [], (line) => + (line.textContent ?? "").trim(), + ), + ), + ); + const expandBelowHunk = () => + browser.evaluate(() => { + for (const host of document.querySelectorAll("*")) { + const buttons = host.shadowRoot?.querySelectorAll("[data-expand-button]"); + const last = buttons?.[buttons.length - 1]; + if (last) { + last.click(); + return true; + } + } + return false; + }); + + await t3.open(thread); + await expect(screen.getByText("first fixture turn complete", { exact: false })).toBeVisible(); + await screen.getByRole("button", "Toggle right panel").tap(); + await screen.getByText("Diff").tap(); + await screen.getByRole("button", "Expand notes.txt").tap(); + await expect.poll(diffLines).toContain("edited 2"); + await expect.poll(expandBelowHunk).toBe(true); + await app.screenshot("after-expand"); + await expect.poll(diffLines).toContain("line 30"); + await app.screenshot("expanded"); + }, +); From c2b06f9df8491cde7f707e62506aaff1183b19a3 Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 01:54:01 +1100 Subject: [PATCH 143/192] perf(server): Cursor turns no longer write every streamed token to provider logs (#29641) Co-authored-by: Julius Marminge --- .../src/provider/EventNdjsonLogger.test.ts | 59 +++++++++++++++++++ apps/server/src/provider/EventNdjsonLogger.ts | 19 ++++++ 2 files changed, 78 insertions(+) diff --git a/apps/server/src/provider/EventNdjsonLogger.test.ts b/apps/server/src/provider/EventNdjsonLogger.test.ts index eed20381b46cb..0bb55b9bcb39b 100644 --- a/apps/server/src/provider/EventNdjsonLogger.test.ts +++ b/apps/server/src/provider/EventNdjsonLogger.test.ts @@ -255,6 +255,65 @@ it.layer(NodeServices.layer)("EventNdjsonLogger", (it) => { }), ); + it.effect("drops Cursor streaming deltas while keeping its lifecycle updates", () => + Effect.gen(function* () { + const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-")); + const basePath = NodePath.join(tempDir, "events.log"); + const threadId = ThreadId.make("cursor-deltas"); + const cursorUpdate = (update: object) => ({ + provider: "cursor", + kind: "protocol", + event: { + direction: "incoming", + stage: "decoded", + payload: { type: "interaction.update", runId: "run-1", update }, + }, + }); + const toolCall = { type: "shell", args: { command: "ls" } }; + const transient = [ + { type: "text-delta", text: "Hel" }, + { type: "thinking-delta", text: "Hmm" }, + { type: "token-delta", tokens: 3 }, + { type: "shell-output-delta", event: { stdout: "a.ts\n" } }, + { type: "partial-tool-call", callId: "call-1", modelCallId: "m-1", toolCall }, + { + type: "tool-call-delta", + callId: "call-1", + modelCallId: "m-1", + taskUpdate: { type: "text-delta", text: "Sub" }, + }, + ].map(cursorUpdate); + const retained = [ + { type: "tool-call-started", callId: "call-1", modelCallId: "m-1", toolCall }, + { + type: "tool-call-delta", + callId: "call-1", + modelCallId: "m-1", + taskUpdate: { type: "tool-call-completed", callId: "call-2", toolCall }, + }, + { type: "tool-call-completed", callId: "call-1", modelCallId: "m-1", toolCall }, + { type: "turn-ended" }, + ].map(cursorUpdate); + + try { + const store = yield* makeEventNdjsonLogStore(basePath, { batchWindowMs: 0 }); + const native = store.logger("native"); + for (const event of [...transient, ...retained]) { + yield* native.write(event, threadId); + } + yield* store.close(); + + const records = NodeFS.readFileSync(ownedLogPath(basePath, "cursor-deltas"), "utf8") + .trim() + .split("\n") + .map((line) => decodeUnknownJson(parseLogLine(line).payload)); + assert.deepEqual(records, retained); + } finally { + NodeFS.rmSync(tempDir, { recursive: true, force: true }); + } + }), + ); + it.effect("bounds oversized records while retaining failure details", () => Effect.gen(function* () { const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-")); diff --git a/apps/server/src/provider/EventNdjsonLogger.ts b/apps/server/src/provider/EventNdjsonLogger.ts index 6a1d3844b2373..5a7b3578578f1 100644 --- a/apps/server/src/provider/EventNdjsonLogger.ts +++ b/apps/server/src/provider/EventNdjsonLogger.ts @@ -63,6 +63,13 @@ const transientNativeMethods = new Set([ "turn/diff/updated", ]); const transientAcpUpdates = new Set(["agent_message_chunk", "agent_thought_chunk"]); +const transientCursorUpdates = new Set([ + "text-delta", + "thinking-delta", + "token-delta", + "shell-output-delta", + "partial-tool-call", +]); export type EventNdjsonStream = "native" | "canonical" | "orchestration"; @@ -219,6 +226,18 @@ export function shouldPersistProviderEvent(stream: EventNdjsonStream, event: unk } } + if (nativeType === "interaction.update") { + let update = Reflect.get(nativeEvent, "update"); + if (typeof update !== "object" || update === null) return true; + // Subagent updates arrive wrapped; keep the wrapper when it carries a lifecycle record. + if (Reflect.get(update, "type") === "tool-call-delta") { + update = Reflect.get(update, "taskUpdate"); + if (typeof update !== "object" || update === null) return true; + } + const updateType = Reflect.get(update, "type"); + return typeof updateType !== "string" || !transientCursorUpdates.has(updateType); + } + const payload = Reflect.get(nativeEvent, "payload"); if (typeof payload !== "object" || payload === null) return true; From 47474723cbb05295fec5038ca9456186ba6e70fd Mon Sep 17 00:00:00 2001 From: Bilal Irfan Date: Sat, 14 Nov 2026 19:56:40 +0500 Subject: [PATCH 144/192] fix(web): clear failed badges when threads are opened (#28616) Co-authored-by: Julius Marminge --- .../features/threads/thread-list-v2-items.tsx | 6 ++-- .../src/features/threads/threadListV2.test.ts | 17 +++++++++++ .../src/features/threads/threadListV2.ts | 18 +++++++++-- apps/web/src/components/Sidebar.logic.test.ts | 30 +++++++++++++++++++ apps/web/src/components/Sidebar.logic.ts | 27 +++++++++-------- .../components/sidebar/SidebarThreadRow.tsx | 4 +-- .../client-runtime/src/state/threadSettled.ts | 19 ++++++++++++ .../src/state/threadSnoozed.test.ts | 28 ++++++++++++++++- 8 files changed, 129 insertions(+), 20 deletions(-) diff --git a/apps/mobile/src/features/threads/thread-list-v2-items.tsx b/apps/mobile/src/features/threads/thread-list-v2-items.tsx index b6bf0c98e349c..69d0c84ecaa8d 100644 --- a/apps/mobile/src/features/threads/thread-list-v2-items.tsx +++ b/apps/mobile/src/features/threads/thread-list-v2-items.tsx @@ -79,6 +79,7 @@ import { resolveThreadListV2SnoozeMenuSelection, resolveThreadListV2SnoozePresets, threadHasUnseenCompletion, + resolveThreadListV2RowStatus, resolveThreadListV2Status, resolveThreadListV2ProviderDrivers, resolveThreadListV2SwipeActions, @@ -772,7 +773,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { const selected = props.selected === true; const rowAppearance = getThreadListV2RowAppearance(theme, sidebarPane, selected); - const status = resolveThreadListV2Status(thread); + const rawStatus = resolveThreadListV2Status(thread); + const status = resolveThreadListV2RowStatus(thread); const isUnread = status === "ready" && threadHasUnseenCompletion(thread); const workingLabel = STATUS_LABEL_BY_STATUS[status]; const showsWoke = status === "ready" && props.wokeAt !== null; @@ -1298,7 +1300,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { ) : null} - {(status === "failed" || status === "limited") && thread.runtime?.lastError ? ( + {(rawStatus === "failed" || rawStatus === "limited") && thread.runtime?.lastError ? ( { ); }); + it("settles a failure the user opened to ready in the row, keeping the raw status", () => { + const failed = presentThreadShell( + environmentId, + makeRawThreadShell({ + status: "failed", + latestRunId: RunId.make("run-failed"), + latestRunCompletedAt: DateTime.makeUnsafe("2026-06-01T23:59:00.000Z"), + lastVisitedAt: DateTime.makeUnsafe(NOW), + }), + ); + + expect(resolveThreadListV2Status(failed)).toBe("failed"); + expect(resolveThreadListV2RowStatus(failed)).toBe("ready"); + expect(resolveThreadListV2RowStatus({ ...failed, lastVisitedAt: null })).toBe("failed"); + }); + it("shows a waiting guest message as Request, after the agent's own requests", () => { const thread = presentThreadShell( environmentId, diff --git a/apps/mobile/src/features/threads/threadListV2.ts b/apps/mobile/src/features/threads/threadListV2.ts index 7854f9d7fdb85..f4dd34cefd5c0 100644 --- a/apps/mobile/src/features/threads/threadListV2.ts +++ b/apps/mobile/src/features/threads/threadListV2.ts @@ -8,7 +8,10 @@ import { snoozeWakeLabel, threadWokeAt, } from "@t3tools/client-runtime/state/thread-settled"; -import type { SnoozePreset } from "@t3tools/client-runtime/state/thread-settled"; +import { + hasUnseenRunFailure, + type SnoozePreset, +} from "@t3tools/client-runtime/state/thread-settled"; import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; import { resolveThreadProviderStack } from "@t3tools/client-runtime/state/models"; import { @@ -296,6 +299,17 @@ export function resolveThreadListV2Status( return "ready"; } +/** Row status: a failure the user has already opened settles to ready, like Done. */ +export function resolveThreadListV2RowStatus( + thread: Parameters[0] & + Pick, +): ThreadListV2Status { + const status = resolveThreadListV2Status(thread); + // A runtime failure without a failed run has nothing to date it, so it stays. + if (status !== "failed" || thread.latestRun?.status !== "failed") return status; + return hasUnseenRunFailure(thread) ? status : "ready"; +} + function parseTimestampMs(isoDate: string): number { const parsed = Date.parse(isoDate); return Number.isNaN(parsed) ? 0 : parsed; @@ -506,7 +520,7 @@ function resolveThreadListV2ItemTimeLabel( if (showSnoozeWakeLabel) return ""; if ( variant === "card" && - (resolveThreadListV2Status(thread) !== "ready" || + (resolveThreadListV2RowStatus(thread) !== "ready" || item.wokeAt !== null || threadHasUnseenCompletion(thread)) ) diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts index d1d045ed41a35..b89aa1621a0fa 100644 --- a/apps/web/src/components/Sidebar.logic.test.ts +++ b/apps/web/src/components/Sidebar.logic.test.ts @@ -14,6 +14,7 @@ import { orderItemsByPreferredIds, pinOrderKeyBetween, resolveSidebarSweepKeys, + resolveSidebarRowStatus, resolveSidebarThreadStatus, resolveThreadStatusPill, resolveWindowTitleThreadStatus, @@ -331,6 +332,35 @@ describe("resolveSidebarThreadStatus", () => { }); }); +describe("resolveSidebarRowStatus", () => { + const failedRuntime = { + status: "failed" as const, + activeRunId: null, + providerInstanceId: ProviderInstanceId.make("codex"), + providerName: "Codex", + lastError: "Provider turn failed.", + updatedAt: "2026-03-09T10:05:00.000Z", + }; + const failed = { + hasActionableProposedPlan: false, + hasPendingApprovals: false, + hasPendingUserInput: false, + interactionMode: "default" as const, + runtime: failedRuntime, + latestRun: { ...makeLatestRun(), status: "failed" as const }, + }; + + it("settles a failure the user opened to ready, without showing Done", () => { + expect(resolveSidebarRowStatus({ ...failed, lastVisitedAt: undefined })).toBe("failed"); + + const opened = { ...failed, lastVisitedAt: "2026-03-09T10:06:00.000Z" }; + expect(resolveSidebarRowStatus(opened)).toBe("ready"); + expect(hasUnseenCompletion(opened)).toBe(false); + // A runtime failure without a failed run has no date, so a visit cannot settle it. + expect(resolveSidebarRowStatus({ ...opened, latestRun: null })).toBe("failed"); + }); +}); + describe("searchSidebarThreads", () => { const searchThread = (id: string, title: string, latestUserMessageAt: string) => ({ environmentId: localEnvironmentId, diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts index 7f60c1d35215a..7c96c89d30289 100644 --- a/apps/web/src/components/Sidebar.logic.ts +++ b/apps/web/src/components/Sidebar.logic.ts @@ -28,6 +28,7 @@ import type { SidebarProjectSortOrder, SidebarThreadSortOrder } from "@t3tools/c import type { AsyncResult } from "effect/reactivity"; import { effectiveSnoozed, + hasUnseenRunFailure, type ThreadSnoozeShell, } from "@t3tools/client-runtime/state/thread-settled"; import { @@ -807,16 +808,6 @@ export function hasUnseenCompletion(thread: ThreadStatusInput): boolean { return completedAt > lastVisitedAt; } -/** Unopened threads' failures still count as unseen, unlike completions. */ -function hasUnseenFailure(thread: ThreadStatusInput): boolean { - if (thread.latestRun?.status !== "failed") return false; - if (!thread.lastVisitedAt) return true; - const completedAt = Date.parse(thread.latestRun.completedAt ?? ""); - const lastVisitedAt = Date.parse(thread.lastVisitedAt); - if (Number.isNaN(completedAt) || Number.isNaN(lastVisitedAt)) return true; - return completedAt > lastVisitedAt; -} - // Suppress trailing double-click navigation; keyboard and synthetic clicks use detail 0. export function isTrailingDoubleClick(detail: number): boolean { return detail > 1; @@ -991,6 +982,16 @@ export function resolveSidebarThreadStatus(thread: SidebarThreadStatusInput): Si return "ready"; } +/** Sidebar row status: a failure the user has already opened settles to ready, like Done. */ +export function resolveSidebarRowStatus( + thread: ThreadStatusInput & SidebarThreadStatusInput, +): SidebarThreadStatus { + const status = resolveSidebarThreadStatus(thread); + // A runtime failure without a failed run has nothing to date it, so it stays. + if (status !== "failed" || thread.latestRun?.status !== "failed") return status; + return hasUnseenRunFailure(thread) ? status : "ready"; +} + /** What the window title says the open thread is doing, in the words its announcer uses. */ export function resolveWindowTitleThreadStatus(thread: SidebarThreadStatusInput): string | null { // A guest waiting doesn't hide that the agent is still working. @@ -1017,7 +1018,7 @@ export function resolveWindowTitleThreadStatus(thread: SidebarThreadStatusInput) export function resolveSidebarFolderDot( threads: ReadonlyArray, ): "attention" | "failed" | "unread" | "working" | null { - const statuses = new Set(threads.map(resolveSidebarThreadStatus)); + const statuses = new Set(threads.map(resolveSidebarRowStatus)); if ( statuses.has("input") || statuses.has("approval") || @@ -1029,7 +1030,7 @@ export function resolveSidebarFolderDot( if (statuses.has("failed")) return "failed"; // Rows show Done only for ready threads, so the dot follows the same rule. const hasUnread = threads.some( - (thread) => resolveSidebarThreadStatus(thread) === "ready" && hasUnseenCompletion(thread), + (thread) => resolveSidebarRowStatus(thread) === "ready" && hasUnseenCompletion(thread), ); if (hasUnread) return "unread"; if (statuses.has("working") || statuses.has("preparing")) return "working"; @@ -1277,7 +1278,7 @@ export function resolveThreadStatusPill(input: { }; } - if (hasUnseenFailure(thread)) { + if (hasUnseenRunFailure(thread)) { return { label: "Failed", colorClass: "text-red-600 dark:text-red-300/90", diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx index db896481cf857..5b32e2d949f13 100644 --- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx @@ -86,7 +86,7 @@ import { hasUnseenCompletion, resolveSidebarRowAccessibility, type SidebarDropVerb, - resolveSidebarThreadStatus, + resolveSidebarRowStatus, resolveThreadLastVisitedAt, shouldRecedeSidebarThread, resolveWorkingStartedAt, @@ -731,7 +731,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP : null; const isUnread = hasUnseenCompletion({ ...thread, lastVisitedAt }); - const status = resolveSidebarThreadStatus(thread); + const status = resolveSidebarRowStatus({ ...thread, lastVisitedAt }); const lastVisitedDate = lastVisitedAt === undefined ? null : parseTimestampDate(lastVisitedAt); const wokeAtDate = props.wokeAt === null ? null : parseTimestampDate(props.wokeAt); const isWoke = diff --git a/packages/client-runtime/src/state/threadSettled.ts b/packages/client-runtime/src/state/threadSettled.ts index c247f9d80489f..cc18de109de18 100644 --- a/packages/client-runtime/src/state/threadSettled.ts +++ b/packages/client-runtime/src/state/threadSettled.ts @@ -411,3 +411,22 @@ export function isThreadWokeUnseen(input: { .filter((ms) => !Number.isNaN(ms)); return seenAt.every((ms) => ms < wokeAtMs); } + +/** + * Whether the latest run failed after the user last saw the thread. Only the run dates the + * failure, since the shell's updatedAt also moves on renames. A failed run without a + * completion time, from older data, is dated by its start. + */ +export function hasUnseenRunFailure( + thread: Pick & { + readonly lastVisitedAt?: string | null | undefined; + }, +): boolean { + const run = thread.latestRun; + if (run?.status !== "failed") return false; + if (!thread.lastVisitedAt) return true; + const failedAtMs = Date.parse(run.completedAt ?? run.startedAt ?? run.requestedAt ?? ""); + const lastVisitedAtMs = Date.parse(thread.lastVisitedAt); + if (Number.isNaN(failedAtMs) || Number.isNaN(lastVisitedAtMs)) return true; + return failedAtMs > lastVisitedAtMs; +} diff --git a/packages/client-runtime/src/state/threadSnoozed.test.ts b/packages/client-runtime/src/state/threadSnoozed.test.ts index 3abc4dbb39848..e0b4c630b1c87 100644 --- a/packages/client-runtime/src/state/threadSnoozed.test.ts +++ b/packages/client-runtime/src/state/threadSnoozed.test.ts @@ -1,6 +1,6 @@ // @effect-diagnostics globalDate:off -- Tests exercise local calendar snooze boundaries. import * as DateTime from "effect/DateTime"; -import { RuntimeRequestId } from "@t3tools/contracts"; +import { RunId, RuntimeRequestId } from "@t3tools/contracts"; import { ThreadId } from "@t3tools/contracts"; import { TurnId } from "@t3tools/contracts"; import type { SnoozePresetSetting } from "@t3tools/contracts/settings"; @@ -10,6 +10,7 @@ import { canSnooze, effectiveSnoozed, hasQueuedTurnStart, + hasUnseenRunFailure, pickSnoozePreset, resolveSnoozePresets, threadRaisedHandWhileSnoozed, @@ -214,6 +215,31 @@ describe("effectiveSnoozed", () => { }); }); +describe("hasUnseenRunFailure", () => { + it("dates a failure by its run, falling back to the run's start when it never completed", () => { + const run = { + runId: RunId.make("run-failed"), + status: "failed" as const, + requestedAt: "2026-04-10T09:00:00.000Z", + startedAt: "2026-04-10T09:01:00.000Z", + completedAt: "2026-04-10T09:05:00.000Z", + assistantMessageId: null, + }; + const visitedAt = "2026-04-10T09:06:00.000Z"; + + expect(hasUnseenRunFailure({ latestRun: run, lastVisitedAt: null })).toBe(true); + expect(hasUnseenRunFailure({ latestRun: run, lastVisitedAt: visitedAt })).toBe(false); + const failedAgain = { ...run, completedAt: "2026-04-10T09:10:00.000Z" }; + expect(hasUnseenRunFailure({ latestRun: failedAgain, lastVisitedAt: visitedAt })).toBe(true); + + const undated = { ...run, completedAt: null }; + expect(hasUnseenRunFailure({ latestRun: undated, lastVisitedAt: visitedAt })).toBe(false); + expect( + hasUnseenRunFailure({ latestRun: undated, lastVisitedAt: "2026-04-10T09:00:30.000Z" }), + ).toBe(true); + }); +}); + describe("threadRaisedHandWhileSnoozed", () => { it("is true for approvals, input, and failures", () => { expect( From 43a71d9b1acbd8b18257591344296f8e7d1c1d68 Mon Sep 17 00:00:00 2001 From: Salloxy <197323176+Salloxy@users.noreply.github.com> Date: Sat, 14 Nov 2026 10:00:22 -0500 Subject: [PATCH 145/192] fix(web): a terminal reopened while its open is pending no longer closes itself (#29654) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../ThreadTerminalDrawer.browser.test.tsx | 89 ++++++++++++++++--- .../src/components/ThreadTerminalDrawer.tsx | 16 ++-- .../components/chat/useChatTerminalActions.ts | 8 +- .../terminal/closingTerminals.test.ts | 34 +++++++ .../components/terminal/closingTerminals.ts | 29 ++++++ .../terminal/useThreadTerminalActions.ts | 6 +- .../web/src/lib/projectScriptTerminal.test.ts | 17 +++- apps/web/src/lib/projectScriptTerminal.ts | 6 +- 8 files changed, 173 insertions(+), 32 deletions(-) create mode 100644 apps/web/src/components/terminal/closingTerminals.test.ts create mode 100644 apps/web/src/components/terminal/closingTerminals.ts diff --git a/apps/web/src/components/ThreadTerminalDrawer.browser.test.tsx b/apps/web/src/components/ThreadTerminalDrawer.browser.test.tsx index 850b9cb8174f5..12a3ad5acc918 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.browser.test.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.browser.test.tsx @@ -18,8 +18,17 @@ import { act, fakeTimers, render } from "../../test/render"; import ThreadTerminalDrawer from "./ThreadTerminalDrawer"; const fixture = vi.hoisted(() => ({ - statusByThreadId: new Map(), + statusByThreadId: new Map(), pendingSurfaces: [] as Array<() => void>, + typeIntoSurface: null as ((data: string) => void) | null, + pressKey: null as ((event: KeyboardEvent) => boolean) | null, + writes: [] as Array<{ threadId: string; data: string }>, + resetSessions: () => {}, + setSession: ( + _thread: ScopedThreadRef, + _status: "closed" | "starting" | "running", + _version: number, + ) => {}, surfaceOptions: [] as GhosttyTerminalSurfaceOptions[], metadata: new Map< string, @@ -40,21 +49,38 @@ vi.mock("~/terminal/ghostty/surface", () => ({ create: (_mount: HTMLElement, options: GhosttyTerminalSurfaceOptions) => new Promise((resolve) => { fixture.surfaceOptions.push(options); - fixture.pendingSurfaces.push(() => resolve(fakeSurface)); + fixture.pendingSurfaces.push(() => { + fixture.typeIntoSurface = options.onData; + fixture.pressKey = options.beforeKey ?? null; + resolve(fakeSurface); + }); }), }, })); vi.mock("../state/terminalSessions", async () => { const Atom = await import("effect/reactivity/Atom"); - const sessions = Atom.family((_environmentId: EnvironmentId) => - Atom.family((threadId: ThreadId) => - Atom.make({ - ...EMPTY_TERMINAL_SESSION_STATE, - status: fixture.statusByThreadId.get(threadId) ?? "closed", - version: 1, - }), - ), - ); + // Families cache their atoms, so each test needs fresh ones for its own statuses. + const makeSessions = () => + Atom.family((_environmentId: EnvironmentId) => + Atom.family((threadId: ThreadId) => + Atom.make({ + ...EMPTY_TERMINAL_SESSION_STATE, + status: fixture.statusByThreadId.get(threadId) ?? "closed", + version: 1, + }), + ), + ); + let sessions = makeSessions(); + fixture.resetSessions = () => { + sessions = makeSessions(); + }; + fixture.setSession = (thread, status, version) => { + appAtomRegistry.set(sessions(thread.environmentId)(thread.threadId), { + ...EMPTY_TERMINAL_SESSION_STATE, + status, + version, + }); + }; return { attachedTerminalSessionAtom: (environmentId: EnvironmentId, terminal: { threadId: ThreadId }) => sessions(environmentId)(terminal.threadId), @@ -65,7 +91,12 @@ vi.mock("../state/session", () => ({ readEnvironmentScope: () => true, })); vi.mock("../state/use-atom-command", () => ({ - useAtomCommand: () => async () => ({ _tag: "Success" }), + useAtomCommand: () => async (args: { input: { threadId: string; data?: string } }) => { + if (args.input.data !== undefined) { + fixture.writes.push({ threadId: args.input.threadId, data: args.input.data }); + } + return { _tag: "Success" }; + }, })); // The commands are replaced above; importing their runtime would connect a client. vi.mock("../state/server", async () => { @@ -138,7 +169,8 @@ function Drawer(props: { ); } -function setup() { +function setup(originalStatus: "starting" | "exited" = "exited") { + fixture.resetSessions(); const original: ScopedThreadRef = { environmentId: EnvironmentId.make("environment-a"), threadId: ThreadId.make("thread-a"), @@ -148,10 +180,13 @@ function setup() { threadId: ThreadId.make("thread-b"), }; fixture.statusByThreadId = new Map([ - [original.threadId, "exited"], + [original.threadId, originalStatus], [other.threadId, "running"], ]); fixture.pendingSurfaces = []; + fixture.typeIntoSurface = null; + fixture.pressKey = null; + fixture.writes = []; fixture.surfaceOptions = []; fixture.metadata.clear(); useRightPanelStore.setState(useRightPanelStore.getInitialState()); @@ -159,6 +194,32 @@ function setup() { return { original, other }; } +it("drops input until the shell runs instead of failing a write for every key", async () => { + const { original } = setup("starting"); + fixture.setSession(original, "closed", 0); + await using _screen = await render( {}} />); + await act(async () => { + fixture.pendingSurfaces.shift()?.(); + }); + const clearShortcut = () => new KeyboardEvent("keydown", { key: "l", ctrlKey: true }); + fixture.typeIntoSurface?.("before attach"); + fixture.pressKey?.(clearShortcut()); + await act(() => fixture.setSession(original, "running", 0)); + fixture.typeIntoSurface?.("stale metadata"); + fixture.pressKey?.(clearShortcut()); + await act(() => fixture.setSession(original, "starting", 1)); + fixture.typeIntoSurface?.("early"); + fixture.pressKey?.(clearShortcut()); + expect(fixture.writes).toEqual([]); + await act(() => fixture.setSession(original, "running", 1)); + fixture.typeIntoSurface?.("ready"); + fixture.pressKey?.(clearShortcut()); + expect(fixture.writes).toEqual([ + { threadId: original.threadId, data: "ready" }, + { threadId: original.threadId, data: "\u000c" }, + ]); +}); + it("closes an exited terminal on its own thread after navigating to one reusing its ID", async () => { const { original, other } = setup(); const onCloseTerminal = vi.fn(); diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 2e08462eba471..d81de1ae38509 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -916,7 +916,10 @@ function TerminalSessionViewport({ }; const sendTerminalInput = async (data: string, fallbackError: string) => { - if (!hasTerminalWriteAccess()) return; + // The server rejects input until the shell runs, and each rejection would print an error. + if (writableSessionGeneration(readTerminalSession(), hasTerminalWriteAccess()) === null) { + return; + } const activeTerminal = terminalRef.current; if (!activeTerminal) return; const result = await writeTerminal(data); @@ -1025,16 +1028,7 @@ function TerminalSessionViewport({ } function handleData(data: string): void { - if (!hasTerminalWriteAccess()) return; - void (async () => { - const result = await writeTerminal(data); - if (result._tag === "Success" || isAtomCommandInterrupted(result)) return; - const error = squashAtomCommandFailure(result); - writeSystemMessage( - terminal, - error instanceof Error ? error.message : "Terminal write failed", - ); - })(); + void sendTerminalInput(data, "Terminal write failed"); } const themeObserver = new MutationObserver(() => { diff --git a/apps/web/src/components/chat/useChatTerminalActions.ts b/apps/web/src/components/chat/useChatTerminalActions.ts index a64c748e81432..edb310af0ddca 100644 --- a/apps/web/src/components/chat/useChatTerminalActions.ts +++ b/apps/web/src/components/chat/useChatTerminalActions.ts @@ -34,6 +34,7 @@ import { resolveDiscoveredServerUrl } from "../../browser/browserTargetResolver" import { stackedThreadToast, toastManager } from "../ui/toast"; import { confirmTerminalClose } from "../../lib/terminalCloseConfirm"; import { findReusableProjectScriptTerminal } from "../../lib/projectScriptTerminal"; +import { closingTerminalIds } from "../terminal/closingTerminals"; import { type TerminalContextSelection } from "../../lib/terminalContext"; import { focusActiveDrawerTerminal, @@ -152,9 +153,11 @@ export function useChatTerminalActions(input: ChatTerminalActionsInput) { if (nextOpen && hasTerminalWriteAccess() && terminalUiState.terminalIds.length === 0) { // Server-created setup terminals exist before this client's drawer mounts. // Reuse them before allocating a shell from the still-empty UI state. + // The server lists a closing terminal until its close finishes, which would end the reopen. + const closingIds = new Set(closingTerminalIds(activeThreadRef)); + const isReusable = (id: string) => !panelTerminalIds.has(id) && !closingIds.has(id); const existingTerminalId = - runningTerminalIds.find((id) => !panelTerminalIds.has(id)) ?? - activeServerOrderedTerminalIds.find((id) => !panelTerminalIds.has(id)); + runningTerminalIds.find(isReusable) ?? activeServerOrderedTerminalIds.find(isReusable); if (existingTerminalId !== undefined) { storeEnsureTerminal(activeThreadRef, existingTerminalId, { open: true, active: true }); focusActiveDrawerTerminal(activeThreadRef); @@ -232,6 +235,7 @@ export function useChatTerminalActions(input: ChatTerminalActionsInput) { activeTerminalId: terminalUiState.activeTerminalId, panelTerminalIds, runningTerminalIds, + closingTerminalIds: activeThreadRef ? closingTerminalIds(activeThreadRef) : [], }); const targetWorktreePath = options?.worktreePath ?? activeThread.worktreePath ?? null; diff --git a/apps/web/src/components/terminal/closingTerminals.test.ts b/apps/web/src/components/terminal/closingTerminals.test.ts new file mode 100644 index 0000000000000..f0276e2a48034 --- /dev/null +++ b/apps/web/src/components/terminal/closingTerminals.test.ts @@ -0,0 +1,34 @@ +import { EnvironmentId, ThreadId } from "@t3tools/contracts"; +import { expect, it } from "vite-plus/test"; + +import { closingTerminalIds, holdTerminalIdWhileClosing } from "./closingTerminals"; + +function deferred() { + let resolve!: () => void; + let reject!: (error: Error) => void; + const promise = new Promise((onResolve, onReject) => { + resolve = onResolve; + reject = onReject; + }); + return { promise, resolve, reject }; +} + +it("keeps a closing terminal's ID out of reach until its last close settles", async () => { + const thread = { environmentId: EnvironmentId.make("env"), threadId: ThreadId.make("thread") }; + const otherThread = { ...thread, threadId: ThreadId.make("other") }; + const first = deferred(); + const second = deferred(); + + const firstClose = holdTerminalIdWhileClosing(thread, "term-1", () => first.promise); + const secondClose = holdTerminalIdWhileClosing(thread, "term-1", () => second.promise); + expect(closingTerminalIds(thread)).toEqual(["term-1"]); + expect(closingTerminalIds(otherThread)).toEqual([]); + + first.reject(new Error("close failed")); + await expect(firstClose).rejects.toThrow("close failed"); + expect(closingTerminalIds(thread)).toEqual(["term-1"]); + + second.resolve(); + await secondClose; + expect(closingTerminalIds(thread)).toEqual([]); +}); diff --git a/apps/web/src/components/terminal/closingTerminals.ts b/apps/web/src/components/terminal/closingTerminals.ts new file mode 100644 index 0000000000000..a1ba2bf397d8f --- /dev/null +++ b/apps/web/src/components/terminal/closingTerminals.ts @@ -0,0 +1,29 @@ +import { scopedThreadKey } from "@t3tools/client-runtime/environment"; +import type { ScopedThreadRef } from "@t3tools/contracts"; + +// Lifecycle commands run in order, so a queued close ends whichever shell holds its ID by then. +const closingIdsByThread = new Map>(); + +/** IDs a new terminal must not take until their pending closes settle. */ +export function closingTerminalIds(threadRef: ScopedThreadRef): ReadonlyArray { + return [...(closingIdsByThread.get(scopedThreadKey(threadRef))?.keys() ?? [])]; +} + +export async function holdTerminalIdWhileClosing( + threadRef: ScopedThreadRef, + terminalId: string, + close: () => Promise, +): Promise { + const key = scopedThreadKey(threadRef); + const closing = closingIdsByThread.get(key) ?? new Map(); + closingIdsByThread.set(key, closing); + closing.set(terminalId, (closing.get(terminalId) ?? 0) + 1); + try { + return await close(); + } finally { + const remaining = (closing.get(terminalId) ?? 1) - 1; + if (remaining > 0) closing.set(terminalId, remaining); + else closing.delete(terminalId); + if (closing.size === 0) closingIdsByThread.delete(key); + } +} diff --git a/apps/web/src/components/terminal/useThreadTerminalActions.ts b/apps/web/src/components/terminal/useThreadTerminalActions.ts index 9112f41519754..de568dc9101fe 100644 --- a/apps/web/src/components/terminal/useThreadTerminalActions.ts +++ b/apps/web/src/components/terminal/useThreadTerminalActions.ts @@ -17,6 +17,7 @@ import { terminalEnvironment } from "../../state/terminal"; import { useKnownTerminalSessions } from "../../state/terminalSessions"; import { useAtomCommand } from "../../state/use-atom-command"; import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../../terminalUiStateStore"; +import { closingTerminalIds, holdTerminalIdWhileClosing } from "./closingTerminals"; /** Where a new terminal starts. Omitted fields fall back to the thread's project and worktree. */ export interface TerminalLaunchLocation { @@ -62,6 +63,7 @@ export function useThreadTerminalActions(threadRef: ScopedThreadRef | null) { ...panelSurfaces.flatMap((surface) => surface.kind === "terminal" ? surface.terminalIds : [], ), + ...(threadRef ? closingTerminalIds(threadRef) : []), ]); const canReuse = knownSessions !== null && @@ -119,7 +121,7 @@ export function useThreadTerminalActions(threadRef: ScopedThreadRef | null) { if (!target || !readEnvironmentScope(target.environmentId, AuthTerminalOperateScope)) return; const environmentId = target.environmentId; const threadId = target.threadId; - void (async () => { + void holdTerminalIdWhileClosing(target, terminalId, async () => { const closeResult = await closeTerminalCommand({ environmentId, input: { threadId, terminalId, deleteHistory: true }, @@ -131,7 +133,7 @@ export function useThreadTerminalActions(threadRef: ScopedThreadRef | null) { ) { await writeTerminal({ environmentId, input: { threadId, terminalId, data: "exit\n" } }); } - })(); + }); }; const writeToTerminal = (terminalId: string, data: string) => { diff --git a/apps/web/src/lib/projectScriptTerminal.test.ts b/apps/web/src/lib/projectScriptTerminal.test.ts index 3fe3f26069179..d9c930a6385de 100644 --- a/apps/web/src/lib/projectScriptTerminal.test.ts +++ b/apps/web/src/lib/projectScriptTerminal.test.ts @@ -36,9 +36,22 @@ describe("findReusableProjectScriptTerminal", () => { runningTerminalIds: ["term-1"], expected: null, }, - ])("$name", ({ panelTerminalIds, expected, ...input }) => { + { + name: "skips an idle terminal whose close is still pending", + drawerTerminalIds: ["term-1"], + activeTerminalId: "term-1", + panelTerminalIds: [], + runningTerminalIds: [], + closingTerminalIds: ["term-1"], + expected: null, + }, + ])("$name", ({ panelTerminalIds, closingTerminalIds = [], expected, ...input }) => { expect( - findReusableProjectScriptTerminal({ ...input, panelTerminalIds: new Set(panelTerminalIds) }), + findReusableProjectScriptTerminal({ + ...input, + panelTerminalIds: new Set(panelTerminalIds), + closingTerminalIds, + }), ).toBe(expected); }); }); diff --git a/apps/web/src/lib/projectScriptTerminal.ts b/apps/web/src/lib/projectScriptTerminal.ts index ebf3e9694f8df..6075882462441 100644 --- a/apps/web/src/lib/projectScriptTerminal.ts +++ b/apps/web/src/lib/projectScriptTerminal.ts @@ -8,10 +8,14 @@ export function findReusableProjectScriptTerminal(input: { readonly activeTerminalId: string; readonly panelTerminalIds: ReadonlySet; readonly runningTerminalIds: ReadonlyArray; + /** A pending close would end the script's shell once the reopen runs. */ + readonly closingTerminalIds: ReadonlyArray; }): string | null { const idleDrawerTerminalIds = input.drawerTerminalIds.filter( (terminalId) => - !input.panelTerminalIds.has(terminalId) && !input.runningTerminalIds.includes(terminalId), + !input.panelTerminalIds.has(terminalId) && + !input.runningTerminalIds.includes(terminalId) && + !input.closingTerminalIds.includes(terminalId), ); if (idleDrawerTerminalIds.includes(input.activeTerminalId)) return input.activeTerminalId; return idleDrawerTerminalIds[0] ?? null; From 1c18c16e931de582677a5efdf77b76d7be0fe3cb Mon Sep 17 00:00:00 2001 From: Mokhles elheni <144564734+M0xoo@users.noreply.github.com> Date: Sat, 14 Nov 2026 16:00:35 +0100 Subject: [PATCH 146/192] perf(server): ACP subagent replies no longer rewrite the database on every chunk (#29422) Co-authored-by: Julius Marminge --- .../Adapters/AcpAdapterV2.test.ts | 126 +++++++++++++++++- .../Adapters/AcpRegistryAdapterV2.test.ts | 36 +++++ .../orchestration-v2/messageSearchIndex.ts | 2 +- packages/provider-acp/src/server/adapter.ts | 77 +++++++++-- 4 files changed, 227 insertions(+), 14 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index 88505e9749d62..03a3a99e2d703 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -3578,14 +3578,14 @@ describe("AcpAdapterV2", () => { "cognition.ai/subagent_context": { parentAgentId: "child-a" }, }, }, - { - sessionUpdate: "agent_message_chunk", - content: { type: "text", text: "ONE" }, + ...["O", "N", "E"].map((text) => ({ + sessionUpdate: "agent_message_chunk" as const, + content: { type: "text" as const, text }, _meta: { "cognition.ai/streamingMessageId": "child-result", "cognition.ai/subagent_context": { parentAgentId: "child-a" }, }, - }, + })), { sessionUpdate: "tool_call", toolCallId: "child-later", @@ -3738,6 +3738,15 @@ describe("AcpAdapterV2", () => { (item) => item.threadId === task?.childThreadId && item.type === "assistant_message", ); assert.equal(new Set(childAnswers.map((item) => item.ordinal)).size, 2); + // Later chunks coalesce; the subagent's completion flushes the final text. + assert.deepEqual( + childAnswers.flatMap((item) => + item.type === "assistant_message" && item.text !== "Checking the code." + ? [item.text] + : [], + ), + ["O", "ONE"], + ); const parentTools = items.filter( (item) => item.threadId === threadId && item.type === "dynamic_tool", ); @@ -3746,6 +3755,115 @@ describe("AcpAdapterV2", () => { }).pipe(Effect.provide(layerTest)), ); + it.effect("emits coalesced subagent text before a failed turn's terminal", () => + Effect.gen(function* () { + const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const path = yield* Path.Path; + const mockAgentPath = yield* path.fromFileUrl( + new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), + ); + type Runtime = AcpSessionRuntime.AcpSessionRuntime["Service"]; + let handler: Parameters[0] | undefined; + const instanceId = ProviderInstanceId.make("devin-failed-flush"); + const adapter = yield* makeAcpAdapterV2({ + instanceId, + selfInvocation: yield* resolveSelfInvocation(), + flavor: { + driver: ACP_TEST_DRIVER, + capabilities: AcpProviderCapabilitiesV2, + normalizeSessionUpdate: normalizeDevinSessionUpdate, + normalizeToolCall: normalizeDevinToolCall, + extractSubagentUpdate: extractDevinSubagentUpdate, + makeRuntime: makeMockRuntime({ + childProcessSpawner, + mockAgentPath, + wrapRuntime: (runtime) => ({ + ...runtime, + handleSessionUpdate: (next) => + Effect.sync(() => { + handler = next; + }).pipe(Effect.andThen(runtime.handleSessionUpdate(next))), + prompt: () => + Effect.gen(function* () { + const updates = [ + { + sessionUpdate: "tool_call_update", + toolCallId: "child-a", + status: "in_progress", + _meta: { + "cognition.ai/subagent_started": { + agentId: "child-a", + title: "Answer", + task: "Reply ONE.", + }, + }, + }, + ...["O", "N", "E"].map((text) => ({ + sessionUpdate: "agent_message_chunk" as const, + content: { type: "text" as const, text }, + _meta: { + "cognition.ai/streamingMessageId": "child-result", + "cognition.ai/subagent_context": { parentAgentId: "child-a" }, + }, + })), + ] satisfies Array; + for (const update of updates) + yield* handler!({ sessionId: "mock-session-1", update }); + return yield* new EffectAcpErrors.AcpRequestError({ + code: -32603, + errorMessage: "prompt failed", + }); + }), + }), + }), + }, + }); + const threadId = ThreadId.make("devin-failed-flush-parent"); + const modelSelection = { instanceId, model: "default" }; + const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ + runtimeMode: "full-access", + interactionMode: "default", + cwd: process.cwd(), + }); + const runtime = yield* adapter.openSession({ + threadId, + providerSessionId: ProviderSessionId.make("devin-failed-flush-session"), + modelSelection, + runtimePolicy, + }); + const providerThread = yield* runtime.ensureThread({ + threadId, + modelSelection, + runtimePolicy, + }); + yield* runtime.startTurn( + makeTurnInput({ + threadId, + providerThread, + instanceId, + runtimePolicy, + now: yield* DateTime.now, + }), + ); + const events = Array.from( + yield* runtime.events.pipe( + Stream.takeUntil((event) => event.type === "turn.terminal"), + Stream.runCollect, + ), + ); + const terminal = events.at(-1); + assert.equal(terminal?.type === "turn.terminal" ? terminal.status : undefined, "failed"); + const childText = events.flatMap((event) => + event.type === "turn_item.updated" && + event.turnItem.type === "assistant_message" && + event.turnItem.threadId !== threadId + ? [event.turnItem.text] + : [], + ); + assert.equal(childText.at(-1), "ONE"); + }).pipe(Effect.provide(layerTest), Effect.scoped), + ); + it.effect("routes interleaved Copilot subagent output into subagent threads", () => Effect.gen(function* () { const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; diff --git a/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts index d0014aa6574d4..8982fb122fc7b 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts @@ -2685,6 +2685,42 @@ describe("AcpRegistryAdapterV2", () => { }).pipe(Effect.provide(layerTest)), ); + it.effect("writes a child's coalesced text before a restart resets it", () => + Effect.gen(function* () { + const { update, childState, idle, prompt } = claudeFrames; + const reply = (text: string) => + update("child-1", { + sessionUpdate: "agent_message_chunk", + messageId: "reply", + content: { type: "text", text }, + }); + const session = yield* openClaudeReplaySession([ + ...claudeFrames.open, + ...prompt(1), + ...claudeChild.slice(0, -1), + reply("Review "), + reply("done."), + // A working grandchild keeps the idle child from publishing (and flushing) its status. + childState("child-1", "grandchild", "running"), + childState(CLAUDE_ROOT_SESSION, "child-1", "idle"), + childState(CLAUDE_ROOT_SESSION, "child-1", "running"), + idle("end_turn"), + claudeFrames.closeRequest, + claudeFrames.closed, + ]); + yield* session.runtime.startTurn(session.turnInput(1)); + const events = yield* session.until((event) => event.type === "turn.terminal"); + assert.isTrue( + events.some( + (event) => + event.type === "message.updated" && + event.message.role === "assistant" && + event.message.text === "Review done.", + ), + ); + }).pipe(Effect.provide(layerTest), Effect.scoped), + ); + it.effect("fails active Claude work and releases its children when the process exits", () => Effect.gen(function* () { const session = yield* openClaudeReplaySession([ diff --git a/apps/server/src/orchestration-v2/messageSearchIndex.ts b/apps/server/src/orchestration-v2/messageSearchIndex.ts index 87d7256b6809d..c35f35eac37d1 100644 --- a/apps/server/src/orchestration-v2/messageSearchIndex.ts +++ b/apps/server/src/orchestration-v2/messageSearchIndex.ts @@ -18,7 +18,7 @@ export const nonPlaceholderMessageCondition = (sql: SqlClient.SqlClient, row = " /** * Exclude attachment placeholders, generated notices and subagent threads. ACP subagents - * rewrite their finished answer on every streamed chunk. + * rewrite their finished answer while it streams. * Migration triggers, backfill, rebuild and scans share this predicate; `row` may be `new`. */ export const indexedMessageCondition = (sql: SqlClient.SqlClient, row = "messages") => diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index 77c68669eea06..0875b039d79eb 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -179,6 +179,9 @@ function sendPromptWhenIdle( * This avoids quadratic output; scheduled flushes can run late under host load. */ const ACP_TEXT_FLUSH_INTERVAL: Duration.Input = "50 millis"; +// Subagent messages are stored as finished, so they skip the server's paragraph +// throttle and every snapshot is persisted. Match that throttle's 400 ms cadence. +const ACP_SUBAGENT_TEXT_FLUSH_INTERVAL: Duration.Input = "400 millis"; // Output of the running prompt. Session metadata and unknown updates do not reset its timeout. const ACP_PROMPT_ACTIVITY_UPDATES: ReadonlySet = new Set([ @@ -1679,6 +1682,8 @@ interface ActiveAcpSubagent { childSessionId: string | null; assistantText: string; readonly assistantMessages: Map; + // Messages whose latest text waits for a delayed flush. + readonly pendingAssistantMessages: Set; readonly childItemOrdinals: Map; nextChildOrdinal: number; terminalStatusProjected: boolean; @@ -3097,10 +3102,53 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ) { if (text.length === 0 && mode === "append") return; const nativeItemId = `${subagent.task.nativeTaskRef?.nativeId ?? subagent.task.id}:message:${messageId ?? "result"}`; - const previous = subagent.assistantMessages.get(nativeItemId) ?? ""; - const messageText = mode === "replace" ? text : `${previous}${text}`; + const previous = subagent.assistantMessages.get(nativeItemId); + const messageText = mode === "replace" ? text : `${previous ?? ""}${text}`; subagent.assistantMessages.set(nativeItemId, messageText); subagent.assistantText = messageText; + if (mode === "append" && previous !== undefined) { + if (subagent.pendingAssistantMessages.has(nativeItemId)) return; + subagent.pendingAssistantMessages.add(nativeItemId); + yield* writeSubagentAssistant(subagent, nativeItemId, true).pipe( + Effect.delay(ACP_SUBAGENT_TEXT_FLUSH_INTERVAL), + Effect.forkIn(sessionScope), + ); + return; + } + subagent.pendingAssistantMessages.delete(nativeItemId); + yield* writeSubagentAssistant(subagent, nativeItemId, false); + }); + + // Reads the pending set under the permit, so it also waits for a write in progress. + const flushPendingSubagentAssistant = (subagent: ActiveAcpSubagent) => + Effect.suspend(() => + Effect.forEach( + [...subagent.pendingAssistantMessages], + (nativeItemId) => writeSubagentAssistantUnlocked(subagent, nativeItemId, true), + { discard: true }, + ), + ); + + const flushSubagentAssistant = (subagent: ActiveAcpSubagent) => + textEmissionPermit.withPermit(flushPendingSubagentAssistant(subagent)); + + const writeSubagentAssistant = ( + subagent: ActiveAcpSubagent, + nativeItemId: string, + onlyIfPending: boolean, + ) => + textEmissionPermit.withPermit( + writeSubagentAssistantUnlocked(subagent, nativeItemId, onlyIfPending), + ); + + const writeSubagentAssistantUnlocked = Effect.fnUntraced(function* ( + subagent: ActiveAcpSubagent, + nativeItemId: string, + onlyIfPending: boolean, + ) { + if (onlyIfPending && !subagent.pendingAssistantMessages.delete(nativeItemId)) return; + const text = subagent.assistantMessages.get(nativeItemId); + if (text === undefined) return; const now = yield* DateTime.now; const ordinal = resolveSubagentChildOrdinal(subagent, nativeItemId); const artifacts = makeSubagentConversationArtifacts({ @@ -3112,7 +3160,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( providerTurnId: null, nativeItemRef: { driver, nativeId: nativeItemId, strength: "weak" }, role: "assistant", - text: subagent.assistantText, + text, ordinal, now, }); @@ -3355,6 +3403,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( childSessionId: null, assistantText: "", assistantMessages: new Map(), + pendingAssistantMessages: new Set(), childItemOrdinals: new Map(), nextChildOrdinal: 101, terminalStatusProjected: false, @@ -3457,6 +3506,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ); } + yield* flushSubagentAssistant(subagent); // A failed child's result explains the failure; it is not the child's reply. if ( taskStatus !== "running" && @@ -3569,20 +3619,22 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( return child; }; - const setNativeChildState = ( + const setNativeChildState = Effect.fnUntraced(function* ( child: AcpNativeChildSession, status: AcpNativeChildSession["status"], result: string | null, - ) => { + ) { if (child.status !== "running" && status === "running" && child.subagent !== undefined) { + yield* flushSubagentAssistant(child.subagent); // A new execution replaces its result; live ownership transfers keep their progress. child.subagent.task = { ...child.subagent.task, result: null }; child.subagent.assistantText = ""; child.subagent.assistantMessages.clear(); + child.subagent.pendingAssistantMessages.clear(); } child.status = status; child.result = result; - }; + }); const nativeChildOwner = (sessionId: string): AcpNativeChildSession | undefined => { const visited = new Set(); @@ -3657,7 +3709,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( child.delegationId !== subagent.task.nativeTaskRef?.nativeId && !subagent.childItemOrdinals.has(`${sessionId}:prompt:${child.delegationId}`); if (newDelegation && child.status !== "running") - setNativeChildState(child, "running", null); + yield* setNativeChildState(child, "running", null); const hasNativeWork = child.status === "running" || hasWorkingDescendant(sessionId); if ( (hasNativeWork && acpSubagentStatusIsTerminal(subagent.task.status)) || @@ -3803,7 +3855,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( const child = nativeChild(event.childSessionId); child.parentSessionId = event.parentSessionId ?? child.parentSessionId; if (event.type === "state") { - setNativeChildState(child, event.status, event.result); + yield* setNativeChildState(child, event.status, event.result); } else { child.delegationId = event.delegationId; child.prompt = event.prompt; @@ -3832,7 +3884,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( const child = nativeChild(notification.sessionId); const unfinishedMessage = update.state === "idle" ? acpUnfinishedStopMessage(update.stopReason) : undefined; - setNativeChildState( + yield* setNativeChildState( child, update.state !== "idle" ? "running" @@ -6152,6 +6204,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( status: OrchestrationV2Subagent["status"], resultOverride?: string | null, ) { + yield* flushSubagentAssistant(subagent); yield* mutateCarryoverSubagentStatus(subagent, status, resultOverride); const now = subagent.task.updatedAt; const nativeTaskId = subagent.task.nativeTaskRef?.nativeId ?? subagent.task.id; @@ -7879,6 +7932,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( }); } yield* closeTextStreams(context); + // Ingestion may stop at turn.terminal, before a delayed flush would run. + yield* textEmissionPermit.withPermit( + Effect.forEach(context.subagents.values(), flushPendingSubagentAssistant, { + discard: true, + }), + ); if (settledStatus !== "failed") { yield* emitProviderRetry(context, settledStatus); } From 4954322f02f7b569bd0369caa4e1552393635a1c Mon Sep 17 00:00:00 2001 From: maximkes Date: Sat, 14 Nov 2026 18:01:01 +0300 Subject: [PATCH 147/192] fix(web): don't send completion notifications for snoozed threads (#27458) Co-authored-by: maximkes <22573701+maximkes@users.noreply.github.com> Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/orchestration-v2/ProjectionStore.ts | 6 +++ .../src/relay/AgentAwarenessRelay.test.ts | 31 +++++++++++ apps/server/src/relay/AgentAwarenessRelay.ts | 18 ++++++- ...hreadNotificationCoordinator.logic.test.ts | 51 ++++++++++++++++++- .../ThreadNotificationCoordinator.logic.ts | 29 +++++++++-- .../ThreadNotificationCoordinator.tsx | 3 ++ 6 files changed, 132 insertions(+), 6 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index 32007af08573f..e0b0b3035d5eb 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -1081,6 +1081,9 @@ export type AgentAwarenessThread = ProjectThreadAwarenessV2Input["thread"] & | "creationSource" | "latestRunCompletedAt" | "archivedAt" + | "snoozedUntil" + | "snoozedAt" + | "pendingGuestMessageCount" | "activeRunId" | "latestRunId" | "hasActionableProposedPlan" @@ -8685,6 +8688,9 @@ export const layer: Layer.Layer = projectId: state.thread.projectId, creationSource: state.thread.creationSource, archivedAt: state.thread.archivedAt, + snoozedUntil: state.thread.snoozedUntil ?? null, + snoozedAt: state.thread.snoozedAt ?? null, + pendingGuestMessageCount: pendingGuestMessageCount(state.thread.guestShare), activeRunId: state.activeRunId, latestRunId: state.latestRunId, hasActionableProposedPlan: state.hasActionableProposedPlan, diff --git a/apps/server/src/relay/AgentAwarenessRelay.test.ts b/apps/server/src/relay/AgentAwarenessRelay.test.ts index 68e2289524c09..10388e99fe7c5 100644 --- a/apps/server/src/relay/AgentAwarenessRelay.test.ts +++ b/apps/server/src/relay/AgentAwarenessRelay.test.ts @@ -753,6 +753,37 @@ describe("AgentAwarenessRelay", () => { }), ); + it.effect( + "clears a completion that landed while snoozed, even when processed after the wake", + () => + Effect.gen(function* () { + yield* TestClock.setTime(DateTime.toEpochMillis(DateTime.makeUnsafe(NOW))); + const { relay, currentShell, publications } = yield* makeTestRelay(); + const snooze = { + snoozedAt: DateTime.makeUnsafe(NOW), + snoozedUntil: DateTime.makeUnsafe("2026-09-04T13:00:00.000Z"), + }; + yield* Ref.set(currentShell, shell(snooze)); + yield* relay.publishThread(THREAD_ID); + yield* TestClock.adjust("2 hours"); + yield* Ref.set( + currentShell, + shell({ + ...snooze, + status: "completed", + latestRunCompletedAt: DateTime.makeUnsafe("2026-09-04T12:30:00.000Z"), + }), + ); + yield* relay.publishThread(THREAD_ID); + yield* TestClock.adjust("5 seconds"); + yield* relay.drain; + assert.deepEqual( + publications.map((publication) => publication.state?.phase ?? null), + ["running", null], + ); + }), + ); + it.effect("still publishes a completion when the thread settles during the hold", () => Effect.gen(function* () { const { relay, currentShell, publications } = yield* makeTestRelay(); diff --git a/apps/server/src/relay/AgentAwarenessRelay.ts b/apps/server/src/relay/AgentAwarenessRelay.ts index 76f854548deb6..ef2e34799742c 100644 --- a/apps/server/src/relay/AgentAwarenessRelay.ts +++ b/apps/server/src/relay/AgentAwarenessRelay.ts @@ -51,6 +51,7 @@ import { import { EnvironmentSigningKey } from "../cloud/environmentKeys.ts"; import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; import * as ThreadManagement from "../orchestration-v2/ThreadManagementService.ts"; +import { isSnoozed } from "../orchestration-v2/ThreadSettlementService.ts"; import * as ProjectService from "../project/ProjectService.ts"; import { forkParked } from "../serverActivation.ts"; @@ -360,12 +361,27 @@ function resolveAgentAwarenessRelayPublishSnapshot(input: { state?.phase !== projectThreadAwarenessV2(awarenessInput)?.phase; return { projectId: input.thread.value.projectId, - state: sanitizeRelayAgentActivityState(state), + state: snoozedCompletion(input.thread.value, state) + ? null + : sanitizeRelayAgentActivityState(state), subagentWait: childDrivesWait ? input.pendingSubagentRequest : null, reason: "snapshot", }; } +// Checked at completion time, so a completion processed after the wake stays silent too. +function snoozedCompletion( + thread: AgentAwarenessThread, + state: { readonly phase: string } | null, +): boolean { + if (state?.phase !== "completed" || thread.latestRunCompletedAt == null) return false; + const completedAt = DateTime.toEpochMillis(thread.latestRunCompletedAt); + if (thread.snoozedAt != null && completedAt < DateTime.toEpochMillis(thread.snoozedAt)) { + return false; + } + return isSnoozed(thread, completedAt); +} + function terminalWorkSinceStart(thread: AgentAwarenessThread, startedAt: number): boolean { return ( thread.latestRunCompletedAt != null && diff --git a/apps/web/src/components/ThreadNotificationCoordinator.logic.test.ts b/apps/web/src/components/ThreadNotificationCoordinator.logic.test.ts index 965dba45ce8a2..0efca647316ba 100644 --- a/apps/web/src/components/ThreadNotificationCoordinator.logic.test.ts +++ b/apps/web/src/components/ThreadNotificationCoordinator.logic.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vite-plus/test"; import { createThreadNotificationGate, + isSnoozedForNotification, RELEASED_COMPLETION_GRACE_MS, type ThreadNotificationObservation, } from "./ThreadNotificationCoordinator.logic"; @@ -25,7 +26,8 @@ const woke = (at: number): ThreadNotificationObservation => ({ ...completed(at), backgroundWake: true, }); -const visible = { archived: false, quiet: false }; +const visible = { archived: false, quiet: false, snoozed: false }; +const snoozed = { ...visible, snoozed: true }; function fakeTimers(): Disposable { vi.useFakeTimers(); @@ -44,6 +46,16 @@ function harness() { } describe("createThreadNotificationGate", () => { + it("skips a completion seen while snoozed, but still notifies attention", () => { + const { notify, observe } = harness(); + observe(working); + observe(completed(1), snoozed); + observe({ ...completed(1) }); + expect(notify).not.toHaveBeenCalled(); + observe({ ...working, attention: "run-2:approval" }, snoozed); + expect(notify.mock.calls).toEqual([["thread", "input"]]); + }); + it("notifies a completion immediately and only once", () => { const { notify, observe } = harness(); observe(working); @@ -180,6 +192,16 @@ describe("createThreadNotificationGate", () => { expect(notify).toHaveBeenCalledTimes(1); }); + it("drops a pending wake completion when the thread is snoozed", () => { + using _timers = fakeTimers(); + const { notify, observe } = harness(); + observe(waking); + observe(woke(1)); + observe({ ...woke(1) }, snoozed); + vi.advanceTimersByTime(RELEASED_COMPLETION_GRACE_MS); + expect(notify).not.toHaveBeenCalled(); + }); + it("drops a pending wake completion when the thread is removed or the gate is cleared", () => { using _timers = fakeTimers(); for (const reset of [ @@ -195,3 +217,30 @@ describe("createThreadNotificationGate", () => { } }); }); + +describe("isSnoozedForNotification", () => { + const snoozedAt = Date.parse("2026-01-01T11:00:00.000Z"); + const wakeAt = Date.parse("2026-01-01T12:00:00.000Z"); + const thread = { + snoozedAt: new Date(snoozedAt).toISOString(), + snoozedUntil: new Date(wakeAt).toISOString(), + hasPendingApprovals: false, + hasPendingUserInput: false, + }; + const later = wakeAt + 60_000; + + it.each([ + ["during the snooze, processed after the wake", thread, wakeAt - 1, later, true], + ["before the snooze, processed after the wake", thread, snoozedAt - 1, later, false], + ["after the wake", thread, wakeAt + 1, later, false], + [ + "after an early wake for attention", + { ...thread, hasPendingApprovals: true }, + wakeAt - 1, + wakeAt - 1, + false, + ], + ])("a completion %s is suppressed: %s", (_name, shell, completedAt, now, expected) => { + expect(isSnoozedForNotification(shell, completedAt, now)).toBe(expected); + }); +}); diff --git a/apps/web/src/components/ThreadNotificationCoordinator.logic.ts b/apps/web/src/components/ThreadNotificationCoordinator.logic.ts index 81002179ea9c7..057d976a17837 100644 --- a/apps/web/src/components/ThreadNotificationCoordinator.logic.ts +++ b/apps/web/src/components/ThreadNotificationCoordinator.logic.ts @@ -1,3 +1,8 @@ +import { + effectiveSnoozed, + type ThreadSnoozeShell, +} from "@t3tools/client-runtime/state/thread-settled"; + export type ThreadNotificationKind = "input" | "completion"; export interface ThreadNotificationObservation { @@ -16,6 +21,21 @@ export interface ThreadNotificationObservation { /** Matches the push relay's completion confirmation: a final wake can start right after a progress wake. */ export const RELEASED_COMPLETION_GRACE_MS = 5_000; +/** + * Whether completion notifications are suppressed by snooze. A completion that landed + * during the snooze stays suppressed even when its update is processed after the wake. + */ +export function isSnoozedForNotification( + thread: ThreadSnoozeShell, + completedAt: number | null, + now: number, +): boolean { + if (effectiveSnoozed(thread, { now: new Date(now).toISOString() })) return true; + if (completedAt === null) return false; + if (thread.snoozedAt != null && completedAt < Date.parse(thread.snoozedAt)) return false; + return effectiveSnoozed(thread, { now: new Date(completedAt).toISOString() }); +} + export function createThreadNotificationGate( notify: (key: Key, kind: ThreadNotificationKind) => void, ) { @@ -64,17 +84,18 @@ export function createThreadNotificationGate( key: Key, prior: ThreadNotificationObservation | undefined, next: ThreadNotificationObservation, - options: { readonly archived: boolean; readonly quiet: boolean }, + options: { readonly archived: boolean; readonly quiet: boolean; readonly snoozed: boolean }, ): void { // The grace survives updates that keep the wake's completion; anything else cancels it. const pending = releases.get(key); - if (pending?.at === next.completedAt && !options.archived && !options.quiet) { + const silenced = options.archived || options.quiet || options.snoozed; + if (pending?.at === next.completedAt && !silenced) { if (pending.timer === undefined) release(key, pending.at); return; } cancel(key); - // The first sighting, archived threads, and stopped or silent runs never notify completion. - if (!prior || options.archived || options.quiet) skip(key, next.completedAt); + // The first sighting, archived or snoozed threads, and stopped or silent runs never notify completion. + if (!prior || silenced) skip(key, next.completedAt); const fresh = attend(key, prior, next); if (!prior || options.archived) return; if (fresh) { diff --git a/apps/web/src/components/ThreadNotificationCoordinator.tsx b/apps/web/src/components/ThreadNotificationCoordinator.tsx index 73daf000cadd0..0268da23dd2e1 100644 --- a/apps/web/src/components/ThreadNotificationCoordinator.tsx +++ b/apps/web/src/components/ThreadNotificationCoordinator.tsx @@ -35,6 +35,7 @@ import { resolveBackgroundThreadRequest } from "./backgroundThreadAnnouncement"; import { resolveSidebarThreadStatus, type SidebarThreadStatus } from "./Sidebar.logic"; import { createThreadNotificationGate, + isSnoozedForNotification, type ThreadNotificationKind, type ThreadNotificationObservation, } from "./ThreadNotificationCoordinator.logic"; @@ -300,10 +301,12 @@ export function startThreadNotifications( entry.reconnecting = false; entry.previous = next; entry.gate.retain(new Set(next.keys())); + const now = Date.now(); for (const [prior, state] of changed) { entry.gate.observe(state.thread.id, prior, state, { archived: state.thread.archivedAt !== null, quiet: state.raw.latestRunStopRequested === true, + snoozed: isSnoozedForNotification(state.thread, state.completedAt, now), }); } }; From cc578d5f9a3899e6aa41e12140e3e623de832b9c Mon Sep 17 00:00:00 2001 From: Md Ehsanul Haque <133691757+EhsanulHaqueSiam@users.noreply.github.com> Date: Sat, 14 Nov 2026 21:01:48 +0600 Subject: [PATCH 148/192] fix(server): settling a usage-limited thread disarms recovery and names the refusal (#28360) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../src/orchestration-v2/Orchestrator.ts | 12 ++++++- .../src/orchestration-v2/runtimeLayer.test.ts | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts index 6049beffadcfe..32ac1ceaf9bdd 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.ts @@ -4476,10 +4476,16 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio cause: "The reset time has passed. Retry the thread manually.", }); } + if (thread.settledOverride === "settled") { + return yield* new OrchestratorDispatchError({ + commandId: command.commandId, + commandType: command.type, + cause: "The thread is settled. Un-settle it to resume at the reset.", + }); + } if ( !Number.isFinite(resetMs) || thread.archivedAt !== null || - thread.settledOverride === "settled" || run?.id !== command.limitRecovery.runId || failure?.class !== "usage_limit" || failure.resetAt !== command.limitRecovery.resetAt || @@ -4599,6 +4605,10 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio ...thread, settledOverride: "settled", settledAt: alreadySettled ? thread.settledAt : (command.settledAt ?? now), + // Settled threads refuse recovery, so an armed resume would stay hidden until un-settled. + limitRecovery: thread.limitRecovery + ? { ...thread.limitRecovery, autoResume: false, snooze: false } + : thread.limitRecovery, pullRequests: thread.pullRequests?.map((link) => withPullRequestWatch(link, undefined)), unsettledAt: null, pinnedAt: null, diff --git a/apps/server/src/orchestration-v2/runtimeLayer.test.ts b/apps/server/src/orchestration-v2/runtimeLayer.test.ts index 9e054e3a0bf89..1b8c7c5bf8c62 100644 --- a/apps/server/src/orchestration-v2/runtimeLayer.test.ts +++ b/apps/server/src/orchestration-v2/runtimeLayer.test.ts @@ -10866,6 +10866,7 @@ it.layer(layerTest)("usage-limit recovery", (it) => { "replacement", "manual-snooze", "invalid-snooze", + "settled", "cancel-resume-keep-snooze", "independent-patches", "expired-snooze", @@ -11019,6 +11020,38 @@ it.layer(layerTest)("usage-limit recovery", (it) => { assert.isNull(current.thread.snoozedUntil); return; } + if (scenario === "settled") { + yield* orchestrator.dispatch({ + type: "thread.metadata.update", + commandId: CommandId.make("recovery:arm-before-settle:settled"), + threadId, + limitRecovery: { runId: run.id, resetAt, autoResume: true, snooze: true }, + }); + yield* orchestrator.dispatch({ + type: "thread.settle", + commandId: CommandId.make("recovery:settle:settled"), + threadId, + }); + const error = yield* orchestrator + .dispatch({ + type: "thread.metadata.update", + commandId: CommandId.make("recovery:arm:settled"), + threadId, + limitRecovery: { runId: run.id, resetAt, autoResume: true }, + }) + .pipe(Effect.flip); + assert(error._tag === "OrchestratorDispatchError"); + assert.equal(error.cause, "The thread is settled. Un-settle it to resume at the reset."); + assert.deepInclude( + (yield* orchestrator.getThreadProjection(threadId)).thread.limitRecovery, + { + runId: run.id, + autoResume: false, + snooze: false, + }, + ); + return; + } const snooze = scenario === "wake" || scenario === "cancel-resume-keep-snooze"; const autoResume = scenario !== "wake"; const arm = limitRecoveryCommand(shell, autoResume, DateTime.toEpochMillis(now), snooze); From 45ff3f5967943f3e2c503958a0891559a5f70d15 Mon Sep 17 00:00:00 2001 From: Jack Singer Date: Sat, 14 Nov 2026 10:02:00 -0500 Subject: [PATCH 149/192] fix(desktop): bound screencast delivery to slow consumers (#28518) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- apps/desktop/src/preview/CdpRelay.test.ts | 69 ++++++++++++++++-- apps/desktop/src/preview/CdpRelay.ts | 70 ++++++++++++++++--- .../src/preview/DesktopBrowserHost.test.ts | 7 ++ .../desktop/src/preview/DesktopBrowserHost.ts | 4 ++ apps/desktop/src/preview/Manager.ts | 21 +----- 5 files changed, 141 insertions(+), 30 deletions(-) diff --git a/apps/desktop/src/preview/CdpRelay.test.ts b/apps/desktop/src/preview/CdpRelay.test.ts index ccc2b868720b4..564b4dc57d6f4 100644 --- a/apps/desktop/src/preview/CdpRelay.test.ts +++ b/apps/desktop/src/preview/CdpRelay.test.ts @@ -9,6 +9,7 @@ import { const makeTarget = ( send: CdpRelayTarget["send"], setDownloadDirectory: CdpRelayTarget["setDownloadDirectory"] = () => {}, + ackScreencastFrame: CdpRelayTarget["ackScreencastFrame"] = () => {}, ): CdpRelayTarget => ({ send, targetId: async () => "GUEST-TARGET", @@ -17,6 +18,7 @@ const makeTarget = ( userAgent: () => "Electron", setDownloadDirectory, receivesScreencast: () => true, + ackScreencastFrame, }); const makeRelay = (target: CdpRelayTarget, write: (raw: string) => void) => @@ -138,9 +140,13 @@ describe("CDP relay", () => { describe("CDP relay extra sessions", () => { it("gives a second page session the page's commands and events", async () => { - const send = vi.fn(async () => ({})); + const send = vi.fn(async () => ({})); const written: Array> = []; - const relay = makeRelay(makeTarget(send), (raw) => written.push(JSON.parse(raw))); + const nativeAcks: Array = []; + const relay = makeRelay( + makeTarget(send, undefined, (frameId) => nativeAcks.push(frameId)), + (raw) => written.push(JSON.parse(raw)), + ); relay.receive(JSON.stringify({ id: 1, method: "Target.setAutoAttach", params: {} })); relay.receive(JSON.stringify({ id: 2, method: "Target.attachToBrowserTarget" })); await settle(); @@ -170,8 +176,63 @@ describe("CDP relay extra sessions", () => { await settle(); expect(send).toHaveBeenLastCalledWith("Page.startScreencast", {}, undefined, extra); written.length = 0; - relay.event("Page.screencastFrame", { data: "x" }, undefined); - expect(written.map((m) => m["sessionId"])).toEqual(["t3-preview-page", extra]); + const frame = (frameId: number) => + relay.event("Page.screencastFrame", { data: "x", sessionId: frameId }, undefined); + const ack = (sessionId: string, frameId: number) => + relay.receive( + JSON.stringify({ + id: 6, + method: "Page.screencastFrameAck", + params: { sessionId: frameId }, + sessionId, + }), + ); + const delivered = () => { + const frames = written + .filter((m) => m["method"]) + .map((m) => [m["sessionId"], (m["params"] as { sessionId?: number }).sessionId]); + written.length = 0; + return frames; + }; + frame(1); + expect(delivered()).toEqual([ + ["t3-preview-page", 1], + [extra, 1], + ]); + // A session that has not acknowledged its frame gets only the newest one, on its ack. + for (let i = 2; i < 100; i++) frame(i); + expect(delivered()).toEqual([]); + // Frames every session is busy for stay unacknowledged, so Chromium pauses at its in-flight limit. + expect(nativeAcks).toEqual([1]); + ack(extra, 1); + await settle(); + expect(delivered()).toEqual([[extra, 99]]); + // Each consumer ack lets Chromium send one more frame. + expect(nativeAcks).toEqual([1, 2]); + ack(extra, 99); + await settle(); + expect(delivered()).toEqual([]); + expect(nativeAcks).toEqual([1, 2, 3]); + frame(100); + expect(delivered()).toEqual([[extra, 100]]); + ack(extra, 99); + await settle(); + frame(101); + expect(delivered()).toEqual([]); + expect(send.mock.calls.some(([method]) => method === "Page.screencastFrameAck")).toBe(false); + // A restart keeps the unacknowledged frame's slot until its own acknowledgement. + relay.receive(JSON.stringify({ id: 7, method: "Page.stopScreencast", sessionId: extra })); + relay.receive(JSON.stringify({ id: 8, method: "Page.startScreencast", sessionId: extra })); + await settle(); + delivered(); + frame(102); + expect(delivered()).toEqual([]); + ack(extra, 100); + await settle(); + expect(delivered()).toEqual([[extra, 102]]); + // Other events still reach a session while its frame is unacknowledged. + relay.event("Page.loadEventFired", { timestamp: 1 }, undefined); + expect(delivered().map(([session]) => session)).toEqual(["t3-preview-page", extra]); }); }); diff --git a/apps/desktop/src/preview/CdpRelay.ts b/apps/desktop/src/preview/CdpRelay.ts index 658e1cf70623d..e9f3cf509b446 100644 --- a/apps/desktop/src/preview/CdpRelay.ts +++ b/apps/desktop/src/preview/CdpRelay.ts @@ -11,6 +11,8 @@ export interface CdpRelayTarget { readonly sessionDetached?: (owner: string) => void; /** Whether a page session, by owner as `send` names it, gets the page's screencast frames. */ readonly receivesScreencast: (owner: string | undefined) => boolean; + /** Acknowledges a native screencast frame, letting Chromium send the next one. */ + readonly ackScreencastFrame: (frameId: number) => void; /** The page's own CDP target id; Playwright keys the main frame by it. */ readonly targetId: () => Promise; readonly url: () => string; @@ -103,6 +105,20 @@ export function createCdpRelayConnection( let downloadEventsEnabled = false; /** Relay-created page sessions share one real debugger; child frame sessions retain their own ids. */ const sessions = new Map([[PAGE_SESSION_ID, "page"]]); + // Each page session holds one unacknowledged frame and keeps the newest one skipped meanwhile. + const unackedFrames = new Map(); + const skippedFrames = new Map>(); + // Chromium keeps a few frames in flight and reuses one frame id per run, so pacing it means + // leaving frames every session is busy for unacknowledged and releasing one per consumer ack. + const heldNativeFrames: Array = []; + const releaseHeldFramesWithoutHolders = () => { + if (skippedFrames.size === 0) + for (const frameId of heldNativeFrames.splice(0)) target.ackScreencastFrame(frameId); + }; + const sendFrame = (sessionId: string, params: Record) => { + unackedFrames.set(sessionId, params["sessionId"] as number); + send({ method: "Page.screencastFrame", params, sessionId }); + }; let sessionSequence = 0; const newSession = (kind: "browser" | "page") => { const sessionId = `t3-preview-${kind}-${++sessionSequence}`; @@ -170,6 +186,9 @@ export function createCdpRelayConnection( const sessionId = command.params?.["sessionId"]; if (typeof sessionId === "string" && sessionId !== PAGE_SESSION_ID) { sessions.delete(sessionId); + unackedFrames.delete(sessionId); + skippedFrames.delete(sessionId); + releaseHeldFramesWithoutHolders(); target.sessionDetached?.(sessionId); } return {}; @@ -202,6 +221,27 @@ export function createCdpRelayConnection( } // Playwright asks for this on every page; the host emulates focus only while it is in use. if (command.method === "Emulation.setFocusEmulationEnabled") return Promise.resolve({}); + if (command.method === "Page.screencastFrameAck" && child === undefined) { + // A restart keeps the slot too, or restarting would let frames past an unacknowledged one. + const sessionId = command.sessionId!; + if (unackedFrames.get(sessionId) === command.params?.["sessionId"]) { + unackedFrames.delete(sessionId); + const skipped = skippedFrames.get(sessionId); + skippedFrames.delete(sessionId); + if ( + skipped !== undefined && + target.receivesScreencast(sessionId === PAGE_SESSION_ID ? undefined : sessionId) + ) + sendFrame(sessionId, skipped); + const released = heldNativeFrames.shift(); + if (released !== undefined) target.ackScreencastFrame(released); + } + return Promise.resolve({}); + } + if (command.method === "Page.stopScreencast" && child === undefined) { + skippedFrames.delete(command.sessionId!); + releaseHeldFramesWithoutHolders(); + } // The shared debugger keeps Runtime enabled after an earlier connection, and a repeated // enable reports no existing contexts; Playwright waits for them, so reset once per connection. if (command.method === "Runtime.enable" && child === undefined) { @@ -262,15 +302,29 @@ export function createCdpRelayConnection( send({ method, params, sessionId }); return; } - for (const [id, kind] of sessions) { - if (kind !== "page") continue; - if ( - method === "Page.screencastFrame" && - !target.receivesScreencast(id === PAGE_SESSION_ID ? undefined : id) - ) - continue; - send({ method, params, sessionId: id }); + if (method === "Page.screencastFrame") { + const frame = (params ?? {}) as Record; + const frameId = frame["sessionId"]; + if (typeof frameId !== "number") return; + let held = false; + let delivered = false; + for (const [id, kind] of sessions) { + if (kind !== "page") continue; + if (!target.receivesScreencast(id === PAGE_SESSION_ID ? undefined : id)) continue; + if (unackedFrames.has(id)) { + skippedFrames.set(id, frame); + held = true; + } else { + sendFrame(id, frame); + delivered = true; + } + } + if (held && !delivered) heldNativeFrames.push(frameId); + else target.ackScreencastFrame(frameId); + return; } + for (const [id, kind] of sessions) + if (kind === "page") send({ method, params, sessionId: id }); }, }; } diff --git a/apps/desktop/src/preview/DesktopBrowserHost.test.ts b/apps/desktop/src/preview/DesktopBrowserHost.test.ts index d6f25d66aa810..cf6c7736f81d3 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.test.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.test.ts @@ -1157,6 +1157,13 @@ describe("DesktopBrowserHost", () => { debuggee.refuse(6); debuggee.release(); expect([yield* Queue.take(replies), yield* Queue.take(replies)].toSorted()).toEqual([5, 6]); + yield* raw({ + id: 7, + method: "Page.screencastFrameAck", + params: { sessionId: 1 }, + sessionId: "t3-preview-page-1", + }); + expect(yield* Queue.take(replies)).toBe(7); yield* settle; frames.length = 0; debuggee.emit("Page.screencastFrame", { data: "frame", sessionId: 2 }); diff --git a/apps/desktop/src/preview/DesktopBrowserHost.ts b/apps/desktop/src/preview/DesktopBrowserHost.ts index 296be2fa6709f..e0326b7b76ec1 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.ts @@ -421,6 +421,10 @@ export const make = Effect.gen(function* () { dropSessionHolds(tab, owner); }, receivesScreencast: (owner) => tab.screencasts.has(owner), + ackScreencastFrame: (frameId) => + void debuggee + .sendCommand("Page.screencastFrameAck", { sessionId: frameId }) + .catch(() => {}), targetId: () => debuggee .sendCommand("Target.getTargetInfo") diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index b30e39366a047..957a00d6a4b6b 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -1096,28 +1096,13 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function ), scope, ); - const handleDebuggerMessage = Effect.fnUntraced(function* ( - method: string, - params: Record, - ) { - if (method === "Page.screencastFrame") { - const sessionId = params["sessionId"]; - if (typeof sessionId === "number") { - yield* attemptPromise( - { - operation: "ackScreencastFrame", - webContentsId: wc.id, - }, - () => wcDebugger.sendCommand("Page.screencastFrameAck", { sessionId }), - ).pipe(Effect.ignore); - } - } + const handleDebuggerMessage = Effect.fnUntraced(function* (method: string) { if (method === "Runtime.consoleAPICalled" || method === "Runtime.exceptionThrown") { yield* Queue.offer(consoleReleases, undefined); } }); - const onMessage: BrowserControlSession["onMessage"] = (_event, method, params) => { - runFork(handleDebuggerMessage(method, params)); + const onMessage: BrowserControlSession["onMessage"] = (_event, method) => { + runFork(handleDebuggerMessage(method)); }; const control: BrowserControlSession = { webContentsId: wc.id, From 1fcb24518a04652fdc82ab6eea66b82b1a55335e Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 02:02:13 +1100 Subject: [PATCH 150/192] fix(server): Claude API-key logins show as Claude API Key in provider status (#29855) Co-authored-by: Julius Marminge --- apps/server/src/provider/ClaudeProvider.ts | 11 ++++++++--- apps/server/src/provider/ProviderRegistry.test.ts | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/server/src/provider/ClaudeProvider.ts b/apps/server/src/provider/ClaudeProvider.ts index 0fb7ea8b2e1fd..0d669659a09ce 100644 --- a/apps/server/src/provider/ClaudeProvider.ts +++ b/apps/server/src/provider/ClaudeProvider.ts @@ -177,11 +177,16 @@ function formatClaudeSubscriptionAuthLabel(subscriptionType: string): string { return `Claude ${subscriptionLabel} Subscription`; } +function hasApiKey(apiKeySource: string | undefined): boolean { + return Boolean(apiKeySource) && apiKeySource !== "none"; +} + function claudeAuthMetadata(input: { readonly subscriptionType: string | undefined; readonly authMethod: string | undefined; + readonly apiKeySource: string | undefined; }): { readonly type: string; readonly label: string } | undefined { - if (normalizeClaudeAuthMethod(input.authMethod) === "apiKey") { + if (normalizeClaudeAuthMethod(input.authMethod) === "apiKey" || hasApiKey(input.apiKeySource)) { return { type: "apiKey", label: "Claude API Key", @@ -216,8 +221,7 @@ function claudeAuthStatus( } if (capabilities.tokenSource !== "none") return "authenticated"; // API keys can authenticate with tokenSource: none; also check key and account fields. - const hasApiKey = Boolean(capabilities.apiKeySource) && capabilities.apiKeySource !== "none"; - return hasApiKey || capabilities.email || capabilities.subscriptionType + return hasApiKey(capabilities.apiKeySource) || capabilities.email || capabilities.subscriptionType ? "authenticated" : "unauthenticated"; } @@ -844,6 +848,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( claudeAuthMetadata({ subscriptionType: capabilities.subscriptionType, authMethod: capabilities.tokenSource, + apiKeySource: capabilities.apiKeySource, }); const usageCheckedAt = capabilities.usageCheckedAt ?? checkedAt; const usageLimits = !capabilities.usage diff --git a/apps/server/src/provider/ProviderRegistry.test.ts b/apps/server/src/provider/ProviderRegistry.test.ts index 564a90f40e6ce..be043bae0f847 100644 --- a/apps/server/src/provider/ProviderRegistry.test.ts +++ b/apps/server/src/provider/ProviderRegistry.test.ts @@ -4261,6 +4261,7 @@ it.layer( ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "authenticated"); + assert.strictEqual(status.auth.label, "Claude API Key"); }).pipe( Effect.provide( layerMockSpawner((args) => { From 86ec940ad3e825a15f85f34063c4329116483da8 Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 02:05:43 +1100 Subject: [PATCH 151/192] fix(mobile): a failed archive or delete keeps the earlier Undo (#29628) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Julius Marminge --- .../src/features/home/useThreadListActions.test.ts | 13 +++++++++++++ .../src/features/home/useThreadListActions.ts | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/mobile/src/features/home/useThreadListActions.test.ts b/apps/mobile/src/features/home/useThreadListActions.test.ts index 0bea39e419e21..d137eb99283bf 100644 --- a/apps/mobile/src/features/home/useThreadListActions.test.ts +++ b/apps/mobile/src/features/home/useThreadListActions.test.ts @@ -594,6 +594,19 @@ describe("thread action undo", () => { await undo(); expect(state.requests.map(({ action }) => action)).toEqual(["unpin"]); }); + + it("keeps the earlier Undo until archiving succeeds", async () => { + const actions = useThreadListActions(); + const thread = makeThread(); + await actions.settleThread(thread); + state.failedAction = "archive"; + expect(await actions.archiveThread(thread)).toBe(false); + expect(readThreadUndoNotice()?.action).toBe("Settled"); + + state.failedAction = null; + expect(await actions.archiveThread(thread)).toBe(true); + expect(readThreadUndoNotice()).toBeNull(); + }); }); describe("thread archive guard", () => { diff --git a/apps/mobile/src/features/home/useThreadListActions.ts b/apps/mobile/src/features/home/useThreadListActions.ts index 3cd180db6273f..27652e37588fa 100644 --- a/apps/mobile/src/features/home/useThreadListActions.ts +++ b/apps/mobile/src/features/home/useThreadListActions.ts @@ -145,9 +145,6 @@ function useThreadActionExecutor( return false; } if (action === "unsettle") ThreadUndo.invalidate("settle", key); - if (action === "archive" || action === "delete") { - for (const kind of ["settle", "snooze", "pin"]) ThreadUndo.invalidate(kind, key); - } onStarted?.(); const result = await withThreadDismissal( key, @@ -177,6 +174,9 @@ function useThreadActionExecutor( Dialog.alert(actionFailureTitle(action), actionFailureMessage(action, result.cause)); return false; } + if (action === "archive" || action === "delete") { + for (const kind of ["settle", "snooze", "pin"]) ThreadUndo.invalidate(kind, key); + } if (action === "archive" || action === "unarchive" || action === "delete") { refreshArchivedThreadsForEnvironment(thread.environmentId); } From 66a40c7ca8223b2126a2a4baf94e7374169c0703 Mon Sep 17 00:00:00 2001 From: Jex Date: Sat, 14 Nov 2026 23:10:20 +0800 Subject: [PATCH 152/192] fix(server): turns stuck in provider retries for 10 minutes fail instead of running forever (#29208) Co-authored-by: maria-rcks Co-authored-by: Julius Marminge --- .../Adapters/ClaudeAdapterV2.test.ts | 168 ++++++++++++++++++ .../Adapters/ClaudeAdapterV2.ts | 67 ++++++- .../Adapters/CodexAdapterV2.test.ts | 39 ++-- .../Adapters/CodexAdapterV2.ts | 12 +- packages/provider-core/src/server/failure.ts | 7 + 5 files changed, 269 insertions(+), 24 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index 92f0e4205b639..b232252ee7fbe 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -11409,7 +11409,175 @@ describe("ClaudeAdapterV2 background wake turns", () => { }), ); yield* harness.awaitTerminal(1); + // A recovered retry must not fire its budget later. + yield* TestClock.adjust("10 minutes"); + assert.lengthOf(harness.terminalEvents(), 1); + assert.equal(harness.terminalEvents()[0]?.status, "completed"); + }).pipe( + Effect.provide( + Layer.mergeAll( + IdAllocator.layer, + McpProviderSessions.layer, + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ), + ); + + const offerApiRetry = (harness: Effect.Success, uuid: string) => + Effect.gen(function* () { + yield* Queue.offer( + harness.sdkMessages, + claudeSdkFrame({ + type: "system", + subtype: "api_retry", + attempt: 1, + max_retries: 10, + retry_delay_ms: 600, + error_status: 503, + error: "server_error", + uuid, + session_id: WAKE_NATIVE_SESSION, + }), + ); + yield* harness.until(() => + harness.events.some( + (event) => + event.type === "turn_item.updated" && + event.turnItem.type === "error" && + event.turnItem.status === "running", + ), + ); + }); + + it.effect.each(["closed", "interrupt-hangs", "unconfirmed", "close-hangs", "synthetic"] as const)( + "fails a turn whose API retries outlast the retry budget: %s", + (response) => + Effect.gen(function* () { + const interruptSent = yield* Deferred.make(); + const closeRequested = yield* Deferred.make(); + const releaseClose = yield* Deferred.make(); + const harness = yield* makeWakeHarnessWithOptions({ + interrupt: Deferred.succeed(interruptSent, undefined).pipe( + Effect.andThen(response === "interrupt-hangs" ? Effect.never : Effect.void), + ), + close: (sdkMessages) => + Deferred.succeed(closeRequested, undefined).pipe( + Effect.andThen( + response === "close-hangs" + ? Deferred.await(releaseClose).pipe(Effect.andThen(Queue.shutdown(sdkMessages))) + : response === "unconfirmed" + ? Effect.void + : Queue.shutdown(sdkMessages), + ), + ), + }); + yield* Effect.addFinalizer(() => Deferred.succeed(releaseClose, undefined)); + yield* harness.runtime.startTurn( + makeClaudeTestTurnInput({ + threadId: harness.threadId, + providerThread: harness.providerThread, + now: yield* DateTime.now, + attemptId: RunAttemptId.make("attempt-claude-retry-budget"), + text: "Summarize the repo.", + attachments: [], + }), + ); + yield* offerApiRetry(harness, "00000000-0000-4000-8000-000000000211"); + if (response === "synthetic") { + // The CLI reports the API error as a assistant frame; that is not recovery. + yield* Queue.offer( + harness.sdkMessages, + claudeSdkFrame({ + type: "assistant", + message: { + model: "", + id: "retry-notice", + type: "message", + role: "assistant", + content: [{ type: "text", text: "API Error: 503" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + input_tokens: 0, + output_tokens: 0, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + }, + parent_tool_use_id: null, + uuid: "retry-notice", + session_id: WAKE_NATIVE_SESSION, + }), + ); + yield* harness.until(() => + harness.events.some( + (event) => + event.type === "turn_item.updated" && event.turnItem.type === "assistant_message", + ), + ); + } + yield* TestClock.adjust("9 minutes"); + assert.lengthOf(harness.terminalEvents(), 0); + yield* TestClock.adjust("1 minute"); + yield* Deferred.await(interruptSent); + if (response !== "closed" && response !== "synthetic") { + yield* TestClock.adjust("20 seconds"); + } + const terminal = yield* Queue.take(harness.terminalReceipts); + assert.equal(terminal.status, "failed"); + if (terminal.status !== "failed") return; + assert.equal(terminal.failure.code, "api_error_503"); + assert.equal(terminal.retry?.attempt, 1); + assert.lengthOf(harness.terminalEvents(), 1); + assert.isTrue(yield* Deferred.isDone(closeRequested), "the query must be closed"); + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll( + IdAllocator.layer, + McpProviderSessions.layer, + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ), + ); + + it.effect("keeps a user Stop interrupted when the retry budget passes before it settles", () => + Effect.gen(function* () { + const harness = yield* makeWakeHarness; + yield* harness.runtime.startTurn( + makeClaudeTestTurnInput({ + threadId: harness.threadId, + providerThread: harness.providerThread, + now: yield* DateTime.now, + attemptId: RunAttemptId.make("attempt-claude-retry-budget-user-stop"), + text: "Summarize the repo.", + attachments: [], + }), + ); + yield* offerApiRetry(harness, "00000000-0000-4000-8000-000000000212"); + const active = harness.events.find( + (event) => + event.type === "provider_turn.updated" && event.providerTurn.status === "running", + ); + if (active?.type !== "provider_turn.updated") + return yield* Effect.die("Missing running native turn"); + // The CLI has not confirmed the Stop when the budget passes. + yield* harness.runtime + .interruptTurn({ + providerThread: harness.providerThread, + providerTurnId: active.providerTurn.id, + }) + .pipe(Effect.ignore, Effect.forkScoped); + yield* TestClock.adjust("10 minutes"); + yield* Queue.shutdown(harness.sdkMessages); + assert.equal((yield* Queue.take(harness.terminalReceipts)).status, "interrupted"); + assert.lengthOf(harness.terminalEvents(), 1); }).pipe( + Effect.scoped, Effect.provide( Layer.mergeAll( IdAllocator.layer, diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 4ea7b6f8008e4..6c30816d1ff0d 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -227,6 +227,7 @@ import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import { makeProviderFailure, makeProviderRetryTurnItem, + PROVIDER_RETRY_BUDGET_MS, } from "@t3tools/provider-core/server/failure"; import { turnScopedSelectionTransition } from "@t3tools/provider-core/server/selectionTransition"; import { providerMessageTextWithAttachmentPaths } from "@t3tools/provider-core/server/attachmentPrompt"; @@ -4813,6 +4814,7 @@ function formatClaudeUsageLimitWait(waitMs: number): string { } interface ActiveClaudeTurnContext { + retryBudgetFailure?: OrchestrationV2ProviderFailure; readonly queryPolicy: ClaudeRuntimeQueryPolicy; readonly input: ProviderAdapter.ProviderAdapterV2TurnInput; readonly autoCompactWindow: string; @@ -6213,6 +6215,51 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( }).pipe(Effect.withSpan("ClaudeAdapterV2.completeProviderRetry")); }); + // A hung request can take many minutes per attempt, so the budget spans the + // whole retry window rather than counting attempts. + const failExpiredProviderRetry = Effect.fn("ClaudeAdapterV2.failExpiredProviderRetry")( + function* (context: ActiveClaudeTurnContext, startedAt: DateTime.Utc) { + const isCurrentRetry = Effect.gen(function* () { + const retry = (yield* Ref.get(providerRetries)).get(context.providerTurnId); + return retry?.startedAt === startedAt && (yield* Ref.get(activeTurn)) === context + ? retry + : undefined; + }); + const retry = yield* isCurrentRetry; + // A user Stop already in flight keeps its interrupted outcome. + if ( + retry === undefined || + (yield* Ref.get(interruptedTurns)).has(context.providerTurnId) + ) + return; + context.retryBudgetFailure = { ...retry.failure, retryable: false }; + const liveQuery = yield* Ref.get(queryContext); + yield* interruptTurn({ + providerThread: context.input.providerThread, + providerTurnId: context.providerTurnId, + }).pipe( + Effect.timeoutOption(CLAUDE_STOP_CONFIRM_TIMEOUT_MS), + Effect.catch((cause) => + Effect.logWarning("orchestration-v2.claude-retry-budget-stop-failed", { cause }), + ), + ); + if (liveQuery === null || (yield* isCurrentRetry) === undefined) return; + // The CLI did not confirm the stop; settle the turn without it. + const ownedLiveQuery = yield* Ref.modify(queryContext, (current) => + current?.query === liveQuery.query ? [true, null] : [false, current], + ); + if (!ownedLiveQuery) return; + liveQuery.stopping = true; + yield* liveQuery.query.close.pipe( + Effect.timeoutOption(CLAUDE_STOP_CONFIRM_TIMEOUT_MS), + Effect.ignore, + ); + yield* finalizeActiveTurnAfterQueryExit(liveQuery); + yield* endSubagentCallsOfClosedQuery(liveQuery.nativeThreadId, "interrupted"); + yield* Deferred.succeed(liveQuery.closed, undefined); + }, + ); + const rememberProviderThread = /* Provider events include streaming deltas; avoid a span for each update. */ Effect.fnUntraced(function* (providerThread: OrchestrationV2ProviderThread) { @@ -8879,7 +8926,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( } }); - const finalizeActiveTurn = Effect.fnUntraced(function* (input: { + const finalizeActiveTurn = Effect.fnUntraced(function* (requested: { readonly context: ActiveClaudeTurnContext; readonly status: Extract< OrchestrationV2ProviderTurn["status"], @@ -8890,6 +8937,12 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( readonly threadDisposition?: "reusable" | "broken"; readonly result?: SDKResultMessage; }) { + // A turn stopped by the retry budget settles as failed with the retry's error. + const retryBudgetFailure = requested.context.retryBudgetFailure; + const input = + requested.status === "interrupted" && retryBudgetFailure !== undefined + ? { ...requested, status: "failed" as const, failure: retryBudgetFailure } + : requested; input.context.steersClosed = true; yield* reasoningDeltas.flushTurn(input.context.nativeTurnId); if (input.status === "completed") { @@ -10747,12 +10800,20 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( updatedAt, }), }); + if (previous === undefined) { + yield* failExpiredProviderRetry(context, state.startedAt).pipe( + Effect.delay(PROVIDER_RETRY_BUDGET_MS), + Effect.forkIn(sessionScope), + ); + } return; } if (message.type === "assistant" && input.replayed !== true) { - const now = yield* DateTime.now; - yield* completeProviderRetry(context, now); + if (message.message.model !== "") { + const now = yield* DateTime.now; + yield* completeProviderRetry(context, now); + } // CLI-generated frames report zero usage, not context usage. if ( message.parent_tool_use_id === null && diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 76c10382b3cb8..b41e6e0c13345 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -4596,27 +4596,30 @@ describe("CodexAdapterV2 post-settle continuation", () => { const scenario = "codex-provider-retry-stall"; const nativeThreadId = `native-${scenario}-thread`; const nativeTurnId = `native-${scenario}-turn`; + const retryNotice = (attempt: number) => ({ + type: "emit_inbound" as const, + label: `error/retry-${attempt}`, + frame: { + method: "error", + params: { + threadId: nativeThreadId, + turnId: nativeTurnId, + willRetry: true, + error: { + message: `Reconnecting... ${attempt}/5`, + additionalDetails: "exceeded retry limit, last status: 429 Too Many Requests", + codexErrorInfo: { responseStreamDisconnected: { httpStatusCode: 429 } }, + }, + }, + }, + }); const transcript = makeCodexReplayTranscript({ scenario, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "Continue." }), - { - type: "emit_inbound", - label: "error/retry", - frame: { - method: "error", - params: { - threadId: nativeThreadId, - turnId: nativeTurnId, - willRetry: true, - error: { - message: "Reconnecting... 1/5", - additionalDetails: "exceeded retry limit, last status: 429 Too Many Requests", - codexErrorInfo: { responseStreamDisconnected: { httpStatusCode: 429 } }, - }, - }, - }, - }, + retryNotice(1), + // A later notice in the same retry window must not push the deadline back. + { ...retryNotice(2), afterMs: 5 * 60 * 1000 }, { type: "expect_outbound", label: "turn/interrupt", @@ -4675,7 +4678,7 @@ describe("CodexAdapterV2 post-settle continuation", () => { terminal.failure.message, "exceeded retry limit, last status: 429 Too Many Requests", ); - assert.equal(terminal.retry?.attempt, 1); + assert.equal(terminal.retry?.attempt, 2); assert.equal(terminal.threadDisposition, "reusable"); }).pipe( Effect.provide( diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index f7d3b49a687e5..1589b7d1ea46f 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -144,6 +144,7 @@ import { makeProviderFailure, makeProviderFailureTurnItem, makeProviderRetryTurnItem, + PROVIDER_RETRY_BUDGET_MS, } from "@t3tools/provider-core/server/failure"; import { turnScopedSelectionTransition } from "@t3tools/provider-core/server/selectionTransition"; import { @@ -162,8 +163,6 @@ const CODEX_PROVIDER = ProviderDriverKind.make("codex"); const CODEX_CAPACITY_MAX_RETRIES = 5; // How long Stop waits for Codex to end the turn before the user is offered Force stop. const CODEX_STOP_CONFIRM_TIMEOUT_MS = 10_000; -// Codex can announce a retry and never resume. Recovered retries resume model output within ~75s. -const CODEX_PROVIDER_RETRY_STALL_TIMEOUT = "10 minutes"; export const CODEX_DRIVER_KIND = CODEX_PROVIDER; export const CODEX_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(CODEX_DRIVER_KIND); @@ -5173,7 +5172,14 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( return updated; }); yield* failStalledProviderRetry(context, state, payload.error.codexErrorInfo).pipe( - Effect.delay(CODEX_PROVIDER_RETRY_STALL_TIMEOUT), + // Every notice aims at the same deadline, so repeated notices cannot extend it. + Effect.delay( + Math.max( + 0, + PROVIDER_RETRY_BUDGET_MS - + (DateTime.toEpochMillis(updatedAt) - DateTime.toEpochMillis(state.startedAt)), + ), + ), Effect.forkIn(scope, { startImmediately: true }), ); yield* emitProviderEvent({ diff --git a/packages/provider-core/src/server/failure.ts b/packages/provider-core/src/server/failure.ts index d5e9277633bc8..86919622f03d4 100644 --- a/packages/provider-core/src/server/failure.ts +++ b/packages/provider-core/src/server/failure.ts @@ -333,6 +333,13 @@ export function makeProviderFailureTurnItem(input: { }; } +/** + * Longest a turn may stay in provider retries before the adapter stops it as + * failed. Codex waits for the network without limit and a hung Claude request + * can take many minutes per attempt, so counting attempts alone never ends them. + */ +export const PROVIDER_RETRY_BUDGET_MS = 10 * 60 * 1000; + export function makeProviderRetryTurnItem(input: { readonly driver: ProviderDriverKind; readonly threadId: ThreadId; From 382312aba8fd4b2197d2f902495e5ab765aee3ec Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Sun, 15 Nov 2026 02:10:32 +1100 Subject: [PATCH 153/192] fix(web): the sidebar shortcut honors terminal and preview focus conditions (#29612) Co-authored-by: maria-rcks Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Julius Marminge --- .../AppSidebarLayout.browser.test.tsx | 42 +++++++++++++++++++ apps/web/src/components/AppSidebarLayout.tsx | 16 ++++--- 2 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/components/AppSidebarLayout.browser.test.tsx diff --git a/apps/web/src/components/AppSidebarLayout.browser.test.tsx b/apps/web/src/components/AppSidebarLayout.browser.test.tsx new file mode 100644 index 0000000000000..c97ea5e6412aa --- /dev/null +++ b/apps/web/src/components/AppSidebarLayout.browser.test.tsx @@ -0,0 +1,42 @@ +import { page, userEvent } from "vite-plus/test/browser"; +import { describe, expect, it, vi } from "vite-plus/test"; + +import { render } from "../../test/render"; + +vi.mock(import("../state/server"), async (original) => { + const actual = await original(); + const { Atom } = await import("effect/reactivity"); + const { compileResolvedKeybindingsConfig } = await import("@t3tools/shared/keybindings"); + const keybindings = compileResolvedKeybindingsConfig([ + { key: "mod+b", command: "sidebar.toggle", when: "!terminalFocus" }, + ]); + return { ...actual, primaryServerKeybindingsAtom: Atom.make(keybindings) }; +}); +// The thread list needs live environments; the shortcut lives in the shell around it. +vi.mock(import("./Sidebar"), () => ({ default: (() => null) as never })); + +import { AppSidebarLayout } from "./AppSidebarLayout"; + +describe("sidebar toggle shortcut", () => { + it("leaves the key to a focused terminal when its rule excludes terminalFocus", async () => { + await page.viewport(1280, 800); + using _platform = vi.spyOn(navigator, "platform", "get").mockReturnValue("Linux x86_64"); + await using screen = await render( + +

+

VR+L@p|AO%1iye1=1VE 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 delta 166649 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�Hd$ozVDG|0G3gsZ%%q$W>>%f@ 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