Commit 696f6ca3870d
dots/pi/agent/extensions/github/actions/issue.ts
@@ -190,14 +190,52 @@ export async function handleIssueCreate(
// APPROVAL GATE
if (ctx.hasUI) {
- const confirmMessage = buildIssueCreateConfirmation(params);
- const confirmed = await ctx.ui.confirm("Create Issue?", confirmMessage);
- if (!confirmed) {
- ctx.ui.notify("Issue creation cancelled", "info");
- return {
- content: [{ type: "text", text: "Issue creation cancelled by user" }],
- details: { action: "issue-create", cancelled: true } as GhDetails,
- };
+ let confirmMessage = buildIssueCreateConfirmation(params);
+
+ // If body is long, offer to preview full content
+ if (params.body && params.body.length > 200) {
+ confirmMessage += `\n\n๐ Body: ${params.body.length} characters (truncated in preview)`;
+
+ const choice = await ctx.ui.select(
+ "Create Issue?",
+ ["Create issue", "Preview body first", "Cancel"]
+ );
+
+ if (choice === undefined || choice === "Cancel") {
+ ctx.ui.notify("Issue creation cancelled", "info");
+ return {
+ content: [{ type: "text", text: "Issue creation cancelled by user" }],
+ details: { action: "issue-create", cancelled: true } as GhDetails,
+ };
+ }
+
+ if (choice === "Preview body first") {
+ // Show full body in editor (read-only preview via input with initial value)
+ await ctx.ui.editor(
+ `Issue Body Preview (${params.body.length} chars):\n\nTitle: ${params.title}\n\n---\n\n`,
+ params.body
+ );
+
+ // Ask again after preview
+ const finalConfirm = await ctx.ui.confirm("Create Issue?", confirmMessage);
+ if (!finalConfirm) {
+ ctx.ui.notify("Issue creation cancelled", "info");
+ return {
+ content: [{ type: "text", text: "Issue creation cancelled by user" }],
+ details: { action: "issue-create", cancelled: true } as GhDetails,
+ };
+ }
+ }
+ } else {
+ // Short body or no body - simple confirm
+ const confirmed = await ctx.ui.confirm("Create Issue?", confirmMessage);
+ if (!confirmed) {
+ ctx.ui.notify("Issue creation cancelled", "info");
+ return {
+ content: [{ type: "text", text: "Issue creation cancelled by user" }],
+ details: { action: "issue-create", cancelled: true } as GhDetails,
+ };
+ }
}
}
dots/pi/agent/extensions/github/actions/pr.ts
@@ -250,13 +250,52 @@ export async function handlePRCreate(
if (template) {
confirmMessage += `\n\nTemplate: ${template}`;
}
- const confirmed = await ctx.ui.confirm("Create Pull Request?", confirmMessage);
- if (!confirmed) {
- ctx.ui.notify("PR creation cancelled", "info");
- return {
- content: [{ type: "text", text: "PR creation cancelled by user" }],
- details: { action: "pr-create", cancelled: true } as GhDetails,
- };
+
+ // If body is long, offer to preview full content
+ if (params.body && params.body.length > 200) {
+ confirmMessage += `\n\n๐ Body: ${params.body.length} characters (truncated in preview)`;
+ confirmMessage += `\n Press 'y' to create, 'p' to preview full body, 'n' to cancel`;
+
+ const choice = await ctx.ui.select(
+ "Create Pull Request?",
+ ["Create PR", "Preview body first", "Cancel"]
+ );
+
+ if (choice === undefined || choice === "Cancel") {
+ ctx.ui.notify("PR creation cancelled", "info");
+ return {
+ content: [{ type: "text", text: "PR creation cancelled by user" }],
+ details: { action: "pr-create", cancelled: true } as GhDetails,
+ };
+ }
+
+ if (choice === "Preview body first") {
+ // Show full body in editor (read-only preview via input with initial value)
+ await ctx.ui.editor(
+ `PR Body Preview (${params.body.length} chars):\n\nTitle: ${params.title}\n\n---\n\n`,
+ params.body
+ );
+
+ // Ask again after preview
+ const finalConfirm = await ctx.ui.confirm("Create Pull Request?", confirmMessage);
+ if (!finalConfirm) {
+ ctx.ui.notify("PR creation cancelled", "info");
+ return {
+ content: [{ type: "text", text: "PR creation cancelled by user" }],
+ details: { action: "pr-create", cancelled: true } as GhDetails,
+ };
+ }
+ }
+ } else {
+ // Short body or no body - simple confirm
+ const confirmed = await ctx.ui.confirm("Create Pull Request?", confirmMessage);
+ if (!confirmed) {
+ ctx.ui.notify("PR creation cancelled", "info");
+ return {
+ content: [{ type: "text", text: "PR creation cancelled by user" }],
+ details: { action: "pr-create", cancelled: true } as GhDetails,
+ };
+ }
}
}
dots/pi/agent/extensions/github/utils.ts
@@ -33,7 +33,7 @@ async function findGitRoot(pi: ExtensionAPI, cwd: string): Promise<string | null
/**
* Detect if we're working in a git worktree context.
- * Checks recent bash operations to find where actual work is happening.
+ * Checks git_worktree tool results and recent bash operations.
* Returns the worktree path if detected, null otherwise.
*/
async function detectWorktreeContext(
@@ -53,8 +53,45 @@ async function detectWorktreeContext(
}
}
- // Strategy 2: Check for recent git operations in session messages
- // Look for bash tool results with git commands that mention worktree paths
+ // Strategy 2: Check for git_worktree tool results (most reliable)
+ try {
+ const entries = ctx.sessionManager.getBranch();
+ let mostRecentWorktree: string | null = null;
+ let mostRecentTimestamp = 0;
+
+ for (const entry of entries) {
+ if (entry.type !== "message") continue;
+ const msg = entry.message;
+
+ // Look for git_worktree tool results (from custom git extension)
+ if (msg.role === "toolResult" && msg.toolName === "git_worktree") {
+ const details = msg.details as any;
+ const timestamp = entry.timestamp || 0;
+
+ // git_worktree create returns: { path: string, branch: string }
+ if (details?.path && timestamp > mostRecentTimestamp) {
+ mostRecentWorktree = details.path;
+ mostRecentTimestamp = timestamp;
+ }
+ }
+ }
+
+ if (mostRecentWorktree) {
+ // Verify it still exists and is a valid git directory
+ const checkResult = await pi.exec("git", ["rev-parse", "--show-toplevel"], {
+ cwd: mostRecentWorktree,
+ timeout: 5000
+ });
+ if (checkResult.code === 0 && checkResult.stdout.trim()) {
+ cachedWorktreeRoot = checkResult.stdout.trim();
+ return cachedWorktreeRoot;
+ }
+ }
+ } catch {
+ // Ignore errors in git_worktree detection
+ }
+
+ // Strategy 3: Fallback to checking bash history for worktree operations
try {
const entries = ctx.sessionManager.getBranch();
for (const entry of entries.reverse()) { // Start from most recent
@@ -79,7 +116,7 @@ async function detectWorktreeContext(
}
}
} catch {
- // Ignore errors in session scanning
+ // Ignore errors in bash history scanning
}
return null;