mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-09 09:54:40 +00:00
fix(app): keep agent details and stay quiet after a current timeline answer
The timeline fetch that a current answer skips also delivered the full agent row, and the replica cache stores only part of it. After a cold reload with an empty incremental directory page, usage, modes, permissions and runtime info stayed empty. The subscribe answer now carries the row for each current chat, gated with the rest of the freshness check. The panel also reported ready before the timeline owner was published, so a connected render with cached content showed Updating messages. No owner now reads as a check that has not begun.
This commit is contained in:
@@ -262,11 +262,15 @@ test("subscribing with a cursor answers whether each chat is at its head", async
|
||||
},
|
||||
);
|
||||
|
||||
expect((await observation.ready).timelines).toEqual({
|
||||
const answer = await observation.ready;
|
||||
expect(answer.timelines).toEqual({
|
||||
[atHead.agentId]: "current",
|
||||
[staleSeq.agentId]: "behind",
|
||||
[otherEpoch.agentId]: "behind",
|
||||
});
|
||||
// Only a current chat skips the fetch that would have delivered its agent row.
|
||||
expect(Object.keys(answer.agents ?? {})).toEqual([atHead.agentId]);
|
||||
expect(answer.agents?.[atHead.agentId]).toMatchObject({ id: atHead.agentId, title: "At head" });
|
||||
|
||||
connected.clear();
|
||||
await daemon.daemon.agentManager.appendTimelineItem(atHead.agentId, {
|
||||
@@ -292,7 +296,9 @@ test("subscribing with a cursor answers behind for a stored agent and leaves it
|
||||
cursors: { [stored.agentId]: stored.head },
|
||||
});
|
||||
|
||||
expect((await observation.ready).timelines).toEqual({ [stored.agentId]: "behind" });
|
||||
const answer = await observation.ready;
|
||||
expect(answer.timelines).toEqual({ [stored.agentId]: "behind" });
|
||||
expect(answer.agents).toEqual({});
|
||||
expect(daemon.daemon.agentManager.getAgent(stored.agentId)).toBeNull();
|
||||
await observation.release();
|
||||
});
|
||||
|
||||
@@ -1423,6 +1423,50 @@ export class Session {
|
||||
return timelines;
|
||||
}
|
||||
|
||||
private async answerTimelineFreshness(
|
||||
agentIds: string[],
|
||||
cursors: Record<string, AgentTimelineCursor>,
|
||||
): Promise<{
|
||||
timelines: Record<string, "current" | "behind">;
|
||||
agents: Record<string, AgentSnapshotPayload>;
|
||||
}> {
|
||||
const snapshots = await Promise.all(
|
||||
agentIds.flatMap((agentId) => {
|
||||
const agent = cursors[agentId] ? this.agentManager.getAgent(agentId) : null;
|
||||
return agent ? [this.buildAgentPayload(agent)] : [];
|
||||
}),
|
||||
);
|
||||
// Compared after the await, so a row appended while reading the snapshots answers behind.
|
||||
const timelines = this.compareTimelineCursors(agentIds, cursors);
|
||||
const agents: Record<string, AgentSnapshotPayload> = {};
|
||||
for (const snapshot of snapshots) {
|
||||
if (timelines[snapshot.id] === "current") agents[snapshot.id] = snapshot;
|
||||
}
|
||||
return { timelines, agents };
|
||||
}
|
||||
|
||||
private async handleAgentTimelineSubscription(
|
||||
msg: Extract<SessionInboundMessage, { type: "agent.timeline.set_subscription.request" }>,
|
||||
source?: object,
|
||||
): Promise<void> {
|
||||
const agentIds = [...new Set(msg.agentIds)].sort();
|
||||
// Attach before comparing: a row appended after the comparison reaches this owner live.
|
||||
const owner = this.subscribeAgentTimelines(agentIds);
|
||||
const freshness = msg.cursors ? await this.answerTimelineFreshness(agentIds, msg.cursors) : {};
|
||||
this.emitForSource(
|
||||
{
|
||||
type: "agent.timeline.set_subscription.response",
|
||||
payload: {
|
||||
agentIds,
|
||||
requestId: msg.requestId,
|
||||
subscriptionId: owner.responseId,
|
||||
...freshness,
|
||||
},
|
||||
},
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
private subscribeAgentTimelines(agentIds: string[]): OwnedSubscription {
|
||||
const owner = this.delivery.begin("timelines", undefined, (id) => {
|
||||
this.timelineSubscriptions.delete(id);
|
||||
@@ -3058,26 +3102,8 @@ export class Session {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
case "agent.timeline.set_subscription.request": {
|
||||
const agentIds = [...new Set(msg.agentIds)].sort();
|
||||
// Attach before comparing: a row appended after the comparison reaches this owner live.
|
||||
const owner = this.subscribeAgentTimelines(agentIds);
|
||||
this.emitForSource(
|
||||
{
|
||||
type: "agent.timeline.set_subscription.response",
|
||||
payload: {
|
||||
agentIds,
|
||||
requestId: msg.requestId,
|
||||
subscriptionId: owner.responseId,
|
||||
...(msg.cursors
|
||||
? { timelines: this.compareTimelineCursors(agentIds, msg.cursors) }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
source,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
case "agent.timeline.set_subscription.request":
|
||||
return this.handleAgentTimelineSubscription(msg, source);
|
||||
case "agent.fork_context.request":
|
||||
return this.handleAgentForkContextRequest(msg);
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user