From d3a3c49e54d46c924d527188453863cceee3fecc Mon Sep 17 00:00:00 2001 From: Marius Matei Date: Wed, 28 Jan 2026 10:02:06 +0000 Subject: [PATCH] fix: revert to v1.1.0 codebase --- auto-update.ts | 14 +++----- bun.lock | 1 - index.test.ts | 66 ---------------------------------- index.ts | 92 +++++------------------------------------------- package.json | 5 ++- tsdown.config.ts | 4 +-- 6 files changed, 17 insertions(+), 165 deletions(-) diff --git a/auto-update.ts b/auto-update.ts index 841dc73..fd583ca 100644 --- a/auto-update.ts +++ b/auto-update.ts @@ -1,7 +1,6 @@ import * as fs from "fs"; import * as path from "path"; import * as os from "os"; -import { spawn } from "child_process"; import type { PluginInput } from "@opencode-ai/plugin"; const PACKAGE_NAME = "opencode-session-handoff"; @@ -205,19 +204,16 @@ function invalidatePackage(): boolean { async function runBunInstall(): Promise { try { - const proc = spawn("bun", ["install"], { + const proc = Bun.spawn(["bun", "install"], { cwd: getConfigDir(), - stdio: "pipe", - }); - - const exitPromise = new Promise((resolve) => { - proc.on("close", (code) => resolve(code)); - proc.on("error", () => resolve(null)); + stdout: "pipe", + stderr: "pipe", }); const timeoutPromise = new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), BUN_INSTALL_TIMEOUT_MS), ); + const exitPromise = proc.exited.then(() => "completed" as const); const result = await Promise.race([exitPromise, timeoutPromise]); @@ -230,7 +226,7 @@ async function runBunInstall(): Promise { return false; } - return result === 0; + return proc.exitCode === 0; } catch { return false; } diff --git a/bun.lock b/bun.lock index 322fd94..a2a0775 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,6 @@ "name": "opencode-handoff", "dependencies": { "@opencode-ai/plugin": "1.1.12", - "zod": "4.1.8", }, "devDependencies": { "@commitlint/cli": "^20.3.1", diff --git a/index.test.ts b/index.test.ts index 4a4f4ea..23e904c 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,6 +1,5 @@ import { describe, it, expect } from "vitest"; import { buildHandoffPrompt, type HandoffArgs } from "./prompt.ts"; -import { isHandoffTrigger, extractGoalFromHandoff } from "./index.ts"; const baseArgs: HandoffArgs = { previousSessionId: "ses_123", @@ -96,68 +95,3 @@ describe("buildHandoffPrompt - next steps", () => { expect(build({ next_steps: [] })).not.toContain("**Next:**"); }); }); - -describe("isHandoffTrigger", () => { - it("returns true for 'handoff'", () => { - expect(isHandoffTrigger("handoff")).toBe(true); - expect(isHandoffTrigger("Handoff")).toBe(true); - expect(isHandoffTrigger("HANDOFF")).toBe(true); - expect(isHandoffTrigger(" handoff ")).toBe(true); - }); - - it("returns true for '/handoff'", () => { - expect(isHandoffTrigger("/handoff")).toBe(true); - expect(isHandoffTrigger("/Handoff")).toBe(true); - expect(isHandoffTrigger(" /handoff ")).toBe(true); - }); - - it("returns true for 'session handoff'", () => { - expect(isHandoffTrigger("session handoff")).toBe(true); - expect(isHandoffTrigger("Session Handoff")).toBe(true); - }); - - it("returns true for 'handoff '", () => { - expect(isHandoffTrigger("handoff implement login")).toBe(true); - expect(isHandoffTrigger("handoff fix the bug")).toBe(true); - }); - - it("returns true for '/handoff '", () => { - expect(isHandoffTrigger("/handoff implement login")).toBe(true); - expect(isHandoffTrigger("/handoff fix tests")).toBe(true); - }); - - it("returns false for non-handoff messages", () => { - expect(isHandoffTrigger("implement handoff feature")).toBe(false); - expect(isHandoffTrigger("hello world")).toBe(false); - expect(isHandoffTrigger("hand off the work")).toBe(false); - }); -}); - -describe("extractGoalFromHandoff", () => { - it("extracts goal from 'handoff '", () => { - expect(extractGoalFromHandoff("handoff implement login")).toBe("implement login"); - expect(extractGoalFromHandoff("handoff fix the failing tests")).toBe("fix the failing tests"); - expect(extractGoalFromHandoff("Handoff Create PR")).toBe("Create PR"); - }); - - it("extracts goal from '/handoff '", () => { - expect(extractGoalFromHandoff("/handoff implement login")).toBe("implement login"); - expect(extractGoalFromHandoff("/handoff fix tests")).toBe("fix tests"); - }); - - it("returns null for standalone handoff", () => { - expect(extractGoalFromHandoff("handoff")).toBe(null); - expect(extractGoalFromHandoff("/handoff")).toBe(null); - expect(extractGoalFromHandoff(" handoff ")).toBe(null); - }); - - it("returns null for 'handoff ' with only whitespace after", () => { - expect(extractGoalFromHandoff("handoff ")).toBe(null); - expect(extractGoalFromHandoff("/handoff ")).toBe(null); - }); - - it("returns null for non-handoff messages", () => { - expect(extractGoalFromHandoff("hello world")).toBe(null); - expect(extractGoalFromHandoff("session handoff")).toBe(null); - }); -}); diff --git a/index.ts b/index.ts index 0411432..c5ed11a 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,5 @@ import type { Plugin, PluginInput } from "@opencode-ai/plugin"; import type { Message, Part } from "@opencode-ai/sdk"; -import { z } from "zod"; import { buildHandoffPrompt } from "./prompt.ts"; import { createAutoUpdateHook } from "./auto-update.ts"; @@ -211,27 +210,10 @@ async function executeHandoff( return `✓ Session "${newTitle}" created (${context.agent || "default"} · ${modelDisplay}). Select it from the picker.`; } -const handoffArgsSchema = { - summary: z.string().describe("1-3 sentence summary of current state (required)"), - goal: z.string().optional().describe("Goal for the next session if user specified one"), - next_steps: z.array(z.string()).optional().describe("Array of remaining tasks"), - blocked: z.string().optional().describe("Current blocker if any"), - key_decisions: z.array(z.string()).optional().describe("Important decisions made"), - files_modified: z.array(z.string()).optional().describe("Key files that were changed"), -}; - function createHandoffTool(pluginCtx: PluginContext) { return { description: `Generate a compact continuation prompt and start a new session with it. -**TRIGGER: When the user's message starts with "handoff" (case-insensitive), ALWAYS invoke this tool immediately.** Any text after "handoff" should be treated as the goal parameter. - -Examples that MUST trigger this tool: -- "handoff" → invoke with summary only -- "handoff can you check the lint warnings" → invoke with goal="can you check the lint warnings" -- "Handoff fix the failing tests" → invoke with goal="fix the failing tests" -- "HANDOFF implement login feature" → invoke with goal="implement login feature" - When called, this tool: 1. Uses YOUR summary of what was accomplished (required) 2. Auto-fetches todo state from current session @@ -240,8 +222,16 @@ When called, this tool: IMPORTANT: You MUST provide a concise summary. Do not dump the entire conversation - distill it to essential context only. +Arguments (pass as JSON object): +- summary (required): 1-3 sentence summary of current state +- goal (optional): If the user said "handoff " or "session_handoff ", extract what comes after as the goal for the next session +- next_steps (optional): Array of remaining tasks +- blocked (optional): Current blocker if any +- key_decisions (optional): Array of important decisions made +- files_modified (optional): Array of key files changed + The new session will have access to \`read_session\` tool if more context is needed later.`, - args: handoffArgsSchema, + args: {}, async execute(args: Record, ctx: { sessionID: string }) { return executeHandoff(pluginCtx, args as unknown as HandoffToolArgs, ctx.sessionID); }, @@ -300,30 +290,6 @@ This tool fetches the last 20 messages which uses significant tokens. The handof }; } -export function isHandoffTrigger(text: string): boolean { - const trimmed = text.trim().toLowerCase(); - return ( - trimmed === "handoff" || - trimmed === "/handoff" || - trimmed === "session handoff" || - trimmed.startsWith("handoff ") || - trimmed.startsWith("/handoff ") - ); -} - -export function extractGoalFromHandoff(text: string): string | null { - const trimmed = text.trim(); - const lower = trimmed.toLowerCase(); - - if (lower.startsWith("/handoff ")) { - return trimmed.slice(9).trim() || null; - } - if (lower.startsWith("handoff ")) { - return trimmed.slice(8).trim() || null; - } - return null; -} - const HandoffPlugin: Plugin = async (ctx) => { const autoUpdateHook = createAutoUpdateHook({ directory: ctx.directory, @@ -343,46 +309,6 @@ const HandoffPlugin: Plugin = async (ctx) => { client: ctx.client, }), }, - "chat.message": async ( - _input: { - sessionID: string; - agent?: string; - model?: { providerID: string; modelID: string }; - messageID?: string; - variant?: string; - }, - output: { message: unknown; parts: Part[] }, - ) => { - const textParts = output.parts.filter( - (p): p is Part & { type: "text"; text: string } => - p.type === "text" && typeof (p as { text?: string }).text === "string", - ); - - for (const part of textParts) { - if (isHandoffTrigger(part.text)) { - const goal = extractGoalFromHandoff(part.text); - - part.text = [ - '', - "The user has triggered a session handoff. You MUST invoke the `session_handoff` tool immediately.", - "", - "DO NOT interpret this as a regular task request.", - "DO NOT continue working on any previous tasks.", - "DO NOT ask clarifying questions.", - "", - "IMMEDIATELY call the session_handoff tool with:", - "- summary: A brief summary of what was accomplished in this session", - goal ? `- goal: "${goal}"` : "- goal: (not specified)", - "- next_steps: Any remaining tasks from the todo list", - "- key_decisions: Important decisions made during the session", - "- files_modified: Key files that were changed", - "", - ].join("\n"); - - break; - } - } - }, }; }; diff --git a/package.json b/package.json index df1cc63..a0541a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-session-handoff", - "version": "1.1.5", + "version": "1.1.6", "description": "OpenCode plugin for seamless session continuation when context windows fill up", "keywords": [ "context-window", @@ -49,8 +49,7 @@ "prepare": "husky" }, "dependencies": { - "@opencode-ai/plugin": "1.1.12", - "zod": "4.1.8" + "@opencode-ai/plugin": "1.1.12" }, "devDependencies": { "@commitlint/cli": "^20.3.1", diff --git a/tsdown.config.ts b/tsdown.config.ts index c9035aa..7660d95 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -7,7 +7,5 @@ export default defineConfig({ clean: true, outDir: "dist", sourcemap: true, - external: ["@opencode-ai/plugin"], - noExternal: ["zod"], - inlineOnly: false, + external: ["zod", "@opencode-ai/plugin"], });