mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
Stop re-rendering every chat message on each agent update (#8916)
* fix(app): keep unchanged agent fields when an agent update is accepted * fix(app): share unchanged agent fields at the store write, not in the update policy
This commit is contained in:
@@ -390,4 +390,39 @@ describe("AgentDirectoryReplica", () => {
|
||||
expect(useSessionStore.getState().sessions[serverId]?.agents.has("agent")).toBe(true);
|
||||
store.clearSession(serverId);
|
||||
});
|
||||
it("keeps unchanged agent fields when an update changes only the status", () => {
|
||||
const serverId = "agent-replica-unchanged-fields";
|
||||
const store = useSessionStore.getState();
|
||||
store.initializeSession(serverId, null as unknown as DaemonClient);
|
||||
const replica = new AgentDirectoryReplica(
|
||||
serverId,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
const agentInStore = () => useSessionStore.getState().sessions[serverId]?.agents.get("agent");
|
||||
replica.commitSnapshot([entry(payload("agent"))], []);
|
||||
const before = agentInStore();
|
||||
|
||||
replica.applyDelta({
|
||||
kind: "upsert",
|
||||
agent: { ...payload("agent"), status: "running", updatedAt: "2026-07-17T00:02:00.000Z" },
|
||||
project: entry(payload("agent")).project,
|
||||
});
|
||||
const afterDelta = agentInStore();
|
||||
replica.commitSnapshot(
|
||||
[entry({ ...payload("agent"), status: "idle", updatedAt: "2026-07-17T00:03:00.000Z" })],
|
||||
[],
|
||||
);
|
||||
const afterSnapshot = agentInStore();
|
||||
|
||||
expect(afterDelta?.status).toBe("running");
|
||||
expect(afterSnapshot?.status).toBe("idle");
|
||||
for (const agent of [afterDelta, afterSnapshot]) {
|
||||
expect(agent?.capabilities).toBe(before?.capabilities);
|
||||
expect(agent?.availableModes).toBe(before?.availableModes);
|
||||
expect(agent?.labels).toBe(before?.labels);
|
||||
expect(agent?.projectPlacement).toBe(before?.projectPlacement);
|
||||
}
|
||||
store.clearSession(serverId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,26 @@ function withoutAgent(agentId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Every update arrives as a whole new agent, so each nested value (capabilities, modes, labels) is
|
||||
* a new object even when it did not change. Keep the stored value for every field that is equal,
|
||||
* and the stored agent when nothing changed, so a reader of one field sees a new value only when
|
||||
* that field changes.
|
||||
*/
|
||||
function keepUnchangedFields(stored: Agent, incoming: Agent): Agent {
|
||||
const next = { ...incoming };
|
||||
let changed = Object.keys(stored).length !== Object.keys(next).length;
|
||||
for (const key of Object.keys(next) as (keyof Agent)[]) {
|
||||
if (next[key] === stored[key]) continue;
|
||||
if (key in stored && equal(next[key], stored[key])) {
|
||||
Object.assign(next, { [key]: stored[key] });
|
||||
} else {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed ? next : stored;
|
||||
}
|
||||
|
||||
function mergeSnapshotTurn(previous: Agent | undefined, incoming: Agent): Agent {
|
||||
if (!previous) return incoming;
|
||||
const activeTurn =
|
||||
@@ -59,7 +79,8 @@ export class AgentStoreProjection {
|
||||
let accepted = agent;
|
||||
useSessionStore.getState().setAgents(this.serverId, (current) => {
|
||||
const previous = current.get(agent.id);
|
||||
accepted = acceptAgentDirectoryUpdate(previous, mergeSnapshotTurn(previous, agent));
|
||||
const winner = acceptAgentDirectoryUpdate(previous, mergeSnapshotTurn(previous, agent));
|
||||
accepted = previous ? keepUnchangedFields(previous, winner) : winner;
|
||||
if (accepted === previous) return current;
|
||||
const next = new Map(current);
|
||||
next.set(agent.id, accepted);
|
||||
@@ -117,7 +138,7 @@ export class AgentStoreProjection {
|
||||
for (const [agentId, fetchedAgent] of fetched) {
|
||||
const existing = current.get(agentId);
|
||||
const merged = mergeSnapshotTurn(existing, fetchedAgent);
|
||||
agents.set(agentId, existing && equal(existing, merged) ? existing : merged);
|
||||
agents.set(agentId, existing ? keepUnchangedFields(existing, merged) : merged);
|
||||
if (fetchedAgent.archivedAt) {
|
||||
clearArchiveAgentPending({ queryClient, serverId: this.serverId, agentId });
|
||||
}
|
||||
|
||||
@@ -126,6 +126,23 @@ describe("agent directory reconciliation", () => {
|
||||
}).toEqual({ title: "newer page", status: "running", projectName: "repo" });
|
||||
});
|
||||
|
||||
it("takes the project of a newer buffered upsert", () => {
|
||||
const snapshotEntry = entry("agent", "idle");
|
||||
const newerProject = { ...snapshotEntry.project, projectName: "renamed project" };
|
||||
const result = reconcileAgentDirectory({
|
||||
snapshot: [snapshotEntry],
|
||||
deltas: [
|
||||
{
|
||||
kind: "upsert",
|
||||
agent: { ...snapshot("agent", "idle"), updatedAt: "2026-07-12T11:00:00.000Z" },
|
||||
project: newerProject,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result[0]?.project.projectName).toBe("renamed project");
|
||||
});
|
||||
|
||||
it("clears a snapshot stop when a newer buffered upsert is running", () => {
|
||||
const result = reconcileAgentDirectory({
|
||||
snapshot: [entry("agent", "idle")],
|
||||
|
||||
Reference in New Issue
Block a user