fix(server): apply get_agent_activity limit to projected entries

The MCP get_agent_activity tool sliced the raw timeline before passing
to the curator, so `limit: 5` could land mid-message and return a
trailing fragment of streamed assistant text. Now goes through the
existing projection pipeline so the limit counts whole projected
entries — assistant deltas merged, tool calls collapsed by callId,
reasoning chunks merged.

Same bug fixed in the wait_for_agent timeout's "Recent activity"
preview, which used the same raw slice.

Consolidates the merge logic: activity-curator no longer ships its own
collapseTimeline, it delegates to projectTimelineRows. One source of
truth for "what is a complete message".

Drops the meaningless `provider` parameter that was threaded through
the projection module — merge logic never branched on it. The wire
schema keeps `provider` per entry; session stamps it once at the emit
boundary. Adds `reasoning_merge` to the projection kind enum (additive
wire change).
This commit is contained in:
Mohamed Boudra
2026-05-02 14:24:45 +07:00
parent 1b5d082d1a
commit da85814940
9 changed files with 228 additions and 176 deletions
+13 -11
View File
@@ -20,6 +20,7 @@ import {
toAgentPayload,
} from "./agent-projections.js";
import { curateAgentActivity } from "./activity-curator.js";
import { selectItemsByProjectedLimit } from "./timeline-projection.js";
import type { AgentStorage } from "./agent-storage.js";
import { ensureAgentLoaded } from "./agent-loading.js";
import { isStoredAgentProviderAvailable } from "../persistence-hooks.js";
@@ -1935,18 +1936,19 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
const timeline = agentManager.getTimeline(agentId);
const snapshot = agentManager.getAgent(agentId);
const activitiesToCurate = limit ? timeline.slice(-limit) : timeline;
const selection = selectItemsByProjectedLimit({
items: timeline,
direction: "tail",
limit: limit ?? 0,
});
const curatedContent = curateAgentActivity(selection.items);
const { totalProjected, shownProjected } = selection;
const curatedContent = curateAgentActivity(activitiesToCurate);
const totalCount = timeline.length;
const shownCount = activitiesToCurate.length;
let countHeader: string;
if (limit && shownCount < totalCount) {
countHeader = `Showing ${shownCount} of ${totalCount} ${totalCount === 1 ? "activity" : "activities"} (limited to ${limit})`;
} else {
countHeader = `Showing all ${totalCount} ${totalCount === 1 ? "activity" : "activities"}`;
}
const noun = totalProjected === 1 ? "activity" : "activities";
const countHeader =
limit && shownProjected < totalProjected
? `Showing ${shownProjected} of ${totalProjected} ${noun} (limited to ${limit})`
: `Showing all ${totalProjected} ${noun}`;
const contentWithCount = `${countHeader}\n\n${curatedContent}`;