diff --git a/patches-port/0004-Suggest-the-same-model-on-a-subscription-with-more-r.patch b/patches-port/0004-Suggest-the-same-model-on-a-subscription-with-more-r.patch
index 6189fb4..1c0567f 100644
--- a/patches-port/0004-Suggest-the-same-model-on-a-subscription-with-more-r.patch
+++ b/patches-port/0004-Suggest-the-same-model-on-a-subscription-with-more-r.patch
@@ -24,7 +24,7 @@ explains why and when a started thread cannot switch.
create mode 100644 apps/web/src/components/chat/SubscriptionSwitch.logic.ts
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
-index e6e37e0c32..e7e0b72af5 100644
+index 57453af5b8..0a76fc890e 100644
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -219,6 +219,8 @@ import {
@@ -54,7 +54,7 @@ index e6e37e0c32..e7e0b72af5 100644
const activeProviderName =
activeProviderStatus?.displayName?.trim() || activeProviderStatus?.instanceId || "";
const composerStats = (() => {
-@@ -3860,6 +3873,13 @@ export default function ChatView(props: ChatViewProps) {
+@@ -3873,6 +3886,13 @@ export default function ChatView(props: ChatViewProps) {
: compactDisabledReason
}
isCompacting={isCompacting}
diff --git a/patches-port/base.txt b/patches-port/base.txt
index 8fc6541..ff3e296 100644
--- a/patches-port/base.txt
+++ b/patches-port/base.txt
@@ -1 +1 @@
-80120b1aa34a3125c05da393c8616c2076f5888d
+115739c1e02e181d6c176a4d8b43716f8f83d1d4
diff --git a/patches-tt/0001-fix-server-measure-Claude-context-against-autoCompac.patch b/patches-tt/0001-fix-server-measure-Claude-context-against-autoCompac.patch
index 5f4f3de..31bebf0 100644
--- a/patches-tt/0001-fix-server-measure-Claude-context-against-autoCompac.patch
+++ b/patches-tt/0001-fix-server-measure-Claude-context-against-autoCompac.patch
@@ -13,10 +13,10 @@ Subject: [PATCH 01/10] fix(server): measure Claude context against
5 files changed, 157 insertions(+), 8 deletions(-)
diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
-index 5f6b80fc6e..eea8894879 100644
+index 065f16c37c..7317d2f873 100644
--- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
+++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
-@@ -745,6 +745,27 @@ describe("ClaudeAdapterV2 context usage", () => {
+@@ -746,6 +746,27 @@ describe("ClaudeAdapterV2 context usage", () => {
);
assert.strictEqual(usage.promptCacheExpiresAt, "2026-08-29T00:05:00.000Z");
});
@@ -45,10 +45,10 @@ index 5f6b80fc6e..eea8894879 100644
describe("ClaudeAdapterV2 approval presentation", () => {
diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
-index 21955d95cc..a71c5edd50 100644
+index cdb826fd83..d8cb1c692d 100644
--- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
+++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
-@@ -142,6 +142,7 @@ import {
+@@ -150,6 +150,7 @@ import {
import {
claudeLaunchArgDirectories,
discoverClaudeSkills,
@@ -56,7 +56,7 @@ index 21955d95cc..a71c5edd50 100644
} from "../../provider/Drivers/ClaudeSkills.ts";
import {
compileClaudeModelSelection,
-@@ -220,6 +221,23 @@ function claudeContextWindow(modelSelection: ModelSelection): number | null {
+@@ -228,6 +229,23 @@ function claudeContextWindow(modelSelection: ModelSelection): number | null {
);
}
@@ -80,7 +80,7 @@ index 21955d95cc..a71c5edd50 100644
interface ClaudeCacheCreation {
readonly ephemeral_1h_input_tokens: number;
readonly ephemeral_5m_input_tokens: number;
-@@ -270,11 +288,13 @@ export function claudeProviderTurnTokenUsage(
+@@ -278,11 +296,13 @@ export function claudeProviderTurnTokenUsage(
updatedAt: string,
autoCompactWindow = "",
requestedAt?: DateTime.Utc,
@@ -95,7 +95,7 @@ index 21955d95cc..a71c5edd50 100644
// The cache lifetime starts with the request, not when its response ends.
const promptCacheExpiresAt =
promptCacheTtlMs === undefined || requestedAt === undefined
-@@ -282,13 +302,13 @@ export function claudeProviderTurnTokenUsage(
+@@ -290,13 +310,13 @@ export function claudeProviderTurnTokenUsage(
: DateTime.formatIso(DateTime.add(requestedAt, { milliseconds: promptCacheTtlMs }));
return {
usedTokens: inputTokens + outputTokens,
@@ -111,7 +111,7 @@ index 21955d95cc..a71c5edd50 100644
...(promptCacheExpiresAt === undefined ? {} : { promptCacheExpiresAt }),
updatedAt,
};
-@@ -3978,6 +3998,8 @@ function formatClaudeUsageLimitWait(waitMs: number): string {
+@@ -4088,6 +4108,8 @@ function formatClaudeUsageLimitWait(waitMs: number): string {
interface ActiveClaudeTurnContext {
readonly queryPolicy: ClaudeRuntimeQueryPolicy;
readonly responseThroughput: ClaudeResponseThroughput;
@@ -120,7 +120,7 @@ index 21955d95cc..a71c5edd50 100644
readonly input: ProviderAdapter.ProviderAdapterV2TurnInput;
readonly nativeThreadId: string;
readonly nativeTurnId: string;
-@@ -4691,6 +4713,16 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
+@@ -4808,6 +4830,16 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
model: input.modelSelection.model,
now,
});
@@ -137,7 +137,7 @@ index 21955d95cc..a71c5edd50 100644
const events = yield* Queue.unbounded
();
const activeTurn = yield* Ref.make(null);
const taskOutputTails = new Map<
-@@ -5035,6 +5067,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
+@@ -5157,6 +5189,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
DateTime.formatIso(now),
adapterOptions.settings.autoCompactWindow,
context.streamUsage?.requestedAt,
@@ -145,7 +145,7 @@ index 21955d95cc..a71c5edd50 100644
);
if (tokenUsage.inputTokens <= 0) return;
yield* emitProviderEvent({
-@@ -8620,7 +8653,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
+@@ -8854,7 +8887,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
? Math.round(postTokens)
: undefined;
if (afterTokenCount !== undefined) {
@@ -154,7 +154,7 @@ index 21955d95cc..a71c5edd50 100644
yield* emitProviderEvent({
type: "provider_turn.updated",
driver: CLAUDE_PROVIDER,
-@@ -8641,10 +8674,10 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
+@@ -8875,10 +8908,10 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
completedAt: null,
tokenUsage: {
usedTokens: afterTokenCount,
@@ -167,7 +167,7 @@ index 21955d95cc..a71c5edd50 100644
),
updatedAt: DateTime.formatIso(now),
},
-@@ -10490,6 +10523,11 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
+@@ -10788,6 +10821,11 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
const querySession = yield* openQuery(turnInput, nativeThreadId);
const context: ActiveClaudeTurnContext = {
responseThroughput: new ClaudeResponseThroughput(),
@@ -242,10 +242,10 @@ index 860280aa91..d6f8aedffe 100644
+ );
+});
diff --git a/apps/server/src/provider/Drivers/ClaudeSkills.ts b/apps/server/src/provider/Drivers/ClaudeSkills.ts
-index 6aff76d250..d3baccf2ae 100644
+index c73385374f..4954ae485d 100644
--- a/apps/server/src/provider/Drivers/ClaudeSkills.ts
+++ b/apps/server/src/provider/Drivers/ClaudeSkills.ts
-@@ -485,6 +485,51 @@ export function claudeLaunchArgDirectories(launchArgs?: string): ReadonlyArray {
const thread = options.thread === undefined ? {} : options.thread;
const scope = makeScope(options.capabilities ?? new Set(["preview", "worktree"]));
@@ -76,7 +76,7 @@ index 3be8705cc0..0a715a2f89 100644
(options.dispatchGate ?? Effect.void).pipe(
Effect.andThen(
options.dispatchInterrupts
-@@ -127,7 +132,45 @@ const makeHarness = (options: HarnessOptions = {}) => {
+@@ -128,7 +133,45 @@ const makeHarness = (options: HarnessOptions = {}) => {
? Effect.die(new Error("dispatch defect"))
: options.dispatchFails
? (Effect.fail("simulated dispatch failure") as never)
@@ -123,7 +123,7 @@ index 3be8705cc0..0a715a2f89 100644
),
),
);
-@@ -175,22 +218,9 @@ const makeHarness = (options: HarnessOptions = {}) => {
+@@ -176,22 +219,9 @@ const makeHarness = (options: HarnessOptions = {}) => {
? Effect.succeed(makeProjection(thread))
: Effect.fail(new OrchestratorProjectionError({ threadId: id }));
});
@@ -149,7 +149,7 @@ index 3be8705cc0..0a715a2f89 100644
const getById = vi.fn((id: ProjectId) =>
options.projectReadFails
? (Effect.fail("simulated project read failure") as never)
-@@ -370,19 +400,68 @@ const runStatus = (harness: ReturnType) =>
+@@ -372,19 +402,68 @@ const runStatus = (harness: ReturnType) =>
const decodeHandoffInput = Schema.decodeUnknownEffect(WorktreeMcpHandoffInput);
describe("t3_worktree_handoff", () => {
@@ -224,7 +224,7 @@ index 3be8705cc0..0a715a2f89 100644
it.effect("does not queue a continuation when the thread update fails", () => {
const harness = makeHarness({ dispatchFails: true });
return Effect.gen(function* () {
-@@ -586,6 +665,9 @@ describe("t3_worktree_handoff", () => {
+@@ -588,6 +667,9 @@ describe("t3_worktree_handoff", () => {
const gate = yield* Deferred.make();
const harness = makeHarness({ dispatchGate: Deferred.await(gate) });
@@ -234,7 +234,7 @@ index 3be8705cc0..0a715a2f89 100644
const fiber = yield* Effect.forkChild(
runHandoff(harness, {
branch: "feature/interrupted",
-@@ -600,7 +682,14 @@ describe("t3_worktree_handoff", () => {
+@@ -602,7 +684,14 @@ describe("t3_worktree_handoff", () => {
yield* Fiber.join(interruption);
expect(harness.dispatch).toHaveBeenCalledTimes(1);
@@ -251,7 +251,7 @@ index 3be8705cc0..0a715a2f89 100644
}),
);
diff --git a/apps/server/src/mcp/WorktreeMcpService.ts b/apps/server/src/mcp/WorktreeMcpService.ts
-index 267df07f24..9624a0eb5c 100644
+index 8a2231f6e4..a0ef941dbd 100644
--- a/apps/server/src/mcp/WorktreeMcpService.ts
+++ b/apps/server/src/mcp/WorktreeMcpService.ts
@@ -130,7 +130,6 @@ const make = Effect.gen(function* () {
@@ -341,7 +341,7 @@ index 92e1e01ad4..3fd9c675aa 100644
}),
Schema.Struct({
diff --git a/apps/server/src/orchestration-v2/EffectWorker.ts b/apps/server/src/orchestration-v2/EffectWorker.ts
-index 534043aeb4..6c71a7b0be 100644
+index c5b5ea9b38..443e58615f 100644
--- a/apps/server/src/orchestration-v2/EffectWorker.ts
+++ b/apps/server/src/orchestration-v2/EffectWorker.ts
@@ -124,6 +124,7 @@ export const layerExecutor: Layer.Layer<
@@ -353,10 +353,10 @@ index 534043aeb4..6c71a7b0be 100644
? {}
: { revokeMcpCredential: effect.request.revokeMcpCredential }),
diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts
-index 35e9a78c4c..0f016d17ea 100644
+index 59b708f5ba..06a2cba3a6 100644
--- a/apps/server/src/orchestration-v2/Orchestrator.ts
+++ b/apps/server/src/orchestration-v2/Orchestrator.ts
-@@ -3087,6 +3087,26 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -3158,6 +3158,26 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
cause: `Thread ${command.threadId} branch changed before the metadata update could be applied.`,
});
}
@@ -383,7 +383,7 @@ index 35e9a78c4c..0f016d17ea 100644
if (command.type === "thread.metadata.update" && command.expectedEmpty === true) {
const records = yield* projectionStore
.getThreadRecords(command.threadId, ["runs"])
-@@ -4095,6 +4115,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -4179,6 +4199,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
? "Runtime mode changed."
: "Provider or model selection changed.",
// Terminal detaches revoke MCP credentials; reattachments need other detach reasons to preserve them.
@@ -393,7 +393,7 @@ index 35e9a78c4c..0f016d17ea 100644
...(command.type === "thread.archive" ? { revokeMcpCredential: true } : {}),
},
} satisfies PendingOrchestrationEffectV2;
-@@ -5267,6 +5290,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -5391,6 +5414,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
command: Extract,
events: Ref.Ref>,
effects: Ref.Ref>,
@@ -401,7 +401,7 @@ index 35e9a78c4c..0f016d17ea 100644
) {
let projection = yield* getProjectionWithPendingEvents(command.threadId, events);
if (command.manualContinuationOfRunId !== undefined) {
-@@ -5337,10 +5361,11 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -5461,10 +5485,11 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
const source = projection.runs.find((run) => run.id === command.restartContinuationOfRunId);
if (
!source ||
@@ -417,7 +417,7 @@ index 35e9a78c4c..0f016d17ea 100644
projection.thread.archivedAt !== null ||
projection.thread.deletedAt !== null ||
projection.thread.providerInstanceId !== source.providerInstanceId ||
-@@ -5353,6 +5378,13 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -5477,6 +5502,13 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
) ||
(yield* stopReachedRun(command, command.threadId, source.id))
) {
@@ -431,15 +431,15 @@ index 35e9a78c4c..0f016d17ea 100644
// Preserve the current row so stale automatic deliveries receive an
// accepted receipt without changing work or repeatedly retrying.
yield* emit(
-@@ -12975,7 +13007,6 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
- case "thread.pin.reorder":
+@@ -13168,7 +13200,6 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
case "thread.active.reorder":
+ case "thread.tag.set":
case "thread.mark-unread":
- case "thread.metadata.update":
case "thread.pull-request.link":
case "thread.pull-request.unlink":
case "thread.pull-request-link.sync":
-@@ -12988,6 +13019,34 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -13181,6 +13212,34 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
case "provider.switch":
yield* dispatchThreadMutation(command, events, effects);
break;
@@ -475,10 +475,10 @@ index 35e9a78c4c..0f016d17ea 100644
yield* dispatchPullRequestWatchSync(command, events, effects);
break;
diff --git a/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts b/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
-index 5bbb3926e2..1bac1bdbbe 100644
+index d8c046c018..12332c2aba 100644
--- a/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
+++ b/apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
-@@ -1583,8 +1583,8 @@ it.effect("ProviderSessionManagerV2 interrupts event subscriptions on server shu
+@@ -1680,8 +1680,8 @@ it.effect("ProviderSessionManagerV2 interrupts event subscriptions on server shu
modelSelection,
runtimePolicy,
});
@@ -489,7 +489,7 @@ index 5bbb3926e2..1bac1bdbbe 100644
const adapterQueue = (yield* Ref.get(state)).eventQueues.get(String(providerSessionId));
assert.isDefined(adapterQueue);
yield* Queue.offer(adapterQueue!, {
-@@ -1626,7 +1626,7 @@ it.effect("ProviderSessionManagerV2 drains subscribers when the provider stops",
+@@ -1723,7 +1723,7 @@ it.effect("ProviderSessionManagerV2 drains subscribers when the provider stops",
modelSelection,
runtimePolicy,
});
@@ -498,7 +498,7 @@ index 5bbb3926e2..1bac1bdbbe 100644
const collected = yield* subscription.events.pipe(Stream.runCollect, Effect.forkScoped);
const adapterQueue = (yield* Ref.get(state)).eventQueues.get(String(providerSessionId));
assert.isDefined(adapterQueue);
-@@ -3363,8 +3363,8 @@ it.effect(
+@@ -3460,8 +3460,8 @@ it.effect(
const subscribe = firstRuntime.subscribeEvents;
assert.isDefined(subscribe);
if (subscribe === undefined) return;
@@ -509,7 +509,7 @@ index 5bbb3926e2..1bac1bdbbe 100644
const queue = (yield* Ref.get(state)).eventQueues.get(String(providerSessionId));
assert.isDefined(queue);
yield* Queue.offer(queue!, {
-@@ -3474,7 +3474,7 @@ it.effect(
+@@ -3571,7 +3571,7 @@ it.effect(
const publish = Effect.fnUntraced(function* (
event: ProviderAdapter.ProviderAdapterV2Event,
) {
@@ -519,7 +519,7 @@ index 5bbb3926e2..1bac1bdbbe 100644
// Observing the exact event ensures the manager has handled it before resuming.
assert.deepEqual(yield* subscription.events.pipe(Stream.runHead), Option.some(event));
diff --git a/apps/server/src/orchestration-v2/ProviderSessionManager.ts b/apps/server/src/orchestration-v2/ProviderSessionManager.ts
-index 03a7278774..c65d2654d4 100644
+index f06011676c..8f37107019 100644
--- a/apps/server/src/orchestration-v2/ProviderSessionManager.ts
+++ b/apps/server/src/orchestration-v2/ProviderSessionManager.ts
@@ -187,6 +187,7 @@ export interface ProviderSessionManagerV2Shape {
@@ -724,10 +724,10 @@ index 03a7278774..c65d2654d4 100644
detached.value.attachedThreadIds.size === 0 &&
!detached.value.supportsMultipleProviderThreads
diff --git a/apps/server/src/orchestration-v2/RunExecutionService.test.ts b/apps/server/src/orchestration-v2/RunExecutionService.test.ts
-index dee246855b..12430742f4 100644
+index f3f1542146..24e85b857f 100644
--- a/apps/server/src/orchestration-v2/RunExecutionService.test.ts
+++ b/apps/server/src/orchestration-v2/RunExecutionService.test.ts
-@@ -2190,98 +2190,99 @@ it.effect(
+@@ -2214,98 +2214,99 @@ it.effect(
providerSessionId: ProviderSessionId.make("session:subagent-interrupt-cascade"),
session: {
events: Stream.empty,
@@ -915,7 +915,7 @@ index dee246855b..12430742f4 100644
startTurn: () => Effect.void,
} as unknown as ProviderAdapter.ProviderAdapterV2SessionRuntime,
run: {
-@@ -2618,6 +2619,126 @@ it.effect("does not refresh pull requests for auxiliary or stale provider termin
+@@ -2663,6 +2664,126 @@ it.effect("does not refresh pull requests for auxiliary or stale provider termin
}),
);
@@ -1042,7 +1042,7 @@ index dee246855b..12430742f4 100644
it.effect("refreshes pull requests after a provider stream exits with an error", () =>
Effect.gen(function* () {
const { written, observed } = yield* captureRootRunTermination({
-@@ -3092,6 +3213,7 @@ function captureRootRunTermination(input: {
+@@ -3167,6 +3288,7 @@ function captureRootRunTermination(input: {
});
const writtenItems = yield* Ref.make>([]);
const finalEvents = yield* Ref.make>([]);
@@ -1050,7 +1050,7 @@ index dee246855b..12430742f4 100644
const observed = yield* Ref.make>([]);
const submittedEffects = yield* Ref.make>([]);
const committedEffects = yield* Ref.make>([]);
-@@ -3153,7 +3275,10 @@ function captureRootRunTermination(input: {
+@@ -3228,7 +3350,10 @@ function captureRootRunTermination(input: {
}),
IdAllocator.layer,
Layer.mock(ProviderEventIngestor.ProviderEventIngestorV2)({
@@ -1062,7 +1062,7 @@ index dee246855b..12430742f4 100644
}),
ServerSettings.layerTest(),
Layer.succeed(RunFinalizationService.RunFinalizationObserver, {
-@@ -3176,37 +3301,38 @@ function captureRootRunTermination(input: {
+@@ -3251,37 +3376,38 @@ function captureRootRunTermination(input: {
providerSessionId: ProviderSessionId.make(`session:${input.key}`),
session: {
events: Stream.empty,
@@ -1131,7 +1131,7 @@ index dee246855b..12430742f4 100644
startTurn: input.startTurn ?? (() => Effect.void),
interruptTurn: input.interruptTurn ?? (() => Effect.void),
hasPendingBackgroundWorkForThread: input.hasPendingBackgroundWorkForThread,
-@@ -3279,6 +3405,7 @@ function captureRootRunTermination(input: {
+@@ -3357,6 +3483,7 @@ function captureRootRunTermination(input: {
previewActivityEnded,
written: yield* Ref.get(writtenItems),
finalEvents: yield* Ref.get(finalEvents),
@@ -1139,7 +1139,7 @@ index dee246855b..12430742f4 100644
observed: yield* Ref.get(observed),
submittedEffects: yield* Ref.get(submittedEffects),
committedEffects: yield* Ref.get(committedEffects),
-@@ -3354,7 +3481,7 @@ function childBackgroundTurnItemEvent(
+@@ -3432,7 +3559,7 @@ function childBackgroundTurnItemEvent(
function backgroundTurnItemEvent(
ids: BackgroundScenarioIds,
type: "command_execution" | "dynamic_tool" | "subagent",
@@ -1148,7 +1148,7 @@ index dee246855b..12430742f4 100644
ordinal: number,
itemId?: TurnItemId,
): ProviderAdapter.ProviderAdapterV2Event {
-@@ -3689,26 +3816,27 @@ function runBackgroundItemScenario(
+@@ -3767,26 +3894,27 @@ function runBackgroundItemScenario(
...(options?.hasPendingBackgroundWorkForThread === undefined
? {}
: { hasPendingBackgroundWorkForThread: options.hasPendingBackgroundWorkForThread }),
@@ -1197,7 +1197,7 @@ index dee246855b..12430742f4 100644
} as unknown as ProviderAdapter.ProviderAdapterV2SessionRuntime,
run: {
diff --git a/apps/server/src/orchestration-v2/RunExecutionService.ts b/apps/server/src/orchestration-v2/RunExecutionService.ts
-index b9b2b09bd2..8b0817865a 100644
+index 6a52fac447..c726b55d2e 100644
--- a/apps/server/src/orchestration-v2/RunExecutionService.ts
+++ b/apps/server/src/orchestration-v2/RunExecutionService.ts
@@ -5,6 +5,8 @@ import {
@@ -1209,7 +1209,7 @@ index b9b2b09bd2..8b0817865a 100644
type NodeId,
type OrchestrationV2AppThread,
type OrchestrationV2CheckpointScope,
-@@ -630,6 +632,7 @@ export const layer: Layer.Layer<
+@@ -632,6 +634,7 @@ export const layer: Layer.Layer<
// Root turn snapshot written with the run when T3 ends the run itself.
readonly abandonedProviderTurn?: OrchestrationV2ProviderTurn;
readonly terminal: ProviderTerminalEvent;
@@ -1217,7 +1217,7 @@ index b9b2b09bd2..8b0817865a 100644
readonly completedAt?: DateTime.Utc;
readonly failureItemPersisted: boolean;
readonly refreshAfterTurn: Effect.Effect;
-@@ -796,6 +799,9 @@ export const layer: Layer.Layer<
+@@ -800,6 +803,9 @@ export const layer: Layer.Layer<
providerThread: input.providerThread,
reason,
completedAt,
@@ -1227,17 +1227,17 @@ index b9b2b09bd2..8b0817865a 100644
}),
},
]
-@@ -995,6 +1001,9 @@ export const layer: Layer.Layer<
- const latestTurnItemOrdinal = yield* Ref.make(input.providerTurnOrdinal * 100);
- const latestProviderThread = yield* Ref.make(input.providerThread);
- const latestRootProviderTurn = yield* Ref.make(null);
+@@ -1024,6 +1030,9 @@ export const layer: Layer.Layer<
+ context: { ...analyticsContext, segment: "initial" },
+ });
+ }
+ // A planned detach closes ingestion before the adapter can end its open root items.
+ const openRootTurnItems = new Map();
+ const streamingRootMessages = new Map();
const routeIdentity: ProviderEventRouteIdentity = {
threadId: input.run.threadId,
runId: input.run.id,
-@@ -1004,7 +1013,7 @@ export const layer: Layer.Layer<
+@@ -1033,7 +1042,7 @@ export const layer: Layer.Layer<
const eventSubscription =
input.session.subscribeEvents === undefined
? { events: input.session.events, close: Effect.void }
@@ -1246,7 +1246,7 @@ index b9b2b09bd2..8b0817865a 100644
const inheritedBackgroundTurnItems = yield* (
input.loadInheritedBackgroundTurnItems?.() ?? Effect.succeed([])
).pipe(
-@@ -1036,6 +1045,7 @@ export const layer: Layer.Layer<
+@@ -1065,6 +1074,7 @@ export const layer: Layer.Layer<
);
const rootTerminalSeen = yield* Ref.make(false);
const rootRunFinalized = yield* Ref.make(false);
@@ -1254,7 +1254,7 @@ index b9b2b09bd2..8b0817865a 100644
const providerThreadOwnerLost = yield* Ref.make(false);
const pendingBackgroundProviderTurns = new Set();
const activeChildProviderTurns = yield* Ref.make>(new Set());
-@@ -1052,6 +1062,7 @@ export const layer: Layer.Layer<
+@@ -1081,6 +1091,7 @@ export const layer: Layer.Layer<
}
const providerThread = yield* Ref.get(latestProviderThread);
const openSubagents = yield* Ref.get(openRunOwnedSubagents);
@@ -1262,7 +1262,7 @@ index b9b2b09bd2..8b0817865a 100644
yield* writeFinalRunEvents({
run: input.run,
rootNode: input.rootNode,
-@@ -1079,6 +1090,7 @@ export const layer: Layer.Layer<
+@@ -1108,6 +1119,7 @@ export const layer: Layer.Layer<
: { loadRunInterruptReason: input.loadRunInterruptReason }),
openRunOwnedSubagents: openSubagents,
terminal,
@@ -1270,7 +1270,7 @@ index b9b2b09bd2..8b0817865a 100644
failureItemPersisted: terminal.status === "failed",
refreshAfterTurn,
}).pipe(
-@@ -1294,6 +1306,86 @@ export const layer: Layer.Layer<
+@@ -1323,6 +1335,86 @@ export const layer: Layer.Layer<
// as a dev server keep this consumer alive afterwards and must not pin preview focus.
const endPreviewActivity = yield* previewAgentActivity.begin(input.run.threadId);
const providerEventFiber = yield* eventSubscription.events.pipe(
@@ -1357,7 +1357,7 @@ index b9b2b09bd2..8b0817865a 100644
Stream.mapEffect((event) =>
Effect.gen(function* () {
const previous = yield* Ref.get(eventRouting);
-@@ -1384,6 +1476,16 @@ export const layer: Layer.Layer<
+@@ -1413,6 +1505,16 @@ export const layer: Layer.Layer<
) {
yield* Ref.set(latestRootProviderTurn, event.providerTurn);
}
@@ -1374,7 +1374,7 @@ index b9b2b09bd2..8b0817865a 100644
let storedEventCount = 0;
const deliveredEvent = filterAssistantEvent(
event,
-@@ -1464,6 +1566,15 @@ export const layer: Layer.Layer<
+@@ -1503,6 +1605,15 @@ export const layer: Layer.Layer<
yield* Ref.update(latestTurnItemOrdinal, (current) =>
Math.max(current, event.turnItem.ordinal),
);
@@ -1390,15 +1390,15 @@ index b9b2b09bd2..8b0817865a 100644
}
if (event.type === "turn.terminal") {
yield* Ref.set(terminalEvent, event);
-@@ -1778,6 +1889,7 @@ export function makeInterruptResultTurnItem(input: {
- /** The Stop's own reason, from its run_interrupt_request item. */
+@@ -1816,6 +1927,7 @@ export function makeInterruptResultTurnItem(input: {
+ readonly providerThread: OrchestrationV2ProviderThread;
readonly reason: string | undefined;
readonly completedAt: DateTime.Utc;
+ readonly message?: string;
}): OrchestrationV2TurnItem {
return {
id: input.idAllocator.derive.runSignalTurnItem({
-@@ -1801,6 +1913,6 @@ export function makeInterruptResultTurnItem(input: {
+@@ -1839,6 +1951,6 @@ export function makeInterruptResultTurnItem(input: {
completedAt: input.completedAt,
updatedAt: input.completedAt,
type: "run_interrupt_result",
@@ -1407,10 +1407,10 @@ index b9b2b09bd2..8b0817865a 100644
};
}
diff --git a/apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts b/apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts
-index d059c4c20e..a7f4e3e90f 100644
+index 4352d8b2d6..b0300960df 100644
--- a/apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts
+++ b/apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts
-@@ -17,6 +17,7 @@ import {
+@@ -18,6 +18,7 @@ import {
ThreadId,
} from "@t3tools/contracts";
import * as DateTime from "effect/DateTime";
@@ -1418,7 +1418,7 @@ index d059c4c20e..a7f4e3e90f 100644
import * as Effect from "effect/Effect";
import * as Fiber from "effect/Fiber";
import * as Layer from "effect/Layer";
-@@ -24,10 +25,12 @@ import * as Queue from "effect/Queue";
+@@ -25,10 +26,12 @@ import * as Queue from "effect/Queue";
import * as Ref from "effect/Ref";
import * as Stream from "effect/Stream";
@@ -1431,7 +1431,7 @@ index d059c4c20e..a7f4e3e90f 100644
import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
import { checkpointWorkspace } from "@t3tools/provider-testing/replayWorkspace";
-@@ -81,6 +84,11 @@ function makeRestartAdapter(
+@@ -86,6 +89,11 @@ function makeRestartAdapter(
state: Ref.Ref,
sessionCapabilities: OrchestrationV2ProviderCapabilities = pooledCapabilities,
providerInstanceId = initialSelection.instanceId,
@@ -1443,7 +1443,7 @@ index d059c4c20e..a7f4e3e90f 100644
): ProviderAdapter.ProviderAdapterV2["Service"] {
return {
instanceId: providerInstanceId,
-@@ -221,11 +229,21 @@ function makeRestartAdapter(
+@@ -226,11 +234,21 @@ function makeRestartAdapter(
},
],
}));
@@ -1466,7 +1466,7 @@ index d059c4c20e..a7f4e3e90f 100644
const occurredAt = yield* DateTime.now;
yield* Ref.update(state, (current) => ({ ...current, activeTurn: active }));
yield* Queue.offer(events, {
-@@ -249,6 +267,48 @@ function makeRestartAdapter(
+@@ -254,6 +272,48 @@ function makeRestartAdapter(
});
return;
}
@@ -1515,7 +1515,7 @@ index d059c4c20e..a7f4e3e90f 100644
yield* publishTerminal(active, "completed");
}),
steerTurn: () => Effect.void,
-@@ -886,3 +946,397 @@ it.live.each(["active", "idle", "selection-command", "pooled", "separate-home"]
+@@ -897,3 +957,397 @@ it.live.each(["active", "idle", "selection-command", "pooled", "separate-home"]
}),
),
);
@@ -1914,10 +1914,10 @@ index d059c4c20e..a7f4e3e90f 100644
+ ),
+);
diff --git a/apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts b/apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
-index afd11a9ab7..0530eacf0a 100644
+index 746ff0f844..48507aee25 100644
--- a/apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
+++ b/apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
-@@ -268,6 +268,7 @@ export function layerWithRegistry(
+@@ -276,6 +276,7 @@ export function layerWithRegistry(
SqlClient.SqlClient,
MigrationError | PlatformError.PlatformError | SqlError
>;
@@ -1925,7 +1925,7 @@ index afd11a9ab7..0530eacf0a 100644
readonly runEffectWorker?: boolean;
readonly runContinuationWorker?: boolean;
readonly recoverOnStartup?: boolean;
-@@ -383,7 +384,7 @@ export function layerWithRegistry(
+@@ -402,7 +403,7 @@ export function layerWithRegistry(
const layerRunExecutionServiceProvided = RunExecutionService.layer.pipe(
Layer.provide(
Layer.mergeAll(
@@ -1950,10 +1950,10 @@ index 9e5cf6c9d8..2a5c5f7cc4 100644
startup as well as live RPC traffic. Compatibility work must account for stored history, not just
what the newest client sends.
diff --git a/docs/user/composer.md b/docs/user/composer.md
-index 49f49a87e3..b4839da8e8 100644
+index a3e06c9aed..183b580b93 100644
--- a/docs/user/composer.md
+++ b/docs/user/composer.md
-@@ -65,6 +65,10 @@ If your **Send shortcut** requires `Cmd+Enter` or `Ctrl+Enter`, that shortcut us
+@@ -66,6 +66,10 @@ If your **Send shortcut** requires `Cmd+Enter` or `Ctrl+Enter`, that shortcut us
your default follow-up behavior; add **Shift** to use the opposite action. This
applies only to multiline messages when you choose the multiline send option.
@@ -1965,10 +1965,10 @@ index 49f49a87e3..b4839da8e8 100644
the oldest queued message as a steer. This leaves the current draft intact and
requires an active turn that supports steering. If that shortcut sends the opposite
diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts
-index e651d00b25..36c096a333 100644
+index 769a31daa9..8a054d175a 100644
--- a/packages/contracts/src/orchestrationV2.ts
+++ b/packages/contracts/src/orchestrationV2.ts
-@@ -3037,6 +3037,10 @@ export const OrchestrationV2Command = Schema.Union([
+@@ -3062,6 +3062,10 @@ export const OrchestrationV2Command = Schema.Union([
worktreePath: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
expectedWorktreePath: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
expectedBranch: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
@@ -1980,10 +1980,10 @@ index e651d00b25..36c096a333 100644
expectedEmpty: Schema.optional(Schema.Boolean),
limitRecovery: Schema.optional(Schema.NullOr(OrchestrationV2LimitRecoveryUpdate)),
diff --git a/packages/provider-core/src/server/ProviderAdapter.ts b/packages/provider-core/src/server/ProviderAdapter.ts
-index cadf4173a6..6103bd1728 100644
+index 9d83da489b..41832004e1 100644
--- a/packages/provider-core/src/server/ProviderAdapter.ts
+++ b/packages/provider-core/src/server/ProviderAdapter.ts
-@@ -382,6 +382,15 @@ const ProviderAdapterFailureReason = Schema.Literals([
+@@ -390,6 +390,15 @@ const ProviderAdapterFailureReason = Schema.Literals([
"attachment_unreadable",
]);
export type ProviderAdapterFailureReason = typeof ProviderAdapterFailureReason.Type;
@@ -1999,7 +1999,7 @@ index cadf4173a6..6103bd1728 100644
export class ProviderAdapterProtocolError extends Schema.TaggedError()(
"ProviderAdapterProtocolError",
-@@ -414,6 +423,7 @@ export const ProviderAdapterV2Error = Schema.Union([
+@@ -422,6 +431,7 @@ export const ProviderAdapterV2Error = Schema.Union([
ProviderAdapterInterruptTimeoutError,
ProviderAdapterRuntimeRequestResponseError,
ProviderAdapterEventStreamError,
@@ -2007,7 +2007,7 @@ index cadf4173a6..6103bd1728 100644
ProviderAdapterProtocolError,
]);
export type ProviderAdapterV2Error = typeof ProviderAdapterV2Error.Type;
-@@ -570,8 +580,10 @@ export interface ProviderAdapterV2SessionRuntime {
+@@ -579,8 +589,10 @@ export interface ProviderAdapterV2SessionRuntime {
readonly providerSessionId: ProviderSessionId;
readonly providerSession: OrchestrationV2ProviderSession;
readonly events: Stream.Stream;
diff --git a/patches-tt/0003-feat-nest-agent-opened-child-threads-under-their-ope.patch b/patches-tt/0003-feat-nest-agent-opened-child-threads-under-their-ope.patch
index 03d1bf7..10ccf33 100644
--- a/patches-tt/0003-feat-nest-agent-opened-child-threads-under-their-ope.patch
+++ b/patches-tt/0003-feat-nest-agent-opened-child-threads-under-their-ope.patch
@@ -10,7 +10,7 @@ a child nests under its opener only within the same inbox section, with a
guide line, context-menu collapse and a summary stub.
---
apps/server/src/mcp/OrchestratorMcpService.ts | 1 +
- .../src/mcp/toolkits/project/handlers.test.ts | 78 +++
+ .../src/mcp/toolkits/project/handlers.test.ts | 79 ++-
.../src/mcp/toolkits/project/handlers.ts | 1 +
.../src/orchestration-v2/Orchestrator.ts | 10 +-
.../src/orchestration-v2/ProjectionStore.ts | 15 +
@@ -38,7 +38,7 @@ guide line, context-menu collapse and a summary stub.
.../src/state/threadCommands.ts | 14 +-
.../src/state/threadShell.test.ts | 37 ++
packages/contracts/src/orchestrationV2.ts | 25 +
- 29 files changed, 2471 insertions(+), 49 deletions(-)
+ 29 files changed, 2471 insertions(+), 50 deletions(-)
create mode 100644 apps/web/src/components/Sidebar.childThreads.browser.test.tsx
create mode 100644 apps/web/src/components/Sidebar.childThreads.test.ts
create mode 100644 apps/web/src/components/Sidebar.childThreads.tsx
@@ -46,10 +46,10 @@ guide line, context-menu collapse and a summary stub.
create mode 100644 apps/web/src/components/Sidebar.families.ts
diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts
-index b124a03ea2..c64ff7af9d 100644
+index b937453f39..2a2b7c37f5 100644
--- a/apps/server/src/mcp/OrchestratorMcpService.ts
+++ b/apps/server/src/mcp/OrchestratorMcpService.ts
-@@ -2803,6 +2803,7 @@ const make = Effect.gen(function* () {
+@@ -2827,6 +2827,7 @@ const make = Effect.gen(function* () {
...(parent.thread.linkOrigin === undefined
? {}
: { linkOrigin: parent.thread.linkOrigin }),
@@ -58,10 +58,10 @@ index b124a03ea2..c64ff7af9d 100644
.pipe(
Effect.mapError((error) =>
diff --git a/apps/server/src/mcp/toolkits/project/handlers.test.ts b/apps/server/src/mcp/toolkits/project/handlers.test.ts
-index 748234387c..b907eeda24 100644
+index 8d7369bc85..0e36856678 100644
--- a/apps/server/src/mcp/toolkits/project/handlers.test.ts
+++ b/apps/server/src/mcp/toolkits/project/handlers.test.ts
-@@ -6,6 +6,7 @@ import {
+@@ -6,11 +6,11 @@ import {
ProviderInstanceId,
ProviderDriverKind,
type ServerProvider,
@@ -69,7 +69,12 @@ index 748234387c..b907eeda24 100644
type Project as ProjectRecord,
type ModelSelection,
type RuntimeMode,
-@@ -38,6 +39,82 @@ import * as McpToolAccess from "../../McpToolAccess.ts";
+ type OrchestrationV2ThreadShell,
+- ThreadId,
+ } from "@t3tools/contracts";
+ import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
+ import * as NodeServices from "@effect/platform-node/NodeServices";
+@@ -40,6 +40,82 @@ import * as McpToolAccess from "../../McpToolAccess.ts";
import * as ProjectHandlers from "./handlers.ts";
import { ProjectToolkit } from "./tools.ts";
@@ -152,7 +157,7 @@ index 748234387c..b907eeda24 100644
const clientLaunchHarness = (input: {
readonly runtimeModeCeiling: RuntimeMode;
readonly defaultRuntimeMode?: RuntimeMode;
-@@ -203,6 +280,7 @@ it.effect(
+@@ -225,6 +301,7 @@ it.effect(
});
expect(belowCeiling.launched[0]?.runtimeMode).toBe("approval-required");
expect(belowCeiling.launched[0]?.initialMessage?.senderThreadId).toBeUndefined();
@@ -161,10 +166,10 @@ index 748234387c..b907eeda24 100644
const aboveCeiling = yield* launchWith("auto-accept-edits", "full-access", {
title: "Fix",
diff --git a/apps/server/src/mcp/toolkits/project/handlers.ts b/apps/server/src/mcp/toolkits/project/handlers.ts
-index 7253bcedb0..d56e0f262c 100644
+index 253964cc55..dff7f00b70 100644
--- a/apps/server/src/mcp/toolkits/project/handlers.ts
+++ b/apps/server/src/mcp/toolkits/project/handlers.ts
-@@ -238,6 +238,7 @@ export const layer = McpToolAccess.toLayer(ProjectToolkit, {
+@@ -242,6 +242,7 @@ export const layer = McpToolAccess.toLayer(ProjectToolkit, {
attachments,
},
}),
@@ -173,10 +178,10 @@ index 7253bcedb0..d56e0f262c 100644
creationSource: "mcp",
...(linkOrigin === undefined ? {} : { linkOrigin }),
diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts
-index 0f016d17ea..71d89f22a3 100644
+index 06a2cba3a6..3bd5e44104 100644
--- a/apps/server/src/orchestration-v2/Orchestrator.ts
+++ b/apps/server/src/orchestration-v2/Orchestrator.ts
-@@ -2704,6 +2704,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -2774,6 +2774,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
relationshipToParent: null,
rootThreadId: command.threadId,
},
@@ -186,7 +191,7 @@ index 0f016d17ea..71d89f22a3 100644
forkedFrom: null,
createdAt: now,
updatedAt: now,
-@@ -3435,8 +3438,12 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -3506,8 +3509,12 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
const wasPinned = thread.pinnedAt != null;
const alreadySettled =
thread.settledOverride === "settled" && thread.settledAt !== null && !wasPinned;
@@ -200,7 +205,7 @@ index 0f016d17ea..71d89f22a3 100644
settledOverride: "settled",
settledAt: alreadySettled ? thread.settledAt : (command.settledAt ?? now),
pullRequests: thread.pullRequests?.map((link) => withPullRequestWatch(link, undefined)),
-@@ -12928,6 +12935,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
+@@ -13120,6 +13127,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
type: "thread.settle",
commandId: command.commandId,
threadId: command.threadId,
@@ -209,10 +214,10 @@ index 0f016d17ea..71d89f22a3 100644
},
events,
diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts
-index c2df567ea0..240239cd17 100644
+index 14c7dc4802..1f92db8b04 100644
--- a/apps/server/src/orchestration-v2/ProjectionStore.ts
+++ b/apps/server/src/orchestration-v2/ProjectionStore.ts
-@@ -1512,6 +1512,12 @@ export function threadShellFromProjection(
+@@ -1549,6 +1549,12 @@ export function threadShellFromProjection(
? {}
: { activeOrderKey: projection.thread.activeOrderKey }),
lineage: projection.thread.lineage,
@@ -225,7 +230,7 @@ index c2df567ea0..240239cd17 100644
forkedFrom: projection.thread.forkedFrom,
activeProviderThreadId: projection.thread.activeProviderThreadId,
...(projection.thread.historyOrigin === undefined
-@@ -1572,6 +1578,7 @@ export function threadShellFromProjection(
+@@ -1608,6 +1614,7 @@ export function threadShellFromProjection(
unsettledAt: projection.thread.unsettledAt ?? null,
snoozedUntil: projection.thread.snoozedUntil ?? null,
snoozedAt: projection.thread.snoozedAt ?? null,
@@ -233,7 +238,7 @@ index c2df567ea0..240239cd17 100644
pinnedAt: projection.thread.pinnedAt ?? null,
autoSettleDisabledAt: projection.thread.autoSettleDisabledAt ?? null,
-@@ -1793,6 +1800,12 @@ function shellFromState(input: {
+@@ -1830,6 +1837,12 @@ function shellFromState(input: {
? {}
: { activeOrderKey: input.state.thread.activeOrderKey }),
lineage: input.state.thread.lineage,
@@ -246,7 +251,7 @@ index c2df567ea0..240239cd17 100644
forkedFrom: input.state.thread.forkedFrom,
activeProviderThreadId: input.state.thread.activeProviderThreadId,
...(input.state.thread.historyOrigin === undefined
-@@ -1837,6 +1850,7 @@ function shellFromState(input: {
+@@ -1874,6 +1887,7 @@ function shellFromState(input: {
unsettledAt: input.state.thread.unsettledAt ?? null,
snoozedUntil: input.state.thread.snoozedUntil ?? null,
snoozedAt: input.state.thread.snoozedAt ?? null,
@@ -254,7 +259,7 @@ index c2df567ea0..240239cd17 100644
pinnedAt: input.state.thread.pinnedAt ?? null,
autoSettleDisabledAt: input.state.thread.autoSettleDisabledAt ?? null,
-@@ -6335,6 +6349,7 @@ export const layer: Layer.Layer =
+@@ -6455,6 +6469,7 @@ export const layer: Layer.Layer =
autoSettleDisabledAt: thread.autoSettleDisabledAt ?? null,
snoozedUntil: thread.snoozedUntil ?? null,
snoozedAt: thread.snoozedAt ?? null,
@@ -263,10 +268,10 @@ index c2df567ea0..240239cd17 100644
latestRunId,
latestRunRequestedAt:
diff --git a/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts b/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts
-index d3c62a7e13..ad0cab72a4 100644
+index ec0e370565..0b3e98e43d 100644
--- a/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts
+++ b/apps/server/src/orchestration-v2/ThreadLaunchService.test.ts
-@@ -509,6 +509,25 @@ it.effect("retains automation and sender attribution while a message waits in th
+@@ -513,6 +513,25 @@ it.effect("retains automation and sender attribution while a message waits in th
}).pipe(Effect.provide(harness.layer));
});
@@ -293,7 +298,7 @@ index d3c62a7e13..ad0cab72a4 100644
Effect.gen(function* () {
const setupCount = yield* Ref.make(0);
diff --git a/apps/server/src/orchestration-v2/ThreadLaunchService.ts b/apps/server/src/orchestration-v2/ThreadLaunchService.ts
-index b68eef4b2a..2583ca5c6d 100644
+index 4ab38c4b30..821f81ae4f 100644
--- a/apps/server/src/orchestration-v2/ThreadLaunchService.ts
+++ b/apps/server/src/orchestration-v2/ThreadLaunchService.ts
@@ -89,6 +89,8 @@ export interface ThreadLaunchInput {
@@ -305,7 +310,7 @@ index b68eef4b2a..2583ca5c6d 100644
readonly importedNativeThread?: {
readonly ref: {
readonly driver: ProviderDriverKind;
-@@ -913,6 +915,9 @@ const make = Effect.gen(function* () {
+@@ -916,6 +918,9 @@ const make = Effect.gen(function* () {
interactionMode: input.interactionMode,
branch: initialBranch,
worktreePath: initialWorktreePath,
@@ -343,10 +348,10 @@ index 9285f72d46..671b0107f8 100644
);
diff --git a/apps/server/src/orchestration-v2/ThreadManagementService.ts b/apps/server/src/orchestration-v2/ThreadManagementService.ts
-index c1727567f9..3f31307558 100644
+index f0346b2bec..cb3e063550 100644
--- a/apps/server/src/orchestration-v2/ThreadManagementService.ts
+++ b/apps/server/src/orchestration-v2/ThreadManagementService.ts
-@@ -1037,6 +1037,7 @@ const make = Effect.gen(function* () {
+@@ -1038,6 +1038,7 @@ const make = Effect.gen(function* () {
type: "thread.settle",
commandId: CommandId.make(`server:settle-after-run:${input.runId}`),
threadId: input.threadId,
@@ -354,7 +359,7 @@ index c1727567f9..3f31307558 100644
});
});
-@@ -1056,6 +1057,7 @@ const make = Effect.gen(function* () {
+@@ -1057,6 +1058,7 @@ const make = Effect.gen(function* () {
type: "thread.settle",
commandId: input.commandId,
threadId: input.threadId,
@@ -363,17 +368,17 @@ index c1727567f9..3f31307558 100644
return { sequence: result.sequence };
});
diff --git a/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts b/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts
-index e55d585c30..33bb2da429 100644
+index 1d8e514942..e2263fad9e 100644
--- a/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts
+++ b/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts
-@@ -464,6 +464,7 @@ describe("autoSettlementSettingsKey", () => {
+@@ -466,6 +466,7 @@ describe("autoSettlementSettingsKey", () => {
});
type AutoSettleCommand = Extract;
+type SettleCommand = Extract;
- const testCrypto = Crypto.make({
- randomBytes: (size) => new Uint8Array(size).fill(1),
+ const testCrypto = SecureRandom.make((size) => new Uint8Array(size).fill(1));
+
@@ -562,6 +563,8 @@ interface HarnessOptions {
) => Effect.Effect;
readonly onCloseIdle?: () => Effect.Effect;
@@ -428,7 +433,7 @@ index e55d585c30..33bb2da429 100644
getThread: (threadId) => {
const thread = options.currentThreads?.find((candidate) => candidate.id === threadId);
return thread
-@@ -717,6 +744,7 @@ const makeHarness = Effect.fn("makeThreadSettlementHarness")(function* (options:
+@@ -728,6 +755,7 @@ const makeHarness = Effect.fn("makeThreadSettlementHarness")(function* (options:
snapshotReads,
candidateReads,
commands,
@@ -436,7 +441,7 @@ index e55d585c30..33bb2da429 100644
branchCalls,
summaryCalls,
summaryRecovery,
-@@ -1355,6 +1383,43 @@ describe("ThreadSettlementServiceV2 terminals", () => {
+@@ -1477,6 +1505,43 @@ describe("ThreadSettlementServiceV2 terminals", () => {
),
);
@@ -480,7 +485,7 @@ index e55d585c30..33bb2da429 100644
it.effect("runs the settle script in the thread's own worktree", () =>
Effect.scoped(
Effect.gen(function* () {
-@@ -1487,3 +1552,56 @@ describe("isSnoozed", () => {
+@@ -1609,3 +1674,56 @@ describe("isSnoozed", () => {
).toBeNull();
});
});
@@ -538,7 +543,7 @@ index e55d585c30..33bb2da429 100644
+ });
+});
diff --git a/apps/server/src/orchestration-v2/ThreadSettlementService.ts b/apps/server/src/orchestration-v2/ThreadSettlementService.ts
-index fa0bf3dff4..4411978353 100644
+index 8c2dcfe886..243f19a9e1 100644
--- a/apps/server/src/orchestration-v2/ThreadSettlementService.ts
+++ b/apps/server/src/orchestration-v2/ThreadSettlementService.ts
@@ -7,6 +7,7 @@ import { visibleThreadPullRequests } from "@t3tools/shared/threadPullRequests";
@@ -549,7 +554,7 @@ index fa0bf3dff4..4411978353 100644
type ThreadId,
type OrchestrationV2DomainEvent,
type OrchestrationV2ThreadShell,
-@@ -158,6 +159,29 @@ export function isAutoSettlementCandidate(
+@@ -159,6 +160,29 @@ export function isAutoSettlementCandidate(
return !isSnoozed(thread, nowMs);
}
@@ -579,7 +584,7 @@ index fa0bf3dff4..4411978353 100644
/** Match client effectiveSnoozed so agents and sidebar agree. */
export function isSnoozed(
thread: Pick<
-@@ -621,10 +645,62 @@ export const make = Effect.gen(function* () {
+@@ -625,10 +649,62 @@ export const make = Effect.gen(function* () {
),
);
@@ -643,7 +648,7 @@ index fa0bf3dff4..4411978353 100644
case "thread.pull-request-link-updated":
case "thread.pull-request-synced":
case "provider-session.detached":
-@@ -705,6 +781,9 @@ export const make = Effect.gen(function* () {
+@@ -709,6 +785,9 @@ export const make = Effect.gen(function* () {
return {
start,
@@ -655,10 +660,10 @@ index fa0bf3dff4..4411978353 100644
} satisfies ThreadSettlementServiceV2["Service"];
});
diff --git a/apps/server/src/orchestration-v2/runtimeLayer.test.ts b/apps/server/src/orchestration-v2/runtimeLayer.test.ts
-index 8e1ca75fca..604a3f5a1b 100644
+index b38fb737c9..869a744a97 100644
--- a/apps/server/src/orchestration-v2/runtimeLayer.test.ts
+++ b/apps/server/src/orchestration-v2/runtimeLayer.test.ts
-@@ -2565,6 +2565,12 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
+@@ -2603,6 +2603,12 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
const autoSettledProjection = yield* orchestrator.getThreadProjection(threadId);
assert.equal(autoSettledProjection.thread.settledOverride, "settled");
assert.isNull(autoSettledProjection.thread.activeOrderKey);
@@ -671,7 +676,7 @@ index 8e1ca75fca..604a3f5a1b 100644
yield* orchestrator.dispatch({
type: "thread.unsettle",
commandId: CommandId.make("runtime-layer-lifecycle-auto-unsettle"),
-@@ -2590,6 +2596,15 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
+@@ -2628,6 +2634,15 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
const settledProjection = yield* orchestrator.getThreadProjection(threadId);
assert.equal(settledProjection.thread.settledOverride, "settled");
assert.isNotNull(settledProjection.thread.settledAt);
@@ -687,7 +692,7 @@ index 8e1ca75fca..604a3f5a1b 100644
yield* orchestrator.dispatch({
type: "thread.unsettle",
-@@ -7007,6 +7022,11 @@ it.layer(layerSharedApplicationDataPlaneTest)("snooze projection", (it) => {
+@@ -7145,6 +7160,11 @@ it.layer(layerSharedApplicationDataPlaneTest)("snooze projection", (it) => {
);
const [candidate] = yield* projections.getSettlementCandidates(threadId);
assert.deepEqual(candidate?.lastSnoozeWakeAt, yield* DateTime.now);
@@ -697,8 +702,8 @@ index 8e1ca75fca..604a3f5a1b 100644
+ );
+ assert.deepEqual(wokenShell?.lastSnoozeWakeAt, yield* DateTime.now);
yield* orchestrator.dispatch({
- type: "thread.snooze",
- commandId: CommandId.make("runtime-layer-snoozed-thread-snooze-after-wake"),
+ type: "thread.settle",
+ commandId: CommandId.make("runtime-layer-snoozed-thread-settle"),
diff --git a/apps/web/src/components/Sidebar.childThreads.browser.test.tsx b/apps/web/src/components/Sidebar.childThreads.browser.test.tsx
new file mode 100644
index 0000000000..64c05d7c0d
@@ -2629,7 +2634,7 @@ index 0000000000..ec6adb8b89
+ ].join(", ");
+}
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
-index deede26763..915d280e2b 100644
+index 868aa85cec..44d826dbac 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -214,6 +214,7 @@ import {
@@ -2790,7 +2795,7 @@ index deede26763..915d280e2b 100644
case "copy-path":
if (!threadWorkspacePath) {
toastManager.add(
-@@ -3438,6 +3452,13 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
+@@ -3439,6 +3453,13 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
// Folders draw active rows in their own place below.
if (projectFolders !== null && item.section === "active") continue;
items.push(renderThreadRow(threadByKey.get(item.key)!, item.section));
@@ -2804,7 +2809,7 @@ index deede26763..915d280e2b 100644
continue;
}
if (projectFolders !== null && item.marker === "active-placeholder") {
-@@ -3453,7 +3474,8 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
+@@ -3454,7 +3475,8 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
onContextMenu={handleProjectFolderContextMenu}
/>,
);
@@ -2814,7 +2819,7 @@ index deede26763..915d280e2b 100644
items.push(
renderThreadRow(
thread,
-@@ -3461,6 +3483,13 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
+@@ -3462,6 +3484,13 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
sidebarFolderRowId(folder.group.projectKey),
),
);
@@ -2828,7 +2833,7 @@ index deede26763..915d280e2b 100644
}
}
continue;
-@@ -3598,7 +3627,7 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
+@@ -3597,7 +3626,7 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
? "Loading"
: olderSettledShelf.failed
? "Could not load older threads. Retry"
@@ -2838,7 +2843,7 @@ index deede26763..915d280e2b 100644
) : null}
diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx
-index fc47fd8e21..b50791ae0c 100644
+index ed3a8276be..1826eeecef 100644
--- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx
+++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx
@@ -102,6 +102,7 @@ import {
@@ -3046,10 +3051,10 @@ index 196b5ff39e..271ba3004d 100644
pinnedAt: nullableIso(thread.pinnedAt ?? null),
autoSettleDisabledAt: nullableIso(thread.autoSettleDisabledAt ?? null),
diff --git a/packages/client-runtime/src/state/shell-sync.test.ts b/packages/client-runtime/src/state/shell-sync.test.ts
-index 9d1df33d1f..415a6c6396 100644
+index c74ecd82fe..7924c6a31d 100644
--- a/packages/client-runtime/src/state/shell-sync.test.ts
+++ b/packages/client-runtime/src/state/shell-sync.test.ts
-@@ -686,14 +686,12 @@ describe("environment shell synchronization", () => {
+@@ -870,14 +870,12 @@ describe("environment shell synchronization", () => {
const stateWhere = (predicate: (state: EnvironmentShellState) => boolean) =>
SubscriptionRef.changes(shellState).pipe(Stream.filter(predicate), Stream.runHead);
@@ -3070,10 +3075,10 @@ index 9d1df33d1f..415a6c6396 100644
const loaded = Option.getOrThrow(yield* stateWhere((state) => state.generation > 0));
expect(loaded.removedAt.size).toBe(0);
diff --git a/packages/client-runtime/src/state/shell.ts b/packages/client-runtime/src/state/shell.ts
-index bce13d315b..cafcf2e583 100644
+index 442e99b6f2..928b56cc44 100644
--- a/packages/client-runtime/src/state/shell.ts
+++ b/packages/client-runtime/src/state/shell.ts
-@@ -365,7 +365,9 @@ export const makeEnvironmentShellState = Effect.fn("EnvironmentShellState.make")
+@@ -380,7 +380,9 @@ export const makeEnvironmentShellState = Effect.fn("EnvironmentShellState.make")
yield* Ref.set(activeSubscriptionSession, session);
const { omitIdleSubagents, omitOlderSettled } = yield* session.initialConfig.pipe(
Effect.map((config) => ({
@@ -3085,10 +3090,10 @@ index bce13d315b..cafcf2e583 100644
config.shellOlderSettled === true && config.shellThreadFamilies === true,
})),
diff --git a/packages/client-runtime/src/state/threadCommands.test.ts b/packages/client-runtime/src/state/threadCommands.test.ts
-index 2b1b1162d6..9c0de4ebce 100644
+index bc210e18ac..ad09cac0c7 100644
--- a/packages/client-runtime/src/state/threadCommands.test.ts
+++ b/packages/client-runtime/src/state/threadCommands.test.ts
-@@ -258,7 +258,11 @@ describe("remote thread lifecycle commands", () => {
+@@ -255,7 +255,11 @@ describe("remote thread lifecycle commands", () => {
["settle", {}, { settledOverride: "settled", pinnedAt: null, snoozedUntil: null }],
["unsettle", { reason: "user" }, { settledOverride: "active", settledAt: null }],
["snooze", { snoozedUntil: "2099-01-01T00:00:00.000Z" }, { snoozedUntil: FUTURE }],
@@ -3102,7 +3107,7 @@ index 2b1b1162d6..9c0de4ebce 100644
["unpin", {}, { pinnedAt: null, pinOrderKey: null }],
["setAutoSettle", { enabled: false }, { autoSettleDisabledAt: expect.any(Object) }],
diff --git a/packages/client-runtime/src/state/threadCommands.ts b/packages/client-runtime/src/state/threadCommands.ts
-index e0aae375ec..9873724a3b 100644
+index 88a5759c5e..ee82af9813 100644
--- a/packages/client-runtime/src/state/threadCommands.ts
+++ b/packages/client-runtime/src/state/threadCommands.ts
@@ -68,6 +68,13 @@ export type LoadEarlierThreadHistoryInput = {
@@ -3117,7 +3122,7 @@ index e0aae375ec..9873724a3b 100644
+}
+
export function createThreadEnvironmentAtoms(
- runtime: Atom.AtomRuntime,
+ runtime: Atom.AtomRuntime,
snapshotAtom: (environmentId: EnvironmentId) => Atom.Atom,
@@ -384,7 +391,8 @@ export function createThreadEnvironmentAtoms(
["preparing", "queued", "starting", "running", "waiting"].includes(thread.status))
@@ -3144,10 +3149,10 @@ index e0aae375ec..9873724a3b 100644
setAutoSettle: optimistic.wrap(commands.setAutoSettle, (thread, input, now) => ({
...thread,
diff --git a/packages/client-runtime/src/state/threadShell.test.ts b/packages/client-runtime/src/state/threadShell.test.ts
-index 7eb329320a..16652b51b0 100644
+index 3960757562..236a466081 100644
--- a/packages/client-runtime/src/state/threadShell.test.ts
+++ b/packages/client-runtime/src/state/threadShell.test.ts
-@@ -108,6 +108,43 @@ describe("v2 thread shell lists", () => {
+@@ -112,6 +112,43 @@ describe("v2 thread shell lists", () => {
registry.dispose();
});
@@ -3192,10 +3197,10 @@ index 7eb329320a..16652b51b0 100644
const { registry, threads, snapshotAtom } = makeHarness();
const root = v2ThreadShell;
diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts
-index 36c096a333..96909b7ab2 100644
+index 8a054d175a..c73ae68d3a 100644
--- a/packages/contracts/src/orchestrationV2.ts
+++ b/packages/contracts/src/orchestrationV2.ts
-@@ -457,6 +457,13 @@ export const OrchestrationV2AppThread = Schema.Struct({
+@@ -460,6 +460,13 @@ export const OrchestrationV2AppThread = Schema.Struct({
linkOrigin: Schema.optional(OrchestrationV2LinkOrigin),
delegatedFrom: Schema.optional(OrchestrationV2DelegatedFrom),
lineage: OrchestrationV2AppThreadLineage,
@@ -3209,7 +3214,7 @@ index 36c096a333..96909b7ab2 100644
forkedFrom: Schema.NullOr(
Schema.Union([
Schema.Struct({ type: Schema.Literal("run"), threadId: ThreadId, runId: RunId }),
-@@ -582,6 +589,17 @@ export function idleCompactionOf(
+@@ -587,6 +594,17 @@ export function idleCompactionOf(
: undefined;
}
@@ -3227,7 +3232,7 @@ index 36c096a333..96909b7ab2 100644
export const OrchestrationV2RunStatus = Schema.Literals([
"preparing",
"queued",
-@@ -2080,6 +2098,8 @@ export const OrchestrationV2ThreadShell = Schema.Struct({
+@@ -2094,6 +2112,8 @@ export const OrchestrationV2ThreadShell = Schema.Struct({
pullRequests: Schema.optional(Schema.Array(ThreadPullRequestLink)),
branchPullRequest: Schema.optional(Schema.NullOr(ThreadLinkedPullRequest)),
lineage: OrchestrationV2AppThreadLineage,
@@ -3236,7 +3241,7 @@ index 36c096a333..96909b7ab2 100644
forkedFrom: Schema.NullOr(OrchestrationV2AppThread.fields.forkedFrom),
activeProviderThreadId: Schema.NullOr(ProviderThreadId),
historyOrigin: Schema.optional(OrchestrationV2ThreadHistoryOrigin),
-@@ -2126,6 +2146,8 @@ export const OrchestrationV2ThreadShell = Schema.Struct({
+@@ -2140,6 +2160,8 @@ export const OrchestrationV2ThreadShell = Schema.Struct({
unsettledAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)),
snoozedUntil: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)),
snoozedAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)),
@@ -3245,7 +3250,7 @@ index 36c096a333..96909b7ab2 100644
limitRecovery: Schema.optional(Schema.NullOr(OrchestrationV2LimitRecovery)),
pinnedAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)),
autoSettleDisabledAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)),
-@@ -2912,6 +2934,7 @@ export const OrchestrationV2Command = Schema.Union([
+@@ -2930,6 +2952,7 @@ export const OrchestrationV2Command = Schema.Union([
linkOrigin: Schema.optional(OrchestrationV2LinkOrigin),
/** Only the server sets this, with `linkOrigin`, for a linked environment's delegated task. */
delegatedFrom: Schema.optional(OrchestrationV2DelegatedFrom),
@@ -3253,7 +3258,7 @@ index 36c096a333..96909b7ab2 100644
importedNativeThread: Schema.optional(
Schema.Struct({
ref: Schema.Struct({
-@@ -2956,6 +2979,8 @@ export const OrchestrationV2Command = Schema.Union([
+@@ -2974,6 +2997,8 @@ export const OrchestrationV2Command = Schema.Union([
commandId: CommandId,
threadId: ThreadId,
settledAt: Schema.optional(Schema.DateTimeUtc),
diff --git a/patches-tt/0004-feat-server-push-agent-activity-directly-to-APNs.patch b/patches-tt/0004-feat-server-push-agent-activity-directly-to-APNs.patch
index 1dfe9a1..4ec5937 100644
--- a/patches-tt/0004-feat-server-push-agent-activity-directly-to-APNs.patch
+++ b/patches-tt/0004-feat-server-push-agent-activity-directly-to-APNs.patch
@@ -34,10 +34,10 @@ Activity it filled itself. Without a key the server behaves as upstream.
create mode 100644 apps/server/src/directPush/agentActivityDelivery.ts
diff --git a/apps/server/src/auth/RpcAuthorization.ts b/apps/server/src/auth/RpcAuthorization.ts
-index 6049121b41..29faf8f433 100644
+index f040dbc459..431de7df97 100644
--- a/apps/server/src/auth/RpcAuthorization.ts
+++ b/apps/server/src/auth/RpcAuthorization.ts
-@@ -110,6 +110,9 @@ export const RPC_REQUIRED_SCOPES = {
+@@ -111,6 +111,9 @@ export const RPC_REQUIRED_SCOPES = {
[WS_METHODS.serverRefreshUsageRates]: AuthDiagnosticsReadScope,
[WS_METHODS.serverSignalProcess]: AuthEnvironmentMaintainScope,
[WS_METHODS.serverReportClientActivity]: AuthOrchestrationReadScope,
@@ -1635,7 +1635,7 @@ index 0000000000..288496e494
+ );
+}
diff --git a/apps/server/src/environment/ServerEnvironment.ts b/apps/server/src/environment/ServerEnvironment.ts
-index d542b4ee55..27a9374071 100644
+index 0787a3702c..b67d7d780f 100644
--- a/apps/server/src/environment/ServerEnvironment.ts
+++ b/apps/server/src/environment/ServerEnvironment.ts
@@ -17,6 +17,7 @@ import packageJson from "../../package.json" with { type: "json" };
@@ -1661,10 +1661,10 @@ index d542b4ee55..27a9374071 100644
...descriptor,
capabilities: { ...descriptor.capabilities, agentActivityPublishing },
diff --git a/apps/server/src/observability/RpcInstrumentation.ts b/apps/server/src/observability/RpcInstrumentation.ts
-index 1e3388e413..818e95f19b 100644
+index 17940b8371..e95b4d9a1a 100644
--- a/apps/server/src/observability/RpcInstrumentation.ts
+++ b/apps/server/src/observability/RpcInstrumentation.ts
-@@ -87,6 +87,9 @@ const RPC_AGGREGATES = {
+@@ -88,6 +88,9 @@ const RPC_AGGREGATES = {
[WS_METHODS.serverRefreshUsageRates]: "server",
[WS_METHODS.serverSignalProcess]: "server",
[WS_METHODS.serverReportClientActivity]: "server",
@@ -1675,7 +1675,7 @@ index 1e3388e413..818e95f19b 100644
[WS_METHODS.scheduledTasksUpsert]: "scheduledTasks",
[WS_METHODS.scheduledTasksSetEnabled]: "scheduledTasks",
diff --git a/apps/server/src/relay/AgentAwarenessRelay.test.ts b/apps/server/src/relay/AgentAwarenessRelay.test.ts
-index 4bbaa85fc3..d1ddfbd9b6 100644
+index 87838fd5a5..03aca1ae12 100644
--- a/apps/server/src/relay/AgentAwarenessRelay.test.ts
+++ b/apps/server/src/relay/AgentAwarenessRelay.test.ts
@@ -35,6 +35,7 @@ import {
@@ -1694,7 +1694,7 @@ index 4bbaa85fc3..d1ddfbd9b6 100644
} = {},
) {
const values = new Map(
-@@ -288,6 +290,10 @@ const makeTestRelay = Effect.fnUntraced(function* (
+@@ -289,6 +291,10 @@ const makeTestRelay = Effect.fnUntraced(function* (
}),
Effect.provideService(FetchHttpClient.Fetch, fetch),
Effect.provide(NodeCrypto.layer),
@@ -1705,7 +1705,7 @@ index 4bbaa85fc3..d1ddfbd9b6 100644
);
return { relay, secrets, secretReads, currentShell, shellReads, publications, catchUp };
});
-@@ -340,6 +346,67 @@ describe("AgentAwarenessRelay", () => {
+@@ -341,6 +347,67 @@ describe("AgentAwarenessRelay", () => {
}),
);
@@ -1774,7 +1774,7 @@ index 4bbaa85fc3..d1ddfbd9b6 100644
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 f3c7b893af..7fb1c7df34 100644
+index 6a235911d4..251dfb8f0c 100644
--- a/apps/server/src/relay/AgentAwarenessRelay.ts
+++ b/apps/server/src/relay/AgentAwarenessRelay.ts
@@ -47,6 +47,7 @@ import {
@@ -1785,7 +1785,7 @@ index f3c7b893af..7fb1c7df34 100644
import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
import * as ThreadManagement from "../orchestration-v2/ThreadManagementService.ts";
import * as ProjectService from "../project/ProjectService.ts";
-@@ -371,6 +372,12 @@ export const make = Effect.gen(function* () {
+@@ -383,6 +384,12 @@ export const make = Effect.gen(function* () {
const startedAt = (yield* DateTime.now).epochMilliseconds;
const catchUpRequests = yield* Queue.dropping(1);
const publishedStateByThreadRef = yield* Ref.make(new Map());
@@ -1798,7 +1798,7 @@ index f3c7b893af..7fb1c7df34 100644
const readSecretString = (name: string) =>
secrets
-@@ -435,8 +442,9 @@ export const make = Effect.gen(function* () {
+@@ -447,8 +454,9 @@ export const make = Effect.gen(function* () {
});
const publishThreadUnsafe = Effect.fn("publishThreadUnsafe")(function* (threadId: ThreadId) {
@@ -1809,7 +1809,7 @@ index f3c7b893af..7fb1c7df34 100644
yield* cancelPublishRetries;
yield* resetPublishedConnection;
yield* Effect.logDebug("agent activity publish skipped; publication disabled", {
-@@ -444,8 +452,8 @@ export const make = Effect.gen(function* () {
+@@ -456,8 +464,8 @@ export const make = Effect.gen(function* () {
});
return;
}
@@ -1820,7 +1820,7 @@ index f3c7b893af..7fb1c7df34 100644
yield* cancelPublishRetries;
yield* resetPublishedConnection;
yield* Effect.logDebug("agent activity publish skipped; relay link credentials unavailable", {
-@@ -454,9 +462,10 @@ export const make = Effect.gen(function* () {
+@@ -466,9 +474,10 @@ export const make = Effect.gen(function* () {
return;
}
if (
@@ -1834,7 +1834,7 @@ index f3c7b893af..7fb1c7df34 100644
) {
yield* resetPublishedConnection;
publishedRelayConfig = relayConfig;
-@@ -467,7 +476,16 @@ export const make = Effect.gen(function* () {
+@@ -479,7 +488,16 @@ export const make = Effect.gen(function* () {
readonly projectId: string | null;
readonly state: RelayAgentActivityState | null;
readonly reason: string;
@@ -1851,7 +1851,7 @@ index f3c7b893af..7fb1c7df34 100644
if (cachedRelayClient === undefined) {
cachedRelayClient = yield* makeRelayClient(relayConfig);
}
-@@ -605,6 +623,7 @@ export const make = Effect.gen(function* () {
+@@ -618,6 +636,7 @@ export const make = Effect.gen(function* () {
projectId: unsettled.projectId,
state: completion,
reason: unsettled.reason,
@@ -1859,7 +1859,7 @@ index f3c7b893af..7fb1c7df34 100644
}).pipe(withRelayClientTracing);
heldCompletions.delete(threadId);
publishConfirmDeadlines.set(threadId, nowMs + 5_000);
-@@ -636,6 +655,7 @@ export const make = Effect.gen(function* () {
+@@ -649,6 +668,7 @@ export const make = Effect.gen(function* () {
projectId: snapshot.projectId,
state: snapshot.state,
reason: snapshot.reason,
@@ -1867,7 +1867,7 @@ index f3c7b893af..7fb1c7df34 100644
}).pipe(withRelayClientTracing);
publishConfirmDeadlines.delete(threadId);
heldCompletions.delete(threadId);
-@@ -707,24 +727,33 @@ export const make = Effect.gen(function* () {
+@@ -714,24 +734,33 @@ export const make = Effect.gen(function* () {
});
const publishActiveThreadsUnsafe = Effect.gen(function* () {
@@ -1920,18 +1920,18 @@ index f3c7b893af..7fb1c7df34 100644
const environmentId = yield* serverEnvironment.getEnvironmentId;
const projectSnapshot = yield* projects.snapshot;
diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts
-index 452addfb4a..a655bfaf3f 100644
+index a4433b59e8..1c40e402d7 100644
--- a/apps/server/src/server.ts
+++ b/apps/server/src/server.ts
-@@ -92,6 +92,7 @@ import * as Keybindings from "./keybindings.ts";
+@@ -94,6 +94,7 @@ import * as Keybindings from "./keybindings.ts";
import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
import * as ServerOwnership from "./serverOwnership.ts";
import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts";
+import * as DirectPush from "./directPush/DirectPush.ts";
import { hasCloudPublicConfig } from "./cloud/publicConfig.ts";
import * as ServerSettings from "./serverSettings.ts";
- import * as ProjectEnrichmentService from "./project/ProjectEnrichmentService.ts";
-@@ -683,6 +684,9 @@ const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(
+ import * as ManagedLimits from "./policy/managedLimits.ts";
+@@ -725,6 +726,9 @@ const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(
Layer.provideMerge(CodexManagedTokenBroker.layer),
Layer.provideMerge(layerServerEnvironment),
Layer.provideMerge(layerAuth),
@@ -1942,10 +1942,10 @@ index 452addfb4a..a655bfaf3f 100644
Layer.provideMerge(
Layer.mergeAll(
diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts
-index abc7c71f4b..581e059325 100644
+index 492764c6ae..41767aa26b 100644
--- a/apps/server/src/ws.ts
+++ b/apps/server/src/ws.ts
-@@ -228,6 +228,7 @@ import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts";
+@@ -226,6 +226,7 @@ import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts";
import * as DefectReporter from "./observability/DefectReporter.ts";
import * as EventLoopMonitor from "./observability/EventLoopMonitor.ts";
import * as BackgroundPolicy from "./background/BackgroundPolicy.ts";
@@ -1953,7 +1953,7 @@ index abc7c71f4b..581e059325 100644
import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
import { requiredScopeForDeviceList, rpcAuthorizationError } from "./auth/RpcAuthorization.ts";
import * as RpcAuthorization from "./auth/RpcAuthorization.ts";
-@@ -1387,6 +1388,7 @@ export const layerWsRpc = (
+@@ -1424,6 +1425,7 @@ export const layerWsRpc = (
const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
const backgroundPolicy = yield* BackgroundPolicy.BackgroundPolicy;
@@ -1961,7 +1961,7 @@ index abc7c71f4b..581e059325 100644
const rpcClientIds = yield* Ref.make(new Set());
yield* Effect.addFinalizer(() =>
Ref.get(rpcClientIds).pipe(
-@@ -2602,6 +2604,18 @@ export const layerWsRpc = (
+@@ -2625,6 +2627,18 @@ export const layerWsRpc = (
),
),
),
@@ -1981,7 +1981,7 @@ index abc7c71f4b..581e059325 100644
[WS_METHODS.cloudInstallRelayClient]: (_input) =>
Stream.callback((queue) =>
diff --git a/packages/contracts/src/rpc.ts b/packages/contracts/src/rpc.ts
-index 37ea5866ae..86a26933b2 100644
+index 737059e855..b3eb84fa68 100644
--- a/packages/contracts/src/rpc.ts
+++ b/packages/contracts/src/rpc.ts
@@ -38,6 +38,11 @@ import * as Rpc from "effect/rpc/Rpc";
@@ -1996,7 +1996,7 @@ index 37ea5866ae..86a26933b2 100644
import {
ProviderAuthCancelInput,
ProviderAuthCompleteInput,
-@@ -534,6 +539,11 @@ export const WS_METHODS = {
+@@ -535,6 +540,11 @@ export const WS_METHODS = {
serverPrepareTriage: "server.prepareTriage",
serverSignalProcess: "server.signalProcess",
serverReportClientActivity: "server.reportClientActivity",
@@ -2008,7 +2008,7 @@ index 37ea5866ae..86a26933b2 100644
serverGetUsageSummary: "server.getUsageSummary",
serverRefreshUsageRates: "server.refreshUsageRates",
-@@ -992,6 +1002,30 @@ const WsServerReportClientActivityRpc = Rpc.make(WS_METHODS.serverReportClientAc
+@@ -994,6 +1004,30 @@ const WsServerReportClientActivityRpc = Rpc.make(WS_METHODS.serverReportClientAc
error: EnvironmentAuthorizationError,
});
@@ -2039,7 +2039,7 @@ index 37ea5866ae..86a26933b2 100644
const PullRequestRpcError = Schema.Union([
PullRequestUnavailableError,
PullRequestOperationError,
-@@ -2036,6 +2070,9 @@ export const WsRpcGroup = RpcGroup.make(
+@@ -2054,6 +2088,9 @@ export const WsRpcGroup = RpcGroup.make(
WsScheduledTasksListWebhookDeliveriesRpc,
WsScheduledTasksGetWebhookDeliveryRpc,
WsServerReportClientActivityRpc,
diff --git a/patches-tt/0005-feat-show-the-customized-build-next-to-the-logo-and-.patch b/patches-tt/0005-feat-show-the-customized-build-next-to-the-logo-and-.patch
index c69d806..0b8e610 100644
--- a/patches-tt/0005-feat-show-the-customized-build-next-to-the-logo-and-.patch
+++ b/patches-tt/0005-feat-show-the-customized-build-next-to-the-logo-and-.patch
@@ -218,10 +218,10 @@ index 0000000000..607215f524
+ );
+}
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
-index 5b4fe5042e..50b27ed187 100644
+index 1e9e700ca2..c2104a7754 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
-@@ -26,6 +26,7 @@ import { createModelSelection } from "@t3tools/shared/model";
+@@ -29,6 +29,7 @@ import { resolvePastedPath } from "@t3tools/shared/path";
import * as Equal from "effect/Equal";
import * as Schema from "effect/Schema";
import { APP_VERSION, HOSTED_APP_CHANNEL, HOSTED_APP_CHANNEL_LABEL } from "../../branding";
@@ -229,7 +229,7 @@ index 5b4fe5042e..50b27ed187 100644
import { AboutVersionTitle } from "./AboutVersionTitle";
import { CliCommandSettingsRow } from "./CliCommandSettingsRow";
import { MobileAppSettingsRow } from "./MobileAppSettingsRow";
-@@ -2020,6 +2021,7 @@ export function AboutSettingsPanel() {
+@@ -2068,6 +2069,7 @@ export function AboutSettingsPanel() {
/>
>
)}
diff --git a/patches-tt/0006-feat-desktop-download-nightlies-and-install-them-on-.patch b/patches-tt/0006-feat-desktop-download-nightlies-and-install-them-on-.patch
index 2bfc8c0..6fa054a 100644
--- a/patches-tt/0006-feat-desktop-download-nightlies-and-install-them-on-.patch
+++ b/patches-tt/0006-feat-desktop-download-nightlies-and-install-them-on-.patch
@@ -138,18 +138,18 @@ index 0000000000..feeb28e3dd
+ { defaultValue: () => resolveDesktopUpdateAutomation() },
+);
diff --git a/apps/desktop/src/updates/DesktopUpdates.ts b/apps/desktop/src/updates/DesktopUpdates.ts
-index 3a0b41cd9e..3cad45d4e1 100644
+index 6279e8b614..f92b5bdb00 100644
--- a/apps/desktop/src/updates/DesktopUpdates.ts
+++ b/apps/desktop/src/updates/DesktopUpdates.ts
-@@ -34,6 +34,7 @@ import * as ElectronUpdater from "../electron/ElectronUpdater.ts";
- import * as ElectronWindow from "../electron/ElectronWindow.ts";
+@@ -35,6 +35,7 @@ import * as ElectronWindow from "../electron/ElectronWindow.ts";
import * as IpcChannels from "../ipc/channels.ts";
import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
+ import * as DesktopUpdatePolicy from "./DesktopUpdatePolicy.ts";
+import { DesktopUpdateAutomation } from "./DesktopUpdateAutomation.ts";
import { normalizeDesktopUpdateReleaseNotes } from "./releaseNotes.ts";
import { desktopUpdateChannelOf } from "@t3tools/shared/cliRelease";
import { compareSemverVersions } from "@t3tools/shared/semver";
-@@ -348,6 +349,7 @@ export const make = Effect.gen(function* () {
+@@ -366,6 +367,7 @@ export const make = Effect.gen(function* () {
const pool = yield* DesktopBackendPool.DesktopBackendPool;
const desktopState = yield* DesktopState.DesktopState;
const electronUpdater = yield* ElectronUpdater.ElectronUpdater;
@@ -157,7 +157,7 @@ index 3a0b41cd9e..3cad45d4e1 100644
const electronWindow = yield* ElectronWindow.ElectronWindow;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
-@@ -540,12 +542,22 @@ export const make = Effect.gen(function* () {
+@@ -579,12 +581,22 @@ export const make = Effect.gen(function* () {
);
});
@@ -186,7 +186,7 @@ index 3a0b41cd9e..3cad45d4e1 100644
});
const downloadAvailableUpdate = Effect.gen(function* () {
-@@ -1098,8 +1110,10 @@ export const make = Effect.gen(function* () {
+@@ -1155,8 +1167,10 @@ export const make = Effect.gen(function* () {
}
yield* Ref.set(updaterConfiguredRef, true);
@@ -199,18 +199,18 @@ index 3a0b41cd9e..3cad45d4e1 100644
yield* electronUpdater.setDisableDifferentialDownload(
isArm64HostRunningIntelBuild(environment.runtimeInfo),
diff --git a/apps/desktop/src/updates/updatesTestHarness.ts b/apps/desktop/src/updates/updatesTestHarness.ts
-index d8d16b7dbf..6da10ecdc5 100644
+index 226f00f8cb..ca8b71bc54 100644
--- a/apps/desktop/src/updates/updatesTestHarness.ts
+++ b/apps/desktop/src/updates/updatesTestHarness.ts
-@@ -14,6 +14,7 @@ import * as ElectronWindow from "../electron/ElectronWindow.ts";
- import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
+@@ -16,6 +16,7 @@ import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
import * as DesktopState from "../app/DesktopState.ts";
+ import * as DesktopUpdatePolicy from "./DesktopUpdatePolicy.ts";
import * as DesktopUpdates from "./DesktopUpdates.ts";
+import * as DesktopUpdateAutomation from "./DesktopUpdateAutomation.ts";
export const flushCallbacks = Effect.yieldNow;
-@@ -40,6 +41,7 @@ export interface UpdatesHarnessOptions {
+@@ -43,6 +44,7 @@ export interface UpdatesHarnessOptions {
readonly savedChannel?: DesktopAppSettings.DesktopSettings["updateChannel"];
/** Adds a WSL backend beside the primary, as on a Windows host with WSL enabled. */
readonly wslBackend?: boolean;
@@ -218,7 +218,7 @@ index d8d16b7dbf..6da10ecdc5 100644
}
export function makeHarness(options: UpdatesHarnessOptions = {}) {
-@@ -49,6 +51,8 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
+@@ -52,6 +54,8 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
let allowDowngrade = false;
const checkAllowedDowngrade: boolean[] = [];
let fullChangelog = false;
@@ -227,7 +227,7 @@ index d8d16b7dbf..6da10ecdc5 100644
const feedUrls: ElectronUpdater.ElectronUpdaterFeedUrl[] = [];
const listeners = new Map void>>();
const sentStates: DesktopUpdateState[] = [];
-@@ -76,8 +80,14 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
+@@ -79,8 +83,14 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
Effect.sync(() => {
feedUrls.push(options);
}),
@@ -244,10 +244,10 @@ index d8d16b7dbf..6da10ecdc5 100644
setChannel: () => Effect.void,
setAllowPrerelease: () => Effect.void,
allowDowngrade: Effect.sync(() => allowDowngrade),
-@@ -271,6 +281,12 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
-
+@@ -275,6 +285,12 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
const layer = DesktopUpdates.layer.pipe(
Layer.provide(layerFileSystem),
+ Layer.provide(DesktopUpdatePolicy.layerTest(options.policy)),
+ Layer.provide(
+ Layer.succeed(
+ DesktopUpdateAutomation.DesktopUpdateAutomation,
@@ -257,7 +257,7 @@ index d8d16b7dbf..6da10ecdc5 100644
Layer.provideMerge(layerUpdater),
Layer.provideMerge(layerWindow),
Layer.provideMerge(layerBackend),
-@@ -302,6 +318,8 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
+@@ -306,6 +322,8 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
/** Whether each feed check could offer an older release, and the flag now. */
checkAllowedDowngrade,
allowDowngrade: () => allowDowngrade,
diff --git a/patches-tt/0007-feat-web-label-child-agents-as-agent-or-subagent.patch b/patches-tt/0007-feat-web-label-child-agents-as-agent-or-subagent.patch
index 3879369..6f57f91 100644
--- a/patches-tt/0007-feat-web-label-child-agents-as-agent-or-subagent.patch
+++ b/patches-tt/0007-feat-web-label-child-agents-as-agent-or-subagent.patch
@@ -9,23 +9,23 @@ Subject: [PATCH 07/10] feat(web): label child agents as agent or subagent
.../chat/MessagesTimeline.browser.test.tsx | 22 +++
.../src/components/chat/MessagesTimeline.tsx | 6 +-
.../chat/SubagentTooltipContent.tsx | 5 +
- .../components/chat/ThreadActivityBanners.tsx | 66 +++++--
+ .../components/chat/ThreadActivityBanners.tsx | 63 +++++--
...RelationshipsControl.kind.browser.test.tsx | 163 ++++++++++++++++
.../chat/ThreadRelationshipsControl.logic.ts | 39 ++++
- .../chat/ThreadRelationshipsControl.tsx | 43 +++--
+ .../chat/ThreadRelationshipsControl.tsx | 42 +++--
.../components/chat/TimelineV2EventRow.tsx | 4 +-
- .../chat/V2LifecycleRow.kind.test.tsx | 85 ++++++++
- .../src/components/chat/V2LifecycleRow.tsx | 181 ++++++++++++++----
+ .../chat/V2LifecycleRow.kind.test.tsx | 85 +++++++++
+ .../src/components/chat/V2LifecycleRow.tsx | 175 ++++++++++++++----
.../src/state/subagentDisplay.kind.test.ts | 57 ++++++
.../src/state/subagentDisplay.ts | 40 +++-
- .../src/state/threadExecution.kind.test.ts | 95 +++++++++
- .../src/state/threadExecution.ts | 83 ++++----
+ .../src/state/threadExecution.kind.test.ts | 95 ++++++++++
+ .../src/state/threadExecution.ts | 83 +++++----
.../src/state/threadShell.kind.test.ts | 23 +++
.../src/state/threadShell.test.ts | 2 +-
.../client-runtime/src/state/threadShell.ts | 22 ++-
packages/contracts/src/orchestrationV2.ts | 1 +
.../orchestrationV2PendingBackgroundWork.ts | 2 +
- 21 files changed, 832 insertions(+), 119 deletions(-)
+ 21 files changed, 824 insertions(+), 117 deletions(-)
create mode 100644 apps/web/src/components/chat/ThreadRelationshipsControl.kind.browser.test.tsx
create mode 100644 apps/web/src/components/chat/ThreadRelationshipsControl.logic.ts
create mode 100644 apps/web/src/components/chat/V2LifecycleRow.kind.test.tsx
@@ -34,10 +34,10 @@ Subject: [PATCH 07/10] feat(web): label child agents as agent or subagent
create mode 100644 packages/client-runtime/src/state/threadShell.kind.test.ts
diff --git a/apps/server/src/orchestration-v2/ProjectionStore.test.ts b/apps/server/src/orchestration-v2/ProjectionStore.test.ts
-index 4f9c74732b..f28706499b 100644
+index 1d2db40308..76c08c9efe 100644
--- a/apps/server/src/orchestration-v2/ProjectionStore.test.ts
+++ b/apps/server/src/orchestration-v2/ProjectionStore.test.ts
-@@ -636,7 +636,13 @@ it.effect("SQL shells use the latest subagent owner for duplicate provider-threa
+@@ -893,7 +893,13 @@ it.effect("SQL shells use the latest subagent owner for duplicate provider-threa
}
});
const owned = [
@@ -53,7 +53,7 @@ index 4f9c74732b..f28706499b 100644
yield* assertOwners(threadId, owned);
yield* assertOwners(otherThreadId, owned);
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
-index e7e0b72af5..e747718875 100644
+index 0a76fc890e..735c5cd398 100644
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -97,6 +97,7 @@ import {
@@ -75,10 +75,10 @@ index e7e0b72af5..e747718875 100644
const threadError = isServerThread
? (localServerError ?? serverRuntime?.lastError ?? null)
diff --git a/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx b/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx
-index 2fcaa596a5..146eeb4988 100644
+index 6b1df4ca16..b86774b8af 100644
--- a/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx
-@@ -1440,3 +1440,25 @@ it("keeps concurrent approvals actionable outside collapsed work and restores se
+@@ -1549,3 +1549,25 @@ it("keeps concurrent approvals actionable outside collapsed work and restores se
.element(screen.getByRole("button", { name: "Open the current tab", exact: true }))
.toBeVisible();
});
@@ -105,7 +105,7 @@ index 2fcaa596a5..146eeb4988 100644
+ });
+});
diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx
-index 4c28163766..d1334aff65 100644
+index cea2daa111..2ad05ea523 100644
--- a/apps/web/src/components/chat/MessagesTimeline.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.tsx
@@ -268,6 +268,8 @@ interface MessagesTimelineProps {
@@ -117,7 +117,7 @@ index 4c28163766..d1334aff65 100644
} | null;
workflowOpening?: WorkflowOpening | null;
onForkFromRun?: (input: {
-@@ -1109,7 +1111,9 @@ const ConversationTimeline = memo(function ConversationTimeline({
+@@ -1096,7 +1098,9 @@ const ConversationTimeline = memo(function ConversationTimeline({
diff --git a/apps/web/src/components/chat/ThreadActivityBanners.tsx b/apps/web/src/components/chat/ThreadActivityBanners.tsx
-index 5342bd09b3..99b521919f 100644
+index 368931833d..3ecf6bec99 100644
--- a/apps/web/src/components/chat/ThreadActivityBanners.tsx
+++ b/apps/web/src/components/chat/ThreadActivityBanners.tsx
-@@ -3,7 +3,10 @@ import type {
+@@ -4,7 +4,10 @@ import type {
OrchestrationV2ProviderGoal,
ThreadId,
} from "@t3tools/contracts";
@@ -174,7 +174,7 @@ index 5342bd09b3..99b521919f 100644
import {
presentPendingBackgroundWork,
presentProviderGoal,
-@@ -75,7 +78,10 @@ export function goalNowItem(input: {
+@@ -77,7 +80,10 @@ export function goalNowItem(input: {
readonly threadId: ThreadId;
readonly goal: OrchestrationV2ProviderGoal;
readonly isWorking: boolean;
@@ -186,7 +186,7 @@ index 5342bd09b3..99b521919f 100644
}): ThreadNowItem {
const { isWorking, onSendCommand } = input;
const presentation = presentProviderGoal(input.goal, isWorking);
-@@ -93,7 +99,9 @@ export function goalNowItem(input: {
+@@ -95,7 +101,9 @@ export function goalNowItem(input: {
{presentation.canResume && !isWorking ? (
<>
Resume
-@@ -102,7 +110,9 @@ export function goalNowItem(input: {
+@@ -104,7 +112,9 @@ export function goalNowItem(input: {
) : null}
Clear
-@@ -167,13 +177,16 @@ export function backgroundWorkNowItem(input: {
+@@ -169,7 +179,8 @@ export function backgroundWorkNowItem(input: {
const rows = presentation.items.map((task) => {
// Same key derivePendingBackgroundWork gives turn-item tasks; subagent items carry progress.
const item = input.turnItems.findLast(
@@ -218,7 +218,9 @@ index 5342bd09b3..99b521919f 100644
);
const subagent = item?.type === "subagent" ? item : undefined;
return {
- task,
+@@ -185,7 +196,9 @@ export function backgroundWorkNowItem(input: {
+ task.label
+ ),
activity: subagent?.progress?.replace(/\s+/gu, " ").trim() || null,
- startedAtMs: item?.startedAt ? DateTime.toEpochMillis(item.startedAt) : null,
+ startedAtMs: item?.startedAt
@@ -227,38 +229,22 @@ index 5342bd09b3..99b521919f 100644
updatedAtMs: subagent ? DateTime.toEpochMillis(subagent.updatedAt) : 0,
childThreadId: task.childThreadId ?? subagent?.childThreadId ?? undefined,
};
-@@ -182,7 +195,8 @@ export function backgroundWorkNowItem(input: {
- const newest = rows
- .filter((row) => row.activity)
- .toSorted((left, right) => right.updatedAtMs - left.updatedAtMs)[0];
-- const isCommand = (row: (typeof rows)[number]) => row.task.kind !== "subagent";
-+ const isCommand = (row: (typeof rows)[number]) =>
-+ row.task.kind !== "subagent";
- const verb = presentation.waiting ? "Waiting on" : "Running";
- const onlyThreadId = only?.childThreadId;
- const startedAtMs = only?.startedAtMs ?? null;
-@@ -192,6 +206,9 @@ export function backgroundWorkNowItem(input: {
- title: only ? (
- <>
- {verb}{" "}
-+ {only.task.kind === "subagent" && only.task.label !== only.task.noun
-+ ? `${only.task.noun} `
-+ : null}
- {rows.map((row) => row.task.label).join(", ")}
-+
-+ {rows.map((row) => row.task.label).join(", ")}
-+
- ) : (
- rows.map((row) => row.task.label).join(", ")
- ),
-@@ -224,11 +243,18 @@ export function backgroundWorkNowItem(input: {
+@@ -201,9 +214,12 @@ export function backgroundWorkNowItem(input: {
+ id: `background-work:${input.threadId}`,
+ icon: rows.every(isCommand) ? : ,
+ title:
+- only && typeof only.label !== "string" && only.task.label !== only.task.kind ? (
++ only && typeof only.label !== "string" && only.task.label !== only.task.noun ? (
+ <>
+- {presentation.waiting ? "Waiting" : "Running"} · {only.label}
++ {presentation.waiting && only.task.kind === "subagent"
++ ? `Waiting on ${only.task.noun} `
++ : `${presentation.waiting ? "Waiting" : "Running"} · `}
++ {only.label}
+ >
+ ) : (
+ presentation.title
+@@ -234,11 +250,18 @@ export function backgroundWorkNowItem(input: {
/>
)}
{onlyThreadId === undefined ? null : (
@@ -279,7 +265,7 @@ index 5342bd09b3..99b521919f 100644
{input.stopping ? "Stopping..." : "Stop"}
>
-@@ -239,7 +265,10 @@ export function backgroundWorkNowItem(input: {
+@@ -249,7 +272,10 @@ export function backgroundWorkNowItem(input: {
/** The local checkout moved off the branch this thread last ran on. */
export function branchMismatchBannerItem(input: {
readonly mismatchKey: string;
@@ -291,7 +277,7 @@ index 5342bd09b3..99b521919f 100644
readonly restoreDisabled: boolean;
readonly restoring: boolean;
readonly onRestore: () => void;
-@@ -252,7 +281,9 @@ export function branchMismatchBannerItem(input: {
+@@ -262,7 +288,9 @@ export function branchMismatchBannerItem(input: {
icon: ,
title: (
@@ -302,7 +288,7 @@ index 5342bd09b3..99b521919f 100644
@@ -540,17 +526,18 @@ index 0000000000..deeab95cdf
+ return edge.sourceThreadId === currentThreadId ? "Fork" : "Parent thread";
+}
diff --git a/apps/web/src/components/chat/ThreadRelationshipsControl.tsx b/apps/web/src/components/chat/ThreadRelationshipsControl.tsx
-index a3eaf6e787..b060e46fbf 100644
+index c91d5acebc..1b52035aff 100644
--- a/apps/web/src/components/chat/ThreadRelationshipsControl.tsx
+++ b/apps/web/src/components/chat/ThreadRelationshipsControl.tsx
-@@ -1,5 +1,6 @@
+@@ -1,6 +1,7 @@
import { useAtomValue } from "@effect/atom-react";
+ import { threadKey } from "@t3tools/client-runtime/state/entities";
import { ThreadDetailsControl } from "./ThreadDetailsControl";
+import { relationshipLabel } from "./ThreadRelationshipsControl.logic";
import { ThreadHoverCardPopup } from "../ThreadHoverCard";
import { ThreadDetailsSection } from "./ThreadDetailsSection";
import { SectionHeaderStatus } from "../ui/collapsible-section-header";
-@@ -11,7 +12,11 @@ import {
+@@ -12,7 +13,11 @@ import {
projectedSubagentsToRuntime,
type RuntimeSubagent,
} from "@t3tools/client-runtime/state/subagentRuntime";
@@ -563,15 +550,7 @@ index a3eaf6e787..b060e46fbf 100644
import { deriveProviderSubagentStatus } from "@t3tools/client-runtime/state/thread-execution";
import {
deriveThreadRelationshipGraph,
-@@ -20,7 +25,6 @@ import {
- threadRelationshipRowStatus,
- orderWebThreadLineageRows,
- resolveMergeBackTargetThreadId,
-- type ThreadRelationshipEdge,
- type ThreadRelationshipWalkRow,
- } from "@t3tools/client-runtime/state/thread-relationships";
- import {
-@@ -181,14 +185,6 @@ function ThreadLineageGroup(props: {
+@@ -184,14 +189,6 @@ function ThreadLineageGroup(props: {
);
}
@@ -586,7 +565,7 @@ index a3eaf6e787..b060e46fbf 100644
function relationshipThreadTitle(input: {
readonly title: string;
readonly isSubagent: boolean;
-@@ -571,7 +567,7 @@ export function ThreadRelationshipsPanel(props: {
+@@ -609,7 +606,7 @@ export function ThreadRelationshipsPanel(props: {
const stopSubagent = async (
childThreadId: ThreadId,
@@ -595,7 +574,7 @@ index a3eaf6e787..b060e46fbf 100644
) => {
if (stoppingId !== null) return;
setStoppingId(childThreadId);
-@@ -581,7 +577,10 @@ export function ThreadRelationshipsPanel(props: {
+@@ -619,7 +616,10 @@ export function ThreadRelationshipsPanel(props: {
});
setStoppingId(null);
if (result._tag === "Failure") {
@@ -607,7 +586,7 @@ index a3eaf6e787..b060e46fbf 100644
}
};
-@@ -619,14 +618,15 @@ export function ThreadRelationshipsPanel(props: {
+@@ -659,14 +659,15 @@ export function ThreadRelationshipsPanel(props: {
: node?.thread?.lineage.sideChat === true
? MessagesSquareIcon
: GitForkIcon;
@@ -627,7 +606,7 @@ index a3eaf6e787..b060e46fbf 100644
const failed = rowStatus === "failed" || rowStatus === "error";
const canStop =
agent &&
-@@ -691,10 +691,13 @@ export function ThreadRelationshipsPanel(props: {
+@@ -731,10 +732,13 @@ export function ThreadRelationshipsPanel(props: {
fallbackIcon={RelationshipIcon}
status={rowStatus}
/>
@@ -643,7 +622,7 @@ index a3eaf6e787..b060e46fbf 100644
{agent ? null : (
-@@ -824,7 +827,7 @@ export function ThreadRelationshipsPanel(props: {
+@@ -864,7 +868,7 @@ export function ThreadRelationshipsPanel(props: {
variant="ghost"
part="icon"
tone="destructive"
@@ -652,7 +631,7 @@ index a3eaf6e787..b060e46fbf 100644
disabled={stoppingId !== null}
onClick={() => void stopSubagent(threadId, agent)}
/>
-@@ -836,7 +839,7 @@ export function ThreadRelationshipsPanel(props: {
+@@ -876,7 +880,7 @@ export function ThreadRelationshipsPanel(props: {
)}
@@ -781,7 +760,7 @@ index 0000000000..8403303c63
+ });
+});
diff --git a/apps/web/src/components/chat/V2LifecycleRow.tsx b/apps/web/src/components/chat/V2LifecycleRow.tsx
-index d9320d7148..de813a8b8d 100644
+index c0dd0e0900..f44ba2747c 100644
--- a/apps/web/src/components/chat/V2LifecycleRow.tsx
+++ b/apps/web/src/components/chat/V2LifecycleRow.tsx
@@ -8,7 +8,10 @@ import { buildThreadRouteParams } from "../../threadRoutes";
@@ -796,9 +775,9 @@ index d9320d7148..de813a8b8d 100644
import { environmentThreadDetails } from "../../state/threads";
import { EMPTY_SERVER_PROVIDERS, serverEnvironment } from "../../state/server";
import { MiddleTruncate } from "../ui/middle-truncate";
-@@ -19,7 +22,11 @@ import {
- type HandoffEndpoints,
+@@ -20,7 +23,11 @@ import {
type HandoffTimelineRun,
+ resolveModelSwitchChange,
} from "@t3tools/client-runtime/handoff";
-import { formatSubagentDisplayTitle } from "@t3tools/client-runtime/state/subagent-display";
+import {
@@ -808,8 +787,8 @@ index d9320d7148..de813a8b8d 100644
+} from "@t3tools/client-runtime/state/subagent-display";
import {
ProviderDriverKind,
- type OrchestrationV2Notification,
-@@ -54,7 +61,10 @@ import {
+ type ModelSelection,
+@@ -57,7 +64,10 @@ import {
} from "../../providerInstances";
import { formatShortTimestamp } from "../../timestampFormat";
import { getTriggerDisplayModelName } from "./providerIconUtils";
@@ -821,20 +800,7 @@ index d9320d7148..de813a8b8d 100644
import { cn } from "~/lib/utils";
import { TimelineSystemDivider } from "./TimelineSystemDivider";
import { subagentResumedSince } from "./V2LifecycleRow.logic";
-@@ -73,7 +83,11 @@ export function V2LifecycleRow(props: {
- const { item } = props;
- if (item.type === "run_interrupt_result") {
- return (
--
-+
- );
- }
- if (item.type === "handoff") {
-@@ -86,12 +100,19 @@ export function V2LifecycleRow(props: {
+@@ -94,12 +104,19 @@ export function V2LifecycleRow(props: {
);
}
if (item.type === "fork") {
@@ -857,7 +823,7 @@ index d9320d7148..de813a8b8d 100644
onAction={() => props.onOpenThread(relatedThreadId)}
/>
);
-@@ -104,7 +125,10 @@ export function V2LifecycleRow(props: {
+@@ -112,7 +129,10 @@ export function V2LifecycleRow(props: {
className="flex min-w-0 items-center gap-3 rounded-lg border border-border/60 p-3"
>
@@ -869,7 +835,7 @@ index d9320d7148..de813a8b8d 100644
{item.title ?? "Created thread"}
-@@ -145,7 +169,9 @@ export function V2LifecycleRow(props: {
+@@ -153,7 +173,9 @@ export function V2LifecycleRow(props: {
origin={item.origin}
status={item.status}
driver={item.driver}
@@ -880,7 +846,7 @@ index d9320d7148..de813a8b8d 100644
result={item.result}
progress={item.progress}
startedAt={item.startedAt}
-@@ -199,7 +225,11 @@ function isoOrNull(value: DateTime.Utc | null | undefined): string | null {
+@@ -207,7 +229,11 @@ function isoOrNull(value: DateTime.Utc | null | undefined): string | null {
return value ? DateTime.formatIso(value) : null;
}
@@ -893,7 +859,7 @@ index d9320d7148..de813a8b8d 100644
return ;
}
-@@ -280,7 +310,8 @@ export function EnvironmentSubagentAvatar({
+@@ -288,7 +314,8 @@ export function EnvironmentSubagentAvatar({
const providerDriver = useAtomValue(
providersAtom,
(providers) =>
@@ -903,7 +869,7 @@ index d9320d7148..de813a8b8d 100644
);
const displayName = useAtomValue(
providersAtom,
-@@ -300,7 +331,8 @@ export function EnvironmentSubagentAvatar({
+@@ -308,7 +335,8 @@ export function EnvironmentSubagentAvatar({
const iconUrl = useAtomValue(
providersAtom,
(providers) =>
@@ -913,7 +879,7 @@ index d9320d7148..de813a8b8d 100644
);
return (
-@@ -409,7 +449,10 @@ function SubagentTimelineLink(props: {
+@@ -417,7 +453,10 @@ function SubagentTimelineLink(props: {
}) {
const agent = useAtomValue(
environmentThreadDetails.threadAtom(props.parentRef),
@@ -960,7 +926,7 @@ index d9320d7148..de813a8b8d 100644
);
const remote = props.remote ?? agent?.remoteChild;
const remoteEnvironment = useEnvironment(remote?.environmentId ?? null);
-@@ -423,18 +466,27 @@ function SubagentTimelineLink(props: {
+@@ -431,18 +470,27 @@ function SubagentTimelineLink(props: {
? () =>
void navigate({
to: "/$environmentId/$threadId",
@@ -991,7 +957,7 @@ index d9320d7148..de813a8b8d 100644
const failed = status === "failed";
const timing = {
status: liveStatus,
-@@ -456,7 +508,17 @@ function SubagentTimelineLink(props: {
+@@ -464,7 +512,17 @@ function SubagentTimelineLink(props: {
{props.title}
{remote !== undefined ? (
@@ -1010,7 +976,7 @@ index d9320d7148..de813a8b8d 100644
) : null}
{detail === null ? (
@@ -1019,7 +985,7 @@ index d9320d7148..de813a8b8d 100644
) : detail.includes("/") && !detail.includes(" ") ? (
) : (
-@@ -474,15 +536,23 @@ function SubagentTimelineLink(props: {
+@@ -482,15 +540,23 @@ function SubagentTimelineLink(props: {
)}
@@ -1047,7 +1013,7 @@ index d9320d7148..de813a8b8d 100644
{open !== null ? (
) : (
-@@ -508,7 +582,7 @@ function SubagentTimelineLink(props: {
+@@ -516,7 +586,7 @@ function SubagentTimelineLink(props: {
type="button"
data-v2-item-type="subagent"
aria-label={`Open ${props.title}`}
@@ -1069,7 +1035,7 @@ index d9320d7148..de813a8b8d 100644
onClick={open}
className={cn(
className,
-@@ -523,7 +597,11 @@ function SubagentTimelineLink(props: {
+@@ -531,7 +601,11 @@ function SubagentTimelineLink(props: {
0 ? (
) : null}
@@ -1161,7 +1127,7 @@ index d9320d7148..de813a8b8d 100644
}
/>
-@@ -640,8 +738,11 @@ function HandoffEndpoint(props: {
+@@ -672,8 +766,11 @@ function HandoffEndpoint(props: {
readonly instanceId: ProviderInstanceId;
readonly model?: string | undefined;
}) {
@@ -1175,7 +1141,7 @@ index d9320d7148..de813a8b8d 100644
const model = props.model?.trim();
const providerModel =
model === undefined || model.length === 0
-@@ -665,7 +766,9 @@ function HandoffEndpoint(props: {
+@@ -697,7 +794,9 @@ function HandoffEndpoint(props: {
className="inline-flex min-w-0 items-center gap-1.5 rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
>
();
@@ -1577,10 +1541,10 @@ index 0000000000..9f66e6f578
+ });
+});
diff --git a/packages/client-runtime/src/state/threadShell.test.ts b/packages/client-runtime/src/state/threadShell.test.ts
-index 16652b51b0..22de36bfd3 100644
+index 236a466081..f0c5458811 100644
--- a/packages/client-runtime/src/state/threadShell.test.ts
+++ b/packages/client-runtime/src/state/threadShell.test.ts
-@@ -333,7 +333,7 @@ describe("v2 thread shell lists", () => {
+@@ -337,7 +337,7 @@ describe("v2 thread shell lists", () => {
expect(isThreadWorking(registry.get(parent)!)).toBe(false);
const asking = registry.get(inputs);
expect(asking.map((thread) => thread.id)).toEqual([grandchild.id]);
@@ -1590,18 +1554,18 @@ index 16652b51b0..22de36bfd3 100644
registry.set(snapshotAtom(environmentId), {
...v2ShellSnapshot,
diff --git a/packages/client-runtime/src/state/threadShell.ts b/packages/client-runtime/src/state/threadShell.ts
-index 60c454469b..738e2e2a96 100644
+index f5ced761c5..28babf041c 100644
--- a/packages/client-runtime/src/state/threadShell.ts
+++ b/packages/client-runtime/src/state/threadShell.ts
-@@ -17,6 +17,7 @@ import { Atom } from "effect/reactivity";
- import type { EnvironmentThreadShell, PendingSubagentRequest } from "./models.ts";
+@@ -18,6 +18,7 @@ import type { EnvironmentThreadShell, PendingSubagentRequest } from "./models.ts
import { presentThreadShell, threadRuntimeIsActive } from "./models.ts";
+ import { hiddenThreadKeys, NO_HIDDEN_THREADS } from "./threadTagFilter.ts";
import { type EnvironmentCatalogState, enabledEnvironmentIds } from "./connections.ts";
+import { childAgentCountLabel, subagentFallbackTitle } from "./subagentDisplay.ts";
import {
arrayElementsEqual,
parseProjectRefCollectionKey,
-@@ -152,16 +153,25 @@ const sameRequests = (
+@@ -153,16 +154,25 @@ const sameRequests = (
[...right].every(([threadId, request]) => left.get(threadId) === request);
export function childThreadInputsTitle(
@@ -1634,10 +1598,10 @@ index 60c454469b..738e2e2a96 100644
const sameWireThreadShell = Schema.toEquivalence(OrchestrationV2ThreadShell);
diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts
-index 96909b7ab2..8680e85495 100644
+index c73ae68d3a..8483aab3f8 100644
--- a/packages/contracts/src/orchestrationV2.ts
+++ b/packages/contracts/src/orchestrationV2.ts
-@@ -1035,6 +1035,7 @@ export const OrchestrationV2PendingBackgroundTask = kindUnionWithFallback(
+@@ -1040,6 +1040,7 @@ export const OrchestrationV2PendingBackgroundTask = kindUnionWithFallback(
/** The owning provider thread's instance, populated in derived pending work. */
providerInstanceId: Schema.optional(ProviderInstanceId),
childThreadId: Schema.optional(ThreadId),
diff --git a/patches-tt/0008-feat-server-keep-idle-compactions-silent.patch b/patches-tt/0008-feat-server-keep-idle-compactions-silent.patch
index 3faf6e6..51ac0b9 100644
--- a/patches-tt/0008-feat-server-keep-idle-compactions-silent.patch
+++ b/patches-tt/0008-feat-server-keep-idle-compactions-silent.patch
@@ -172,7 +172,7 @@ index 0000000000..3ad294ad35
+ });
+});
diff --git a/apps/server/src/orchestration-v2/IdleCompactionService.ts b/apps/server/src/orchestration-v2/IdleCompactionService.ts
-index 415bb14fa1..1c0f4ff0fa 100644
+index 3b3b788b18..4af7f4728d 100644
--- a/apps/server/src/orchestration-v2/IdleCompactionService.ts
+++ b/apps/server/src/orchestration-v2/IdleCompactionService.ts
@@ -1,7 +1,6 @@
@@ -197,7 +197,7 @@ index 415bb14fa1..1c0f4ff0fa 100644
import * as Orchestrator from "./Orchestrator.ts";
import * as ProjectionStore from "./ProjectionStore.ts";
-@@ -83,7 +87,7 @@ export const make = Effect.gen(function* () {
+@@ -108,7 +112,7 @@ export const make = Effect.gen(function* () {
type: "message.dispatch",
// One compaction per completed run, even across duplicate events.
commandId: CommandId.make(`server:idle-compact:${threadId}:${runId}`),
@@ -206,7 +206,7 @@ index 415bb14fa1..1c0f4ff0fa 100644
threadId,
text: IDLE_COMPACTION_TEXT,
attachments: [],
-@@ -101,13 +105,36 @@ export const make = Effect.gen(function* () {
+@@ -126,13 +130,36 @@ export const make = Effect.gen(function* () {
),
);
@@ -245,7 +245,7 @@ index 415bb14fa1..1c0f4ff0fa 100644
),
);
diff --git a/apps/server/src/relay/AgentAwarenessRelay.test.ts b/apps/server/src/relay/AgentAwarenessRelay.test.ts
-index d1ddfbd9b6..82fb5ab3f3 100644
+index 03aca1ae12..d10297d7c4 100644
--- a/apps/server/src/relay/AgentAwarenessRelay.test.ts
+++ b/apps/server/src/relay/AgentAwarenessRelay.test.ts
@@ -6,6 +6,7 @@ import {
@@ -267,7 +267,7 @@ index d1ddfbd9b6..82fb5ab3f3 100644
readonly domainEvents?: Stream.Stream;
readonly directPush?: DirectPush.DirectPush["Service"];
} = {},
-@@ -204,7 +209,13 @@ const makeTestRelay = Effect.fnUntraced(function* (
+@@ -205,7 +210,13 @@ const makeTestRelay = Effect.fnUntraced(function* (
getTimelinePage: () => Effect.die("Unused timeline read"),
getMessageCount: () => Effect.die("unused message count"),
getTurnItem: () => Effect.die("unused turn item read"),
@@ -282,7 +282,7 @@ index d1ddfbd9b6..82fb5ab3f3 100644
getThreadProjection: unused,
getCheckpointContext: unused,
getThreadSnapshot: unused,
-@@ -367,6 +378,41 @@ describe("AgentAwarenessRelay", () => {
+@@ -368,6 +379,41 @@ describe("AgentAwarenessRelay", () => {
}),
);
@@ -325,7 +325,7 @@ index d1ddfbd9b6..82fb5ab3f3 100644
Effect.gen(function* () {
const direct: Array = [];
diff --git a/apps/server/src/relay/AgentAwarenessRelay.ts b/apps/server/src/relay/AgentAwarenessRelay.ts
-index 7fb1c7df34..39f7c5ecef 100644
+index 251dfb8f0c..3675fc33c8 100644
--- a/apps/server/src/relay/AgentAwarenessRelay.ts
+++ b/apps/server/src/relay/AgentAwarenessRelay.ts
@@ -49,6 +49,7 @@ import {
@@ -336,7 +336,7 @@ index 7fb1c7df34..39f7c5ecef 100644
import * as ThreadManagement from "../orchestration-v2/ThreadManagementService.ts";
import * as ProjectService from "../project/ProjectService.ts";
import { forkParked } from "../serverActivation.ts";
-@@ -531,6 +532,22 @@ export const make = Effect.gen(function* () {
+@@ -543,6 +544,22 @@ export const make = Effect.gen(function* () {
// Read only this thread; full shell reads would make each activity event scale with all threads.
const threadShell = yield* threads.getThreadShell(threadId);
diff --git a/patches-tt/0009-feat-desktop-add-macOS-window-glass-with-an-inset-co.patch b/patches-tt/0009-feat-desktop-add-macOS-window-glass-with-an-inset-co.patch
index 1f096ee..3e73d41 100644
--- a/patches-tt/0009-feat-desktop-add-macOS-window-glass-with-an-inset-co.patch
+++ b/patches-tt/0009-feat-desktop-add-macOS-window-glass-with-an-inset-co.patch
@@ -51,10 +51,10 @@ index 0000000000..2e8fe8bc8f
+ });
+});
diff --git a/apps/desktop/src/window/DesktopWindow.ts b/apps/desktop/src/window/DesktopWindow.ts
-index 1080108984..37b0ce2766 100644
+index 5e02f4cfd8..c75b4ca3f8 100644
--- a/apps/desktop/src/window/DesktopWindow.ts
+++ b/apps/desktop/src/window/DesktopWindow.ts
-@@ -135,6 +135,24 @@ function getInitialWindowBackgroundColor(shouldUseDarkColors: boolean): string {
+@@ -136,6 +136,24 @@ function getInitialWindowBackgroundColor(shouldUseDarkColors: boolean): string {
return shouldUseDarkColors ? "#000000" : "#ffffff";
}
@@ -79,7 +79,7 @@ index 1080108984..37b0ce2766 100644
type DisplayBounds = Pick;
function windowFitsWithinDisplay(
-@@ -265,7 +283,9 @@ function syncWindowAppearance(
+@@ -266,7 +284,9 @@ function syncWindowAppearance(
return;
}
@@ -90,7 +90,7 @@ index 1080108984..37b0ce2766 100644
const { titleBarOverlay } = getWindowTitleBarOptions(shouldUseDarkColors, platform);
if (typeof titleBarOverlay === "object") {
window.setTitleBarOverlay(titleBarOverlay);
-@@ -387,6 +407,7 @@ export const make = Effect.gen(function* () {
+@@ -395,6 +415,7 @@ export const make = Effect.gen(function* () {
autoHideMenuBar: true,
...(environment.platform === "darwin" ? { disableAutoHideCursor: true } : {}),
backgroundColor: getInitialWindowBackgroundColor(shouldUseDarkColors),
@@ -98,7 +98,7 @@ index 1080108984..37b0ce2766 100644
...iconOption,
title: environment.displayName,
...getWindowTitleBarOptions(shouldUseDarkColors, environment.platform),
-@@ -410,6 +431,7 @@ export const make = Effect.gen(function* () {
+@@ -419,6 +440,7 @@ export const make = Effect.gen(function* () {
yield* rendererHistory.register(window.webContents, { surface: "main" });
if (environment.platform === "darwin") {
window.setAutoHideCursor(false);
@@ -161,7 +161,7 @@ index 9d77c47515..868e2b8366 100644
previous?.fontFamilySans !== settings.fontFamilySans ||
previous.fontFamilyCode !== settings.fontFamilyCode ||
diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx
-index 88a945becc..1b6c19432e 100644
+index 59e8f237ab..d5a4ecd198 100644
--- a/apps/web/src/components/ThreadTerminalDrawer.tsx
+++ b/apps/web/src/components/ThreadTerminalDrawer.tsx
@@ -378,6 +378,7 @@ export function terminalThemeFromApp(mountElement?: HTMLElement | null): Ghostty
@@ -322,10 +322,10 @@ index 00783b085d..225d2faa09 100644
{...searchableSetting("chat-width")}
description="Set how wide messages and the composer can grow on large screens."
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
-index 50b27ed187..3efff63c99 100644
+index c2104a7754..9f46201fac 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
-@@ -423,6 +423,11 @@ export function useSettingsRestore(onRestored?: () => void) {
+@@ -449,6 +449,11 @@ export function useSettingsRestore(onRestored?: () => void) {
? ["Contrast"]
: []),
...(settings.glassOpacity !== DEFAULT_UNIFIED_SETTINGS.glassOpacity ? ["Glass opacity"] : []),
@@ -337,7 +337,7 @@ index 50b27ed187..3efff63c99 100644
...(settings.diffColorScheme !== DEFAULT_UNIFIED_SETTINGS.diffColorScheme
? ["Diff colors"]
: []),
-@@ -624,6 +629,9 @@ export function useSettingsRestore(onRestored?: () => void) {
+@@ -650,6 +655,9 @@ export function useSettingsRestore(onRestored?: () => void) {
contextWindowMeterEnabled: DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled,
environmentIdentificationMode: DEFAULT_UNIFIED_SETTINGS.environmentIdentificationMode,
glassOpacity: DEFAULT_UNIFIED_SETTINGS.glassOpacity,
@@ -348,10 +348,10 @@ index 50b27ed187..3efff63c99 100644
sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode,
sidebarProjectSortOrder: DEFAULT_UNIFIED_SETTINGS.sidebarProjectSortOrder,
diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts
-index faeeace43d..f18033e3a4 100644
+index 533584ea51..fd00bc76c0 100644
--- a/apps/web/src/components/settings/settingsSearch.ts
+++ b/apps/web/src/components/settings/settingsSearch.ts
-@@ -321,6 +321,24 @@ export const SETTINGS_SEARCH_ITEMS = [
+@@ -323,6 +323,24 @@ export const SETTINGS_SEARCH_ITEMS = [
to: "/settings/appearance",
searchTerms: ["transparent transparency solid menus dialogs composer"],
},
@@ -1071,10 +1071,10 @@ index 0000000000..e58808e548
+ root.style.setProperty("--window-glass-card", `${Math.round(glass.cardAlpha * 1000) / 10}%`);
+}
diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts
-index d608fc8068..3e20b4f8f4 100644
+index 00784d571f..a6248f9fa1 100644
--- a/packages/contracts/src/settings.ts
+++ b/packages/contracts/src/settings.ts
-@@ -133,6 +133,19 @@ export const GlassOpacity = Schema.Int.check(
+@@ -134,6 +134,19 @@ export const GlassOpacity = Schema.Int.check(
export type GlassOpacity = typeof GlassOpacity.Type;
const DEFAULT_GLASS_OPACITY: GlassOpacity = 80;
@@ -1094,7 +1094,7 @@ index d608fc8068..3e20b4f8f4 100644
export const MIN_APPEARANCE_CONTRAST = 50;
export const MAX_APPEARANCE_CONTRAST = 200;
export const AppearanceContrast = Schema.Int.check(
-@@ -426,6 +439,11 @@ export const ClientSettingsSchema = Schema.Struct({
+@@ -427,6 +440,11 @@ export const ClientSettingsSchema = Schema.Struct({
glassOpacity: GlassOpacity.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_GLASS_OPACITY)),
),
@@ -1106,7 +1106,7 @@ index d608fc8068..3e20b4f8f4 100644
fontSizeInterface: InterfaceFontSize.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_INTERFACE_FONT_SIZE)),
),
-@@ -1750,6 +1768,9 @@ export const ClientSettingsPatch = Schema.Struct({
+@@ -1788,6 +1806,9 @@ export const ClientSettingsPatch = Schema.Struct({
diffLayout: Schema.optionalKey(DiffLayout),
environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode),
glassOpacity: Schema.optionalKey(GlassOpacity),