Compare commits

..
Author SHA1 Message Date
paseo-bot[bot]andluke 8c0224bdcc chore(plugins): verify message provenance on current main
Co-authored-by: luke <luopeihuan@gmail.com>
2026-11-01 20:51:21 +00:00
lukeandRoundRedDot 09745c32ba chore: merge main baseline into PR #7495
Integrate upstream 4845e1da58 while preserving this PR scope.

Co-authored-by: RoundRedDot <roundreddot@gmail.com>
2026-10-25 18:29:20 +08:00
lukeandRoundRedDot 3aad5e805d merge: sync main into PR #7495
Integrate getpaseo/paseo main at b8bb335c56.

Co-authored-by: RoundRedDot <roundreddot@gmail.com>
2026-10-24 19:18:10 +08:00
lukeandRoundRedDot e2f8b40a61 merge: sync main into PR #7495
Integrate getpaseo/paseo main at 77453388e4.

Co-authored-by: RoundRedDot <roundreddot@gmail.com>
2026-10-21 14:25:38 +08:00
lukeandRoundRedDot 5a5f8121d8 merge: sync main into PR #7495
Integrate getpaseo/paseo main at bd15e40e94.

Co-authored-by: RoundRedDot <roundreddot@gmail.com>
2026-10-21 14:20:27 +08:00
lukeandRoundRedDot e513a9130a fix(plugins): preserve agent message provenance in timeline transforms
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
2026-10-20 18:23:48 +08:00
2 changed files with 41 additions and 1 deletions
@@ -14,6 +14,45 @@ function thought(text: string, status: "loading" | "ready" = "loading"): StreamI
}
describe("plugin timeline projection", () => {
it.each(["message", "finished", "errored", "permission-required", "closed"] as const)(
"preserves immutable agent-message provenance for %s cards",
(event) => {
const agentMessage = {
event,
sender: { id: "remote::reviewer", title: "QA question-timing #7014/#7000/#6809" },
text: "The original message, including **Markdown**.",
};
const source: StreamItem = {
kind: "tool_call",
id: "message-1",
timestamp: new Date("2026-01-01T00:00:00.000Z"),
payload: {
source: "agent",
data: {
provider: "codex",
callId: "delivery-1",
name: "agent_message",
status: "completed",
error: null,
detail: { type: "plain_text", text: agentMessage.text, icon: "bot" },
agentMessage,
},
},
};
const inspect: TimelineItemTransform = ({ item }) => {
expect(item.type).toBe("tool_call");
if (item.type !== "tool_call") throw new Error("Expected a tool call");
expect(item.agentMessage).toEqual(agentMessage);
expect(item.agentMessage).not.toBe(agentMessage);
expect(Object.isFrozen(item.agentMessage)).toBe(true);
expect(Object.isFrozen(item.agentMessage?.sender)).toBe(true);
return undefined;
};
expect(projectPluginTimelineItems([source], inspect)).toEqual([source]);
expect(Object.isFrozen(agentMessage.sender)).toBe(false);
},
);
it("does not expose mutable tool detail from stream state", () => {
const detail = { type: "read" as const, filePath: "/repo/original.ts" };
const source: StreamItem = {
@@ -22,13 +22,14 @@ function cloneAndFreeze<T>(value: T): T {
}
function sourceToolCallTimelineItem(data: AgentToolCallData): ToolCallTimelineItem {
const { callId, name, status, error, detail, metadata } = data;
const { callId, name, status, error, detail, metadata, agentMessage } = data;
const base = {
type: "tool_call" as const,
callId,
name,
detail,
...(metadata ? { metadata } : {}),
...(agentMessage ? { agentMessage } : {}),
};
switch (status) {
case "running":