-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfirm-destructive.ts
More file actions
140 lines (117 loc) · 4.54 KB
/
Copy pathconfirm-destructive.ts
File metadata and controls
140 lines (117 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* Confirm Destructive Actions Extension
*
* Prompts for confirmation before destructive session actions (clear, switch, branch).
* Can be toggled with /confirm-destructive on|off|toggle|status.
* Demonstrates how to cancel session events using the before_* events.
*/
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@mariozechner/pi-coding-agent";
type ConfirmDestructiveArg = "on" | "off" | "toggle" | "status";
const STATE_PATH = path.join(os.homedir(), ".pi", "agent", "state", "confirm-destructive.json");
let confirmDestructiveEnabled = loadConfirmDestructive();
function loadConfirmDestructive(): boolean {
try {
const parsed = JSON.parse(fs.readFileSync(STATE_PATH, "utf8"));
return parsed?.enabled !== false;
} catch {
return true;
}
}
function saveConfirmDestructive(enabled: boolean) {
fs.mkdirSync(path.dirname(STATE_PATH), { recursive: true });
fs.writeFileSync(STATE_PATH, JSON.stringify({ enabled, updatedAt: new Date().toISOString() }, null, 2));
}
function parseArg(raw: string): ConfirmDestructiveArg {
const arg = raw.trim().toLowerCase();
if (arg === "") return "toggle";
if (arg === "on" || arg === "enable" || arg === "enabled" || arg === "true" || arg === "1") return "on";
if (arg === "off" || arg === "disable" || arg === "disabled" || arg === "false" || arg === "0") return "off";
if (arg === "toggle") return "toggle";
if (arg === "status" || arg === "show" || arg === "?") return "status";
return "status";
}
function renderStatus(ctx: {
ui: { theme: { fg(color: "warning" | "dim", text: string): string }; setStatus(key: string, text: string | undefined): void };
}) {
const label = confirmDestructiveEnabled
? ctx.ui.theme.fg("warning", " destructive confirm:on")
: ctx.ui.theme.fg("dim", " destructive confirm:off");
ctx.ui.setStatus("confirm-destructive", label);
}
function isEnabled(): boolean {
confirmDestructiveEnabled = loadConfirmDestructive();
return confirmDestructiveEnabled;
}
export default function (pi: ExtensionAPI) {
pi.on("session_start", async (_event, ctx) => {
if (!ctx.hasUI) return;
confirmDestructiveEnabled = loadConfirmDestructive();
renderStatus(ctx);
});
pi.registerCommand("confirm-destructive", {
description: "Toggle confirmations before destructive session actions",
getArgumentCompletions: (prefix) => {
const options = ["on", "off", "toggle", "status"];
const matches = options.filter((option) => option.startsWith(prefix.trim().toLowerCase()));
return matches.length ? matches.map((value) => ({ value, label: value })) : null;
},
handler: async (args, ctx) => {
const action = parseArg(args);
if (action === "toggle") {
confirmDestructiveEnabled = !loadConfirmDestructive();
} else if (action === "on") {
confirmDestructiveEnabled = true;
} else if (action === "off") {
confirmDestructiveEnabled = false;
} else {
confirmDestructiveEnabled = loadConfirmDestructive();
}
if (action !== "status") saveConfirmDestructive(confirmDestructiveEnabled);
if (ctx.hasUI) renderStatus(ctx);
ctx.ui.notify(`Destructive confirmations ${confirmDestructiveEnabled ? "enabled" : "disabled"}`, "info");
},
});
pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => {
if (!ctx.hasUI || !isEnabled()) return;
if (event.reason === "new") {
const confirmed = await ctx.ui.confirm(
"Clear session?",
"This will delete all messages in the current session.",
);
if (!confirmed) {
ctx.ui.notify("Clear cancelled", "info");
return { cancel: true };
}
return;
}
// reason === "resume" - check if there are unsaved changes (messages since last assistant response)
const entries = ctx.sessionManager.getEntries();
const hasUnsavedWork = entries.some(
(e): e is SessionMessageEntry => e.type === "message" && e.message.role === "user",
);
if (hasUnsavedWork) {
const confirmed = await ctx.ui.confirm(
"Switch session?",
"You have messages in the current session. Switch anyway?",
);
if (!confirmed) {
ctx.ui.notify("Switch cancelled", "info");
return { cancel: true };
}
}
});
pi.on("session_before_fork", async (event, ctx) => {
if (!ctx.hasUI || !isEnabled()) return;
const choice = await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, [
"Yes, create fork",
"No, stay in current session",
]);
if (choice !== "Yes, create fork") {
ctx.ui.notify("Fork cancelled", "info");
return { cancel: true };
}
});
}