feat(plugins): add live timeline rows and client slash commands

Plugins can transform live timeline source items at render time, append durable plugin rows from daemon handlers, and register client-only slash commands in the composer.

Related to #3940 and #3952.
This commit is contained in:
Mohamed Boudra
2026-09-02 13:12:09 +02:00
committed by GitHub
parent 49784fac15
commit e34aea206e
66 changed files with 2122 additions and 511 deletions
+13
View File
@@ -2883,6 +2883,19 @@ export class DaemonClient {
return payload;
}
async appendAgentTimelineItem(
agentId: string,
item: Omit<import("@getpaseo/protocol/agent-types").PluginTimelineItem, "pluginId">,
): Promise<{ seq: number; epoch: string }> {
const requestId = this.createRequestId();
const payload = await this.sendCorrelatedSessionRequest({
requestId,
message: { type: "agent.timeline.append.request", requestId, agentId, item },
responseType: "agent.timeline.append.response",
});
return { seq: payload.seq, epoch: payload.epoch };
}
async listAgentTimelinePrompts(
agentId: string,
options: { requestId?: string; timeout?: number } = {},
+21
View File
@@ -737,6 +737,27 @@ test("agent handles delegate create, send, timeline refetch, archive, and local
await timelinePromise;
expect(agent.current()).toEqual(timelineAgent);
const appendPromise = agent.timeline.append({
type: "plugin",
id: "review-1",
kind: "review",
version: 1,
data: { status: "running" },
});
const appendRequest = parseSentSessionMessage(ws.sent.at(-1));
expect(appendRequest).toMatchObject({
type: "agent.timeline.append.request",
agentId: "agent_sdk",
item: { id: "review-1", kind: "review" },
});
ws.message(
sessionMessage({
type: "agent.timeline.append.response",
payload: { requestId: appendRequest.requestId, seq: 8, epoch: "epoch-sdk" },
}),
);
await expect(appendPromise).resolves.toEqual({ seq: 8, epoch: "epoch-sdk" });
const archivePromise = agent.archive();
const archiveRequest = parseSentSessionMessage(ws.sent.at(-1));
expect(archiveRequest).toMatchObject({
+3
View File
@@ -24,6 +24,7 @@ import type {
WorkspaceCreateRequest,
} from "@getpaseo/protocol/messages";
import { DaemonClient } from "./daemon-client.js";
import type { PluginTimelineItem } from "@getpaseo/protocol/agent-types";
import type {
FetchAgentsEntry,
FetchAgentsOptions,
@@ -251,6 +252,7 @@ export type PaseoAgentStream = Extract<SessionOutboundMessage, { type: "agent_st
export type PaseoAgentUpdateHandler = (update: PaseoAgentUpdate) => void;
export interface PaseoAgentTimelineHandle {
append(item: Omit<PluginTimelineItem, "pluginId">): Promise<{ seq: number; epoch: string }>;
/**
* Fetches a fresh timeline page through the existing daemon RPC. If the daemon
* includes an agent snapshot in the response, the parent handle is updated to
@@ -606,6 +608,7 @@ function createAgentHandleFactory(daemonClient: DaemonClient): AgentHandleFactor
const handle: PaseoAgentHandle = {
id,
timeline: {
append: (item) => daemonClient.appendAgentTimelineItem(id, item),
refetch: async (options) => {
const result = await daemonClient.fetchAgentTimeline(id, options);
if (result.agent) {