pi-x-ide 1.15.2 → 1.16.0
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/CHANGELOG.md +7 -0
- package/dist/src/pi/commands.js +14 -19
- package/dist/src/pi/connection.js +30 -38
- package/dist/src/pi/context.js +16 -21
- package/dist/src/pi/diagnostic-renderer.js +71 -0
- package/dist/src/pi/diagnostics.js +28 -25
- package/dist/src/pi/discovery.js +26 -31
- package/dist/src/pi/ide-host.js +17 -24
- package/dist/src/pi/index.js +63 -64
- package/dist/src/pi/install.js +35 -68
- package/dist/src/pi/reconnect.js +6 -12
- package/dist/src/pi/safety.js +10 -15
- package/dist/src/pi/state.js +1 -4
- package/dist/src/pi/ui.js +28 -34
- package/dist/src/pi/zed.js +64 -74
- package/dist/src/shared/config-options.js +6 -10
- package/dist/src/shared/config.js +16 -24
- package/dist/src/shared/display-width.js +2 -6
- package/dist/src/shared/errors.js +5 -12
- package/dist/src/shared/format.js +17 -28
- package/dist/src/shared/ide-server.js +20 -57
- package/dist/src/shared/lock-file.js +21 -29
- package/dist/src/shared/paths.js +30 -40
- package/dist/src/shared/platform.js +13 -19
- package/dist/src/shared/protocol.js +3 -6
- package/dist/src/shared/schema.js +9 -20
- package/dist/src/shared/version.js +1 -0
- package/dist/src/shared/ws.js +5 -8
- package/package.json +5 -3
package/dist/src/pi/ide-host.js
CHANGED
|
@@ -1,34 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const platform_1 = require("../shared/platform");
|
|
13
|
-
exports.PI_X_IDE_HOST_OVERRIDE_ENV = "PI_X_IDE_HOST_OVERRIDE";
|
|
14
|
-
exports.IDE_HOST_TCP_PROBE_TIMEOUT_MS = 500;
|
|
15
|
-
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
16
|
-
async function resolveIdeHost(lock, options = {}) {
|
|
17
|
-
const env = (0, config_1.resolvePiConfigEnv)(options.env ?? process.env);
|
|
18
|
-
const override = env[exports.PI_X_IDE_HOST_OVERRIDE_ENV]?.trim();
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { resolvePiConfigEnv } from "../shared/config.js";
|
|
5
|
+
import { isWsl } from "../shared/platform.js";
|
|
6
|
+
export const PI_X_IDE_HOST_OVERRIDE_ENV = "PI_X_IDE_HOST_OVERRIDE";
|
|
7
|
+
export const IDE_HOST_TCP_PROBE_TIMEOUT_MS = 500;
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
export async function resolveIdeHost(lock, options = {}) {
|
|
10
|
+
const env = resolvePiConfigEnv(options.env ?? process.env);
|
|
11
|
+
const override = env[PI_X_IDE_HOST_OVERRIDE_ENV]?.trim();
|
|
19
12
|
if (override)
|
|
20
13
|
return override;
|
|
21
|
-
if (lock.runningInWindows === true &&
|
|
14
|
+
if (lock.runningInWindows === true && isWsl(env)) {
|
|
22
15
|
const gateway = await resolveWslDefaultGateway(options.runCommand ?? defaultRunCommand);
|
|
23
16
|
if (gateway) {
|
|
24
|
-
const reachable = await (options.tcpProbe ?? tcpReachable)(gateway, lock.port, options.timeoutMs ??
|
|
17
|
+
const reachable = await (options.tcpProbe ?? tcpReachable)(gateway, lock.port, options.timeoutMs ?? IDE_HOST_TCP_PROBE_TIMEOUT_MS);
|
|
25
18
|
if (reachable)
|
|
26
19
|
return gateway;
|
|
27
20
|
}
|
|
28
21
|
}
|
|
29
22
|
return lock.host || "127.0.0.1";
|
|
30
23
|
}
|
|
31
|
-
async function resolveWslDefaultGateway(runCommand = defaultRunCommand) {
|
|
24
|
+
export async function resolveWslDefaultGateway(runCommand = defaultRunCommand) {
|
|
32
25
|
try {
|
|
33
26
|
const { stdout } = await runCommand("ip", ["route", "show"]);
|
|
34
27
|
return parseDefaultGateway(stdout);
|
|
@@ -37,7 +30,7 @@ async function resolveWslDefaultGateway(runCommand = defaultRunCommand) {
|
|
|
37
30
|
return undefined;
|
|
38
31
|
}
|
|
39
32
|
}
|
|
40
|
-
function parseDefaultGateway(output) {
|
|
33
|
+
export function parseDefaultGateway(output) {
|
|
41
34
|
for (const line of output.split(/\r?\n/)) {
|
|
42
35
|
const match = line.match(/^default\s+via\s+(\d{1,3}(?:\.\d{1,3}){3})(?:\s|$)/);
|
|
43
36
|
if (match)
|
|
@@ -49,9 +42,9 @@ async function defaultRunCommand(command, args) {
|
|
|
49
42
|
const { stdout } = await execFileAsync(command, args, { encoding: "utf8" });
|
|
50
43
|
return { stdout };
|
|
51
44
|
}
|
|
52
|
-
function tcpReachable(host, port, timeoutMs =
|
|
45
|
+
export function tcpReachable(host, port, timeoutMs = IDE_HOST_TCP_PROBE_TIMEOUT_MS) {
|
|
53
46
|
return new Promise((resolve) => {
|
|
54
|
-
const socket =
|
|
47
|
+
const socket = createConnection({ host, port });
|
|
55
48
|
let settled = false;
|
|
56
49
|
const finish = (reachable) => {
|
|
57
50
|
if (settled)
|
package/dist/src/pi/index.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const ui_1 = require("./ui");
|
|
17
|
-
const zed_1 = require("./zed");
|
|
1
|
+
import { logExtensionError } from "../shared/errors.js";
|
|
2
|
+
import { formatRangeMention } from "../shared/format.js";
|
|
3
|
+
import { hasDirectWorkspaceMatch } from "../shared/paths.js";
|
|
4
|
+
import { discoverIdeCandidates } from "./discovery.js";
|
|
5
|
+
import { IdeConnection, IdeConnectionTimeoutError } from "./connection.js";
|
|
6
|
+
import { discoverInstallCandidates, installIdeExtension, isAutoInstallEnabled, selectAutoInstallCandidate, } from "./install.js";
|
|
7
|
+
import { registerIdeCommand } from "./commands.js";
|
|
8
|
+
import { clearLatestSelection, registerContextHandlers, setLatestSelection } from "./context.js";
|
|
9
|
+
import { handleDiagnosticFixRequested } from "./diagnostics.js";
|
|
10
|
+
import { registerDiagnosticRenderer } from "./diagnostic-renderer.js";
|
|
11
|
+
import { formatReconnectLimitMessage, recordReconnectAttempt, resetReconnectState } from "./reconnect.js";
|
|
12
|
+
import { containPiError, runPiBoundary, runPiBoundaryAsync } from "./safety.js";
|
|
13
|
+
import { createRuntime } from "./state.js";
|
|
14
|
+
import { clearIdeUi, updateIdeUi } from "./ui.js";
|
|
15
|
+
import { startZedPolling, stopZedPolling } from "./zed.js";
|
|
18
16
|
const RECONNECT_DELAY_MS = 2_000;
|
|
19
17
|
const INSTALL_RECONNECT_RETRY_MS = 1_500;
|
|
20
18
|
const INSTALL_RECONNECT_TIMEOUT_MS = 15_000;
|
|
@@ -22,51 +20,52 @@ let activePi;
|
|
|
22
20
|
function formatConnectTimeoutMessage(error) {
|
|
23
21
|
return `${error.message}.`;
|
|
24
22
|
}
|
|
25
|
-
function
|
|
23
|
+
export default function (pi) {
|
|
26
24
|
activePi = pi;
|
|
27
|
-
const runtime =
|
|
28
|
-
|
|
29
|
-
(
|
|
25
|
+
const runtime = createRuntime();
|
|
26
|
+
registerContextHandlers(pi, runtime);
|
|
27
|
+
registerDiagnosticRenderer(pi);
|
|
28
|
+
registerIdeCommand(pi, runtime, {
|
|
30
29
|
refreshCandidates: (ctx) => refreshCandidates(runtime, ctx),
|
|
31
30
|
connectAuto: (ctx) => connectAutoWithZedFallback(runtime, ctx),
|
|
32
31
|
connectCandidate: (candidate, ctx) => connectCandidate(runtime, candidate, ctx),
|
|
33
32
|
disconnect: (ctx, disabled) => disconnect(runtime, ctx, disabled),
|
|
34
33
|
installExtension: (ctx) => installExtension(runtime, ctx),
|
|
35
34
|
});
|
|
36
|
-
pi.on("session_start", (_event, ctx) =>
|
|
35
|
+
pi.on("session_start", (_event, ctx) => runPiBoundaryAsync("Pi session start", runtime, async () => {
|
|
37
36
|
runtime.sessionGeneration += 1;
|
|
38
37
|
const generation = runtime.sessionGeneration;
|
|
39
38
|
runtime.ctx = ctx;
|
|
40
39
|
runtime.cwd = ctx.cwd;
|
|
41
|
-
|
|
40
|
+
stopZedPolling(runtime);
|
|
42
41
|
if (!runtime.enabled) {
|
|
43
42
|
runtime.connectionStatus = "disabled";
|
|
44
|
-
|
|
43
|
+
updateIdeUi(runtime, ctx);
|
|
45
44
|
return;
|
|
46
45
|
}
|
|
47
46
|
void maybeAutoInstallAndReconnect(runtime, ctx, generation);
|
|
48
47
|
await connectAutoWithZedFallback(runtime, ctx, generation);
|
|
49
48
|
}, ctx));
|
|
50
|
-
pi.on("session_shutdown", (_event, ctx) =>
|
|
49
|
+
pi.on("session_shutdown", (_event, ctx) => runPiBoundary("Pi session shutdown", runtime, () => {
|
|
51
50
|
runtime.sessionGeneration += 1;
|
|
52
51
|
runtime.ctx = ctx;
|
|
53
|
-
|
|
52
|
+
stopZedPolling(runtime);
|
|
54
53
|
if (runtime.reconnectTimer)
|
|
55
54
|
clearTimeout(runtime.reconnectTimer);
|
|
56
55
|
runtime.reconnectTimer = undefined;
|
|
57
56
|
const connection = runtime.connection;
|
|
58
57
|
runtime.connection = undefined;
|
|
59
58
|
connection?.disconnect();
|
|
60
|
-
|
|
59
|
+
clearIdeUi(runtime, ctx);
|
|
61
60
|
runtime.ctx = undefined;
|
|
62
61
|
}, ctx));
|
|
63
62
|
}
|
|
64
63
|
async function maybeAutoInstallAndReconnect(runtime, ctx, generation) {
|
|
65
64
|
try {
|
|
66
|
-
if (!
|
|
65
|
+
if (!isAutoInstallEnabled() || !isInstallSessionActive(runtime, generation))
|
|
67
66
|
return;
|
|
68
|
-
const candidates = await
|
|
69
|
-
const candidate =
|
|
67
|
+
const candidates = await discoverInstallCandidates();
|
|
68
|
+
const candidate = selectAutoInstallCandidate(candidates);
|
|
70
69
|
if (!candidate)
|
|
71
70
|
return;
|
|
72
71
|
if (!isInstallSessionActive(runtime, generation))
|
|
@@ -74,7 +73,7 @@ async function maybeAutoInstallAndReconnect(runtime, ctx, generation) {
|
|
|
74
73
|
if (!candidate.needsInstall)
|
|
75
74
|
return;
|
|
76
75
|
notifyInstall(ctx, `Installing Pi x IDE extension for ${candidate.label}...`, "info");
|
|
77
|
-
const result = await
|
|
76
|
+
const result = await installIdeExtension(candidate, runtime);
|
|
78
77
|
if (!isInstallSessionActive(runtime, generation))
|
|
79
78
|
return;
|
|
80
79
|
if (!result.success) {
|
|
@@ -104,7 +103,7 @@ async function maybeAutoInstallAndReconnect(runtime, ctx, generation) {
|
|
|
104
103
|
async function installExtension(runtime, ctx) {
|
|
105
104
|
runtime.ctx = ctx;
|
|
106
105
|
runtime.cwd = ctx.cwd;
|
|
107
|
-
const candidates = await
|
|
106
|
+
const candidates = await discoverInstallCandidates({ includeLowConfidence: true });
|
|
108
107
|
if (candidates.length === 0) {
|
|
109
108
|
ctx.ui.notify("No supported VS Code-family IDE CLI found. Install VS Code, Cursor, or Windsurf CLI first.", "warning");
|
|
110
109
|
return;
|
|
@@ -119,7 +118,7 @@ async function installExtension(runtime, ctx) {
|
|
|
119
118
|
return;
|
|
120
119
|
}
|
|
121
120
|
ctx.ui.notify(`${installActionLabel(candidate)} Pi x IDE extension for ${candidate.label}...`, "info");
|
|
122
|
-
const result = await
|
|
121
|
+
const result = await installIdeExtension(candidate, runtime);
|
|
123
122
|
if (!result.success) {
|
|
124
123
|
ctx.ui.notify(`Failed to install Pi x IDE extension for ${candidate.label}: ${describeInstallError(result.error, result.stderr)}`, "warning");
|
|
125
124
|
return;
|
|
@@ -177,7 +176,7 @@ function notifyInstall(ctx, message, level) {
|
|
|
177
176
|
ctx.ui.notify(message, level);
|
|
178
177
|
}
|
|
179
178
|
catch (error) {
|
|
180
|
-
|
|
179
|
+
logExtensionError("IDE install notification", error);
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
function describeInstallError(error, stderr) {
|
|
@@ -193,13 +192,13 @@ function sleep(ms) {
|
|
|
193
192
|
async function refreshCandidates(runtime, ctx) {
|
|
194
193
|
runtime.ctx = ctx;
|
|
195
194
|
runtime.cwd = ctx.cwd;
|
|
196
|
-
runtime.candidates = await
|
|
195
|
+
runtime.candidates = await discoverIdeCandidates({ cwd: ctx.cwd });
|
|
197
196
|
return runtime.candidates;
|
|
198
197
|
}
|
|
199
198
|
async function connectAutoWithZedFallback(runtime, ctx, generation = runtime.sessionGeneration, options = {}) {
|
|
200
199
|
await connectAuto(runtime, ctx, options);
|
|
201
200
|
if (runtime.connectionStatus !== "connected") {
|
|
202
|
-
|
|
201
|
+
startZedPolling(runtime, ctx, { generation });
|
|
203
202
|
}
|
|
204
203
|
}
|
|
205
204
|
async function connectAuto(runtime, ctx, options = {}) {
|
|
@@ -208,7 +207,7 @@ async function connectAuto(runtime, ctx, options = {}) {
|
|
|
208
207
|
const candidate = candidates.find((candidate) => isAutoConnectCandidate(candidate, ctx.cwd));
|
|
209
208
|
if (!candidate) {
|
|
210
209
|
if (options.resetReconnectState !== false)
|
|
211
|
-
|
|
210
|
+
resetReconnectState(runtime);
|
|
212
211
|
runtime.connectionStatus = "disconnected";
|
|
213
212
|
runtime.connectionMessage =
|
|
214
213
|
candidates.length > 0
|
|
@@ -216,21 +215,21 @@ async function connectAuto(runtime, ctx, options = {}) {
|
|
|
216
215
|
: "No matching IDE lock files found.";
|
|
217
216
|
runtime.currentCandidate = undefined;
|
|
218
217
|
runtime.connectedServer = undefined;
|
|
219
|
-
|
|
218
|
+
updateIdeUi(runtime, ctx);
|
|
220
219
|
return;
|
|
221
220
|
}
|
|
222
221
|
await connectCandidate(runtime, candidate, ctx, options);
|
|
223
222
|
}
|
|
224
223
|
function isAutoConnectCandidate(candidate, cwd) {
|
|
225
|
-
return
|
|
224
|
+
return hasDirectWorkspaceMatch(candidate.lock.workspaceFolders, cwd);
|
|
226
225
|
}
|
|
227
226
|
async function connectCandidate(runtime, candidate, ctx, options = {}) {
|
|
228
227
|
if (options.resetReconnectState !== false)
|
|
229
|
-
|
|
228
|
+
resetReconnectState(runtime);
|
|
230
229
|
runtime.ctx = ctx;
|
|
231
230
|
runtime.cwd = ctx.cwd;
|
|
232
231
|
runtime.enabled = true;
|
|
233
|
-
|
|
232
|
+
stopZedPolling(runtime);
|
|
234
233
|
if (runtime.reconnectTimer)
|
|
235
234
|
clearTimeout(runtime.reconnectTimer);
|
|
236
235
|
runtime.reconnectTimer = undefined;
|
|
@@ -241,10 +240,10 @@ async function connectCandidate(runtime, candidate, ctx, options = {}) {
|
|
|
241
240
|
runtime.connectedServer = undefined;
|
|
242
241
|
runtime.connectionStatus = "connecting";
|
|
243
242
|
runtime.connectionMessage = `Connecting to ${candidate.lock.name} at ${candidate.lock.host}:${candidate.lock.port}`;
|
|
244
|
-
|
|
243
|
+
updateIdeUi(runtime, ctx);
|
|
245
244
|
const generation = runtime.sessionGeneration;
|
|
246
245
|
const connectionRef = {};
|
|
247
|
-
const connection = new
|
|
246
|
+
const connection = new IdeConnection(candidate, ctx.cwd, createConnectionCallbacks(activePi, runtime, () => connectionRef.current, generation));
|
|
248
247
|
connectionRef.current = connection;
|
|
249
248
|
runtime.connection = connection;
|
|
250
249
|
try {
|
|
@@ -252,8 +251,8 @@ async function connectCandidate(runtime, candidate, ctx, options = {}) {
|
|
|
252
251
|
if (runtime.connection === connection && runtime.connectionStatus === "connecting") {
|
|
253
252
|
runtime.connectionStatus = "connected";
|
|
254
253
|
runtime.connectionMessage = undefined;
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
resetReconnectState(runtime);
|
|
255
|
+
updateIdeUi(runtime, ctx);
|
|
257
256
|
}
|
|
258
257
|
}
|
|
259
258
|
catch (error) {
|
|
@@ -261,9 +260,9 @@ async function connectCandidate(runtime, candidate, ctx, options = {}) {
|
|
|
261
260
|
runtime.connection = undefined;
|
|
262
261
|
runtime.connectionStatus = "error";
|
|
263
262
|
runtime.connectionMessage =
|
|
264
|
-
error instanceof
|
|
265
|
-
|
|
266
|
-
if (!(error instanceof
|
|
263
|
+
error instanceof IdeConnectionTimeoutError ? formatConnectTimeoutMessage(error) : errorMessage(error);
|
|
264
|
+
updateIdeUi(runtime, ctx);
|
|
265
|
+
if (!(error instanceof IdeConnectionTimeoutError))
|
|
267
266
|
scheduleReconnect(runtime);
|
|
268
267
|
}
|
|
269
268
|
}
|
|
@@ -276,8 +275,8 @@ function createConnectionCallbacks(pi, runtime, getConnection, generation) {
|
|
|
276
275
|
runtime.connectedServer = server;
|
|
277
276
|
runtime.connectionStatus = "connected";
|
|
278
277
|
runtime.connectionMessage = undefined;
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
resetReconnectState(runtime);
|
|
279
|
+
updateIdeUi(runtime);
|
|
281
280
|
},
|
|
282
281
|
onDisconnected: (reason) => {
|
|
283
282
|
if (!isCurrentConnection(runtime, getConnection(), generation))
|
|
@@ -286,19 +285,19 @@ function createConnectionCallbacks(pi, runtime, getConnection, generation) {
|
|
|
286
285
|
runtime.connectedServer = undefined;
|
|
287
286
|
runtime.connectionStatus = runtime.enabled ? "disconnected" : "disabled";
|
|
288
287
|
runtime.connectionMessage = reason;
|
|
289
|
-
|
|
288
|
+
updateIdeUi(runtime);
|
|
290
289
|
if (runtime.enabled)
|
|
291
290
|
scheduleReconnect(runtime);
|
|
292
291
|
},
|
|
293
292
|
onSelectionChanged: (snapshot) => {
|
|
294
293
|
if (!isCurrentConnection(runtime, getConnection(), generation))
|
|
295
294
|
return;
|
|
296
|
-
|
|
295
|
+
setLatestSelection(runtime, snapshot);
|
|
297
296
|
},
|
|
298
297
|
onSelectionCleared: () => {
|
|
299
298
|
if (!isCurrentConnection(runtime, getConnection(), generation))
|
|
300
299
|
return;
|
|
301
|
-
|
|
300
|
+
clearLatestSelection(runtime);
|
|
302
301
|
},
|
|
303
302
|
onAtMentioned: (params) => {
|
|
304
303
|
if (!isCurrentConnection(runtime, getConnection(), generation))
|
|
@@ -308,12 +307,12 @@ function createConnectionCallbacks(pi, runtime, getConnection, generation) {
|
|
|
308
307
|
onDiagnosticFixRequested: (params) => {
|
|
309
308
|
if (!pi || !isCurrentConnection(runtime, getConnection(), generation))
|
|
310
309
|
return;
|
|
311
|
-
|
|
310
|
+
handleDiagnosticFixRequested(pi, runtime, params);
|
|
312
311
|
},
|
|
313
312
|
onError: (error) => {
|
|
314
313
|
if (!isCurrentConnection(runtime, getConnection(), generation))
|
|
315
314
|
return;
|
|
316
|
-
|
|
315
|
+
containPiError(runtime, "IDE connection error", error);
|
|
317
316
|
},
|
|
318
317
|
};
|
|
319
318
|
}
|
|
@@ -322,14 +321,14 @@ function isCurrentConnection(runtime, connection, generation = runtime.sessionGe
|
|
|
322
321
|
}
|
|
323
322
|
function disconnect(runtime, ctx, disabled = false) {
|
|
324
323
|
runtime.ctx = ctx;
|
|
325
|
-
|
|
324
|
+
stopZedPolling(runtime);
|
|
326
325
|
if (runtime.reconnectTimer)
|
|
327
326
|
clearTimeout(runtime.reconnectTimer);
|
|
328
327
|
runtime.reconnectTimer = undefined;
|
|
329
328
|
const connection = runtime.connection;
|
|
330
329
|
runtime.connection = undefined;
|
|
331
330
|
connection?.disconnect();
|
|
332
|
-
|
|
331
|
+
resetReconnectState(runtime);
|
|
333
332
|
runtime.enabled = !disabled;
|
|
334
333
|
runtime.connectedServer = undefined;
|
|
335
334
|
runtime.connectionStatus = disabled ? "disabled" : "disconnected";
|
|
@@ -340,17 +339,17 @@ function disconnect(runtime, ctx, disabled = false) {
|
|
|
340
339
|
runtime.turnSelection = undefined;
|
|
341
340
|
runtime.attachState = "idle";
|
|
342
341
|
}
|
|
343
|
-
|
|
342
|
+
updateIdeUi(runtime, ctx);
|
|
344
343
|
}
|
|
345
344
|
function scheduleReconnect(runtime) {
|
|
346
345
|
if (runtime.reconnectTimer || !runtime.enabled)
|
|
347
346
|
return;
|
|
348
347
|
const generation = runtime.sessionGeneration;
|
|
349
|
-
const attempt =
|
|
348
|
+
const attempt = recordReconnectAttempt(runtime, runtime.currentCandidate);
|
|
350
349
|
if (attempt === undefined) {
|
|
351
350
|
runtime.connectionStatus = "error";
|
|
352
|
-
runtime.connectionMessage =
|
|
353
|
-
|
|
351
|
+
runtime.connectionMessage = formatReconnectLimitMessage(runtime.currentCandidate);
|
|
352
|
+
updateIdeUi(runtime);
|
|
354
353
|
return;
|
|
355
354
|
}
|
|
356
355
|
runtime.reconnectTimer = setTimeout(() => {
|
|
@@ -358,15 +357,15 @@ function scheduleReconnect(runtime) {
|
|
|
358
357
|
const ctx = runtime.ctx;
|
|
359
358
|
if (!ctx || !runtime.enabled || runtime.sessionGeneration !== generation)
|
|
360
359
|
return;
|
|
361
|
-
connectAutoWithZedFallback(runtime, ctx, runtime.sessionGeneration, { resetReconnectState: false }).catch((error) =>
|
|
360
|
+
connectAutoWithZedFallback(runtime, ctx, runtime.sessionGeneration, { resetReconnectState: false }).catch((error) => containPiError(runtime, "IDE reconnect", error));
|
|
362
361
|
}, RECONNECT_DELAY_MS);
|
|
363
362
|
}
|
|
364
363
|
function handleAtMentioned(runtime, params) {
|
|
365
|
-
|
|
364
|
+
setLatestSelection(runtime, params);
|
|
366
365
|
const ctx = runtime.ctx;
|
|
367
366
|
if (!ctx?.hasUI)
|
|
368
367
|
return;
|
|
369
|
-
const text = params.rangeText ||
|
|
368
|
+
const text = params.rangeText || formatRangeMention(params, { cwd: ctx.cwd });
|
|
370
369
|
ctx.ui.pasteToEditor(text);
|
|
371
370
|
ctx.ui.notify(`Attached ${text}`, "info");
|
|
372
371
|
}
|
package/dist/src/pi/install.js
CHANGED
|
@@ -1,59 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
exports.selectAutoInstallCandidate = selectAutoInstallCandidate;
|
|
13
|
-
exports.installIdeExtension = installIdeExtension;
|
|
14
|
-
const node_child_process_1 = require("node:child_process");
|
|
15
|
-
const node_fs_1 = require("node:fs");
|
|
16
|
-
const promises_1 = require("node:fs/promises");
|
|
17
|
-
const node_path_1 = require("node:path");
|
|
18
|
-
const node_util_1 = require("node:util");
|
|
19
|
-
const config_1 = require("../shared/config");
|
|
20
|
-
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
21
|
-
exports.PI_X_IDE_EXTENSION_ID = "balaenis.pi-x-ide";
|
|
22
|
-
exports.PI_X_IDE_AUTO_INSTALL_ENV = "PI_X_IDE_AUTO_INSTALL";
|
|
23
|
-
exports.PI_X_IDE_TARGET_VERSION = readPackageVersion();
|
|
24
|
-
function readPackageVersion(startDir = __dirname) {
|
|
25
|
-
let currentDir = startDir;
|
|
26
|
-
while (true) {
|
|
27
|
-
const packageJsonPath = (0, node_path_1.join)(currentDir, "package.json");
|
|
28
|
-
try {
|
|
29
|
-
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, "utf8"));
|
|
30
|
-
if (packageJson.name === "pi-x-ide" && typeof packageJson.version === "string") {
|
|
31
|
-
return packageJson.version;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
if (error.code !== "ENOENT")
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
const parentDir = (0, node_path_1.dirname)(currentDir);
|
|
39
|
-
if (parentDir === currentDir)
|
|
40
|
-
throw new Error("Unable to locate pi-x-ide package.json");
|
|
41
|
-
currentDir = parentDir;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
exports.SUPPORTED_IDE_CLI_PROFILES = [
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { resolvePiConfigEnv } from "../shared/config.js";
|
|
6
|
+
import { PI_X_IDE_VERSION } from "../shared/version.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
export const PI_X_IDE_EXTENSION_ID = "balaenis.pi-x-ide";
|
|
9
|
+
export const PI_X_IDE_AUTO_INSTALL_ENV = "PI_X_IDE_AUTO_INSTALL";
|
|
10
|
+
export const PI_X_IDE_TARGET_VERSION = PI_X_IDE_VERSION;
|
|
11
|
+
export const SUPPORTED_IDE_CLI_PROFILES = [
|
|
45
12
|
{ id: "vscode", label: "VS Code", command: "code" },
|
|
46
13
|
{ id: "cursor", label: "Cursor", command: "cursor" },
|
|
47
14
|
{ id: "windsurf", label: "Windsurf", command: "windsurf" },
|
|
48
15
|
];
|
|
49
|
-
function isAutoInstallEnabled(env = process.env) {
|
|
50
|
-
const configuredEnv =
|
|
51
|
-
const value = configuredEnv[
|
|
16
|
+
export function isAutoInstallEnabled(env = process.env) {
|
|
17
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
18
|
+
const value = configuredEnv[PI_X_IDE_AUTO_INSTALL_ENV];
|
|
52
19
|
if (value === undefined)
|
|
53
20
|
return true;
|
|
54
21
|
return !["0", "false", "off"].includes(value.trim().toLowerCase());
|
|
55
22
|
}
|
|
56
|
-
function parseInstalledExtensionVersion(output, extensionId =
|
|
23
|
+
export function parseInstalledExtensionVersion(output, extensionId = PI_X_IDE_EXTENSION_ID) {
|
|
57
24
|
const target = extensionId.toLowerCase();
|
|
58
25
|
for (const rawLine of output.split(/\r?\n/)) {
|
|
59
26
|
const line = rawLine.trim();
|
|
@@ -68,7 +35,7 @@ function parseInstalledExtensionVersion(output, extensionId = exports.PI_X_IDE_E
|
|
|
68
35
|
}
|
|
69
36
|
return undefined;
|
|
70
37
|
}
|
|
71
|
-
function compareExtensionVersions(installed, target) {
|
|
38
|
+
export function compareExtensionVersions(installed, target) {
|
|
72
39
|
if (!installed)
|
|
73
40
|
return "unknown";
|
|
74
41
|
const installedParts = parseStableVersion(installed);
|
|
@@ -83,8 +50,8 @@ function compareExtensionVersions(installed, target) {
|
|
|
83
50
|
}
|
|
84
51
|
return "equal";
|
|
85
52
|
}
|
|
86
|
-
function inferCurrentIdeFromEnv(env = process.env) {
|
|
87
|
-
const configuredEnv =
|
|
53
|
+
export function inferCurrentIdeFromEnv(env = process.env) {
|
|
54
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
88
55
|
const matches = new Set();
|
|
89
56
|
const hasWindsurfMarker = hasWindsurfEnvMarker(configuredEnv);
|
|
90
57
|
const hasCursorMarker = hasCursorEnvMarker(configuredEnv);
|
|
@@ -126,24 +93,24 @@ function envValueMatches(value, patterns) {
|
|
|
126
93
|
const upperValue = value?.toUpperCase();
|
|
127
94
|
return upperValue ? patterns.some((pattern) => pattern.test(upperValue)) : false;
|
|
128
95
|
}
|
|
129
|
-
function buildInstallArgs() {
|
|
130
|
-
return ["--force", "--install-extension",
|
|
96
|
+
export function buildInstallArgs() {
|
|
97
|
+
return ["--force", "--install-extension", PI_X_IDE_EXTENSION_ID];
|
|
131
98
|
}
|
|
132
|
-
async function findExecutable(command, env = process.env) {
|
|
133
|
-
const configuredEnv =
|
|
99
|
+
export async function findExecutable(command, env = process.env) {
|
|
100
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
134
101
|
const pathEnv = configuredEnv.PATH ?? configuredEnv.Path ?? configuredEnv.path;
|
|
135
102
|
if (!pathEnv)
|
|
136
103
|
return undefined;
|
|
137
104
|
const extensions = process.platform === "win32" ? parsePathExt(configuredEnv) : [""];
|
|
138
|
-
const candidates =
|
|
105
|
+
const candidates = isAbsolute(command)
|
|
139
106
|
? [command]
|
|
140
107
|
: pathEnv
|
|
141
|
-
.split(
|
|
108
|
+
.split(delimiter)
|
|
142
109
|
.filter(Boolean)
|
|
143
|
-
.flatMap((directory) => extensions.map((extension) =>
|
|
110
|
+
.flatMap((directory) => extensions.map((extension) => join(directory, withExtension(command, extension))));
|
|
144
111
|
for (const candidate of candidates) {
|
|
145
112
|
try {
|
|
146
|
-
await
|
|
113
|
+
await access(candidate);
|
|
147
114
|
return candidate;
|
|
148
115
|
}
|
|
149
116
|
catch {
|
|
@@ -151,7 +118,7 @@ async function findExecutable(command, env = process.env) {
|
|
|
151
118
|
}
|
|
152
119
|
return undefined;
|
|
153
120
|
}
|
|
154
|
-
async function runCli(cliPath, args, timeoutMs = 15_000) {
|
|
121
|
+
export async function runCli(cliPath, args, timeoutMs = 15_000) {
|
|
155
122
|
if (process.platform === "win32" && isCmdOrBatFile(cliPath)) {
|
|
156
123
|
const { stdout, stderr } = await execFileAsync(resolveCmdExe(), ["/d", "/c", cliPath, ...args], {
|
|
157
124
|
timeout: timeoutMs,
|
|
@@ -167,13 +134,13 @@ async function runCli(cliPath, args, timeoutMs = 15_000) {
|
|
|
167
134
|
});
|
|
168
135
|
return { stdout: String(stdout), stderr: String(stderr) };
|
|
169
136
|
}
|
|
170
|
-
async function discoverInstallCandidates(options = {}) {
|
|
171
|
-
const env =
|
|
137
|
+
export async function discoverInstallCandidates(options = {}) {
|
|
138
|
+
const env = resolvePiConfigEnv(options.env ?? process.env);
|
|
172
139
|
const currentIde = inferCurrentIdeFromEnv(env);
|
|
173
140
|
const includeLowConfidence = options.includeLowConfidence ?? false;
|
|
174
141
|
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
175
142
|
const candidates = [];
|
|
176
|
-
for (const profile of
|
|
143
|
+
for (const profile of SUPPORTED_IDE_CLI_PROFILES) {
|
|
177
144
|
const cliPath = await findExecutable(profile.command, env);
|
|
178
145
|
if (!cliPath)
|
|
179
146
|
continue;
|
|
@@ -189,7 +156,7 @@ async function discoverInstallCandidates(options = {}) {
|
|
|
189
156
|
catch (error) {
|
|
190
157
|
listError = error instanceof Error ? error.message : String(error);
|
|
191
158
|
}
|
|
192
|
-
const reason = resolveInstallReason(installedVersion,
|
|
159
|
+
const reason = resolveInstallReason(installedVersion, PI_X_IDE_TARGET_VERSION, listError);
|
|
193
160
|
candidates.push({
|
|
194
161
|
id: profile.id,
|
|
195
162
|
label: profile.label,
|
|
@@ -197,7 +164,7 @@ async function discoverInstallCandidates(options = {}) {
|
|
|
197
164
|
cliPath,
|
|
198
165
|
confidence,
|
|
199
166
|
installedVersion,
|
|
200
|
-
targetVersion:
|
|
167
|
+
targetVersion: PI_X_IDE_TARGET_VERSION,
|
|
201
168
|
needsInstall: reason === "missing" || reason === "outdated" || reason === "unknown",
|
|
202
169
|
reason,
|
|
203
170
|
listError,
|
|
@@ -205,15 +172,15 @@ async function discoverInstallCandidates(options = {}) {
|
|
|
205
172
|
}
|
|
206
173
|
return candidates;
|
|
207
174
|
}
|
|
208
|
-
function selectAutoInstallCandidate(candidates, env = process.env) {
|
|
209
|
-
const configuredEnv =
|
|
175
|
+
export function selectAutoInstallCandidate(candidates, env = process.env) {
|
|
176
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
210
177
|
const currentIde = inferCurrentIdeFromEnv(configuredEnv);
|
|
211
178
|
const highConfidence = candidates.filter((candidate) => candidate.confidence === "current-terminal" &&
|
|
212
179
|
candidate.reason !== "unknown" &&
|
|
213
180
|
(!currentIde || candidate.id === currentIde));
|
|
214
181
|
return highConfidence.length === 1 ? highConfidence[0] : undefined;
|
|
215
182
|
}
|
|
216
|
-
async function installIdeExtension(candidate, runtime, options = {}) {
|
|
183
|
+
export async function installIdeExtension(candidate, runtime, options = {}) {
|
|
217
184
|
if (!candidate.needsInstall) {
|
|
218
185
|
return { candidate, skipped: true, success: true, stdout: "", stderr: "" };
|
|
219
186
|
}
|
package/dist/src/pi/reconnect.js
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.MAX_RECONNECT_ATTEMPTS = void 0;
|
|
4
|
-
exports.resetReconnectState = resetReconnectState;
|
|
5
|
-
exports.recordReconnectAttempt = recordReconnectAttempt;
|
|
6
|
-
exports.formatReconnectLimitMessage = formatReconnectLimitMessage;
|
|
7
|
-
exports.MAX_RECONNECT_ATTEMPTS = 3;
|
|
8
|
-
function resetReconnectState(runtime) {
|
|
1
|
+
export const MAX_RECONNECT_ATTEMPTS = 3;
|
|
2
|
+
export function resetReconnectState(runtime) {
|
|
9
3
|
runtime.reconnectAttempts = 0;
|
|
10
4
|
runtime.reconnectCandidateKey = undefined;
|
|
11
5
|
}
|
|
12
|
-
function recordReconnectAttempt(runtime, candidate) {
|
|
6
|
+
export function recordReconnectAttempt(runtime, candidate) {
|
|
13
7
|
const key = candidate ? reconnectCandidateKey(candidate) : undefined;
|
|
14
8
|
if (runtime.reconnectCandidateKey !== key) {
|
|
15
9
|
runtime.reconnectCandidateKey = key;
|
|
16
10
|
runtime.reconnectAttempts = 0;
|
|
17
11
|
}
|
|
18
|
-
if (runtime.reconnectAttempts >=
|
|
12
|
+
if (runtime.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS)
|
|
19
13
|
return undefined;
|
|
20
14
|
runtime.reconnectAttempts += 1;
|
|
21
15
|
return runtime.reconnectAttempts;
|
|
22
16
|
}
|
|
23
|
-
function formatReconnectLimitMessage(candidate) {
|
|
17
|
+
export function formatReconnectLimitMessage(candidate) {
|
|
24
18
|
const target = candidate ? `${candidate.lock.name} at ${candidate.lock.host}:${candidate.lock.port}` : "IDE";
|
|
25
|
-
return `Stopped reconnecting to ${target} after ${
|
|
19
|
+
return `Stopped reconnecting to ${target} after ${MAX_RECONNECT_ATTEMPTS} attempts.`;
|
|
26
20
|
}
|
|
27
21
|
function reconnectCandidateKey(candidate) {
|
|
28
22
|
return `${candidate.path}:${candidate.lock.host}:${candidate.lock.port}:${candidate.mtimeMs}`;
|