Fix mobile agent timeline catch-up
This commit is contained in:
@@ -96,6 +96,31 @@ describe("client activity tracking", () => {
|
||||
});
|
||||
}
|
||||
|
||||
function waitForAssistantTimeline(
|
||||
client: DaemonClient,
|
||||
agentId: string,
|
||||
timeout = 60000,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error(`Timeout waiting for assistant timeline (${timeout}ms)`));
|
||||
}, timeout);
|
||||
|
||||
const cleanup = client.on("agent_stream", (msg) => {
|
||||
if (msg.type !== "agent_stream") return;
|
||||
if (msg.payload.agentId !== agentId) return;
|
||||
if (msg.payload.event.type !== "timeline") return;
|
||||
const item = msg.payload.event.item;
|
||||
if (item.type !== "assistant_message") return;
|
||||
|
||||
clearTimeout(timer);
|
||||
cleanup();
|
||||
resolve(item.text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// SINGLE CLIENT SCENARIOS
|
||||
// ===========================================================================
|
||||
@@ -355,6 +380,31 @@ describe("client activity tracking", () => {
|
||||
// ===========================================================================
|
||||
|
||||
describe("web and mobile clients", () => {
|
||||
test("mobile stream delivery does not depend on heartbeat focus", async () => {
|
||||
client1 = await createClient(); // web
|
||||
client2 = await createClient(); // mobile
|
||||
|
||||
const agent = await createAgent({
|
||||
client: client1,
|
||||
title: "Mobile Stale Focus Stream Test",
|
||||
});
|
||||
|
||||
client2.sendHeartbeat({
|
||||
deviceType: "mobile",
|
||||
focusedAgentId: null,
|
||||
lastActivityAt: new Date(Date.now() - PRESENCE_THRESHOLD_MS - 60_000).toISOString(),
|
||||
appVisible: false,
|
||||
appVisibilityChangedAt: new Date(Date.now() - 120_000).toISOString(),
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
const mobileStream = waitForAssistantTimeline(client2, agent.id);
|
||||
await client1.sendMessage(agent.id, "Say 'hello' and nothing else");
|
||||
|
||||
await expect(mobileStream).resolves.toMatch(/hello/i);
|
||||
}, 120000);
|
||||
|
||||
test("no notification to either when user actively on agent (web)", async () => {
|
||||
client1 = await createClient(); // web
|
||||
client2 = await createClient(); // mobile
|
||||
|
||||
@@ -783,7 +783,6 @@ export class Session {
|
||||
appVisible: boolean;
|
||||
appVisibilityChangedAt: Date;
|
||||
} | null = null;
|
||||
private readonly MOBILE_BACKGROUND_STREAM_GRACE_MS = 60_000;
|
||||
private readonly terminalManager: TerminalManager | null;
|
||||
private readonly providerSnapshotManager: ProviderSnapshotManager;
|
||||
private unsubscribeProviderSnapshotEvents: (() => void) | null = null;
|
||||
@@ -1302,13 +1301,6 @@ export class Session {
|
||||
});
|
||||
}
|
||||
|
||||
// Reduce bandwidth/CPU on mobile: only forward high-frequency agent stream events
|
||||
// for the focused agent, with a short grace window while backgrounded.
|
||||
// History catch-up is handled via pull-based `fetch_agent_timeline_request`.
|
||||
if (this.shouldSkipAgentStreamForward(event.agentId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serializedEvent = serializeAgentStreamEvent(event.event);
|
||||
if (!serializedEvent) {
|
||||
return;
|
||||
@@ -1355,21 +1347,6 @@ export class Session {
|
||||
);
|
||||
}
|
||||
|
||||
private shouldSkipAgentStreamForward(agentId: string): boolean {
|
||||
const activity = this.clientActivity;
|
||||
if (activity?.deviceType !== "mobile") {
|
||||
return false;
|
||||
}
|
||||
if (!activity.focusedAgentId || activity.focusedAgentId !== agentId) {
|
||||
return true;
|
||||
}
|
||||
if (activity.appVisible) {
|
||||
return false;
|
||||
}
|
||||
const hiddenForMs = Date.now() - activity.appVisibilityChangedAt.getTime();
|
||||
return hiddenForMs >= this.MOBILE_BACKGROUND_STREAM_GRACE_MS;
|
||||
}
|
||||
|
||||
private buildAgentStreamPayload(
|
||||
event: Extract<AgentManagerEvent, { type: "agent_stream" }>,
|
||||
serializedEvent: Extract<SessionOutboundMessage, { type: "agent_stream" }>["payload"]["event"],
|
||||
|
||||
Reference in New Issue
Block a user