summaryrefslogtreecommitdiff
path: root/pi/agent/extensions/handoff/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'pi/agent/extensions/handoff/index.ts')
-rw-r--r--pi/agent/extensions/handoff/index.ts130
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");
+ },
+ });
+}