| author | |
| committer | |
| log | b1e223e2b0d87124883b1410ab52dd6a1338e40d |
| tree | 6c715b608c2b2dc61deb3f3e91907c516fe8dd54 |
| parent | 57b23a09f9dff15d57e3fa288c111381259a9a12 |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | ); |
| 49 | 49 | ||
| 50 | it.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 | |||
| 50 | const projectionSnapshotLayer = it.layer( | 102 | const 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 | }); |
| 522 | 522 | ||
| 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 | SELECT | 533 | 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_projects | 546 | 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 ASC | 549 | 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 | ); |
| 2949 | 2956 | ||
| 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>; |
| 168 | 168 | ||
| 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"), |