diff options
Diffstat (limited to 'pi/agent/extensions/handoff/index.ts')
| -rw-r--r-- | pi/agent/extensions/handoff/index.ts | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/pi/agent/extensions/handoff/index.ts b/pi/agent/extensions/handoff/index.ts new file mode 100644 index 0000000..6e9fab0 --- /dev/null +++ b/pi/agent/extensions/handoff/index.ts @@ -0,0 +1,130 @@ +import { complete, type Message } from "@mariozechner/pi-ai"; +import type { ExtensionAPI, SessionEntry } from "@mariozechner/pi-coding-agent"; +import { BorderedLoader, convertToLlm, serializeConversation } from "@mariozechner/pi-coding-agent"; + +const SYSTEM_PROMPT = `You are a context transfer assistant. Given a conversation history and the user's goal for a new thread, generate a focused prompt that: + +1. Summarizes relevant context from the conversation (decisions made, approaches taken, key findings) +2. Lists any relevant files that were discussed or modified +3. Clearly states the next task based on the user's goal +4. Is self-contained - the new thread should be able to proceed without the old conversation + +Format your response as a prompt the user can send to start the new thread. Be concise but include all necessary context. Do not include any preamble like "Here's the prompt" - just output the prompt itself. + +Example output format: +## Context +We've been working on X. Key decisions: +- Decision 1 +- Decision 2 + +Files involved: +- path/to/file1.ts +- path/to/file2.ts + +## Task +[Clear description of what to do next based on the user's goal]`; + +export default function (pi: ExtensionAPI) { + pi.registerCommand("handoff", { + description: "Transfer context to a new focused session", + handler: async (args, ctx) => { + if (!ctx.hasUI) { + ctx.ui.notify("handoff requires interactive mode", "error"); + return; + } + + if (!ctx.model) { + ctx.ui.notify("No model selected", "error"); + return; + } + + const goal = args.trim(); + if (!goal) { + ctx.ui.notify("Usage: /handoff <goal for new thread>", "error"); + return; + } + + const branch = ctx.sessionManager.getBranch(); + const messages = branch + .filter((entry): entry is SessionEntry & { type: "message" } => entry.type === "message") + .map((entry) => entry.message); + + if (messages.length === 0) { + ctx.ui.notify("No conversation to hand off", "error"); + return; + } + + const llmMessages = convertToLlm(messages); + const conversationText = serializeConversation(llmMessages); + const currentSessionFile = ctx.sessionManager.getSessionFile(); + + const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => { + const loader = new BorderedLoader(tui, theme, "Generating handoff prompt..."); + loader.onAbort = () => done(null); + + const doGenerate = async () => { + const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!); + + const userMessage: Message = { + role: "user", + content: [ + { + type: "text", + text: `## Conversation History\n\n${conversationText}\n\n## User's Goal for New Thread\n\n${goal}`, + }, + ], + timestamp: Date.now(), + }; + + const response = await complete( + ctx.model!, + { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] }, + { apiKey, signal: loader.signal }, + ); + + if (response.stopReason === "aborted") { + return null; + } + + return response.content + .filter((content): content is { type: "text"; text: string } => content.type === "text") + .map((content) => content.text) + .join("\n"); + }; + + doGenerate() + .then(done) + .catch((err) => { + console.error("Handoff generation failed:", err); + done(null); + }); + + return loader; + }); + + if (result === null) { + ctx.ui.notify("Cancelled", "info"); + return; + } + + const editedPrompt = await ctx.ui.editor("Edit handoff prompt", result); + + if (editedPrompt === undefined) { + ctx.ui.notify("Cancelled", "info"); + return; + } + + const newSessionResult = await ctx.newSession({ + parentSession: currentSessionFile, + }); + + if (newSessionResult.cancelled) { + ctx.ui.notify("New session cancelled", "info"); + return; + } + + ctx.ui.setEditorText(editedPrompt); + ctx.ui.notify("Handoff ready. Submit when ready.", "info"); + }, + }); +} |
