diff --git a/index.ts b/index.ts index cabfe6c..45e9f1a 100644 --- a/index.ts +++ b/index.ts @@ -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,12 @@ EXAMPLES: }, }) - return `New session created with ${args.agent || "build"} agent (ID: ${newSession.data.id})` + const sanitizeForPrompt = (str: string) => str.replace(/\n/g, " ") + const safeName = args.name ? sanitizeForPrompt(args.name) : null + const safeAgent = sanitizeForPrompt(args.agent || "build") + return safeName + ? `New session "${safeName}" created with ${safeAgent} agent (ID: ${newSession.data.id})` + : `New session created with ${safeAgent} agent (ID: ${newSession.data.id})` case "compact": try { @@ -470,6 +490,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 +506,12 @@ EXAMPLES: }, }) - return `Forked session with ${args.agent || "build"} agent - history preserved (ID: ${forkedSession.data.id})` + const sanitizeFork = (str: string) => str.replace(/\n/g, " ") + const forkSafeName = args.name ? sanitizeFork(args.name) : null + const forkSafeAgent = sanitizeFork(args.agent || "build") + return forkSafeName + ? `Forked session "${forkSafeName}" with ${forkSafeAgent} agent - history preserved (ID: ${forkedSession.data.id})` + : `Forked session with ${forkSafeAgent} agent - history preserved (ID: ${forkedSession.data.id})` } } catch (error) { const message = @@ -498,6 +530,102 @@ EXAMPLES: } }, }), + + session_list: tool({ + description: `List all OpenCode sessions with optional filtering. + +Returns a list of available sessions with metadata including title and creation date. + +Arguments: +- limit (optional): Maximum number of sessions to return +- from_date (optional): Filter sessions from this date (ISO 8601 format) +- to_date (optional): Filter sessions until this date (ISO 8601 format) + +Example output: +| Session ID | Title | Created | +|------------|-------|---------| +| ses_abc123 | API Research | 2026-02-21 | +| ses_def456 | Auth Implementation | 2026-02-20 |`, + + 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) + toDate.setUTCHours(23, 59, 59, 999) + const toTimestamp = toDate.getTime() + sessions = sessions.filter((s) => { + const created = s.time?.created + ? new Date(s.time.created).getTime() + : 0 + return created <= toTimestamp + }) + } + + if (args.limit && args.limit > 0) { + sessions = sessions.slice(0, args.limit) + } + + if (sessions.length === 0) { + return "No sessions found matching criteria." + } + + const header = + "| Session ID | Title | Created |" + const separator = + "|------------|-------|---------|" + + const rows = sessions.map((s) => { + const sanitizeForMarkdown = (str: string) => + str.replace(/\|/g, "\\|").replace(/\n/g, " ") + const id = sanitizeForMarkdown(s.id || "unknown") + const title = sanitizeForMarkdown(s.title || "Untitled") + const created = s.time?.created + ? new Date(s.time.created).toISOString().split("T")[0] + : "N/A" + return `| ${id} | ${title} | ${created} |` + }) + + return [header, separator, ...rows].join("\n") + } catch (error) { + const message = + error instanceof Error ? error.message : String(error) + return `Error listing sessions: ${message}` + } + }, + }), }, } }