authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-07 02:00:36-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-07 02:00:36-07:00
log6e42231cb1da130069cbc694f9da4a185067a81f
tree1bbfcc62d7523148122295a3046edf9a3384efe3
parentbdf5aed665a91f2e1755e6a8ef72cd29627fa937
signature Signed by PGP key B5690EEEBB952194

Extend Done display to 15 minutes and show up to 5 Live Activity banner rows (#3761)


4 files changed, 25 insertions(+), 10 deletions(-)

infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+13-4
......@@ -736,7 +736,7 @@ describe("makeAggregateState", () => {
736736 ...state,
737737 threadId: "thread-done" as RelayAgentActivityState["threadId"],
738738 phase: "completed",
739 updatedAt: "1970-01-01T00:50:00.000Z",
739 updatedAt: "1970-01-01T00:44:00.000Z",
740740 };
741741 const aggregate = AgentActivityPublisher.makeAggregateState({
742742 activeStates: [active, staleCompleted],
......@@ -769,7 +769,7 @@ describe("makeAggregateState", () => {
769769 });
770770 expect(
771771 AgentActivityPublisher.makeAggregateState({
772 activeStates: [{ ...lingeringCompleted, updatedAt: "1970-01-01T00:50:00.000Z" }],
772 activeStates: [{ ...lingeringCompleted, updatedAt: "1970-01-01T00:44:00.000Z" }],
773773 terminalState: null,
774774 nowMs: hourMs,
775775 }),
......@@ -789,16 +789,25 @@ describe("makeAggregateState", () => {
789789 updatedAt: "1970-01-01T00:59:00.000Z",
790790 };
791791 const aggregate = AgentActivityPublisher.makeAggregateState({
792 activeStates: [mkActive("a-1"), mkActive("a-2"), mkActive("a-3"), justCompleted],
792 activeStates: [
793 mkActive("a-1"),
794 mkActive("a-2"),
795 mkActive("a-3"),
796 mkActive("a-4"),
797 mkActive("a-5"),
798 justCompleted,
799 ],
793800 terminalState: null,
794801 nowMs: hourMs,
795802 });
796803
797 expect(aggregate?.activeCount).toBe(3);
804 expect(aggregate?.activeCount).toBe(5);
798805 expect(aggregate?.activities).toMatchObject([
799806 { threadId: "a-1" },
800807 { threadId: "a-2" },
801808 { threadId: "a-3" },
809 { threadId: "a-4" },
810 { threadId: "a-5" },
802811 ]);
803812 });
804813});
infra/relay/src/agentActivity/AgentActivityPublisher.ts+7-3
......@@ -13,6 +13,7 @@ import * as Option from "effect/Option";
1313import {
1414 isExpiredAgentActivityState,
1515 isTerminalPhase,
16 MAX_ACTIVITY_ROWS,
1617 sanitizeAgentActivityAggregateState,
1718} from "./agentActivityPayloads.ts";
1819
......@@ -228,7 +229,7 @@ function terminalAggregateState(state: RelayAgentActivityState): RelayAgentActiv
228229// How long a finished thread keeps its Done/Failed row in the aggregate while
229230// other agents are still active. Long enough to be seen on the lock screen,
230231// short enough that the activity list stays about live work.
231export const TERMINAL_AGENT_ACTIVITY_DISPLAY_TTL_MS = 5 * 60 * 1_000;
232export const TERMINAL_AGENT_ACTIVITY_DISPLAY_TTL_MS = 15 * 60 * 1_000;
232233
233234function isRecentTerminalState(state: RelayAgentActivityState, nowMs: number): boolean {
234235 if (!isTerminalPhase(state)) {
......@@ -273,7 +274,7 @@ export function makeAggregateState(input: {
273274 subtitle: newest.phase === "failed" ? "Agent work failed" : "Agent work completed",
274275 activeCount: 0,
275276 updatedAt: newest.updatedAt,
276 activities: recentTerminal.slice(0, 3).map(aggregateRowForState),
277 activities: recentTerminal.slice(0, MAX_ACTIVITY_ROWS).map(aggregateRowForState),
277278 });
278279 }
279280 // Recently finished threads ride along after the active ones (display slots
......@@ -282,7 +283,10 @@ export function makeAggregateState(input: {
282283 const recentTerminalStates = input.activeStates
283284 .filter((state) => isRecentTerminalState(state, input.nowMs))
284285 .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
285 const displayedStates = [...activeStates.slice(0, 3), ...recentTerminalStates].slice(0, 3);
286 const displayedStates = [
287 ...activeStates.slice(0, MAX_ACTIVITY_ROWS),
288 ...recentTerminalStates,
289 ].slice(0, MAX_ACTIVITY_ROWS);
286290 const updatedAt = [...activeStates, ...recentTerminalStates].reduce((latest, state) =>
287291 state.updatedAt.localeCompare(latest.updatedAt) > 0 ? state : latest,
288292 ).updatedAt;
infra/relay/src/agentActivity/ApnsDeliveries.test.ts+2-2
......@@ -373,7 +373,7 @@ describe("ApnsDeliveries", () => {
373373 ...aggregate,
374374 title: longTitle,
375375 subtitle: longTitle,
376 activities: [0, 1, 2, 3].map((index) =>
376 activities: [0, 1, 2, 3, 4, 5].map((index) =>
377377 Object.assign({}, aggregate.activities[0]!, {
378378 projectTitle: longTitle,
379379 threadTitle: longTitle,
......@@ -396,7 +396,7 @@ describe("ApnsDeliveries", () => {
396396 const payloadAggregate = queuedJobs[0]?.payload.aggregate;
397397 expect(payloadAggregate?.title.length).toBeLessThanOrEqual(120);
398398 expect(payloadAggregate?.subtitle.length).toBeLessThanOrEqual(120);
399 expect(payloadAggregate?.activities).toHaveLength(3);
399 expect(payloadAggregate?.activities).toHaveLength(5);
400400 expect(payloadAggregate?.activities[0]?.projectTitle.length).toBeLessThanOrEqual(120);
401401 expect(payloadAggregate?.activities[0]?.status.length).toBeLessThanOrEqual(40);
402402 expect(payloadAggregate?.activities[0]?.deepLink).toBe("/");
infra/relay/src/agentActivity/agentActivityPayloads.ts+3-1
......@@ -43,7 +43,9 @@ export function isExpiredAgentActivityState(
4343const MAX_SUMMARY_TEXT_LENGTH = 120;
4444const MAX_STATUS_TEXT_LENGTH = 40;
4545const MAX_DEEP_LINK_LENGTH = 512;
46const MAX_ACTIVITY_ROWS = 3;
46// The Live Activity banner (lock screen / Notification Center) renders up to
47// five rows; the expanded Dynamic Island shows the top three of these.
48export const MAX_ACTIVITY_ROWS = 5;
4749
4850function truncateText(value: string, maxLength: number): string {
4951 const trimmed = value.trim();