| author | |
| committer | |
| log | 6e42231cb1da130069cbc694f9da4a185067a81f |
| tree | 1bbfcc62d7523148122295a3046edf9a3384efe3 |
| parent | bdf5aed665a91f2e1755e6a8ef72cd29627fa937 |
| signature | Signed by PGP key B5690EEEBB952194 |
4 files changed, 25 insertions(+), 10 deletions(-)
infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+13-4| ... | @@ -736,7 +736,7 @@ describe("makeAggregateState", () => { | ... | @@ -736,7 +736,7 @@ describe("makeAggregateState", () => { |
| 736 | ...state, | 736 | ...state, |
| 737 | threadId: "thread-done" as RelayAgentActivityState["threadId"], | 737 | threadId: "thread-done" as RelayAgentActivityState["threadId"], |
| 738 | phase: "completed", | 738 | phase: "completed", |
| 739 | updatedAt: "1970-01-01T00:50:00.000Z", | 739 | updatedAt: "1970-01-01T00:44:00.000Z", |
| 740 | }; | 740 | }; |
| 741 | const aggregate = AgentActivityPublisher.makeAggregateState({ | 741 | const aggregate = AgentActivityPublisher.makeAggregateState({ |
| 742 | activeStates: [active, staleCompleted], | 742 | activeStates: [active, staleCompleted], |
| ... | @@ -769,7 +769,7 @@ describe("makeAggregateState", () => { | ... | @@ -769,7 +769,7 @@ describe("makeAggregateState", () => { |
| 769 | }); | 769 | }); |
| 770 | expect( | 770 | expect( |
| 771 | AgentActivityPublisher.makeAggregateState({ | 771 | AgentActivityPublisher.makeAggregateState({ |
| 772 | activeStates: [{ ...lingeringCompleted, updatedAt: "1970-01-01T00:50:00.000Z" }], | 772 | activeStates: [{ ...lingeringCompleted, updatedAt: "1970-01-01T00:44:00.000Z" }], |
| 773 | terminalState: null, | 773 | terminalState: null, |
| 774 | nowMs: hourMs, | 774 | nowMs: hourMs, |
| 775 | }), | 775 | }), |
| ... | @@ -789,16 +789,25 @@ describe("makeAggregateState", () => { | ... | @@ -789,16 +789,25 @@ describe("makeAggregateState", () => { |
| 789 | updatedAt: "1970-01-01T00:59:00.000Z", | 789 | updatedAt: "1970-01-01T00:59:00.000Z", |
| 790 | }; | 790 | }; |
| 791 | const aggregate = AgentActivityPublisher.makeAggregateState({ | 791 | 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 | ], | ||
| 793 | terminalState: null, | 800 | terminalState: null, |
| 794 | nowMs: hourMs, | 801 | nowMs: hourMs, |
| 795 | }); | 802 | }); |
| 796 | 803 | ||
| 797 | expect(aggregate?.activeCount).toBe(3); | 804 | expect(aggregate?.activeCount).toBe(5); |
| 798 | expect(aggregate?.activities).toMatchObject([ | 805 | expect(aggregate?.activities).toMatchObject([ |
| 799 | { threadId: "a-1" }, | 806 | { threadId: "a-1" }, |
| 800 | { threadId: "a-2" }, | 807 | { threadId: "a-2" }, |
| 801 | { threadId: "a-3" }, | 808 | { threadId: "a-3" }, |
| 809 | { threadId: "a-4" }, | ||
| 810 | { threadId: "a-5" }, | ||
| 802 | ]); | 811 | ]); |
| 803 | }); | 812 | }); |
| 804 | }); | 813 | }); |
infra/relay/src/agentActivity/AgentActivityPublisher.ts+7-3| ... | @@ -13,6 +13,7 @@ import * as Option from "effect/Option"; | ... | @@ -13,6 +13,7 @@ import * as Option from "effect/Option"; |
| 13 | import { | 13 | import { |
| 14 | isExpiredAgentActivityState, | 14 | isExpiredAgentActivityState, |
| 15 | isTerminalPhase, | 15 | isTerminalPhase, |
| 16 | MAX_ACTIVITY_ROWS, | ||
| 16 | sanitizeAgentActivityAggregateState, | 17 | sanitizeAgentActivityAggregateState, |
| 17 | } from "./agentActivityPayloads.ts"; | 18 | } from "./agentActivityPayloads.ts"; |
| 18 | 19 | ||
| ... | @@ -228,7 +229,7 @@ function terminalAggregateState(state: RelayAgentActivityState): RelayAgentActiv | ... | @@ -228,7 +229,7 @@ function terminalAggregateState(state: RelayAgentActivityState): RelayAgentActiv |
| 228 | // How long a finished thread keeps its Done/Failed row in the aggregate while | 229 | // How long a finished thread keeps its Done/Failed row in the aggregate while |
| 229 | // other agents are still active. Long enough to be seen on the lock screen, | 230 | // other agents are still active. Long enough to be seen on the lock screen, |
| 230 | // short enough that the activity list stays about live work. | 231 | // short enough that the activity list stays about live work. |
| 231 | export const TERMINAL_AGENT_ACTIVITY_DISPLAY_TTL_MS = 5 * 60 * 1_000; | 232 | export const TERMINAL_AGENT_ACTIVITY_DISPLAY_TTL_MS = 15 * 60 * 1_000; |
| 232 | 233 | ||
| 233 | function isRecentTerminalState(state: RelayAgentActivityState, nowMs: number): boolean { | 234 | function isRecentTerminalState(state: RelayAgentActivityState, nowMs: number): boolean { |
| 234 | if (!isTerminalPhase(state)) { | 235 | if (!isTerminalPhase(state)) { |
| ... | @@ -273,7 +274,7 @@ export function makeAggregateState(input: { | ... | @@ -273,7 +274,7 @@ export function makeAggregateState(input: { |
| 273 | subtitle: newest.phase === "failed" ? "Agent work failed" : "Agent work completed", | 274 | subtitle: newest.phase === "failed" ? "Agent work failed" : "Agent work completed", |
| 274 | activeCount: 0, | 275 | activeCount: 0, |
| 275 | updatedAt: newest.updatedAt, | 276 | updatedAt: newest.updatedAt, |
| 276 | activities: recentTerminal.slice(0, 3).map(aggregateRowForState), | 277 | activities: recentTerminal.slice(0, MAX_ACTIVITY_ROWS).map(aggregateRowForState), |
| 277 | }); | 278 | }); |
| 278 | } | 279 | } |
| 279 | // Recently finished threads ride along after the active ones (display slots | 280 | // Recently finished threads ride along after the active ones (display slots |
| ... | @@ -282,7 +283,10 @@ export function makeAggregateState(input: { | ... | @@ -282,7 +283,10 @@ export function makeAggregateState(input: { |
| 282 | const recentTerminalStates = input.activeStates | 283 | const recentTerminalStates = input.activeStates |
| 283 | .filter((state) => isRecentTerminalState(state, input.nowMs)) | 284 | .filter((state) => isRecentTerminalState(state, input.nowMs)) |
| 284 | .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); | 285 | .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); | ||
| 286 | const updatedAt = [...activeStates, ...recentTerminalStates].reduce((latest, state) => | 290 | const updatedAt = [...activeStates, ...recentTerminalStates].reduce((latest, state) => |
| 287 | state.updatedAt.localeCompare(latest.updatedAt) > 0 ? state : latest, | 291 | state.updatedAt.localeCompare(latest.updatedAt) > 0 ? state : latest, |
| 288 | ).updatedAt; | 292 | ).updatedAt; |
infra/relay/src/agentActivity/ApnsDeliveries.test.ts+2-2| ... | @@ -373,7 +373,7 @@ describe("ApnsDeliveries", () => { | ... | @@ -373,7 +373,7 @@ describe("ApnsDeliveries", () => { |
| 373 | ...aggregate, | 373 | ...aggregate, |
| 374 | title: longTitle, | 374 | title: longTitle, |
| 375 | subtitle: longTitle, | 375 | subtitle: longTitle, |
| 376 | activities: [0, 1, 2, 3].map((index) => | 376 | activities: [0, 1, 2, 3, 4, 5].map((index) => |
| 377 | Object.assign({}, aggregate.activities[0]!, { | 377 | Object.assign({}, aggregate.activities[0]!, { |
| 378 | projectTitle: longTitle, | 378 | projectTitle: longTitle, |
| 379 | threadTitle: longTitle, | 379 | threadTitle: longTitle, |
| ... | @@ -396,7 +396,7 @@ describe("ApnsDeliveries", () => { | ... | @@ -396,7 +396,7 @@ describe("ApnsDeliveries", () => { |
| 396 | const payloadAggregate = queuedJobs[0]?.payload.aggregate; | 396 | const payloadAggregate = queuedJobs[0]?.payload.aggregate; |
| 397 | expect(payloadAggregate?.title.length).toBeLessThanOrEqual(120); | 397 | expect(payloadAggregate?.title.length).toBeLessThanOrEqual(120); |
| 398 | expect(payloadAggregate?.subtitle.length).toBeLessThanOrEqual(120); | 398 | expect(payloadAggregate?.subtitle.length).toBeLessThanOrEqual(120); |
| 399 | expect(payloadAggregate?.activities).toHaveLength(3); | 399 | expect(payloadAggregate?.activities).toHaveLength(5); |
| 400 | expect(payloadAggregate?.activities[0]?.projectTitle.length).toBeLessThanOrEqual(120); | 400 | expect(payloadAggregate?.activities[0]?.projectTitle.length).toBeLessThanOrEqual(120); |
| 401 | expect(payloadAggregate?.activities[0]?.status.length).toBeLessThanOrEqual(40); | 401 | expect(payloadAggregate?.activities[0]?.status.length).toBeLessThanOrEqual(40); |
| 402 | expect(payloadAggregate?.activities[0]?.deepLink).toBe("/"); | 402 | expect(payloadAggregate?.activities[0]?.deepLink).toBe("/"); |
infra/relay/src/agentActivity/agentActivityPayloads.ts+3-1| ... | @@ -43,7 +43,9 @@ export function isExpiredAgentActivityState( | ... | @@ -43,7 +43,9 @@ export function isExpiredAgentActivityState( |
| 43 | const MAX_SUMMARY_TEXT_LENGTH = 120; | 43 | const MAX_SUMMARY_TEXT_LENGTH = 120; |
| 44 | const MAX_STATUS_TEXT_LENGTH = 40; | 44 | const MAX_STATUS_TEXT_LENGTH = 40; |
| 45 | const MAX_DEEP_LINK_LENGTH = 512; | 45 | const MAX_DEEP_LINK_LENGTH = 512; |
| 46 | const 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. | ||
| 48 | export const MAX_ACTIVITY_ROWS = 5; | ||
| 47 | 49 | ||
| 48 | function truncateText(value: string, maxLength: number): string { | 50 | function truncateText(value: string, maxLength: number): string { |
| 49 | const trimmed = value.trim(); | 51 | const trimmed = value.trim(); |