diff --git a/.continue/agents/architecture.check b/.continue/agents/architecture.check new file mode 100644 index 0000000..20dc497 --- /dev/null +++ b/.continue/agents/architecture.check @@ -0,0 +1,21 @@ +name: Architecture Review +description: Ensure changes align with single-file plugin architecture +triggers: + - pull_request + +steps: + - name: Review architecture + prompt: | + Review the changes for architectural consistency: + + **Project constraints:** + - Single-file plugin architecture (index.ts is the main entry) + - Event-driven hooks (tool.execute.before, event listeners) + - Plugin should remain lightweight + + **Watch for:** + - Adding new files when the feature could fit in index.ts + - Introducing heavy dependencies + - Breaking the plugin's event-driven pattern + + If changes violate these constraints, suggest alternatives that align with the architecture. diff --git a/.continue/agents/code-style.check b/.continue/agents/code-style.check new file mode 100644 index 0000000..f8e1edc --- /dev/null +++ b/.continue/agents/code-style.check @@ -0,0 +1,26 @@ +name: Code Style Review +description: Enforce code style conventions from AGENTS.md +triggers: + - pull_request + +steps: + - name: Check code style + prompt: | + Review the changed TypeScript files against these conventions from AGENTS.md: + + **Imports:** + - Named imports preferred (not `import *`) + - Type imports must be separate: `import type { X }` + + **Naming:** + - camelCase for variables and functions + - PascalCase for types, interfaces, classes, and exports + + **Async:** + - Prefer async/await over raw promises + + **Error Handling:** + - Try-catch blocks with proper cleanup + - No silent failures (empty catch blocks) + + For each violation, leave a comment on the specific line explaining the issue and suggesting a fix. diff --git a/.continue/agents/commit-format.check b/.continue/agents/commit-format.check new file mode 100644 index 0000000..1098737 --- /dev/null +++ b/.continue/agents/commit-format.check @@ -0,0 +1,24 @@ +name: Commit Message Format +description: Ensure commit messages follow Conventional Commits +triggers: + - pull_request + +steps: + - name: Check commit messages + prompt: | + Review the commit messages in this PR for Conventional Commits format: + + **Required format:** `type: description` + + **Valid types:** + - `feat:` - New feature + - `fix:` - Bug fix + - `docs:` - Documentation changes + - `chore:` - Maintenance tasks + - `refactor:` - Code refactoring + - `test:` - Adding/modifying tests + - `perf:` - Performance improvements + + **Breaking changes:** Use `!` suffix (e.g., `feat!: change API`) + + If any commit message doesn't follow this format, leave a comment suggesting the correct format. diff --git a/.continue/agents/typecheck.check b/.continue/agents/typecheck.check new file mode 100644 index 0000000..aba0d48 --- /dev/null +++ b/.continue/agents/typecheck.check @@ -0,0 +1,20 @@ +name: TypeScript Type Check +description: Verify TypeScript compiles without errors +triggers: + - pull_request + +steps: + - name: Run typecheck + run: npm run typecheck + fail_on_error: true + + - name: Review for type issues + prompt: | + Review the changed files for TypeScript type issues: + + - Check for implicit `any` types that should be explicit + - Verify return types are consistent with function signatures + - Ensure type imports use `import type { X }` syntax + - Look for potential null/undefined issues + + If issues found, leave a comment on the specific line. diff --git a/index.ts b/index.ts index cabfe6c..2111d7a 100644 --- a/index.ts +++ b/index.ts @@ -229,7 +229,7 @@ export const SessionPlugin: Plugin = async (ctx) => { }, }) } catch (error) { - // Silently fail - error handling could be added here if needed + console.error(`Failed to send pending message to session ${sessionID}:`, error) } return } @@ -284,7 +284,7 @@ export const SessionPlugin: Plugin = async (ctx) => { }, }) } catch (error) { - // Silently fail - error handling could be added here if needed + console.error(`Failed to send compacted message to session ${sessionID}:`, error) } } } @@ -321,6 +321,11 @@ ${agentList} If omitted, continues with current agent. +NAME PARAMETER (optional): + +Custom name for new or forked sessions. Helps identify sessions in the session list. +Only used in 'new' and 'fork' modes. Ignored in 'message' and 'compact' modes. + EXAMPLES: # COLLABORATE: Multi-agent problem solving @@ -331,13 +336,14 @@ EXAMPLES: }) # Plan reviews, responds, can pass back to build - # HANDOFF: Clean phase transition + # HANDOFF: Clean phase transition with named session session({ mode: "new", agent: "researcher", + name: "API Research", text: "Research best practices for API design" }) - # Fresh session, no baggage from previous implementation work + # Fresh session titled "API Research", no baggage from previous work # COMPRESS: Long conversation with handoff session({ @@ -347,18 +353,20 @@ EXAMPLES: }) # Compacts history, adds handoff context, plan agent responds - # PARALLELIZE: Try multiple approaches + # PARALLELIZE: Named forked sessions for comparison session({ mode: "fork", - agent: "build", + agent: "plan", + name: "Redux Architecture", text: "Implement using Redux" }) session({ mode: "fork", - agent: "build", + agent: "plan", + name: "Context API Architecture", text: "Implement using Context API" }) - # Two independent sessions, compare results + # Two named sessions for easy identification, compare results `, args: { @@ -374,6 +382,12 @@ EXAMPLES: .describe( "Primary agent name (e.g., 'build', 'plan') for agent switching", ), + name: tool.schema + .string() + .optional() + .describe( + "Custom name for the session (used in 'new' and 'fork' modes)", + ), }, async execute(args, toolCtx) { @@ -388,11 +402,12 @@ EXAMPLES: case "new": // Create session via SDK for agent control + const sessionTitle = args.name || (args.agent + ? `Session via ${args.agent}` + : "New session") const newSession = await ctx.client.session.create({ body: { - title: args.agent - ? `Session via ${args.agent}` - : "New session", + title: sessionTitle, }, }) @@ -405,7 +420,9 @@ EXAMPLES: }, }) - return `New session created with ${args.agent || "build"} agent (ID: ${newSession.data.id})` + return args.name + ? `New session "${args.name}" created with ${args.agent || "build"} agent (ID: ${newSession.data.id})` + : `New session created with ${args.agent || "build"} agent (ID: ${newSession.data.id})` case "compact": try { @@ -470,6 +487,13 @@ EXAMPLES: body: {}, }) + if (args.name) { + await ctx.client.session.update({ + path: { id: forkedSession.data.id }, + body: { title: args.name }, + }) + } + // Send new message in forked session await ctx.client.session.prompt({ path: { id: forkedSession.data.id }, @@ -479,7 +503,9 @@ EXAMPLES: }, }) - return `Forked session with ${args.agent || "build"} agent - history preserved (ID: ${forkedSession.data.id})` + return args.name + ? `Forked session "${args.name}" with ${args.agent || "build"} agent - history preserved (ID: ${forkedSession.data.id})` + : `Forked session with ${args.agent || "build"} agent - history preserved (ID: ${forkedSession.data.id})` } } catch (error) { const message = @@ -498,6 +524,82 @@ EXAMPLES: } }, }), + + session_list: tool({ + description: `List all OpenCode sessions with optional filtering. + +Returns a table of sessions with ID, title, and creation date.`, + + args: { + limit: tool.schema + .number() + .optional() + .describe("Maximum number of sessions to return"), + from_date: tool.schema + .string() + .optional() + .describe("Filter sessions from this date (ISO 8601 format)"), + to_date: tool.schema + .string() + .optional() + .describe("Filter sessions until this date (ISO 8601 format)"), + }, + + async execute(args) { + try { + const response = await ctx.client.session.list() + + if (!response.data) { + return "No sessions found." + } + + let sessions = response.data + + if (args.from_date) { + const fromDate = new Date(args.from_date).getTime() + sessions = sessions.filter((s) => { + const created = s.time?.created + ? new Date(s.time.created).getTime() + : 0 + return created >= fromDate + }) + } + + if (args.to_date) { + const toDate = new Date(args.to_date).getTime() + sessions = sessions.filter((s) => { + const created = s.time?.created + ? new Date(s.time.created).getTime() + : 0 + return created <= toDate + }) + } + + if (args.limit && args.limit > 0) { + sessions = sessions.slice(0, args.limit) + } + + if (sessions.length === 0) { + return "No sessions found matching criteria." + } + + const rows = sessions.map((s) => { + const id = s.id || "unknown" + const title = s.title || "Untitled" + const created = s.time?.created + ? new Date(s.time.created).toISOString().split("T")[0] + : "N/A" + return `| ${id} | ${title} | ${created} |` + }) + + return ["| Session ID | Title | Created |", "|------------|-------|---------|", ...rows].join("\n") + } catch (error) { + const message = + error instanceof Error ? error.message : String(error) + return `Error listing sessions: ${message}` + } + }, + }), }, } }