| ... | ... | @@ -57,6 +57,7 @@ import { |
| 57 | 57 | decodeThreadDetailPageCursor, |
| 58 | 58 | encodeThreadDetailPageCursor, |
| 59 | 59 | } from "../threadDetailCursor.ts"; |
| 60 | import { projectActivityPayload } from "../ActivityPayloadProjection.ts"; |
| 60 | 61 | import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts"; |
| 61 | 62 | import { ORCHESTRATION_PROJECTOR_NAMES } from "./ProjectionPipeline.ts"; |
| 62 | 63 | import { |
| ... | ... | @@ -75,6 +76,9 @@ const decodeThread = Schema.decodeUnknownEffect(OrchestrationThread); |
| 75 | 76 | // activity window. Applying the limit in SQL avoids decoding an unbounded |
| 76 | 77 | // payload_json set before the projector can enforce that invariant. |
| 77 | 78 | const THREAD_DETAIL_ACTIVITY_LIMIT = 500; |
| 79 | // Snapshot payloads are decoded and projected in small sequential batches so |
| 80 | // one client read does not retain the raw payloads for the full activity window. |
| 81 | const THREAD_DETAIL_ACTIVITY_PAYLOAD_BATCH_SIZE = 25; |
| 78 | 82 | const ProjectionProjectDbRowSchema = ProjectionProject.mapFields( |
| 79 | 83 | Struct.assign({ |
| 80 | 84 | defaultModelSelection: Schema.NullOr(Schema.fromJsonString(ModelSelection)), |
| ... | ... | @@ -100,6 +104,9 @@ const ProjectionThreadActivityDbRowSchema = ProjectionThreadActivity.mapFields( |
| 100 | 104 | sequence: Schema.NullOr(NonNegativeInt), |
| 101 | 105 | }), |
| 102 | 106 | ); |
| 107 | const ProjectionThreadActivityIdRowSchema = Schema.Struct({ |
| 108 | activityId: ProjectionThreadActivity.fields.activityId, |
| 109 | }); |
| 103 | 110 | const ProjectionThreadSessionDbRowSchema = ProjectionThreadSession; |
| 104 | 111 | const ProjectionCheckpointDbRowSchema = ProjectionCheckpoint.mapFields( |
| 105 | 112 | Struct.assign({ |
| ... | ... | @@ -146,6 +153,9 @@ const ThreadActivityKindsLookupInput = Schema.Struct({ |
| 146 | 153 | threadId: ThreadId, |
| 147 | 154 | activityKinds: Schema.Array(Schema.String), |
| 148 | 155 | }); |
| 156 | const ThreadActivityIdsLookupInput = Schema.Struct({ |
| 157 | activityIds: Schema.Array(ProjectionThreadActivity.fields.activityId), |
| 158 | }); |
| 149 | 159 | // Windowed reads order turns by the stable keyset (anchor, turn key), where |
| 150 | 160 | // anchor is requested_at and turn key is |
| 151 | 161 | // COALESCE(turn_id, ''). Both are event-derived, so cursors survive the |
| ... | ... | @@ -349,6 +359,21 @@ function mapProposedPlanRow( |
| 349 | 359 | }; |
| 350 | 360 | } |
| 351 | 361 | |
| 362 | function mapThreadActivityRow( |
| 363 | row: Schema.Schema.Type<typeof ProjectionThreadActivityDbRowSchema>, |
| 364 | ): OrchestrationThreadActivity { |
| 365 | return { |
| 366 | id: row.activityId, |
| 367 | tone: row.tone, |
| 368 | kind: row.kind, |
| 369 | summary: row.summary, |
| 370 | payload: row.payload, |
| 371 | turnId: row.turnId, |
| 372 | createdAt: row.createdAt, |
| 373 | ...(row.sequence !== null ? { sequence: row.sequence } : {}), |
| 374 | }; |
| 375 | } |
| 376 | |
| 352 | 377 | function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) { |
| 353 | 378 | return (cause: unknown): ProjectionRepositoryError => |
| 354 | 379 | Schema.isSchemaError(cause) |
| ... | ... | @@ -1060,6 +1085,44 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1060 | 1085 | `, |
| 1061 | 1086 | }); |
| 1062 | 1087 | |
| 1088 | const listThreadActivityIdsByThread = SqlSchema.findAll({ |
| 1089 | Request: ThreadIdLookupInput, |
| 1090 | Result: ProjectionThreadActivityIdRowSchema, |
| 1091 | execute: ({ threadId }) => |
| 1092 | sql` |
| 1093 | SELECT activity_id AS "activityId" |
| 1094 | FROM projection_thread_activities |
| 1095 | WHERE thread_id = ${threadId} |
| 1096 | ORDER BY |
| 1097 | sequence DESC, |
| 1098 | created_at DESC, |
| 1099 | activity_id DESC |
| 1100 | LIMIT ${THREAD_DETAIL_ACTIVITY_LIMIT} |
| 1101 | `, |
| 1102 | }); |
| 1103 | |
| 1104 | const listThreadActivityRowsByIds = SqlSchema.findAll({ |
| 1105 | Request: ThreadActivityIdsLookupInput, |
| 1106 | Result: ProjectionThreadActivityDbRowSchema, |
| 1107 | execute: ({ activityIds }) => |
| 1108 | sql` |
| 1109 | SELECT |
| 1110 | activity_id AS "activityId", |
| 1111 | thread_id AS "threadId", |
| 1112 | turn_id AS "turnId", |
| 1113 | tone, |
| 1114 | kind, |
| 1115 | summary, |
| 1116 | payload_json AS "payload", |
| 1117 | sequence, |
| 1118 | created_at AS "createdAt" |
| 1119 | FROM projection_thread_activities |
| 1120 | -- The selectors already scoped these globally unique ids to the |
| 1121 | -- thread inside this transaction. Keep this as a primary-key lookup. |
| 1122 | WHERE ${sql.in("activity_id", activityIds)} |
| 1123 | `, |
| 1124 | }); |
| 1125 | |
| 1063 | 1126 | const listThreadActivityRowsByThreadAndKinds = SqlSchema.findAll({ |
| 1064 | 1127 | Request: ThreadActivityKindsLookupInput, |
| 1065 | 1128 | Result: ProjectionThreadActivityDbRowSchema, |
| ... | ... | @@ -1310,15 +1373,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1310 | 1373 | `, |
| 1311 | 1374 | }); |
| 1312 | 1375 | |
| 1313 | | // Blocking request payloads must remain available even if they predate the |
| 1314 | | // recent activity window. Each CTE returns at most one unresolved row per |
| 1315 | | // request, so the merge below stays bounded by actionable work. |
| 1316 | | const listPinnedThreadActivityRowsByThread = SqlSchema.findAll({ |
| 1317 | | Request: ThreadIdLookupInput, |
| 1318 | | Result: ProjectionThreadActivityDbRowSchema, |
| 1319 | | execute: ({ threadId }) => |
| 1320 | | sql` |
| 1321 | | WITH pending_approval_requests AS ( |
| 1376 | const pinnedThreadActivityIdsCte = (threadId: string) => sql` |
| 1377 | pending_approval_requests AS ( |
| 1322 | 1378 | SELECT request_id, thread_id |
| 1323 | 1379 | FROM projection_pending_approvals |
| 1324 | 1380 | WHERE thread_id = ${threadId} |
| ... | ... | @@ -1382,6 +1438,17 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1382 | 1438 | WHERE request_order = 1 |
| 1383 | 1439 | AND kind = 'user-input.requested' |
| 1384 | 1440 | ) |
| 1441 | `; |
| 1442 | |
| 1443 | // Blocking request payloads must remain available even if they predate the |
| 1444 | // recent activity window. Each CTE returns at most one unresolved row per |
| 1445 | // request, so the merge below stays bounded by actionable work. |
| 1446 | const listPinnedThreadActivityRowsByThread = SqlSchema.findAll({ |
| 1447 | Request: ThreadIdLookupInput, |
| 1448 | Result: ProjectionThreadActivityDbRowSchema, |
| 1449 | execute: ({ threadId }) => |
| 1450 | sql` |
| 1451 | WITH ${pinnedThreadActivityIdsCte(threadId)} |
| 1385 | 1452 | SELECT |
| 1386 | 1453 | activity.activity_id AS "activityId", |
| 1387 | 1454 | activity.thread_id AS "threadId", |
| ... | ... | @@ -1399,6 +1466,17 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1399 | 1466 | `, |
| 1400 | 1467 | }); |
| 1401 | 1468 | |
| 1469 | const listPinnedThreadActivityIdsByThread = SqlSchema.findAll({ |
| 1470 | Request: ThreadIdLookupInput, |
| 1471 | Result: ProjectionThreadActivityIdRowSchema, |
| 1472 | execute: ({ threadId }) => |
| 1473 | sql` |
| 1474 | WITH ${pinnedThreadActivityIdsCte(threadId)} |
| 1475 | SELECT activity_id AS "activityId" |
| 1476 | FROM pinned_activity_ids |
| 1477 | `, |
| 1478 | }); |
| 1479 | |
| 1402 | 1480 | const listThreadActivityRowsByThreadWindow = SqlSchema.findAll({ |
| 1403 | 1481 | Request: ThreadTurnRangeLookupInput, |
| 1404 | 1482 | Result: ProjectionThreadActivityDbRowSchema, |
| ... | ... | @@ -1466,6 +1544,48 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1466 | 1544 | `, |
| 1467 | 1545 | }); |
| 1468 | 1546 | |
| 1547 | const listThreadActivityIdsByThreadWindow = SqlSchema.findAll({ |
| 1548 | Request: ThreadTurnRangeLookupInput, |
| 1549 | Result: ProjectionThreadActivityIdRowSchema, |
| 1550 | execute: ({ threadId, minAnchorAt, minTurnKey, beforeAnchorAt, beforeTurnKey }) => |
| 1551 | sql` |
| 1552 | SELECT activity_id AS "activityId" |
| 1553 | FROM projection_thread_activities |
| 1554 | WHERE thread_id = ${threadId} |
| 1555 | AND ( |
| 1556 | turn_id IN ( |
| 1557 | SELECT turn_id FROM projection_turns |
| 1558 | WHERE thread_id = ${threadId} |
| 1559 | AND turn_id IS NOT NULL |
| 1560 | AND ( |
| 1561 | requested_at > ${minAnchorAt} |
| 1562 | OR ( |
| 1563 | requested_at = ${minAnchorAt} |
| 1564 | AND turn_id >= ${minTurnKey} |
| 1565 | ) |
| 1566 | ) |
| 1567 | AND ( |
| 1568 | requested_at < ${beforeAnchorAt} |
| 1569 | OR ( |
| 1570 | requested_at = ${beforeAnchorAt} |
| 1571 | AND turn_id < ${beforeTurnKey} |
| 1572 | ) |
| 1573 | ) |
| 1574 | ) |
| 1575 | OR ( |
| 1576 | turn_id IS NULL |
| 1577 | AND created_at >= ${minAnchorAt} |
| 1578 | AND created_at < ${beforeAnchorAt} |
| 1579 | ) |
| 1580 | ) |
| 1581 | ORDER BY |
| 1582 | sequence DESC, |
| 1583 | created_at DESC, |
| 1584 | activity_id DESC |
| 1585 | LIMIT ${THREAD_DETAIL_ACTIVITY_LIMIT} |
| 1586 | `, |
| 1587 | }); |
| 1588 | |
| 1469 | 1589 | const getFullThreadDiffContextRow = SqlSchema.findOneOption({ |
| 1470 | 1590 | Request: FullThreadDiffContextLookupInput, |
| 1471 | 1591 | Result: ProjectionFullThreadDiffContextRowSchema, |
| ... | ... | @@ -2571,49 +2691,136 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 2571 | 2691 | readonly beforeTurnKey: string; |
| 2572 | 2692 | } |
| 2573 | 2693 | |
| 2574 | | const getThreadDetailByIdBounded = ( |
| 2575 | | threadId: ThreadId, |
| 2576 | | bounds: ThreadDetailBounds | undefined, |
| 2577 | | query: ProjectionThreadDetailQuery | undefined = undefined, |
| 2578 | | ) => |
| 2579 | | Effect.gen(function* () { |
| 2580 | | const activityRowsEffect = ( |
| 2581 | | query?.activityKinds === undefined |
| 2582 | | ? bounds === undefined |
| 2583 | | ? listThreadActivityRowsByThread({ threadId }) |
| 2584 | | : listThreadActivityRowsByThreadWindow({ threadId, ...bounds }) |
| 2585 | | : query.activityKinds.length === 0 |
| 2586 | | ? Effect.succeed([]) |
| 2587 | | : listThreadActivityRowsByThreadAndKinds({ |
| 2588 | | threadId, |
| 2589 | | activityKinds: query.activityKinds, |
| 2590 | | }) |
| 2694 | type ThreadDetailActivityRead = |
| 2695 | | { |
| 2696 | readonly mode: "raw"; |
| 2697 | readonly query?: ProjectionThreadDetailQuery; |
| 2698 | } |
| 2699 | | { |
| 2700 | readonly mode: "client"; |
| 2701 | }; |
| 2702 | |
| 2703 | const listProjectedThreadActivities = Effect.fn( |
| 2704 | "ProjectionSnapshotQuery.listProjectedThreadActivities", |
| 2705 | )(function* (threadId: ThreadId, bounds: ThreadDetailBounds | undefined) { |
| 2706 | const [activityIdRows, pinnedActivityIdRows] = yield* Effect.all([ |
| 2707 | (bounds === undefined |
| 2708 | ? listThreadActivityIdsByThread({ threadId }) |
| 2709 | : listThreadActivityIdsByThreadWindow({ threadId, ...bounds }) |
| 2591 | 2710 | ).pipe( |
| 2592 | 2711 | Effect.mapError( |
| 2593 | 2712 | toPersistenceSqlOrDecodeError( |
| 2594 | | "ProjectionSnapshotQuery.getThreadDetailById:listActivities:query", |
| 2595 | | "ProjectionSnapshotQuery.getThreadDetailById:listActivities:decodeRows", |
| 2713 | "ProjectionSnapshotQuery.getThreadDetailById:listActivityIds:query", |
| 2714 | "ProjectionSnapshotQuery.getThreadDetailById:listActivityIds:decodeRows", |
| 2596 | 2715 | ), |
| 2597 | 2716 | ), |
| 2717 | ), |
| 2718 | listPinnedThreadActivityIdsByThread({ threadId }).pipe( |
| 2719 | Effect.mapError( |
| 2720 | toPersistenceSqlOrDecodeError( |
| 2721 | "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivityIds:query", |
| 2722 | "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivityIds:decodeRows", |
| 2723 | ), |
| 2724 | ), |
| 2725 | ), |
| 2726 | ]); |
| 2727 | const activityIds = [ |
| 2728 | ...new Set([...activityIdRows, ...pinnedActivityIdRows].map(({ activityId }) => activityId)), |
| 2729 | ]; |
| 2730 | const activities: OrchestrationThreadActivity[] = []; |
| 2731 | |
| 2732 | for ( |
| 2733 | let offset = 0; |
| 2734 | offset < activityIds.length; |
| 2735 | offset += THREAD_DETAIL_ACTIVITY_PAYLOAD_BATCH_SIZE |
| 2736 | ) { |
| 2737 | const batchIds = activityIds.slice( |
| 2738 | offset, |
| 2739 | offset + THREAD_DETAIL_ACTIVITY_PAYLOAD_BATCH_SIZE, |
| 2598 | 2740 | ); |
| 2599 | | const pinnedActivityRowsEffect = |
| 2600 | | query?.activityKinds === undefined |
| 2601 | | ? listPinnedThreadActivityRowsByThread({ threadId }).pipe( |
| 2602 | | Effect.mapError( |
| 2603 | | toPersistenceSqlOrDecodeError( |
| 2604 | | "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivities:query", |
| 2605 | | "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivities:decodeRows", |
| 2741 | const batchRows = yield* listThreadActivityRowsByIds({ activityIds: batchIds }).pipe( |
| 2742 | Effect.mapError( |
| 2743 | toPersistenceSqlOrDecodeError( |
| 2744 | "ProjectionSnapshotQuery.getThreadDetailById:listActivityPayloadBatch:query", |
| 2745 | "ProjectionSnapshotQuery.getThreadDetailById:listActivityPayloadBatch:decodeRows", |
| 2746 | ), |
| 2747 | ), |
| 2748 | ); |
| 2749 | for (const row of batchRows) { |
| 2750 | activities.push(projectActivityPayload(mapThreadActivityRow(row))); |
| 2751 | } |
| 2752 | } |
| 2753 | |
| 2754 | return activities.toSorted( |
| 2755 | (left, right) => |
| 2756 | (left.sequence ?? -1) - (right.sequence ?? -1) || |
| 2757 | left.createdAt.localeCompare(right.createdAt) || |
| 2758 | left.id.localeCompare(right.id), |
| 2759 | ); |
| 2760 | }); |
| 2761 | |
| 2762 | const getThreadDetailByIdBounded = ( |
| 2763 | threadId: ThreadId, |
| 2764 | bounds: ThreadDetailBounds | undefined, |
| 2765 | activityRead: ThreadDetailActivityRead = { mode: "raw" }, |
| 2766 | ) => |
| 2767 | Effect.gen(function* () { |
| 2768 | const activitiesEffect = |
| 2769 | activityRead.mode === "client" |
| 2770 | ? listProjectedThreadActivities(threadId, bounds) |
| 2771 | : Effect.all([ |
| 2772 | (activityRead.query?.activityKinds === undefined |
| 2773 | ? bounds === undefined |
| 2774 | ? listThreadActivityRowsByThread({ threadId }) |
| 2775 | : listThreadActivityRowsByThreadWindow({ threadId, ...bounds }) |
| 2776 | : activityRead.query.activityKinds.length === 0 |
| 2777 | ? Effect.succeed([]) |
| 2778 | : listThreadActivityRowsByThreadAndKinds({ |
| 2779 | threadId, |
| 2780 | activityKinds: activityRead.query.activityKinds, |
| 2781 | }) |
| 2782 | ).pipe( |
| 2783 | Effect.mapError( |
| 2784 | toPersistenceSqlOrDecodeError( |
| 2785 | "ProjectionSnapshotQuery.getThreadDetailById:listActivities:query", |
| 2786 | "ProjectionSnapshotQuery.getThreadDetailById:listActivities:decodeRows", |
| 2787 | ), |
| 2606 | 2788 | ), |
| 2607 | 2789 | ), |
| 2608 | | ) |
| 2609 | | : Effect.succeed([]); |
| 2790 | activityRead.query?.activityKinds === undefined |
| 2791 | ? listPinnedThreadActivityRowsByThread({ threadId }).pipe( |
| 2792 | Effect.mapError( |
| 2793 | toPersistenceSqlOrDecodeError( |
| 2794 | "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivities:query", |
| 2795 | "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivities:decodeRows", |
| 2796 | ), |
| 2797 | ), |
| 2798 | ) |
| 2799 | : Effect.succeed([]), |
| 2800 | ]).pipe( |
| 2801 | Effect.map(([activityRows, pinnedActivityRows]) => |
| 2802 | [ |
| 2803 | ...new Map( |
| 2804 | [...activityRows, ...pinnedActivityRows].map( |
| 2805 | (row) => [row.activityId, row] as const, |
| 2806 | ), |
| 2807 | ).values(), |
| 2808 | ] |
| 2809 | .toSorted( |
| 2810 | (left, right) => |
| 2811 | (left.sequence ?? -1) - (right.sequence ?? -1) || |
| 2812 | left.createdAt.localeCompare(right.createdAt) || |
| 2813 | left.activityId.localeCompare(right.activityId), |
| 2814 | ) |
| 2815 | .map(mapThreadActivityRow), |
| 2816 | ), |
| 2817 | ); |
| 2610 | 2818 | |
| 2611 | 2819 | const [ |
| 2612 | 2820 | threadRow, |
| 2613 | 2821 | messageRows, |
| 2614 | 2822 | proposedPlanRows, |
| 2615 | | activityRows, |
| 2616 | | pinnedActivityRows, |
| 2823 | activities, |
| 2617 | 2824 | checkpointRows, |
| 2618 | 2825 | latestTurnRow, |
| 2619 | 2826 | sessionRow, |
| ... | ... | @@ -2645,8 +2852,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 2645 | 2852 | ), |
| 2646 | 2853 | ), |
| 2647 | 2854 | ), |
| 2648 | | activityRowsEffect, |
| 2649 | | pinnedActivityRowsEffect, |
| 2855 | activitiesEffect, |
| 2650 | 2856 | listCheckpointRowsByThread({ threadId }).pipe( |
| 2651 | 2857 | Effect.mapError( |
| 2652 | 2858 | toPersistenceSqlOrDecodeError( |
| ... | ... | @@ -2677,17 +2883,6 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 2677 | 2883 | return Option.none<OrchestrationThread>(); |
| 2678 | 2884 | } |
| 2679 | 2885 | |
| 2680 | | const selectedActivityRows = [ |
| 2681 | | ...new Map( |
| 2682 | | [...activityRows, ...pinnedActivityRows].map((row) => [row.activityId, row] as const), |
| 2683 | | ).values(), |
| 2684 | | ].toSorted( |
| 2685 | | (left, right) => |
| 2686 | | (left.sequence ?? -1) - (right.sequence ?? -1) || |
| 2687 | | left.createdAt.localeCompare(right.createdAt) || |
| 2688 | | left.activityId.localeCompare(right.activityId), |
| 2689 | | ); |
| 2690 | | |
| 2691 | 2886 | const thread = { |
| 2692 | 2887 | id: threadRow.value.threadId, |
| 2693 | 2888 | projectId: threadRow.value.projectId, |
| ... | ... | @@ -2729,21 +2924,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 2729 | 2924 | return message; |
| 2730 | 2925 | }), |
| 2731 | 2926 | proposedPlans: proposedPlanRows.map(mapProposedPlanRow), |
| 2732 | | activities: selectedActivityRows.map((row) => { |
| 2733 | | const activity = { |
| 2734 | | id: row.activityId, |
| 2735 | | tone: row.tone, |
| 2736 | | kind: row.kind, |
| 2737 | | summary: row.summary, |
| 2738 | | payload: row.payload, |
| 2739 | | turnId: row.turnId, |
| 2740 | | createdAt: row.createdAt, |
| 2741 | | }; |
| 2742 | | if (row.sequence !== null) { |
| 2743 | | return Object.assign(activity, { sequence: row.sequence }); |
| 2744 | | } |
| 2745 | | return activity; |
| 2746 | | }), |
| 2927 | activities, |
| 2747 | 2928 | checkpoints: checkpointRows.map((row) => ({ |
| 2748 | 2929 | turnId: row.turnId, |
| 2749 | 2930 | checkpointTurnCount: row.checkpointTurnCount, |
| ... | ... | @@ -2768,7 +2949,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 2768 | 2949 | const getThreadDetailById: ProjectionSnapshotQueryShape["getThreadDetailById"] = ( |
| 2769 | 2950 | threadId, |
| 2770 | 2951 | query, |
| 2771 | | ) => getThreadDetailByIdBounded(threadId, undefined, query); |
| 2952 | ) => |
| 2953 | getThreadDetailByIdBounded(threadId, undefined, { |
| 2954 | mode: "raw", |
| 2955 | ...(query === undefined ? {} : { query }), |
| 2956 | }); |
| 2772 | 2957 | |
| 2773 | 2958 | // Bounds pathological fan-out: one user turn that spawned hundreds of |
| 2774 | 2959 | // subagent turns still pages in bounded chunks, at the cost of splitting the |
| ... | ... | @@ -2792,7 +2977,9 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 2792 | 2977 | .withTransaction( |
| 2793 | 2978 | Effect.gen(function* () { |
| 2794 | 2979 | if (window?.turnLimit === undefined) { |
| 2795 | | const thread = yield* getThreadDetailById(threadId); |
| 2980 | const thread = yield* getThreadDetailByIdBounded(threadId, undefined, { |
| 2981 | mode: "client", |
| 2982 | }); |
| 2796 | 2983 | if (Option.isNone(thread)) { |
| 2797 | 2984 | return Option.none<OrchestrationThreadDetailSnapshot>(); |
| 2798 | 2985 | } |
| ... | ... | @@ -2845,7 +3032,9 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 2845 | 3032 | ? { minAnchorAt: "", minTurnKey: "", beforeAnchorAt: "", beforeTurnKey: "" } |
| 2846 | 3033 | : undefined; |
| 2847 | 3034 | |
| 2848 | | const thread = yield* getThreadDetailByIdBounded(threadId, emptyBounds ?? bounds); |
| 3035 | const thread = yield* getThreadDetailByIdBounded(threadId, emptyBounds ?? bounds, { |
| 3036 | mode: "client", |
| 3037 | }); |
| 2849 | 3038 | if (Option.isNone(thread)) { |
| 2850 | 3039 | return Option.none<OrchestrationThreadDetailSnapshot>(); |
| 2851 | 3040 | } |