pi-vscode-sr 1.4.6 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/index.js +149 -53
- package/package.json +1 -1
- package/src/index.ts +56 -23
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ Secure code review bridge between **Pi coding agent** and **VS Code**. Every fil
|
|
|
17
17
|
| ✅ **Approve** | Apply this file's changes |
|
|
18
18
|
| ❌ **Reject** | Discard this file's changes — agent sees an error and must retry |
|
|
19
19
|
| 💭 **Rethink** | Open a text input dialog to give the agent feedback — e.g. «use async/await instead of promise chains». Changes are not applied, agent sees your feedback and can retry with corrections. |
|
|
20
|
-
| ⭐ **Approve All** | Auto-approve every future change for this
|
|
21
|
-
| 🚪 **Abort** | Stop the agent session immediately |
|
|
20
|
+
| ⭐ **Approve All** | Auto-approve every future change for this prompt run. Clear on next prompt. |
|
|
21
|
+
| 🚪 **Abort** | Stop the agent session immediately. |
|
|
22
22
|
|
|
23
23
|
You can also approve/reject from the diff tab.
|
|
24
24
|
|
|
@@ -31,6 +31,7 @@ You can also approve/reject from the diff tab.
|
|
|
31
31
|
> - .pi/review-requests/
|
|
32
32
|
> - .pi/review-results/
|
|
33
33
|
> - .pi/tmp/
|
|
34
|
+
> - .pi/.vscode-ready
|
|
34
35
|
|
|
35
36
|
### 1. Pi Extension
|
|
36
37
|
|
|
@@ -38,7 +39,11 @@ You can also approve/reject from the diff tab.
|
|
|
38
39
|
pi install npm:pi-vscode-sr
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
Or install locally:
|
|
42
|
+
Or install locally (**Recommended**):
|
|
43
|
+
|
|
44
|
+
> [!IMPORTANT]
|
|
45
|
+
> I recommend to install it locally because not every folder is a project of Visual Studio Code. Although there is a mechanism to detect if project is opened in VS Code or not anyway it could lead to unexpected behavior.
|
|
46
|
+
|
|
42
47
|
|
|
43
48
|
```bash
|
|
44
49
|
pi install npm:pi-vscode-sr -l
|
package/dist/index.js
CHANGED
|
@@ -9,61 +9,89 @@ const path_1 = require("path");
|
|
|
9
9
|
// ─── Session state ───────────────────────────────────────────────────
|
|
10
10
|
const sessionReviewIds = new Set();
|
|
11
11
|
const sessionApproveAll = new Set(); // review IDs auto-approved
|
|
12
|
+
let projectCwd = null;
|
|
13
|
+
let vscodeNotOpenWarned = false;
|
|
14
|
+
// ─── Helper: check if VS Code is watching this project ─────────────
|
|
15
|
+
function isVscodeReady(cwd) {
|
|
16
|
+
try {
|
|
17
|
+
const readyFile = (0, path_1.join)(cwd, '.pi', '.vscode-ready');
|
|
18
|
+
if (!(0, fs_1.existsSync)(readyFile))
|
|
19
|
+
return false;
|
|
20
|
+
const ts = parseInt((0, fs_1.readFileSync)(readyFile, 'utf-8').trim(), 10);
|
|
21
|
+
if (isNaN(ts))
|
|
22
|
+
return false;
|
|
23
|
+
// Timestamp must be within last 30 seconds (heartbeat = 15s interval)
|
|
24
|
+
return Date.now() - ts < 30_000;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
12
30
|
async function createReviewAndWait(ctx, filePath, original, proposed, description) {
|
|
31
|
+
// Normalize path: LLM may pass absolute-looking path without leading /
|
|
32
|
+
const normalizedPath = resolveSafe(ctx.cwd, filePath);
|
|
13
33
|
const uuid = (0, crypto_1.randomUUID)();
|
|
14
|
-
const requestsDir = (0, path_1.join)(ctx.cwd, ".pi", "review-requests");
|
|
15
34
|
const resultsDir = (0, path_1.join)(ctx.cwd, ".pi", "review-results");
|
|
16
|
-
(0, fs_1.mkdirSync)(requestsDir, { recursive: true });
|
|
17
35
|
(0, fs_1.mkdirSync)(resultsDir, { recursive: true });
|
|
18
|
-
const reviewRequest = {
|
|
19
|
-
id: uuid,
|
|
20
|
-
title: description,
|
|
21
|
-
files: [{ path: filePath, original, proposed, description }],
|
|
22
|
-
};
|
|
23
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(requestsDir, `${uuid}.json`), JSON.stringify(reviewRequest, null, 2), "utf-8");
|
|
24
|
-
ctx.ui.notify(`📝 Review: ${filePath} — check VS Code diff`, "info");
|
|
25
36
|
sessionReviewIds.add(uuid);
|
|
26
37
|
// Check if approve-all was already chosen
|
|
27
38
|
if (sessionApproveAll.size > 0) {
|
|
28
39
|
writeSyncResult(resultsDir, uuid, "approved", proposed);
|
|
29
40
|
return { status: "approved", final: proposed };
|
|
30
41
|
}
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
const deadline = Date.now() + 10 * 60 * 1000;
|
|
36
|
-
const early = await pollResultFile(resultPath, Date.now() + 2000, 100);
|
|
37
|
-
if (early !== "timeout") {
|
|
38
|
-
if (early === "file-rejected")
|
|
39
|
-
return { status: "rejected" };
|
|
40
|
-
return { status: "approved", final: proposed };
|
|
41
|
-
}
|
|
42
|
-
// Sync check: VS Code may have written the result file between poll intervals.
|
|
43
|
-
if ((0, fs_1.existsSync)(resultPath)) {
|
|
44
|
-
const result = JSON.parse((0, fs_1.readFileSync)(resultPath, "utf-8"));
|
|
45
|
-
if (result.status === "rejected" || result.files?.[0]?.status === "rejected") {
|
|
46
|
-
return { status: "rejected" };
|
|
47
|
-
}
|
|
42
|
+
// Detect whether VS Code is open with this project.
|
|
43
|
+
// If not: bypass review entirely — direct write, no TUI, no polling.
|
|
44
|
+
// Warning is shown once at session_start, not here on every tool call.
|
|
45
|
+
if (!isVscodeReady(ctx.cwd)) {
|
|
48
46
|
return { status: "approved", final: proposed };
|
|
49
47
|
}
|
|
50
|
-
//
|
|
51
|
-
const
|
|
48
|
+
// VS Code is open — create review request, poll for results, show TUI
|
|
49
|
+
const requestsDir = (0, path_1.join)(ctx.cwd, ".pi", "review-requests");
|
|
50
|
+
(0, fs_1.mkdirSync)(requestsDir, { recursive: true });
|
|
51
|
+
const reviewRequest = {
|
|
52
|
+
id: uuid,
|
|
53
|
+
title: description,
|
|
54
|
+
files: [{ path: normalizedPath, original, proposed, description }],
|
|
55
|
+
};
|
|
56
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(requestsDir, `${uuid}.json`), JSON.stringify(reviewRequest, null, 2), "utf-8");
|
|
57
|
+
ctx.ui.notify(`📝 Review: ${filePath} — check VS Code diff`, "info");
|
|
58
|
+
// TUI selector races with VS Code result polling
|
|
59
|
+
const resultPath = (0, path_1.join)(resultsDir, `${uuid}.json`);
|
|
60
|
+
const deadline = Date.now() + 10 * 60 * 1000;
|
|
61
|
+
const tuiController = new AbortController();
|
|
62
|
+
const tuiPromise = showTuiSelector(ctx, filePath, { signal: tuiController.signal });
|
|
52
63
|
const pollPromise = pollResultFile(resultPath, deadline, 500);
|
|
53
64
|
const outcome = await Promise.race([tuiPromise, pollPromise]);
|
|
65
|
+
// If poll resolved first (VS Code responded), dismiss the TUI selector
|
|
66
|
+
if (outcome.action === "file-approved" || outcome.action === "file-rejected") {
|
|
67
|
+
tuiController.abort();
|
|
68
|
+
await tuiPromise.catch(() => { });
|
|
69
|
+
}
|
|
54
70
|
// ── Process outcome (return result, never throw) ──
|
|
55
|
-
if (outcome === "abort") {
|
|
71
|
+
if (outcome.action === "abort") {
|
|
56
72
|
writeSyncResult(resultsDir, uuid, "rejected");
|
|
57
73
|
ctx.abort();
|
|
58
74
|
return { status: "rejected" };
|
|
59
75
|
}
|
|
60
|
-
if (outcome === "
|
|
76
|
+
if (outcome.action === "rethink") {
|
|
77
|
+
writeSyncResult(resultsDir, uuid, "rejected");
|
|
78
|
+
return { status: "rethink", prompt: outcome.prompt };
|
|
79
|
+
}
|
|
80
|
+
if (outcome.action === "file-rejected") {
|
|
81
|
+
return { status: "rejected" };
|
|
82
|
+
}
|
|
83
|
+
if (outcome.action === "rejected") {
|
|
84
|
+
writeSyncResult(resultsDir, uuid, "rejected");
|
|
61
85
|
return { status: "rejected" };
|
|
62
86
|
}
|
|
63
|
-
if (outcome === "file-approved"
|
|
87
|
+
if (outcome.action === "file-approved") {
|
|
88
|
+
return { status: "approved", final: proposed };
|
|
89
|
+
}
|
|
90
|
+
if (outcome.action === "approved") {
|
|
91
|
+
writeSyncResult(resultsDir, uuid, "approved", proposed);
|
|
64
92
|
return { status: "approved", final: proposed };
|
|
65
93
|
}
|
|
66
|
-
if (outcome === "approve-all") {
|
|
94
|
+
if (outcome.action === "approve-all") {
|
|
67
95
|
for (const rid of sessionReviewIds) {
|
|
68
96
|
sessionApproveAll.add(rid);
|
|
69
97
|
}
|
|
@@ -84,16 +112,16 @@ async function pollResultFile(resultPath, deadline, interval = 500) {
|
|
|
84
112
|
if ((0, fs_1.existsSync)(resultPath)) {
|
|
85
113
|
const raw = (0, fs_1.readFileSync)(resultPath, "utf-8");
|
|
86
114
|
if (!raw.trim()) {
|
|
87
|
-
// File exists but is empty — still being written
|
|
88
|
-
await sleep(
|
|
115
|
+
// File exists but is empty — still being written, retry next cycle
|
|
116
|
+
await sleep(interval);
|
|
89
117
|
continue;
|
|
90
118
|
}
|
|
91
119
|
const result = JSON.parse(raw);
|
|
92
120
|
const fileResult = result.files?.[0];
|
|
93
121
|
if (result.status === "rejected" || fileResult?.status === "rejected") {
|
|
94
|
-
return "file-rejected";
|
|
122
|
+
return { action: "file-rejected" };
|
|
95
123
|
}
|
|
96
|
-
return "file-approved";
|
|
124
|
+
return { action: "file-approved" };
|
|
97
125
|
}
|
|
98
126
|
}
|
|
99
127
|
catch {
|
|
@@ -101,26 +129,33 @@ async function pollResultFile(resultPath, deadline, interval = 500) {
|
|
|
101
129
|
}
|
|
102
130
|
await sleep(interval);
|
|
103
131
|
}
|
|
104
|
-
return "timeout";
|
|
132
|
+
return { action: "timeout" };
|
|
105
133
|
}
|
|
106
|
-
async function showTuiSelector(ctx, filePath) {
|
|
134
|
+
async function showTuiSelector(ctx, filePath, opts) {
|
|
107
135
|
const choice = await ctx.ui.select(`📝 Review: ${filePath}`, [
|
|
108
136
|
"✅ Approve",
|
|
109
137
|
"❌ Reject",
|
|
138
|
+
"💭 Rethink",
|
|
110
139
|
"⭐ Approve All for this session",
|
|
111
140
|
"🚪 Abort",
|
|
112
|
-
]);
|
|
141
|
+
], opts);
|
|
113
142
|
if (!choice)
|
|
114
|
-
return "timeout";
|
|
143
|
+
return { action: "timeout" };
|
|
115
144
|
if (choice.startsWith("🚪"))
|
|
116
|
-
return "abort";
|
|
145
|
+
return { action: "abort" };
|
|
117
146
|
if (choice.startsWith("⭐"))
|
|
118
|
-
return "approve-all";
|
|
147
|
+
return { action: "approve-all" };
|
|
148
|
+
if (choice.startsWith("💭")) {
|
|
149
|
+
const prompt = await ctx.ui.input("🔄 Rethink — what should the agent reconsider?", "Describe what needs to change...", opts);
|
|
150
|
+
if (!prompt || !prompt.trim())
|
|
151
|
+
return { action: "rejected" };
|
|
152
|
+
return { action: "rethink", prompt: prompt.trim() };
|
|
153
|
+
}
|
|
119
154
|
if (choice.startsWith("✅"))
|
|
120
|
-
return "approved";
|
|
155
|
+
return { action: "approved" };
|
|
121
156
|
if (choice.startsWith("❌"))
|
|
122
|
-
return "rejected";
|
|
123
|
-
return "timeout";
|
|
157
|
+
return { action: "rejected" };
|
|
158
|
+
return { action: "timeout" };
|
|
124
159
|
}
|
|
125
160
|
function sleep(ms) {
|
|
126
161
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -141,6 +176,22 @@ function applyEdits(content, edits) {
|
|
|
141
176
|
}
|
|
142
177
|
return result;
|
|
143
178
|
}
|
|
179
|
+
// ─── Path normalization ──────────────────────────────────────────────
|
|
180
|
+
/**
|
|
181
|
+
* Safe path resolution. If the LLM passes an absolute-looking path
|
|
182
|
+
* without a leading slash (e.g. "home/user/project/file.ts"), resolve()
|
|
183
|
+
* treats it as relative and doubles the cwd. Detect and fix this.
|
|
184
|
+
*/
|
|
185
|
+
function resolveSafe(cwd, filePath) {
|
|
186
|
+
// Strip leading/trailing slashes from cwd for comparison
|
|
187
|
+
const cwdClean = cwd.replace(/\/+$/, "").replace(/^\//, "");
|
|
188
|
+
// If filePath starts with cwdClean/ (LLM forgot the leading /),
|
|
189
|
+
// strip it so resolve doesn't double.
|
|
190
|
+
if (filePath.startsWith(cwdClean + "/")) {
|
|
191
|
+
filePath = filePath.substring(cwdClean.length + 1);
|
|
192
|
+
}
|
|
193
|
+
return (0, path_1.resolve)(cwd, filePath);
|
|
194
|
+
}
|
|
144
195
|
// ─── Override `write` ────────────────────────────────────────────────
|
|
145
196
|
function registerWriteOverride(pi) {
|
|
146
197
|
pi.registerTool({
|
|
@@ -161,7 +212,8 @@ function registerWriteOverride(pi) {
|
|
|
161
212
|
}),
|
|
162
213
|
executionMode: "sequential",
|
|
163
214
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
164
|
-
|
|
215
|
+
projectCwd = ctx.cwd;
|
|
216
|
+
const absolutePath = resolveSafe(ctx.cwd, params.path);
|
|
165
217
|
let original = "";
|
|
166
218
|
let fileExists = false;
|
|
167
219
|
try {
|
|
@@ -194,6 +246,12 @@ function registerWriteOverride(pi) {
|
|
|
194
246
|
details: { path: params.path, status: "approved", bytes: result.final.length },
|
|
195
247
|
};
|
|
196
248
|
});
|
|
249
|
+
case "rethink":
|
|
250
|
+
return {
|
|
251
|
+
isError: true,
|
|
252
|
+
content: [{ type: "text", text: `🔄 ${params.path} — rethinking requested: "${result.prompt}"\nPlease reconsider your changes based on this feedback.` }],
|
|
253
|
+
details: { path: params.path, status: "rethink", prompt: result.prompt },
|
|
254
|
+
};
|
|
197
255
|
case "rejected":
|
|
198
256
|
return {
|
|
199
257
|
isError: true,
|
|
@@ -229,7 +287,8 @@ function registerEditOverride(pi) {
|
|
|
229
287
|
}),
|
|
230
288
|
executionMode: "sequential",
|
|
231
289
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
232
|
-
|
|
290
|
+
projectCwd = ctx.cwd;
|
|
291
|
+
const absolutePath = resolveSafe(ctx.cwd, params.path);
|
|
233
292
|
let original;
|
|
234
293
|
try {
|
|
235
294
|
original = (0, fs_1.readFileSync)(absolutePath, "utf-8");
|
|
@@ -273,6 +332,12 @@ function registerEditOverride(pi) {
|
|
|
273
332
|
details: { path: params.path, status: "approved", bytes: result.final.length },
|
|
274
333
|
};
|
|
275
334
|
});
|
|
335
|
+
case "rethink":
|
|
336
|
+
return {
|
|
337
|
+
isError: true,
|
|
338
|
+
content: [{ type: "text", text: `🔄 ${params.path} — rethinking requested: "${result.prompt}"\nPlease reconsider your changes based on this feedback.` }],
|
|
339
|
+
details: { path: params.path, status: "rethink", prompt: result.prompt },
|
|
340
|
+
};
|
|
276
341
|
case "rejected":
|
|
277
342
|
return {
|
|
278
343
|
isError: true,
|
|
@@ -287,16 +352,47 @@ function registerEditOverride(pi) {
|
|
|
287
352
|
}
|
|
288
353
|
// ─── Extension entry point ───────────────────────────────────────────
|
|
289
354
|
function default_1(pi) {
|
|
290
|
-
// Reset review ID tracking on new session
|
|
355
|
+
// Reset review ID tracking on new session.
|
|
356
|
+
// Re-check VS Code availability — user may have opened/closed VS Code since last session.
|
|
291
357
|
pi.on("session_start", () => {
|
|
292
358
|
sessionReviewIds.clear();
|
|
359
|
+
sessionApproveAll.clear();
|
|
360
|
+
vscodeNotOpenWarned = false;
|
|
361
|
+
const cwd = process.cwd();
|
|
362
|
+
if (!isVscodeReady(cwd)) {
|
|
363
|
+
console.warn("⚠️ VS Code not detected — working without diff review. " +
|
|
364
|
+
"All file changes will be applied directly. " +
|
|
365
|
+
"Open this project in VS Code with Serhioromano.vscode-pi-sr extension " +
|
|
366
|
+
"installed to enable visual review.");
|
|
367
|
+
vscodeNotOpenWarned = true;
|
|
368
|
+
}
|
|
293
369
|
});
|
|
294
|
-
//
|
|
295
|
-
|
|
370
|
+
// Approve All persists across turns within one prompt.
|
|
371
|
+
// before_agent_start fires once per user prompt — clears here.
|
|
372
|
+
pi.on("before_agent_start", () => {
|
|
296
373
|
sessionApproveAll.clear();
|
|
297
|
-
};
|
|
298
|
-
pi.on("
|
|
299
|
-
|
|
374
|
+
});
|
|
375
|
+
pi.on("message_end", () => {
|
|
376
|
+
cleanupPiDir();
|
|
377
|
+
});
|
|
300
378
|
registerWriteOverride(pi);
|
|
301
379
|
registerEditOverride(pi);
|
|
302
380
|
}
|
|
381
|
+
function cleanupPiDir() {
|
|
382
|
+
if (!projectCwd)
|
|
383
|
+
return;
|
|
384
|
+
// Clean up all .pi subdirectories: tmp files, pending requests, and results.
|
|
385
|
+
// After message_end, every review is resolved — no files are needed anymore.
|
|
386
|
+
for (const sub of ["tmp", "review-requests", "review-results"]) {
|
|
387
|
+
const dir = (0, path_1.join)(projectCwd, ".pi", sub);
|
|
388
|
+
try {
|
|
389
|
+
const files = (0, fs_1.readdirSync)(dir);
|
|
390
|
+
for (const f of files) {
|
|
391
|
+
(0, fs_1.rmSync)((0, path_1.join)(dir, f), { recursive: true, force: true });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// Directory doesn't exist or is empty — ok
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,9 +10,23 @@ import { dirname, join, resolve } from "path";
|
|
|
10
10
|
const sessionReviewIds = new Set<string>();
|
|
11
11
|
const sessionApproveAll = new Set<string>(); // review IDs auto-approved
|
|
12
12
|
let projectCwd: string | null = null;
|
|
13
|
+
let vscodeNotOpenWarned = false;
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
// ─── Helper:
|
|
16
|
+
// ─── Helper: check if VS Code is watching this project ─────────────
|
|
17
|
+
|
|
18
|
+
function isVscodeReady(cwd: string): boolean {
|
|
19
|
+
try {
|
|
20
|
+
const readyFile = join(cwd, '.pi', '.vscode-ready');
|
|
21
|
+
if (!existsSync(readyFile)) return false;
|
|
22
|
+
const ts = parseInt(readFileSync(readyFile, 'utf-8').trim(), 10);
|
|
23
|
+
if (isNaN(ts)) return false;
|
|
24
|
+
// Timestamp must be within last 30 seconds (heartbeat = 15s interval)
|
|
25
|
+
return Date.now() - ts < 30_000;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
16
30
|
|
|
17
31
|
type ReviewOutcome =
|
|
18
32
|
| { status: "approved"; final: string }
|
|
@@ -30,20 +44,9 @@ async function createReviewAndWait(
|
|
|
30
44
|
// Normalize path: LLM may pass absolute-looking path without leading /
|
|
31
45
|
const normalizedPath = resolveSafe(ctx.cwd, filePath);
|
|
32
46
|
const uuid = randomUUID();
|
|
33
|
-
const requestsDir = join(ctx.cwd, ".pi", "review-requests");
|
|
34
47
|
const resultsDir = join(ctx.cwd, ".pi", "review-results");
|
|
35
|
-
mkdirSync(requestsDir, { recursive: true });
|
|
36
48
|
mkdirSync(resultsDir, { recursive: true });
|
|
37
49
|
|
|
38
|
-
const reviewRequest = {
|
|
39
|
-
id: uuid,
|
|
40
|
-
title: description,
|
|
41
|
-
files: [{ path: normalizedPath, original, proposed, description }],
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
writeFileSync(join(requestsDir, `${uuid}.json`), JSON.stringify(reviewRequest, null, 2), "utf-8");
|
|
45
|
-
ctx.ui.notify(`📝 Review: ${filePath} — check VS Code diff`, "info");
|
|
46
|
-
|
|
47
50
|
sessionReviewIds.add(uuid);
|
|
48
51
|
|
|
49
52
|
// Check if approve-all was already chosen
|
|
@@ -52,8 +55,26 @@ async function createReviewAndWait(
|
|
|
52
55
|
return { status: "approved", final: proposed };
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
//
|
|
56
|
-
//
|
|
58
|
+
// Detect whether VS Code is open with this project.
|
|
59
|
+
// If not: bypass review entirely — direct write, no TUI, no polling.
|
|
60
|
+
// Warning is shown once at session_start, not here on every tool call.
|
|
61
|
+
if (!isVscodeReady(ctx.cwd)) {
|
|
62
|
+
return { status: "approved", final: proposed };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// VS Code is open — create review request, poll for results, show TUI
|
|
66
|
+
const requestsDir = join(ctx.cwd, ".pi", "review-requests");
|
|
67
|
+
mkdirSync(requestsDir, { recursive: true });
|
|
68
|
+
|
|
69
|
+
const reviewRequest = {
|
|
70
|
+
id: uuid,
|
|
71
|
+
title: description,
|
|
72
|
+
files: [{ path: normalizedPath, original, proposed, description }],
|
|
73
|
+
};
|
|
74
|
+
writeFileSync(join(requestsDir, `${uuid}.json`), JSON.stringify(reviewRequest, null, 2), "utf-8");
|
|
75
|
+
ctx.ui.notify(`📝 Review: ${filePath} — check VS Code diff`, "info");
|
|
76
|
+
|
|
77
|
+
// TUI selector races with VS Code result polling
|
|
57
78
|
const resultPath = join(resultsDir, `${uuid}.json`);
|
|
58
79
|
const deadline = Date.now() + 10 * 60 * 1000;
|
|
59
80
|
const tuiController = new AbortController();
|
|
@@ -62,11 +83,9 @@ async function createReviewAndWait(
|
|
|
62
83
|
|
|
63
84
|
const outcome = await Promise.race([tuiPromise, pollPromise]);
|
|
64
85
|
|
|
65
|
-
// If poll resolved first (VS Code responded), dismiss the TUI selector
|
|
66
|
-
// Without this, the TUI stays on screen even after the review is done.
|
|
86
|
+
// If poll resolved first (VS Code responded), dismiss the TUI selector
|
|
67
87
|
if (outcome.action === "file-approved" || outcome.action === "file-rejected") {
|
|
68
88
|
tuiController.abort();
|
|
69
|
-
// Wait for TUI to close gracefully (aborted select resolves quickly)
|
|
70
89
|
await tuiPromise.catch(() => { });
|
|
71
90
|
}
|
|
72
91
|
|
|
@@ -400,18 +419,32 @@ function registerEditOverride(pi: ExtensionAPI) {
|
|
|
400
419
|
// ─── Extension entry point ───────────────────────────────────────────
|
|
401
420
|
|
|
402
421
|
export default function (pi: ExtensionAPI) {
|
|
403
|
-
// Reset review ID tracking on new session
|
|
422
|
+
// Reset review ID tracking on new session.
|
|
423
|
+
// Re-check VS Code availability — user may have opened/closed VS Code since last session.
|
|
404
424
|
pi.on("session_start", () => {
|
|
405
425
|
sessionReviewIds.clear();
|
|
426
|
+
sessionApproveAll.clear();
|
|
427
|
+
vscodeNotOpenWarned = false;
|
|
428
|
+
|
|
429
|
+
const cwd = process.cwd();
|
|
430
|
+
if (!isVscodeReady(cwd)) {
|
|
431
|
+
console.warn(
|
|
432
|
+
"⚠️ VS Code not detected — working without diff review. " +
|
|
433
|
+
"All file changes will be applied directly. " +
|
|
434
|
+
"Open this project in VS Code with Serhioromano.vscode-pi-sr extension " +
|
|
435
|
+
"installed to enable visual review."
|
|
436
|
+
);
|
|
437
|
+
vscodeNotOpenWarned = true;
|
|
438
|
+
}
|
|
406
439
|
});
|
|
407
440
|
|
|
408
|
-
//
|
|
409
|
-
|
|
441
|
+
// Approve All persists across turns within one prompt.
|
|
442
|
+
// before_agent_start fires once per user prompt — clears here.
|
|
443
|
+
pi.on("before_agent_start", () => {
|
|
410
444
|
sessionApproveAll.clear();
|
|
411
|
-
};
|
|
412
|
-
|
|
445
|
+
});
|
|
446
|
+
|
|
413
447
|
pi.on("message_end", () => {
|
|
414
|
-
clearApproveAll();
|
|
415
448
|
cleanupPiDir();
|
|
416
449
|
});
|
|
417
450
|
|