authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-10-04 00:28:10-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-04 00:28:10-07:00
log1302ccacbd6d3e957f2f95239556adb6eae79a48
tree9fd1adf41b7d4bf7390d3e5ae12274a2f4023b7f
parente19a48d220a18ef198178b48d12aa1a034596489
signature Signed by PGP key B5690EEEBB952194

fix(clients): Working section keeps its order while agents finish and wake (#15418)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

10 files changed, 127 insertions(+), 30 deletions(-)

apps/mobile/src/features/threads/threadListV2.ts+3-2
...@@ -15,6 +15,7 @@ import {...@@ -15,6 +15,7 @@ import {
15 createInboxReturnTracker,15 createInboxReturnTracker,
16 isThreadWorking,16 isThreadWorking,
17 sortInboxThreadsByReturn,17 sortInboxThreadsByReturn,
18 sortWorkingThreadsBySend,
18} from "@t3tools/client-runtime/state/thread-inbox";19} from "@t3tools/client-runtime/state/thread-inbox";
19import {20import {
20 sortActiveThreadsByOrderKey,21 sortActiveThreadsByOrderKey,
...@@ -728,8 +729,8 @@ export function buildThreadListV2Items(input: {...@@ -728,8 +729,8 @@ export function buildThreadListV2Items(input: {
728 const orderedActive = workingShelfEnabled729 const orderedActive = workingShelfEnabled
729 ? sortInboxThreadsByReturn(active, input.inboxReturnAt)730 ? sortInboxThreadsByReturn(active, input.inboxReturnAt)
730 : applyPendingThreadOrder(sortThreadsForListV2(active), "active", pending);731 : applyPendingThreadOrder(sortThreadsForListV2(active), "active", pending);
731 // Newest work first, by the same clock as the inbox.732 // Newest send first; finishing and waking again do not move a row.
732 const orderedWorking = sortInboxThreadsByReturn(working);733 const orderedWorking = sortWorkingThreadsBySend(working);
733 const orderedSnoozed = [...snoozed].sort(734 const orderedSnoozed = [...snoozed].sort(
734 (left, right) =>735 (left, right) =>
735 parseTimestampMs(left.snoozedUntil ?? "") - parseTimestampMs(right.snoozedUntil ?? ""),736 parseTimestampMs(left.snoozedUntil ?? "") - parseTimestampMs(right.snoozedUntil ?? ""),
apps/server/src/orchestration-v2/ProjectionSettlement.test.ts+8
...@@ -343,6 +343,14 @@ it.effect.each([...@@ -343,6 +343,14 @@ it.effect.each([
343 DateTime.formatIso(candidate.latestUserAuthoredMessageAt!),343 DateTime.formatIso(candidate.latestUserAuthoredMessageAt!),
344 DateTime.formatIso(written),344 DateTime.formatIso(written),
345 );345 );
346 // The shell carries the same stamp, which orders the Working section.
347 const shellThread = (yield* store.getShellSnapshot()).threads.find(
348 (thread) => thread.id === threadId,
349 );
350 assert.equal(
351 DateTime.formatIso(shellThread!.latestUserAuthoredMessageAt!),
352 DateTime.formatIso(written),
353 );
346 }).pipe(Effect.provide(testLayer)),354 }).pipe(Effect.provide(testLayer)),
347);355);
348356
apps/server/src/orchestration-v2/ProjectionStore.ts+35-22
...@@ -161,10 +161,7 @@ export type ProjectionThreadPullRequests = Pick<...@@ -161,10 +161,7 @@ export type ProjectionThreadPullRequests = Pick<
161161
162/**162/**
163 * Thread activity needed by settlement, without transcript or fork history.163 * Thread activity needed by settlement, without transcript or fork history.
164 * `latestUserAuthoredMessageAt` is the last message the user wrote. Agent,164 * Settlement always loads `latestUserAuthoredMessageAt`, so it is required here.
165 * provider, and server notifications also use the user role, so
166 * `latestUserMessageAt` moves when background work or a PR watch wakes the
167 * agent.
168 */165 */
169export type ProjectionSettlementCandidate = Pick<166export type ProjectionSettlementCandidate = Pick<
170 OrchestrationV2ThreadShell,167 OrchestrationV2ThreadShell,
...@@ -917,6 +914,7 @@ type ShellThreadRow = {...@@ -917,6 +914,7 @@ type ShellThreadRow = {
917 readonly blocking_failure_payload_json: string | null;914 readonly blocking_failure_payload_json: string | null;
918 readonly pending_request_payload_json: string | null;915 readonly pending_request_payload_json: string | null;
919 readonly latest_user_message_at: string | null;916 readonly latest_user_message_at: string | null;
917 readonly latest_user_authored_message_at: string | null;
920 readonly has_actionable_proposed_plan: number;918 readonly has_actionable_proposed_plan: number;
921 readonly item_count: number;919 readonly item_count: number;
922 readonly runless_item_count: number;920 readonly runless_item_count: number;
...@@ -938,7 +936,8 @@ type SettlementThreadRow = Pick<...@@ -938,7 +936,8 @@ type SettlementThreadRow = Pick<
938 | "latest_run_started_at"936 | "latest_run_started_at"
939 | "latest_run_completed_at"937 | "latest_run_completed_at"
940 | "latest_user_message_at"938 | "latest_user_message_at"
941> & { readonly latest_user_authored_message_at: string | null };939 | "latest_user_authored_message_at"
940>;
942941
943type ShellRunItemCountRow = {942type ShellRunItemCountRow = {
944 readonly thread_id: string;943 readonly thread_id: string;
...@@ -1329,13 +1328,13 @@ export function threadShellFromProjection(...@@ -1329,13 +1328,13 @@ export function threadShellFromProjection(
1329 (left, right) =>1328 (left, right) =>
1330 DateTime.toEpochMillis(right.createdAt) - DateTime.toEpochMillis(left.createdAt),1329 DateTime.toEpochMillis(right.createdAt) - DateTime.toEpochMillis(left.createdAt),
1331 )[0] ?? null;1330 )[0] ?? null;
1332 const latestUserMessage =1331 const userMessages = projection.messages
1333 projection.messages1332 .filter((message) => message.role === "user")
1334 .filter((message) => message.role === "user")1333 .toSorted(
1335 .toSorted(1334 (left, right) =>
1336 (left, right) =>1335 DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt),
1337 DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt),1336 );
1338 )[0] ?? null;1337 const latestUserMessage = userMessages[0] ?? null;
1339 const pendingBackgroundTasks = derivePendingBackgroundWork({1338 const pendingBackgroundTasks = derivePendingBackgroundWork({
1340 latestRun,1339 latestRun,
1341 providerThreads: projection.providerThreads,1340 providerThreads: projection.providerThreads,
...@@ -1396,6 +1395,8 @@ export function threadShellFromProjection(...@@ -1396,6 +1395,8 @@ export function threadShellFromProjection(
1396 // initial hydration and streaming updates independent of transcript size.1395 // initial hydration and streaming updates independent of transcript size.
1397 latestVisibleMessage: null,1396 latestVisibleMessage: null,
1398 latestUserMessageAt: latestUserMessage?.updatedAt ?? null,1397 latestUserMessageAt: latestUserMessage?.updatedAt ?? null,
1398 latestUserAuthoredMessageAt:
1399 userMessages.find((message) => message.createdBy === "user")?.updatedAt ?? null,
1399 hasActionableProposedPlan: projection.plans.some(1400 hasActionableProposedPlan: projection.plans.some(
1400 (plan) => plan.kind === "proposed_plan" && plan.status === "active",1401 (plan) => plan.kind === "proposed_plan" && plan.status === "active",
1401 ),1402 ),
...@@ -1480,6 +1481,7 @@ type ShellThreadState = {...@@ -1480,6 +1481,7 @@ type ShellThreadState = {
1480 readonly usageLimitResetAt: OrchestrationV2ThreadShell["usageLimitResetAt"];1481 readonly usageLimitResetAt: OrchestrationV2ThreadShell["usageLimitResetAt"];
1481 readonly pendingRuntimeRequest: OrchestrationV2ThreadProjection["runtimeRequests"][number] | null;1482 readonly pendingRuntimeRequest: OrchestrationV2ThreadProjection["runtimeRequests"][number] | null;
1482 readonly latestUserMessageAt: DateTime.Utc | null;1483 readonly latestUserMessageAt: DateTime.Utc | null;
1484 readonly latestUserAuthoredMessageAt: DateTime.Utc | null;
1483 readonly hasActionableProposedPlan: boolean;1485 readonly hasActionableProposedPlan: boolean;
1484 readonly pendingBackgroundTasks: OrchestrationV2ThreadShell["pendingBackgroundTasks"];1486 readonly pendingBackgroundTasks: OrchestrationV2ThreadShell["pendingBackgroundTasks"];
1485 readonly providerInstanceHistory: OrchestrationV2ThreadShell["providerInstanceHistory"];1487 readonly providerInstanceHistory: OrchestrationV2ThreadShell["providerInstanceHistory"];
...@@ -1625,6 +1627,7 @@ function shellFromState(input: {...@@ -1625,6 +1627,7 @@ function shellFromState(input: {
1625 },1627 },
1626 latestVisibleMessage: null,1628 latestVisibleMessage: null,
1627 latestUserMessageAt: input.state.latestUserMessageAt,1629 latestUserMessageAt: input.state.latestUserMessageAt,
1630 latestUserAuthoredMessageAt: input.state.latestUserAuthoredMessageAt,
1628 hasActionableProposedPlan: input.state.hasActionableProposedPlan,1631 hasActionableProposedPlan: input.state.hasActionableProposedPlan,
1629 pendingBackgroundTasks: input.state.pendingBackgroundTasks,1632 pendingBackgroundTasks: input.state.pendingBackgroundTasks,
1630 providerInstanceHistory: input.state.providerInstanceHistory,1633 providerInstanceHistory: input.state.providerInstanceHistory,
...@@ -4887,6 +4890,15 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =...@@ -4887,6 +4890,15 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
4887 ORDER BY message.updated_at DESC, message.message_id DESC4890 ORDER BY message.updated_at DESC, message.message_id DESC
4888 LIMIT 14891 LIMIT 1
4889 ) AS latest_user_message_at,4892 ) AS latest_user_message_at,
4893 (
4894 SELECT message.updated_at
4895 FROM orchestration_v2_projection_messages message
4896 WHERE message.thread_id = t.thread_id
4897 AND message.role = 'user'
4898 AND json_extract(message.payload_json, '$.createdBy') = 'user'
4899 ORDER BY message.updated_at DESC, message.message_id DESC
4900 LIMIT 1
4901 ) AS latest_user_authored_message_at,
4890 EXISTS (4902 EXISTS (
4891 SELECT 14903 SELECT 1
4892 FROM orchestration_v2_projection_plans plan4904 FROM orchestration_v2_projection_plans plan
...@@ -5325,6 +5337,10 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =...@@ -5325,6 +5337,10 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
5325 row.latest_user_message_at === null5337 row.latest_user_message_at === null
5326 ? null5338 ? null
5327 : DateTime.makeUnsafe(row.latest_user_message_at),5339 : DateTime.makeUnsafe(row.latest_user_message_at),
5340 latestUserAuthoredMessageAt:
5341 row.latest_user_authored_message_at === null
5342 ? null
5343 : DateTime.makeUnsafe(row.latest_user_authored_message_at),
5328 hasActionableProposedPlan: row.has_actionable_proposed_plan === 1,5344 hasActionableProposedPlan: row.has_actionable_proposed_plan === 1,
5329 pendingBackgroundTasks,5345 pendingBackgroundTasks,
5330 providerInstanceHistory: providerInstanceHistoryForShell({5346 providerInstanceHistory: providerInstanceHistoryForShell({
...@@ -5630,16 +5646,13 @@ export const layerMemory: Layer.Layer<ProjectionStoreV2> = Layer.effect(...@@ -5630,16 +5646,13 @@ export const layerMemory: Layer.Layer<ProjectionStoreV2> = Layer.effect(
5630 !runs.some(isActivityRunForShell) &&5646 !runs.some(isActivityRunForShell) &&
5631 !runtimeRequests.some((request) => request.status === "pending"),5647 !runtimeRequests.some((request) => request.status === "pending"),
5632 )5648 )
5633 .map((projection) => ({5649 .map((projection) => {
5634 ...threadShellFromProjection(projection),5650 const shell = threadShellFromProjection(projection);
5635 latestUserAuthoredMessageAt:5651 return {
5636 projection.messages5652 ...shell,
5637 .filter((message) => message.role === "user" && message.createdBy === "user")5653 latestUserAuthoredMessageAt: shell.latestUserAuthoredMessageAt ?? null,
5638 .map((message) => message.updatedAt)5654 };
5639 .toSorted(5655 })
5640 (left, right) => DateTime.toEpochMillis(right) - DateTime.toEpochMillis(left),
5641 )[0] ?? null,
5642 }))
5643 .toSorted(5656 .toSorted(
5644 (left, right) =>5657 (left, right) =>
5645 DateTime.toEpochMillis(left.updatedAt) - DateTime.toEpochMillis(right.updatedAt) ||5658 DateTime.toEpochMillis(left.updatedAt) - DateTime.toEpochMillis(right.updatedAt) ||
apps/web/src/components/Sidebar.logic.ts+1
...@@ -1055,6 +1055,7 @@ export { sortActiveThreadsByOrderKey as sortThreadsForSidebar } from "@t3tools/c...@@ -1055,6 +1055,7 @@ export { sortActiveThreadsByOrderKey as sortThreadsForSidebar } from "@t3tools/c
1055export {1055export {
1056 isThreadWorking as isSidebarThreadWorking,1056 isThreadWorking as isSidebarThreadWorking,
1057 sortInboxThreadsByReturn,1057 sortInboxThreadsByReturn,
1058 sortWorkingThreadsBySend,
1058} from "@t3tools/client-runtime/state/thread-inbox";1059} from "@t3tools/client-runtime/state/thread-inbox";
10591060
1060// Pinned-reorder key math and the keyed sort live in client-runtime1061// Pinned-reorder key math and the keyed sort live in client-runtime
apps/web/src/components/Sidebar.tsx+3-2
...@@ -209,6 +209,7 @@ import {...@@ -209,6 +209,7 @@ import {
209 sortPinnedThreadsForSidebar,209 sortPinnedThreadsForSidebar,
210 sortSidebarV2ProjectGroups,210 sortSidebarV2ProjectGroups,
211 sortThreadsForSidebar,211 sortThreadsForSidebar,
212 sortWorkingThreadsBySend,
212 useThreadJumpHintVisibility,213 useThreadJumpHintVisibility,
213 useRetainedValue,214 useRetainedValue,
214 useSidebarRowSubscriptionLease,215 useSidebarRowSubscriptionLease,
...@@ -2794,8 +2795,8 @@ export default function Sidebar() {...@@ -2794,8 +2795,8 @@ export default function Sidebar() {
2794 preferredIds: optimisticDrop.order,2795 preferredIds: optimisticDrop.order,
2795 getId: (thread) => scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),2796 getId: (thread) => scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
2796 }),2797 }),
2797 // Newest work first, by the same clock as the inbox.2798 // Newest send first; finishing and waking again do not move a row.
2798 workingThreads: sortInboxThreadsByReturn(working),2799 workingThreads: sortWorkingThreadsBySend(working),
2799 // Soonest wake first: "what comes back next" is the shelf's question.2800 // Soonest wake first: "what comes back next" is the shelf's question.
2800 snoozedThreads: snoozed.toSorted(2801 snoozedThreads: snoozed.toSorted(
2801 (left, right) =>2802 (left, right) =>
docs/user/thread-sidebar.md+3-2
...@@ -117,8 +117,9 @@ open....@@ -117,8 +117,9 @@ open.
117Turn on **Settings → General → Working section (beta)** on web and desktop, or **Settings →117Turn on **Settings → General → Working section (beta)** on web and desktop, or **Settings →
118Thread behavior → Working section** on iOS and Android, to move threads that are working or118Thread behavior → Working section** on iOS and Android, to move threads that are working or
119monitoring into a collapsed **Working** section below the active list. A thread returns to the top119monitoring into a collapsed **Working** section below the active list. A thread returns to the top
120of the active list when it finishes, fails, or needs an approval or answer. Pinned threads stay in120of the active list when it finishes, fails, or needs an approval or answer. The Working section
121the pinned section. Each device keeps its own choice.121lists the thread you last sent work to first. Pinned threads stay in the pinned section. Each
122device keeps its own choice.
122123
123While this is on, the active list is ordered by when each thread last came back to you, so you124While this is on, the active list is ordered by when each thread last came back to you, so you
124cannot drag or move threads within it. Your saved order returns when you turn it off.125cannot drag or move threads within it. Your saved order returns when you turn it off.
packages/client-runtime/src/state/models.ts+5
...@@ -102,6 +102,8 @@ export interface EnvironmentThreadShell {...@@ -102,6 +102,8 @@ export interface EnvironmentThreadShell {
102 readonly latestRun: ThreadRunSummary | null;102 readonly latestRun: ThreadRunSummary | null;
103 readonly runtime: ThreadRuntimeSummary | null;103 readonly runtime: ThreadRuntimeSummary | null;
104 readonly latestUserMessageAt: string | null;104 readonly latestUserMessageAt: string | null;
105 /** The last message the user wrote. `undefined` means the server predates it. */
106 readonly latestUserAuthoredMessageAt?: string | null;
105 readonly hasPendingApprovals: boolean;107 readonly hasPendingApprovals: boolean;
106 readonly hasPendingUserInput: boolean;108 readonly hasPendingUserInput: boolean;
107 readonly hasActionableProposedPlan: boolean;109 readonly hasActionableProposedPlan: boolean;
...@@ -241,6 +243,9 @@ export function presentThreadShell(...@@ -241,6 +243,9 @@ export function presentThreadShell(
241 latestRun,243 latestRun,
242 runtime: shellRuntime(thread),244 runtime: shellRuntime(thread),
243 latestUserMessageAt: nullableIso(thread.latestUserMessageAt),245 latestUserMessageAt: nullableIso(thread.latestUserMessageAt),
246 ...(thread.latestUserAuthoredMessageAt === undefined
247 ? {}
248 : { latestUserAuthoredMessageAt: nullableIso(thread.latestUserAuthoredMessageAt) }),
244 hasPendingApprovals:249 hasPendingApprovals:
245 thread.pendingRuntimeRequest !== null &&250 thread.pendingRuntimeRequest !== null &&
246 thread.pendingRuntimeRequest.kind !== "user_input" &&251 thread.pendingRuntimeRequest.kind !== "user_input" &&
packages/client-runtime/src/state/threadInbox.test.ts+29-2
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import { EnvironmentId, ProviderInstanceId, ThreadId } from "@t3tools/contracts";1import { EnvironmentId, ProviderInstanceId, RunId, ThreadId } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";2import { describe, expect, it } from "vite-plus/test";
33
4import { createInboxReturnTracker } from "./threadInbox.ts";4import { createInboxReturnTracker, sortWorkingThreadsBySend } from "./threadInbox.ts";
55
6const environmentId = EnvironmentId.make("environment-1");6const environmentId = EnvironmentId.make("environment-1");
77
...@@ -57,3 +57,30 @@ describe("createInboxReturnTracker", () => {...@@ -57,3 +57,30 @@ describe("createInboxReturnTracker", () => {
57 expect(tracker.returnedAt(thread("b", false))).toBeDefined();57 expect(tracker.returnedAt(thread("b", false))).toBeDefined();
58 });58 });
59});59});
60
61describe("sortWorkingThreadsBySend", () => {
62 it("orders by the last message the user sent, not by later runs", () => {
63 const sentFirst = {
64 ...thread("sent-first", true),
65 latestUserAuthoredMessageAt: "2026-06-01T01:00:00.000Z",
66 // A wake run requested after the other thread's send.
67 latestRun: {
68 runId: RunId.make("run:wake"),
69 status: "running" as const,
70 requestedAt: "2026-06-01T04:00:00.000Z",
71 startedAt: "2026-06-01T04:00:00.000Z",
72 completedAt: null,
73 assistantMessageId: null,
74 },
75 };
76 const sentLast = {
77 ...thread("sent-last", true),
78 latestUserAuthoredMessageAt: "2026-06-01T02:00:00.000Z",
79 };
80 // Launched by an agent: no user message, so creation time is the send.
81 const launched = { ...thread("launched", true), latestUserAuthoredMessageAt: null };
82 expect(
83 sortWorkingThreadsBySend([launched, sentFirst, sentLast]).map((thread) => thread.id),
84 ).toEqual(["sent-last", "sent-first", "launched"]);
85 });
86});
packages/client-runtime/src/state/threadInbox.ts+34
...@@ -58,6 +58,40 @@ export function sortInboxThreadsByReturn<T extends InboxThreadInput>(...@@ -58,6 +58,40 @@ export function sortInboxThreadsByReturn<T extends InboxThreadInput>(
58 ),58 ),
59 ]),59 ]),
60 );60 );
61 return sortNewestFirst(threads, timestamps);
62}
63
64type WorkingSortInput = Pick<
65 EnvironmentThreadShell,
66 "id" | "environmentId" | "createdAt" | "latestRun" | "latestUserAuthoredMessageAt"
67>;
68
69/** The Working section lists threads newest first by the last message the
70 user sent. Runs ending and wakes (background results, delegated results,
71 PR watches) do not move a row, so the order stays put while agents finish
72 and resume. Servers without the authored stamp fall back to the latest
73 run's request time. */
74export function sortWorkingThreadsBySend<T extends WorkingSortInput>(threads: readonly T[]): T[] {
75 const timestamps = new Map(
76 threads.map((thread) => [
77 thread,
78 Math.max(
79 toSortableTimestamp(thread.createdAt) ?? 0,
80 toSortableTimestamp(
81 (thread.latestUserAuthoredMessageAt === undefined
82 ? thread.latestRun?.requestedAt
83 : thread.latestUserAuthoredMessageAt) ?? undefined,
84 ) ?? 0,
85 ),
86 ]),
87 );
88 return sortNewestFirst(threads, timestamps);
89}
90
91function sortNewestFirst<T extends Pick<EnvironmentThreadShell, "id" | "environmentId">>(
92 threads: readonly T[],
93 timestamps: ReadonlyMap<T, number>,
94): T[] {
61 return [...threads].sort(95 return [...threads].sort(
62 (left, right) =>96 (left, right) =>
63 timestamps.get(right)! - timestamps.get(left)! ||97 timestamps.get(right)! - timestamps.get(left)! ||
packages/contracts/src/orchestrationV2.ts+6
...@@ -1747,6 +1747,11 @@ export const OrchestrationV2ThreadShell = Schema.Struct({...@@ -1747,6 +1747,11 @@ export const OrchestrationV2ThreadShell = Schema.Struct({
1747 pendingRuntimeRequest: Schema.NullOr(OrchestrationV2PendingRuntimeRequestSummary),1747 pendingRuntimeRequest: Schema.NullOr(OrchestrationV2PendingRuntimeRequestSummary),
1748 latestVisibleMessage: Schema.NullOr(OrchestrationV2LatestVisibleMessageSummary),1748 latestVisibleMessage: Schema.NullOr(OrchestrationV2LatestVisibleMessageSummary),
1749 latestUserMessageAt: Schema.NullOr(Schema.DateTimeUtc),1749 latestUserMessageAt: Schema.NullOr(Schema.DateTimeUtc),
1750 /**
1751 * The last message the user wrote. Wakes and agent messages also use the
1752 * user role, so they move latestUserMessageAt but not this.
1753 */
1754 latestUserAuthoredMessageAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)),
1750 hasActionableProposedPlan: Schema.Boolean,1755 hasActionableProposedPlan: Schema.Boolean,
1751 // Normalized post-settlement background work for sidebar Waiting pills.1756 // Normalized post-settlement background work for sidebar Waiting pills.
1752 // Empty when the latest root run is still active or no pending work remains.1757 // Empty when the latest root run is still active or no pending work remains.
...@@ -2269,6 +2274,7 @@ export const OrchestrationV2ThreadShellJson = OrchestrationV2ThreadShell.mapFiel...@@ -2269,6 +2274,7 @@ export const OrchestrationV2ThreadShellJson = OrchestrationV2ThreadShell.mapFiel
2269 pendingRuntimeRequest: Schema.NullOr(OrchestrationV2PendingRuntimeRequestSummaryJson),2274 pendingRuntimeRequest: Schema.NullOr(OrchestrationV2PendingRuntimeRequestSummaryJson),
2270 latestVisibleMessage: Schema.NullOr(OrchestrationV2LatestVisibleMessageSummaryJson),2275 latestVisibleMessage: Schema.NullOr(OrchestrationV2LatestVisibleMessageSummaryJson),
2271 latestUserMessageAt: Schema.NullOr(Schema.DateTimeUtcFromString),2276 latestUserMessageAt: Schema.NullOr(Schema.DateTimeUtcFromString),
2277 latestUserAuthoredMessageAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtcFromString)),
2272 createdAt: Schema.DateTimeUtcFromString,2278 createdAt: Schema.DateTimeUtcFromString,
2273 updatedAt: Schema.DateTimeUtcFromString,2279 updatedAt: Schema.DateTimeUtcFromString,
2274 archivedAt: Schema.NullOr(Schema.DateTimeUtcFromString),2280 archivedAt: Schema.NullOr(Schema.DateTimeUtcFromString),