authorgravatar for 62337003+Bil0000@users.noreply.github.comBilal Bakr <62337003+Bil0000@users.noreply.github.com> 2026-09-12 10:45:54+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-12 00:45:54-07:00
logb1e223e2b0d87124883b1410ab52dd6a1338e40d
tree6c715b608c2b2dc61deb3f3e91907c516fe8dd54
parent57b23a09f9dff15d57e3fa288c111381259a9a12
signature Signed by PGP key B5690EEEBB952194

perf(server): avoid workspace scans when loading pull requests (#11299)


12 files changed, 114 insertions(+), 15 deletions(-)

apps/server/src/checkpointing/CheckpointDiffQuery.test.ts+5
...@@ -88,6 +88,7 @@ describe("CheckpointDiffQuery.layer", () => {...@@ -88,6 +88,7 @@ describe("CheckpointDiffQuery.layer", () => {
88 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),88 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),
89 getEventReplayStats: () => Effect.die("unused"),89 getEventReplayStats: () => Effect.die("unused"),
90 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),90 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
91 getProjectShells: () => Effect.die("unused"),
91 getProjectShellById: () => Effect.succeed(Option.none()),92 getProjectShellById: () => Effect.succeed(Option.none()),
92 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),93 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
93 getImportedAgentSessionSources: () => Effect.die("unused"),94 getImportedAgentSessionSources: () => Effect.die("unused"),
...@@ -202,6 +203,7 @@ describe("CheckpointDiffQuery.layer", () => {...@@ -202,6 +203,7 @@ describe("CheckpointDiffQuery.layer", () => {
202 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),203 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),
203 getEventReplayStats: () => Effect.die("unused"),204 getEventReplayStats: () => Effect.die("unused"),
204 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),205 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
206 getProjectShells: () => Effect.die("unused"),
205 getProjectShellById: () => Effect.succeed(Option.none()),207 getProjectShellById: () => Effect.succeed(Option.none()),
206 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),208 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
207 getImportedAgentSessionSources: () => Effect.die("unused"),209 getImportedAgentSessionSources: () => Effect.die("unused"),
...@@ -291,6 +293,7 @@ describe("CheckpointDiffQuery.layer", () => {...@@ -291,6 +293,7 @@ describe("CheckpointDiffQuery.layer", () => {
291 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),293 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),
292 getEventReplayStats: () => Effect.die("unused"),294 getEventReplayStats: () => Effect.die("unused"),
293 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),295 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
296 getProjectShells: () => Effect.die("unused"),
294 getProjectShellById: () => Effect.succeed(Option.none()),297 getProjectShellById: () => Effect.succeed(Option.none()),
295 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),298 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
296 getImportedAgentSessionSources: () => Effect.die("unused"),299 getImportedAgentSessionSources: () => Effect.die("unused"),
...@@ -365,6 +368,7 @@ describe("CheckpointDiffQuery.layer", () => {...@@ -365,6 +368,7 @@ describe("CheckpointDiffQuery.layer", () => {
365 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),368 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),
366 getEventReplayStats: () => Effect.die("unused"),369 getEventReplayStats: () => Effect.die("unused"),
367 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),370 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
371 getProjectShells: () => Effect.die("unused"),
368 getProjectShellById: () => Effect.succeed(Option.none()),372 getProjectShellById: () => Effect.succeed(Option.none()),
369 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),373 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
370 getImportedAgentSessionSources: () => Effect.die("unused"),374 getImportedAgentSessionSources: () => Effect.die("unused"),
...@@ -424,6 +428,7 @@ describe("CheckpointDiffQuery.layer", () => {...@@ -424,6 +428,7 @@ describe("CheckpointDiffQuery.layer", () => {
424 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),428 getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),
425 getEventReplayStats: () => Effect.die("unused"),429 getEventReplayStats: () => Effect.die("unused"),
426 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),430 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
431 getProjectShells: () => Effect.die("unused"),
427 getProjectShellById: () => Effect.succeed(Option.none()),432 getProjectShellById: () => Effect.succeed(Option.none()),
428 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),433 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
429 getImportedAgentSessionSources: () => Effect.die("unused"),434 getImportedAgentSessionSources: () => Effect.die("unused"),
apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts+1
...@@ -446,6 +446,7 @@ describe("OrchestrationEngine", () => {...@@ -446,6 +446,7 @@ describe("OrchestrationEngine", () => {
446 getEventReplayStats: () => Effect.die("unused"),446 getEventReplayStats: () => Effect.die("unused"),
447 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),447 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
448 getProjectShellById: () => Effect.succeed(Option.none()),448 getProjectShellById: () => Effect.succeed(Option.none()),
449 getProjectShells: () => Effect.succeed([]),
449 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),450 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
450 getImportedAgentSessionSources: () => Effect.die("unused"),451 getImportedAgentSessionSources: () => Effect.die("unused"),
451 getThreadCheckpointContext: () => Effect.succeed(Option.none()),452 getThreadCheckpointContext: () => Effect.succeed(Option.none()),
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts+52
...@@ -47,6 +47,58 @@ const encodeMessageContext = Schema.encodeEffect(...@@ -47,6 +47,58 @@ const encodeMessageContext = Schema.encodeEffect(
47 Schema.fromJsonString(OrchestrationMessageContext),47 Schema.fromJsonString(OrchestrationMessageContext),
48);48);
4949
50it.effect("reads project shells without loading threads or resolving excluded projects", () => {
51 const resolved: string[] = [];
52 const layer = OrchestrationProjectionSnapshotQueryLive.pipe(
53 Layer.provide(ThreadBackgroundLiveness.layer),
54 Layer.provide(ThreadPlanProgress.layer),
55 Layer.provide(
56 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
57 resolve: (root) =>
58 Effect.sync(() => {
59 resolved.push(root);
60 return null;
61 }),
62 }),
63 ),
64 Layer.provideMerge(SqlitePersistenceMemory),
65 );
66 return Effect.gen(function* () {
67 const sql = yield* SqlClient.SqlClient;
68 const query = yield* ProjectionSnapshotQuery;
69 yield* sql`INSERT INTO projection_projects
70 (project_id, title, workspace_root, scripts_json, created_at, updated_at, deleted_at)
71 VALUES
72 ('p1', 'First', '/first', '[]', '2026-09-01T00:00:00Z', '2026-09-01T00:00:00Z', NULL),
73 ('p2', 'Second', '/second', '[]', '2026-09-02T00:00:00Z', '2026-09-02T00:00:00Z', NULL),
74 ('p3', 'Deleted', '/deleted', '[]', '2026-09-03T00:00:00Z', '2026-09-03T00:00:00Z', '2026-09-04T00:00:00Z')`;
75 const expected = (yield* query.getShellSnapshot()).projects;
76 resolved.length = 0;
77 yield* sql`INSERT INTO projection_threads
78 (thread_id, project_id, title, model_selection_json, runtime_mode, interaction_mode, created_at, updated_at)
79 VALUES ('t1', 'p1', 'Thread', 'invalid-json', 'full-access', 'default', '2026-09-01T00:00:00Z', '2026-09-01T00:00:00Z')`;
80
81 const counter = makeSqlStatementCounter();
82 const projects = yield* query.getProjectShells().pipe(Effect.withTracer(counter.tracer));
83 assert.deepStrictEqual(projects, expected);
84 assert.strictEqual(counter.count(), 1);
85 assert.deepStrictEqual(resolved.toSorted(), ["/first", "/second"]);
86 resolved.length = 0;
87 yield* sql`UPDATE projection_projects SET scripts_json = 'invalid-json' WHERE project_id IN ('p1', 'p3')`;
88 assert.deepStrictEqual(yield* query.getProjectShells([asProjectId("p2")]), [expected[1]!]);
89 assert.deepStrictEqual(resolved, ["/second"]);
90 resolved.length = 0;
91 const beforeEmpty = counter.count();
92 assert.deepStrictEqual(
93 yield* query.getProjectShells([]).pipe(Effect.withTracer(counter.tracer)),
94 [],
95 );
96 assert.strictEqual(counter.count(), beforeEmpty);
97 assert.deepStrictEqual(yield* query.getProjectShells([asProjectId("p3")]), []);
98 assert.deepStrictEqual(resolved, []);
99 }).pipe(Effect.provide(layer));
100});
101
50const projectionSnapshotLayer = it.layer(102const projectionSnapshotLayer = it.layer(
51 OrchestrationProjectionSnapshotQueryLive.pipe(103 OrchestrationProjectionSnapshotQueryLive.pipe(
52 Layer.provide(ThreadBackgroundLiveness.layer),104 Layer.provide(ThreadBackgroundLiveness.layer),
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts+29-2
...@@ -521,9 +521,14 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {...@@ -521,9 +521,14 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
521 });521 });
522522
523 const listProjectRows = SqlSchema.findAll({523 const listProjectRows = SqlSchema.findAll({
524 Request: Schema.Void,524 Request: Schema.UndefinedOr(
525 Schema.Struct({
526 activeOnly: Schema.Boolean,
527 projectIds: Schema.optional(Schema.Array(ProjectId)),
528 }),
529 ),
525 Result: ProjectionProjectDbRowSchema,530 Result: ProjectionProjectDbRowSchema,
526 execute: () =>531 execute: (filter) =>
527 sql`532 sql`
528 SELECT533 SELECT
529 project_id AS "projectId",534 project_id AS "projectId",
...@@ -539,6 +544,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {...@@ -539,6 +544,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
539 updated_at AS "updatedAt",544 updated_at AS "updatedAt",
540 deleted_at AS "deletedAt"545 deleted_at AS "deletedAt"
541 FROM projection_projects546 FROM projection_projects
547 WHERE ${filter?.activeOnly === true ? sql`deleted_at IS NULL` : sql`1 = 1`}
548 AND ${filter?.projectIds === undefined ? sql`1 = 1` : sql.in("project_id", filter.projectIds)}
542 ORDER BY created_at ASC, project_id ASC549 ORDER BY created_at ASC, project_id ASC
543 `,550 `,
544 });551 });
...@@ -2947,6 +2954,25 @@ pending_approval_requests AS (...@@ -2947,6 +2954,25 @@ pending_approval_requests AS (
2947 ),2954 ),
2948 );2955 );
29492956
2957 const getProjectShells: ProjectionSnapshotQueryShape["getProjectShells"] = (projectIds) => {
2958 if (projectIds?.length === 0) return Effect.succeed([]);
2959 return listProjectRows({ activeOnly: true, projectIds }).pipe(
2960 Effect.mapError(
2961 toPersistenceSqlOrDecodeError(
2962 "ProjectionSnapshotQuery.getProjectShells:query",
2963 "ProjectionSnapshotQuery.getProjectShells:decodeRows",
2964 ),
2965 ),
2966 Effect.flatMap((projects) =>
2967 resolveRepositoryIdentitiesForProjects(projects).pipe(
2968 Effect.map((identities) =>
2969 projects.map((row) => mapProjectShellRow(row, identities.get(row.projectId) ?? null)),
2970 ),
2971 ),
2972 ),
2973 );
2974 };
2975
2950 const getProjectShellById: ProjectionSnapshotQueryShape["getProjectShellById"] = (projectId) =>2976 const getProjectShellById: ProjectionSnapshotQueryShape["getProjectShellById"] = (projectId) =>
2951 getActiveProjectRowById({ projectId }).pipe(2977 getActiveProjectRowById({ projectId }).pipe(
2952 Effect.mapError(2978 Effect.mapError(
...@@ -3659,6 +3685,7 @@ pending_approval_requests AS (...@@ -3659,6 +3685,7 @@ pending_approval_requests AS (
3659 getEventReplayStats,3685 getEventReplayStats,
3660 getActiveProjectByWorkspaceRoot,3686 getActiveProjectByWorkspaceRoot,
3661 getProjectShellById,3687 getProjectShellById,
3688 getProjectShells,
3662 getFirstActiveThreadIdByProjectId,3689 getFirstActiveThreadIdByProjectId,
3663 getImportedAgentSessionSources,3690 getImportedAgentSessionSources,
3664 getThreadCheckpointContext,3691 getThreadCheckpointContext,
apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts+4
...@@ -166,6 +166,10 @@ export interface ProjectionSnapshotQueryShape {...@@ -166,6 +166,10 @@ export interface ProjectionSnapshotQueryShape {
166 projectId: ProjectId,166 projectId: ProjectId,
167 ) => Effect.Effect<Option.Option<OrchestrationProjectShell>, ProjectionRepositoryError>;167 ) => Effect.Effect<Option.Option<OrchestrationProjectShell>, ProjectionRepositoryError>;
168168
169 readonly getProjectShells: (
170 projectIds?: ReadonlyArray<ProjectId>,
171 ) => Effect.Effect<ReadonlyArray<OrchestrationProjectShell>, ProjectionRepositoryError>;
172
169 /**173 /**
170 * Read the earliest active thread for a project.174 * Read the earliest active thread for a project.
171 */175 */
apps/server/src/project/AgentSessionScanner.test.ts+1
...@@ -51,6 +51,7 @@ const makeProjectionSnapshotQueryLayer = (importedWorkspaceRoots: ReadonlyArray<...@@ -51,6 +51,7 @@ const makeProjectionSnapshotQueryLayer = (importedWorkspaceRoots: ReadonlyArray<
51 getCounts: () => Effect.die("unused"),51 getCounts: () => Effect.die("unused"),
52 getEventReplayStats: () => Effect.die("unused"),52 getEventReplayStats: () => Effect.die("unused"),
53 getActiveProjectByWorkspaceRoot: () => Effect.die("unused"),53 getActiveProjectByWorkspaceRoot: () => Effect.die("unused"),
54 getProjectShells: () => Effect.die("unused"),
54 getProjectShellById: () => Effect.die("unused"),55 getProjectShellById: () => Effect.die("unused"),
55 getImportedAgentSessionSources: () => Effect.succeed([]),56 getImportedAgentSessionSources: () => Effect.succeed([]),
56 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),57 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),
apps/server/src/project/ProjectSetupScriptRunner.test.ts+1
...@@ -39,6 +39,7 @@ const makeProjectionSnapshotQueryLayer = (project: OrchestrationProject) =>...@@ -39,6 +39,7 @@ const makeProjectionSnapshotQueryLayer = (project: OrchestrationProject) =>
39 Effect.succeed(39 Effect.succeed(
40 workspaceRoot === project.workspaceRoot ? Option.some(project) : Option.none(),40 workspaceRoot === project.workspaceRoot ? Option.some(project) : Option.none(),
41 ),41 ),
42 getProjectShells: () => Effect.die("unused"),
42 getProjectShellById: (projectId) =>43 getProjectShellById: (projectId) =>
43 Effect.succeed(projectId === project.id ? Option.some(project) : Option.none()),44 Effect.succeed(projectId === project.id ? Option.some(project) : Option.none()),
44 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),45 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),
apps/server/src/provider/Layers/ProviderService.test.ts+1
...@@ -4928,6 +4928,7 @@ describe("agent browser access", () => {...@@ -4928,6 +4928,7 @@ describe("agent browser access", () => {
4928 getCounts: () => Effect.die("unused"),4928 getCounts: () => Effect.die("unused"),
4929 getEventReplayStats: () => Effect.die("unused"),4929 getEventReplayStats: () => Effect.die("unused"),
4930 getActiveProjectByWorkspaceRoot: () => Effect.die("unused"),4930 getActiveProjectByWorkspaceRoot: () => Effect.die("unused"),
4931 getProjectShells: () => Effect.die("unused"),
4931 getProjectShellById: () => Effect.die("unused"),4932 getProjectShellById: () => Effect.die("unused"),
4932 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),4933 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),
4933 getThreadCheckpointContext: () => Effect.die("unused"),4934 getThreadCheckpointContext: () => Effect.die("unused"),
apps/server/src/provider/Layers/ProviderSessionReaper.test.ts+1
...@@ -243,6 +243,7 @@ describe("ProviderSessionReaper", () => {...@@ -243,6 +243,7 @@ describe("ProviderSessionReaper", () => {
243 getCounts: () => Effect.die("unused"),243 getCounts: () => Effect.die("unused"),
244 getEventReplayStats: () => Effect.die("unused"),244 getEventReplayStats: () => Effect.die("unused"),
245 getActiveProjectByWorkspaceRoot: () => Effect.die("unused"),245 getActiveProjectByWorkspaceRoot: () => Effect.die("unused"),
246 getProjectShells: () => Effect.die("unused"),
246 getProjectShellById: () => Effect.die("unused"),247 getProjectShellById: () => Effect.die("unused"),
247 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),248 getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),
248 getImportedAgentSessionSources: () => Effect.die("unused"),249 getImportedAgentSessionSources: () => Effect.die("unused"),
apps/server/src/pullRequest/PullRequestService.test.ts+6-7
...@@ -193,13 +193,12 @@ function makeService(input: {...@@ -193,13 +193,12 @@ function makeService(input: {
193 input.resolveHandle ?? (() => Effect.die("Unexpected provider refinement")),193 input.resolveHandle ?? (() => Effect.die("Unexpected provider refinement")),
194 }),194 }),
195 Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({195 Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({
196 getShellSnapshot: () =>196 getProjectShells: (projectIds) =>
197 Effect.succeed({197 Effect.succeed(
198 snapshotSequence: 1,198 input.projects.filter((project) => projectIds?.includes(project.id) ?? true),
199 projects: input.projects,199 ),
200 threads: [],200 getProjectShellById: (projectId) =>
201 updatedAt: "2026-07-01T00:00:00Z",201 Effect.succeed(Option.fromNullishOr(input.projects.find((p) => p.id === projectId))),
202 }),
203 }),202 }),
204 SourceControlRateLimit.layer,203 SourceControlRateLimit.layer,
205 Layer.effect(PullRequestReadCache.PullRequestReadCache, PullRequestReadCache.make).pipe(204 Layer.effect(PullRequestReadCache.PullRequestReadCache, PullRequestReadCache.make).pipe(
apps/server/src/pullRequest/PullRequestService.ts+9-6
...@@ -603,7 +603,10 @@ export const make = Effect.gen(function* () {...@@ -603,7 +603,10 @@ export const make = Effect.gen(function* () {
603 const listWorkspaceProjects = (603 const listWorkspaceProjects = (
604 filter: Pick<PullRequestListInput, "projectId" | "projectIds" | "host">,604 filter: Pick<PullRequestListInput, "projectId" | "projectIds" | "host">,
605 ): Effect.Effect<WorkspaceProjects, PullRequestError> =>605 ): Effect.Effect<WorkspaceProjects, PullRequestError> =>
606 projections.getShellSnapshot().pipe(606 (filter.projectId === undefined
607 ? projections.getProjectShells(filter.projectIds)
608 : projections.getProjectShellById(filter.projectId).pipe(Effect.map(Option.toArray))
609 ).pipe(
607 Effect.mapError(610 Effect.mapError(
608 (error) =>611 (error) =>
609 new PullRequestOperationError({612 new PullRequestOperationError({
...@@ -612,12 +615,12 @@ export const make = Effect.gen(function* () {...@@ -612,12 +615,12 @@ export const make = Effect.gen(function* () {
612 cause: error,615 cause: error,
613 }),616 }),
614 ),617 ),
615 Effect.flatMap((snapshot) =>618 Effect.flatMap((projects) =>
616 refineUnknownProjectKinds(snapshot.projects, filter).pipe(619 refineUnknownProjectKinds(projects, filter).pipe(
617 Effect.map((refinedKinds) => ({ refinedKinds, snapshot })),620 Effect.map((refinedKinds) => ({ refinedKinds, projects })),
618 ),621 ),
619 ),622 ),
620 Effect.map(({ refinedKinds, snapshot }) => {623 Effect.map(({ refinedKinds, projects }) => {
621 const supported: SupportedProject[] = [];624 const supported: SupportedProject[] = [];
622 const unimplemented = new Map<625 const unimplemented = new Map<
623 string,626 string,
...@@ -625,7 +628,7 @@ export const make = Effect.gen(function* () {...@@ -625,7 +628,7 @@ export const make = Effect.gen(function* () {
625 >();628 >();
626 const viewerRoots = new Map<string, string[]>();629 const viewerRoots = new Map<string, string[]>();
627 const seen = new Set<string>();630 const seen = new Set<string>();
628 for (const project of snapshot.projects) {631 for (const project of projects) {
629 if (filter.projectId !== undefined && project.id !== filter.projectId) continue;632 if (filter.projectId !== undefined && project.id !== filter.projectId) continue;
630 if (filter.projectIds !== undefined && !filter.projectIds.includes(project.id)) continue;633 if (filter.projectIds !== undefined && !filter.projectIds.includes(project.id)) continue;
631 const identity = project.repositoryIdentity;634 const identity = project.repositoryIdentity;
apps/server/src/serverRuntimeStartup.test.ts+4
...@@ -187,6 +187,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa...@@ -187,6 +187,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa
187 deletedAt: null,187 deletedAt: null,
188 }),188 }),
189 ),189 ),
190 getProjectShells: () => Effect.die("unused"),
190 getProjectShellById: () => Effect.die("unused"),191 getProjectShellById: () => Effect.die("unused"),
191 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.some(bootstrapThreadId)),192 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.some(bootstrapThreadId)),
192 getImportedAgentSessionSources: () => Effect.die("unused"),193 getImportedAgentSessionSources: () => Effect.die("unused"),
...@@ -314,6 +315,7 @@ it.effect.each([...@@ -314,6 +315,7 @@ it.effect.each([
314 })315 })
315 : Option.none(),316 : Option.none(),
316 ),317 ),
318 getProjectShells: () => Effect.die("unused"),
317 getProjectShellById: () => Effect.die("unused"),319 getProjectShellById: () => Effect.die("unused"),
318 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),320 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
319 getImportedAgentSessionSources: () => Effect.die("unused"),321 getImportedAgentSessionSources: () => Effect.die("unused"),
...@@ -384,6 +386,7 @@ it.effect(...@@ -384,6 +386,7 @@ it.effect(
384 getCounts: () => Effect.die("unused"),386 getCounts: () => Effect.die("unused"),
385 getEventReplayStats: () => Effect.die("unused"),387 getEventReplayStats: () => Effect.die("unused"),
386 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),388 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
389 getProjectShells: () => Effect.die("unused"),
387 getProjectShellById: () => Effect.die("unused"),390 getProjectShellById: () => Effect.die("unused"),
388 getFirstActiveThreadIdByProjectId: () => Effect.die("thread lookup failed"),391 getFirstActiveThreadIdByProjectId: () => Effect.die("thread lookup failed"),
389 getImportedAgentSessionSources: () => Effect.die("unused"),392 getImportedAgentSessionSources: () => Effect.die("unused"),
...@@ -446,6 +449,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa...@@ -446,6 +449,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa
446 getCounts: () => Effect.die("unused"),449 getCounts: () => Effect.die("unused"),
447 getEventReplayStats: () => Effect.die("unused"),450 getEventReplayStats: () => Effect.die("unused"),
448 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),451 getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),
452 getProjectShells: () => Effect.die("unused"),
449 getProjectShellById: () => Effect.die("unused"),453 getProjectShellById: () => Effect.die("unused"),
450 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),454 getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
451 getImportedAgentSessionSources: () => Effect.die("unused"),455 getImportedAgentSessionSources: () => Effect.die("unused"),