authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-06 02:17:16-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-06 02:17:16-07:00
log17490c0a00a6cf9a542fc8fa5d85f489fa662e70
tree3d9da94f8500074b2fd23455fa3fcb79747b74c0
parent281b92b48810062275798dd4107364384ebaf432
signature Signed by PGP key B5690EEEBB952194

perf(server): avoid full thread reads on turn start (#10108)


12 files changed, 291 insertions(+), 8 deletions(-)

apps/server/src/checkpointing/CheckpointDiffQuery.test.ts+5
......@@ -109,6 +109,7 @@ describe("CheckpointDiffQuery.layer", () => {
109109 });
110110 }),
111111 getThreadRuntimeContext: () => Effect.die("unused"),
112 getTurnStartMessage: () => Effect.die("unused"),
112113 getThreadShellById: () => Effect.succeed(Option.none()),
113114 getThreadDetailById: () => Effect.succeed(Option.none()),
114115 getThreadDetailSnapshot: () => Effect.succeed(Option.none()),
......@@ -207,6 +208,7 @@ describe("CheckpointDiffQuery.layer", () => {
207208 getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)),
208209 getFullThreadDiffContext: () => Effect.die("unused"),
209210 getThreadRuntimeContext: () => Effect.die("unused"),
211 getTurnStartMessage: () => Effect.die("unused"),
210212 getThreadShellById: () => Effect.succeed(Option.none()),
211213 getThreadDetailById: () => Effect.succeed(Option.none()),
212214 getThreadDetailSnapshot: () => Effect.succeed(Option.none()),
......@@ -295,6 +297,7 @@ describe("CheckpointDiffQuery.layer", () => {
295297 getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)),
296298 getFullThreadDiffContext: () => Effect.die("unused"),
297299 getThreadRuntimeContext: () => Effect.die("unused"),
300 getTurnStartMessage: () => Effect.die("unused"),
298301 getThreadShellById: () => Effect.succeed(Option.none()),
299302 getThreadDetailById: () => Effect.succeed(Option.none()),
300303 getThreadDetailSnapshot: () => Effect.succeed(Option.none()),
......@@ -368,6 +371,7 @@ describe("CheckpointDiffQuery.layer", () => {
368371 getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)),
369372 getFullThreadDiffContext: () => Effect.die("unused"),
370373 getThreadRuntimeContext: () => Effect.die("unused"),
374 getTurnStartMessage: () => Effect.die("unused"),
371375 getThreadShellById: () => Effect.succeed(Option.none()),
372376 getThreadDetailById: () => Effect.succeed(Option.none()),
373377 getThreadDetailSnapshot: () => Effect.succeed(Option.none()),
......@@ -426,6 +430,7 @@ describe("CheckpointDiffQuery.layer", () => {
426430 getThreadCheckpointContext: () => Effect.succeed(Option.none()),
427431 getFullThreadDiffContext: () => Effect.succeed(Option.none()),
428432 getThreadRuntimeContext: () => Effect.die("unused"),
433 getTurnStartMessage: () => Effect.die("unused"),
429434 getThreadShellById: () => Effect.succeed(Option.none()),
430435 getThreadDetailById: () => Effect.succeed(Option.none()),
431436 getThreadDetailSnapshot: () => Effect.succeed(Option.none()),
apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts+1
......@@ -422,6 +422,7 @@ describe("OrchestrationEngine", () => {
422422 getThreadCheckpointContext: () => Effect.succeed(Option.none()),
423423 getFullThreadDiffContext: () => Effect.succeed(Option.none()),
424424 getThreadRuntimeContext: () => Effect.die("unused"),
425 getTurnStartMessage: () => Effect.die("unused"),
425426 getThreadShellById: () => Effect.succeed(Option.none()),
426427 getThreadDetailById: () => Effect.succeed(Option.none()),
427428 getThreadDetailSnapshot: () => Effect.succeed(Option.none()),
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts+141
......@@ -1,5 +1,6 @@
11import {
22 type AgentSessionImportSource,
3 ChatAttachment,
34 CheckpointRef,
45 EventId,
56 MessageId,
......@@ -12,6 +13,7 @@ import { assert, it } from "@effect/vitest";
1213import * as NodeServices from "@effect/platform-node/NodeServices";
1314import * as Effect from "effect/Effect";
1415import * as Layer from "effect/Layer";
16import * as Option from "effect/Option";
1517import * as Schema from "effect/Schema";
1618import * as SqlClient from "effect/unstable/sql/SqlClient";
1719
......@@ -31,6 +33,9 @@ const asTurnId = (value: string): TurnId => TurnId.make(value);
3133const asMessageId = (value: string): MessageId => MessageId.make(value);
3234const asEventId = (value: string): EventId => EventId.make(value);
3335const asCheckpointRef = (value: string): CheckpointRef => CheckpointRef.make(value);
36const encodeChatAttachments = Schema.encodeEffect(
37 Schema.fromJsonString(Schema.Array(ChatAttachment)),
38);
3439
3540const projectionSnapshotLayer = it.layer(
3641 OrchestrationProjectionSnapshotQueryLive.pipe(
......@@ -598,6 +603,142 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => {
598603 }),
599604 );
600605
606 it.effect("reads one turn-start message without decoding unrelated history", () =>
607 Effect.gen(function* () {
608 const query = yield* ProjectionSnapshotQuery;
609 const sql = yield* SqlClient.SqlClient;
610 const threadId = ThreadId.make("thread-turn-start-read");
611 const messageId = MessageId.make("message-turn-start-read");
612 const createdAt = "2026-09-05T00:00:00.000Z";
613 const attachments = [
614 {
615 type: "file" as const,
616 id: "notes",
617 name: "notes.txt",
618 mimeType: "text/plain",
619 sizeBytes: 8,
620 },
621 ];
622 const attachmentsJson = yield* encodeChatAttachments(attachments);
623 yield* sql`
624 WITH RECURSIVE history(n) AS (
625 VALUES (1) UNION ALL SELECT n + 1 FROM history WHERE n < 2000
626 )
627 INSERT INTO projection_thread_messages (
628 message_id, thread_id, turn_id, role, text, attachments_json,
629 is_streaming, created_at, updated_at
630 )
631 SELECT 'turn-start-history:' || n, ${threadId}, 'old-turn:' || n, 'assistant',
632 'Unrelated assistant output', 'not-json', 0, ${createdAt}, ${createdAt}
633 FROM history
634 `;
635 yield* sql`
636 INSERT INTO projection_thread_messages (
637 message_id, thread_id, role, text, attachments_json, is_streaming, created_at, updated_at
638 ) VALUES (${messageId}, ${threadId}, 'user', 'Read these notes',
639 ${attachmentsJson}, 0, ${createdAt}, ${createdAt})
640 `;
641 yield* sql`
642 INSERT INTO projection_thread_messages (
643 message_id, thread_id, role, text, attachments_json, is_streaming, created_at, updated_at
644 ) VALUES ('turn-start-unrelated-user', 'thread-turn-start-unrelated', 'user', 'Unrelated prompt',
645 'not-json', 0, ${createdAt}, ${createdAt})
646 `;
647
648 const counter = makeSqlStatementCounter();
649 const context = yield* query
650 .getTurnStartMessage({ threadId, messageId })
651 .pipe(Effect.withTracer(counter.tracer));
652 assert.equal(counter.count(), 1);
653 assert.deepEqual(
654 context,
655 Option.some({
656 message: {
657 id: messageId,
658 role: "user",
659 text: "Read these notes",
660 turnId: null,
661 streaming: false,
662 createdAt,
663 updatedAt: createdAt,
664 attachments,
665 },
666 hasOtherUserMessages: false,
667 }),
668 );
669 assert.equal(
670 (yield* query.getTurnStartMessage({
671 threadId: ThreadId.make("thread-turn-start-unrelated"),
672 messageId,
673 }))._tag,
674 "None",
675 );
676 assert.equal(
677 (yield* query.getTurnStartMessage({ threadId, messageId: MessageId.make("missing") }))._tag,
678 "None",
679 );
680 }).pipe(
681 Effect.ensuring(
682 Effect.gen(function* () {
683 const sql = yield* SqlClient.SqlClient;
684 yield* sql`
685 DELETE FROM projection_thread_messages
686 WHERE thread_id IN ('thread-turn-start-read', 'thread-turn-start-unrelated')
687 `;
688 }).pipe(Effect.orDie),
689 ),
690 ),
691 );
692
693 it.effect("keeps compaction and queued-message eligibility in the turn-start query", () =>
694 Effect.gen(function* () {
695 const query = yield* ProjectionSnapshotQuery;
696 const sql = yield* SqlClient.SqlClient;
697 const threadId = ThreadId.make("thread-turn-start-eligibility");
698 const messageId = MessageId.make("message-turn-start-eligibility");
699 const createdAt = "2026-09-05T00:00:00.000Z";
700 yield* sql`
701 INSERT INTO projection_thread_messages (
702 message_id, thread_id, role, text, is_streaming, created_at, updated_at
703 ) VALUES (${messageId}, ${threadId}, 'user', 'Start a turn', 0, ${createdAt}, ${createdAt})
704 `;
705 yield* sql`
706 INSERT INTO projection_thread_messages (
707 message_id, thread_id, role, text, attachments_json, is_streaming, created_at, updated_at
708 ) VALUES ('turn-start-other-user', ${threadId}, 'user', '/compact', NULL, 0,
709 '2026-09-05T00:00:01.000Z', '2026-09-05T00:00:01.000Z')
710 `;
711
712 for (const { text, attachments, hasOtherUserMessages } of [
713 { text: "/compact", attachments: null, hasOtherUserMessages: false },
714 {
715 text: "\t\n\r /CoMpAcT\u00a0\u2028\ufeff",
716 attachments: "[ ]",
717 hasOtherUserMessages: false,
718 },
719 { text: "/compact keep recent errors", attachments: "[]", hasOtherUserMessages: true },
720 { text: "", attachments: null, hasOtherUserMessages: true },
721 { text: "Queued prompt", attachments: null, hasOtherUserMessages: true },
722 {
723 text: "/compact",
724 attachments:
725 '[{"type":"file","id":"notes","name":"notes.txt","mimeType":"text/plain","sizeBytes":8}]',
726 hasOtherUserMessages: true,
727 },
728 ]) {
729 yield* sql`
730 UPDATE projection_thread_messages SET text = ${text}, attachments_json = ${attachments}
731 WHERE message_id = 'turn-start-other-user'
732 `;
733 const context = yield* query.getTurnStartMessage({ threadId, messageId });
734 assert.equal(context._tag, "Some");
735 if (context._tag === "Some") {
736 assert.equal(context.value.hasOtherUserMessages, hasOtherUserMessages);
737 }
738 }
739 }),
740 );
741
601742 it.effect("keeps archived threads out of the main shell snapshot", () =>
602743 Effect.gen(function* () {
603744 const snapshotQuery = yield* ProjectionSnapshotQuery;
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts+68
......@@ -91,6 +91,9 @@ const THREAD_DETAIL_ACTIVITY_LIMIT = 500;
9191// Snapshot payloads are decoded and projected in small sequential batches so
9292// one client read does not retain the raw payloads for the full activity window.
9393const THREAD_DETAIL_ACTIVITY_PAYLOAD_BATCH_SIZE = 25;
94// SQLite trim defaults to spaces. Match the whitespace removed by String.trim.
95const MESSAGE_TRIM_WHITESPACE =
96 "\t\n\v\f\r \u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff";
9497const ProjectionProjectDbRowSchema = ProjectionProject.mapFields(
9598 Struct.assign({
9699 defaultModelSelection: Schema.NullOr(Schema.fromJsonString(ModelSelection)),
......@@ -105,6 +108,9 @@ const ProjectionThreadMessageDbRowSchema = ProjectionThreadMessage.mapFields(
105108 attachments: Schema.NullOr(Schema.fromJsonString(Schema.Array(ChatAttachment))),
106109 }),
107110);
111const ProjectionTurnStartMessageDbRowSchema = ProjectionThreadMessageDbRowSchema.mapFields(
112 Struct.assign({ hasOtherUserMessages: Schema.Number }),
113);
108114const ProjectionThreadProposedPlanDbRowSchema = ProjectionThreadProposedPlan;
109115const ProjectionThreadDbRowSchema = ProjectionThread.mapFields(
110116 Struct.assign({
......@@ -180,6 +186,10 @@ const ProjectionImportedAgentSessionSourcesRowSchema = Schema.Struct({
180186const ThreadIdLookupInput = Schema.Struct({
181187 threadId: ThreadId,
182188});
189const TurnStartMessageLookupInput = Schema.Struct({
190 threadId: ThreadId,
191 messageId: MessageId,
192});
183193const ThreadActivityKindsLookupInput = Schema.Struct({
184194 threadId: ThreadId,
185195 activityKinds: Schema.Array(Schema.String),
......@@ -1117,6 +1127,37 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
11171127 ),
11181128 });
11191129
1130 const getTurnStartMessageRow = SqlSchema.findOneOption({
1131 Request: TurnStartMessageLookupInput,
1132 Result: ProjectionTurnStartMessageDbRowSchema,
1133 execute: ({ threadId, messageId }) => sql`
1134 SELECT
1135 message_id AS "messageId",
1136 thread_id AS "threadId",
1137 turn_id AS "turnId",
1138 role,
1139 text,
1140 attachments_json AS "attachments",
1141 is_streaming AS "isStreaming",
1142 created_at AS "createdAt",
1143 updated_at AS "updatedAt",
1144 EXISTS (
1145 SELECT 1
1146 FROM projection_thread_messages AS other
1147 WHERE other.thread_id = ${threadId}
1148 AND other.message_id != ${messageId}
1149 AND other.role = 'user'
1150 AND (
1151 LOWER(TRIM(other.text, ${MESSAGE_TRIM_WHITESPACE})) != '/compact'
1152 OR COALESCE(json_array_length(other.attachments_json), 0) > 0
1153 )
1154 ) AS "hasOtherUserMessages"
1155 FROM projection_thread_messages
1156 WHERE thread_id = ${threadId} AND message_id = ${messageId}
1157 LIMIT 1
1158 `,
1159 });
1160
11201161 const listThreadMessageRowsByThread = SqlSchema.findAll({
11211162 Request: ThreadIdLookupInput,
11221163 Result: ProjectionThreadMessageDbRowSchema,
......@@ -2887,6 +2928,32 @@ pending_approval_requests AS (
28872928 }));
28882929 });
28892930
2931 const getTurnStartMessage: ProjectionSnapshotQueryShape["getTurnStartMessage"] = Effect.fn(
2932 "ProjectionSnapshotQuery.getTurnStartMessage",
2933 )(function* (input) {
2934 const message = yield* getTurnStartMessageRow(input).pipe(
2935 Effect.mapError(
2936 toPersistenceSqlOrDecodeError(
2937 "ProjectionSnapshotQuery.getTurnStartMessage:query",
2938 "ProjectionSnapshotQuery.getTurnStartMessage:decodeRow",
2939 ),
2940 ),
2941 );
2942 return Option.map(message, (row) => ({
2943 message: {
2944 id: row.messageId,
2945 role: row.role,
2946 text: row.text,
2947 turnId: row.turnId,
2948 streaming: row.isStreaming === 1,
2949 createdAt: row.createdAt,
2950 updatedAt: row.updatedAt,
2951 ...(row.attachments !== null ? { attachments: row.attachments } : {}),
2952 },
2953 hasOtherUserMessages: row.hasOtherUserMessages === 1,
2954 }));
2955 });
2956
28902957 // Contiguous turn range bounding a windowed detail read; undefined loads the
28912958 // full thread. Resolved from a window request inside the snapshot
28922959 // transaction (see getThreadDetailSnapshot).
......@@ -3326,6 +3393,7 @@ pending_approval_requests AS (
33263393 getFullThreadDiffContext,
33273394 getThreadShellById,
33283395 getThreadRuntimeContext,
3396 getTurnStartMessage,
33293397 getThreadDetailById,
33303398 getThreadDetailSnapshot,
33313399 } satisfies ProjectionSnapshotQueryShape;
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+42
......@@ -909,6 +909,48 @@ describe("ProviderCommandReactor", () => {
909909 }),
910910 );
911911
912 effectIt.effect("starts a turn and generates its title without loading old message bodies", () =>
913 Effect.gen(function* () {
914 const started = yield* Deferred.make<void>();
915 const titleGenerated = yield* Deferred.make<void>();
916 const harness = yield* Effect.promise(() =>
917 createHarness({
918 unreadableHistory: true,
919 startSessionEffect: (session) =>
920 Deferred.succeed(started, undefined).pipe(Effect.as(session)),
921 }),
922 );
923 harness.generateThreadTitle.mockReturnValue(
924 Deferred.succeed(titleGenerated, undefined).pipe(Effect.as({ title: "Generated title" })),
925 );
926 yield* harness.engine.dispatch({
927 type: "thread.turn.start",
928 commandId: CommandId.make("cmd-turn-start-with-old-history"),
929 threadId: ThreadId.make("thread-1"),
930 message: {
931 messageId: MessageId.make("message-turn-start-with-old-history"),
932 role: "user",
933 text: "Use the current message",
934 attachments: [],
935 },
936 titleSeed: "Thread",
937 interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
938 runtimeMode: "approval-required",
939 createdAt: "2026-01-01T00:00:01.000Z",
940 });
941 yield* Deferred.await(started);
942 yield* Deferred.await(titleGenerated);
943 yield* Effect.promise(() => harness.drain());
944
945 expect(harness.sendTurn).toHaveBeenCalledWith(
946 expect.objectContaining({ input: "Use the current message" }),
947 );
948 expect(harness.generateThreadTitle).toHaveBeenCalledWith(
949 expect.objectContaining({ message: "Use the current message" }),
950 );
951 }),
952 );
953
912954 effectIt.effect("rejects /compact without conversation context", () =>
913955 Effect.gen(function* () {
914956 const harness = yield* Effect.promise(() => createHarness());
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+9-8
......@@ -1188,12 +1188,15 @@ const make = Effect.gen(function* () {
11881188 return;
11891189 }
11901190
1191 const thread = yield* resolveThreadDetail(event.payload.threadId);
1191 const thread = yield* resolveThreadShell(event.payload.threadId);
11921192 if (!thread) {
11931193 return;
11941194 }
1195 const message = thread.messages.find((entry) => entry.id === event.payload.messageId);
1196 if (!message || message.role !== "user") {
1195 const turnStart = yield* projectionSnapshotQuery.getTurnStartMessage({
1196 threadId: thread.id,
1197 messageId: event.payload.messageId,
1198 });
1199 if (Option.isNone(turnStart) || turnStart.value.message.role !== "user") {
11971200 yield* appendProviderFailureActivity({
11981201 threadId: event.payload.threadId,
11991202 kind: "provider.turn.start.failed",
......@@ -1205,6 +1208,7 @@ const make = Effect.gen(function* () {
12051208 });
12061209 return;
12071210 }
1211 const { message, hasOtherUserMessages } = turnStart.value;
12081212 const appendTurnStartFailure = (summary: string, detail: string) =>
12091213 appendProviderFailureActivity({
12101214 threadId: event.payload.threadId,
......@@ -1297,10 +1301,7 @@ const make = Effect.gen(function* () {
12971301 yield* ensureThreadWorktree(thread);
12981302
12991303 const isCompactCommand = isCompactCommandMessage(message);
1300 const nonCompactUserMessageCount = thread.messages.filter(
1301 (entry) => entry.role === "user" && !isCompactCommandMessage(entry),
1302 ).length;
1303 if (nonCompactUserMessageCount === 1 && !isCompactCommand) {
1304 if (!hasOtherUserMessages && !isCompactCommand) {
13041305 const project = yield* resolveProject(thread.projectId);
13051306 const generationCwd =
13061307 resolveThreadWorkspaceCwd({
......@@ -1371,7 +1372,7 @@ const make = Effect.gen(function* () {
13711372 ),
13721373 );
13731374 if (isCompactCommand) {
1374 if (nonCompactUserMessageCount === 0) {
1375 if (!hasOtherUserMessages) {
13751376 return yield* appendTurnStartFailure(
13761377 "Context compaction failed",
13771378 "Context compaction requires an existing conversation.",
apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts+17
......@@ -10,7 +10,9 @@ import type {
1010 AgentSessionImportSource,
1111 ApprovalRequestId,
1212 CheckpointRef,
13 MessageId,
1314 OrchestrationCheckpointSummary,
15 OrchestrationMessage,
1416 OrchestrationProject,
1517 OrchestrationProjectShell,
1618 OrchestrationReadModel,
......@@ -211,6 +213,21 @@ export interface ProjectionSnapshotQueryShape {
211213 ProjectionRepositoryError
212214 >;
213215
216 /**
217 * Read one requested message and whether another non-compaction user message exists.
218 * Newer queued messages count too, preserving first-turn title eligibility.
219 */
220 readonly getTurnStartMessage: (input: {
221 readonly threadId: ThreadId;
222 readonly messageId: MessageId;
223 }) => Effect.Effect<
224 Option.Option<{
225 readonly message: OrchestrationMessage;
226 readonly hasOtherUserMessages: boolean;
227 }>,
228 ProjectionRepositoryError
229 >;
230
214231 /**
215232 * Read a single active thread detail snapshot by id.
216233 */
apps/server/src/project/AgentSessionScanner.test.ts+1
......@@ -58,6 +58,7 @@ const makeProjectionSnapshotQueryLayer = (importedWorkspaceRoots: ReadonlyArray<
5858 getFullThreadDiffContext: () => Effect.die("unused"),
5959 getThreadShellById: () => Effect.die("unused"),
6060 getThreadRuntimeContext: () => Effect.die("unused"),
61 getTurnStartMessage: () => Effect.die("unused"),
6162 getThreadDetailById: () => Effect.die("unused"),
6263 getThreadDetailSnapshot: () => Effect.die("unused"),
6364 searchThreads: () => Effect.die("unused"),
apps/server/src/project/ProjectSetupScriptRunner.test.ts+1
......@@ -46,6 +46,7 @@ const makeProjectionSnapshotQueryLayer = (project: OrchestrationProject) =>
4646 getThreadCheckpointContext: () => Effect.die("unused"),
4747 getFullThreadDiffContext: () => Effect.die("unused"),
4848 getThreadRuntimeContext: () => Effect.die("unused"),
49 getTurnStartMessage: () => Effect.die("unused"),
4950 getThreadShellById: () => Effect.die("unused"),
5051 getThreadDetailById: () => Effect.die("unused"),
5152 getThreadDetailSnapshot: () => Effect.die("unused"),
apps/server/src/provider/Layers/ProviderService.test.ts+1
......@@ -4330,6 +4330,7 @@ describe("agent browser access", () => {
43304330 Layer.provide(runtimeRepositoryLayer),
43314331 );
43324332 const projectionLayer = Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, {
4333 getTurnStartMessage: () => Effect.die("unused"),
43334334 getImportedAgentSessionSources: () => Effect.die("unused"),
43344335 getUserInputActivity: () => Effect.die("unused"),
43354336 getCommandReadModel: () => Effect.die("unused"),
apps/server/src/provider/Layers/ProviderSessionReaper.test.ts+1
......@@ -221,6 +221,7 @@ describe("ProviderSessionReaper", () => {
221221 getThreadCheckpointContext: () => Effect.die("unused"),
222222 getFullThreadDiffContext: () => Effect.die("unused"),
223223 getThreadRuntimeContext: () => Effect.die("unused"),
224 getTurnStartMessage: () => Effect.die("unused"),
224225 getThreadShellById: (threadId) =>
225226 Effect.succeed(
226227 input.readModel.threads.find((thread) => thread.id === threadId)
apps/server/src/serverRuntimeStartup.test.ts+4
......@@ -171,6 +171,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa
171171 getThreadCheckpointContext: () => Effect.succeed(Option.none()),
172172 getFullThreadDiffContext: () => Effect.succeed(Option.none()),
173173 getThreadRuntimeContext: () => Effect.die("unused"),
174 getTurnStartMessage: () => Effect.die("unused"),
174175 getThreadShellById: () => Effect.die("unused"),
175176 getThreadDetailById: () => Effect.die("unused"),
176177 getThreadDetailSnapshot: () => Effect.die("unused"),
......@@ -257,6 +258,7 @@ it.effect.each([
257258 getThreadCheckpointContext: () => Effect.succeed(Option.none()),
258259 getFullThreadDiffContext: () => Effect.succeed(Option.none()),
259260 getThreadRuntimeContext: () => Effect.die("unused"),
261 getTurnStartMessage: () => Effect.die("unused"),
260262 getThreadShellById: () => Effect.die("unused"),
261263 getThreadDetailById: () => Effect.die("unused"),
262264 getThreadDetailSnapshot: () => Effect.die("unused"),
......@@ -325,6 +327,7 @@ it.effect(
325327 getThreadCheckpointContext: () => Effect.succeed(Option.none()),
326328 getFullThreadDiffContext: () => Effect.succeed(Option.none()),
327329 getThreadRuntimeContext: () => Effect.die("unused"),
330 getTurnStartMessage: () => Effect.die("unused"),
328331 getThreadShellById: () => Effect.die("unused"),
329332 getThreadDetailById: () => Effect.die("unused"),
330333 getThreadDetailSnapshot: () => Effect.die("unused"),
......@@ -386,6 +389,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa
386389 getThreadCheckpointContext: () => Effect.succeed(Option.none()),
387390 getFullThreadDiffContext: () => Effect.succeed(Option.none()),
388391 getThreadRuntimeContext: () => Effect.die("unused"),
392 getTurnStartMessage: () => Effect.die("unused"),
389393 getThreadShellById: () => Effect.die("unused"),
390394 getThreadDetailById: () => Effect.die("unused"),
391395 getThreadDetailSnapshot: () => Effect.die("unused"),