mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
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:
@@ -0,0 +1,4 @@
|
||||
# Inline thinking timeline example
|
||||
|
||||
This plugin replaces reasoning rows from the first streaming delta and renders the paced text
|
||||
inline. Install the directory as `inline-thinking`, then run an agent that streams reasoning.
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { PluginContext } from "@getpaseo/plugin";
|
||||
import { InlineThinking, inlineThinkingSchema } from "./thinking.client";
|
||||
|
||||
export default function contribute(plugin: PluginContext) {
|
||||
plugin.addTimelineTransformer({
|
||||
id: "inline-thinking",
|
||||
query: { itemType: "reasoning" },
|
||||
transform: ({ item, phase }) => ({
|
||||
items: [
|
||||
{
|
||||
type: "plugin",
|
||||
kind: "inline-thinking",
|
||||
version: 1,
|
||||
data: { text: item.text, phase },
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
plugin.addTimelineRenderer({
|
||||
kind: "inline-thinking",
|
||||
version: 1,
|
||||
schema: inlineThinkingSchema,
|
||||
Component: InlineThinking,
|
||||
});
|
||||
return () => {};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": "inline-thinking"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { PluginTimelineItemProps } from "@getpaseo/plugin";
|
||||
import { useRevealedText } from "@getpaseo/plugin/react-native";
|
||||
import { useMemo } from "react";
|
||||
import { Text } from "react-native";
|
||||
import { z } from "zod";
|
||||
|
||||
export const inlineThinkingSchema = z.object({
|
||||
text: z.string(),
|
||||
phase: z.enum(["streaming", "complete"]),
|
||||
});
|
||||
|
||||
type InlineThinkingData = z.output<typeof inlineThinkingSchema>;
|
||||
|
||||
export function InlineThinking({ item, theme }: PluginTimelineItemProps<InlineThinkingData>) {
|
||||
const text = useRevealedText(item.data.text, item.data.phase);
|
||||
const style = useMemo(
|
||||
() => ({ color: theme.colors.foregroundMuted }),
|
||||
[theme.colors.foregroundMuted],
|
||||
);
|
||||
return <Text style={style}>{text}</Text>;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useRpc,
|
||||
useWorkspace,
|
||||
} from "@getpaseo/plugin";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { incrementRpc } from "./increment.shared";
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
# Pi tasks timeline plugin example
|
||||
|
||||
This example implements the behavior proposed in Paseo PR #3751 as a plugin. It recognizes
|
||||
completed Pi `todo` tool calls from both `@juicesharp/rpiv-todo` and Pi's
|
||||
This example recognizes Pi `todo` tool calls from both `@juicesharp/rpiv-todo` and Pi's
|
||||
`examples/extensions/todo.ts`, replaces the tool-call entry with a versioned `pi-task-list` item,
|
||||
and renders the current task snapshot with a native React Native component.
|
||||
and renders the current task snapshot with a native React Native component. It transforms both
|
||||
running and completed calls.
|
||||
|
||||
The rpiv shape preserves `pending`, `in_progress`, and `completed`; deleted tombstones are omitted.
|
||||
Pi's example shape maps `done` to `completed` or `pending`. Malformed results and unrelated tool
|
||||
calls return `undefined`, leaving Paseo's original timeline entry unchanged.
|
||||
|
||||
The transformer is a pure client contribution. It receives the daemon's projected timeline item,
|
||||
returns plain plugin item objects, and runs against projected history. Matching live events refresh
|
||||
the authoritative projected tail first. The renderer validates `data` before Paseo mounts the
|
||||
component.
|
||||
The transformer is a pure client contribution. It receives each source item and its streaming phase
|
||||
while Paseo builds the render model, then returns plain plugin item objects. The renderer validates
|
||||
`data` before Paseo mounts the component.
|
||||
|
||||
@@ -15,6 +15,7 @@ function completedTodo(output: unknown) {
|
||||
describe("Pi task timeline example", () => {
|
||||
it("maps @juicesharp/rpiv-todo and drops deleted tasks", () => {
|
||||
const result = transformPiTodoToolCall({
|
||||
phase: "complete",
|
||||
item: completedTodo({
|
||||
content: [{ type: "text", text: "Created #2" }],
|
||||
details: {
|
||||
@@ -48,6 +49,7 @@ describe("Pi task timeline example", () => {
|
||||
|
||||
it("maps Pi's example todo extension", () => {
|
||||
const result = transformPiTodoToolCall({
|
||||
phase: "complete",
|
||||
item: completedTodo({
|
||||
details: {
|
||||
todos: [
|
||||
@@ -69,11 +71,29 @@ describe("Pi task timeline example", () => {
|
||||
it("keeps unrelated and malformed tool calls unchanged", () => {
|
||||
expect(
|
||||
transformPiTodoToolCall({
|
||||
phase: "complete",
|
||||
item: { ...completedTodo({ details: { todos: [] } }), name: "write" },
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
transformPiTodoToolCall({ item: completedTodo({ details: { phases: [] } }) }),
|
||||
transformPiTodoToolCall({
|
||||
phase: "complete",
|
||||
item: completedTodo({ details: { phases: [] } }),
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps a running todo tool call", () => {
|
||||
const result = transformPiTodoToolCall({
|
||||
phase: "streaming",
|
||||
item: {
|
||||
...completedTodo({ details: { todos: [{ text: "live task", done: false }] } }),
|
||||
status: "running",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result?.items[0]?.data).toEqual({
|
||||
tasks: [{ text: "live task", status: "pending" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ function replacement(tasks: PiTask[]) {
|
||||
}
|
||||
|
||||
export const transformPiTodoToolCall: ToolCallTransformer = ({ item }) => {
|
||||
if (item.name !== "todo" || item.status !== "completed" || item.detail.type !== "unknown") {
|
||||
if (item.name !== "todo" || item.detail.type !== "unknown") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user