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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.16.0](https://github.com/balaenis/pi-x-ide/compare/v1.15.2...v1.16.0) (2026-06-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * **pi:** render diagnostic-fix requests as custom TUI messages ([82d1821](https://github.com/balaenis/pi-x-ide/commit/82d18219cdfdfbb48689d6812db093396f229f4d))
9
+
3
10
  ## [1.15.2](https://github.com/balaenis/pi-x-ide/compare/v1.15.1...v1.15.2) (2026-06-29)
4
11
 
5
12
 
@@ -1,22 +1,17 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_ATTACH_SHORTCUT = exports.PI_X_IDE_ATTACH_SHORTCUT_ENV = void 0;
4
- exports.registerIdeCommand = registerIdeCommand;
5
- exports.resolveAttachShortcut = resolveAttachShortcut;
6
- const config_1 = require("../shared/config");
7
- const format_1 = require("../shared/format");
8
- const safety_1 = require("./safety");
9
- const ui_1 = require("./ui");
10
- exports.PI_X_IDE_ATTACH_SHORTCUT_ENV = "PI_X_IDE_ATTACH_SHORTCUT";
11
- exports.DEFAULT_ATTACH_SHORTCUT = "ctrl+alt+k";
1
+ import { resolvePiConfigEnv } from "../shared/config.js";
2
+ import { formatRangeMention } from "../shared/format.js";
3
+ import { runPiBoundary, runPiBoundaryAsync } from "./safety.js";
4
+ import { buildWidget, updateIdeUi } from "./ui.js";
5
+ export const PI_X_IDE_ATTACH_SHORTCUT_ENV = "PI_X_IDE_ATTACH_SHORTCUT";
6
+ export const DEFAULT_ATTACH_SHORTCUT = "ctrl+alt+k";
12
7
  const DISABLED_ATTACH_SHORTCUT_VALUES = new Set(["", "0", "false", "off", "none", "disabled"]);
13
- function registerIdeCommand(pi, runtime, actions, options = {}) {
8
+ export function registerIdeCommand(pi, runtime, actions, options = {}) {
14
9
  const attachShortcut = resolveAttachShortcut(options.env);
15
10
  if (attachShortcut) {
16
11
  pi.registerShortcut(attachShortcut, {
17
12
  description: "Attach latest IDE selection to the prompt",
18
13
  handler: (ctx) => {
19
- (0, safety_1.runPiBoundary)("IDE attach shortcut", runtime, () => {
14
+ runPiBoundary("IDE attach shortcut", runtime, () => {
20
15
  runtime.ctx = ctx;
21
16
  attachLatest(runtime, ctx);
22
17
  }, ctx);
@@ -37,7 +32,7 @@ function registerIdeCommand(pi, runtime, actions, options = {}) {
37
32
  const filtered = subcommands.filter((s) => s.value.startsWith(argumentPrefix));
38
33
  return filtered.length > 0 ? filtered : null;
39
34
  },
40
- handler: (args, ctx) => (0, safety_1.runPiBoundaryAsync)("/ide command", runtime, async () => {
35
+ handler: (args, ctx) => runPiBoundaryAsync("/ide command", runtime, async () => {
41
36
  runtime.ctx = ctx;
42
37
  const [subcommand] = args.trim().split(/\s+/, 1);
43
38
  switch (subcommand || "") {
@@ -92,7 +87,7 @@ async function showPicker(runtime, actions, ctx) {
92
87
  }
93
88
  }
94
89
  function showStatus(runtime, ctx) {
95
- const lines = (0, ui_1.buildWidget)(runtime, ctx.cwd) ?? ["IDE: disconnected"];
90
+ const lines = buildWidget(runtime, ctx.cwd) ?? ["IDE: disconnected"];
96
91
  ctx.ui.notify(lines.join("\n"), runtime.connectionStatus === "error" ? "error" : "info");
97
92
  }
98
93
  async function listCandidates(actions, ctx) {
@@ -105,8 +100,8 @@ async function listCandidates(actions, ctx) {
105
100
  .map((candidate, index) => `${index + 1}. ${candidate.lock.name} ${candidate.lock.host}:${candidate.lock.port}\n ${candidate.workspaceFolder}\n ${candidate.path}`)
106
101
  .join("\n"), "info");
107
102
  }
108
- function resolveAttachShortcut(env = process.env) {
109
- const value = (0, config_1.resolvePiConfigEnv)(env)[exports.PI_X_IDE_ATTACH_SHORTCUT_ENV] ?? exports.DEFAULT_ATTACH_SHORTCUT;
103
+ export function resolveAttachShortcut(env = process.env) {
104
+ const value = resolvePiConfigEnv(env)[PI_X_IDE_ATTACH_SHORTCUT_ENV] ?? DEFAULT_ATTACH_SHORTCUT;
110
105
  const normalized = value.trim().toLowerCase();
111
106
  if (DISABLED_ATTACH_SHORTCUT_VALUES.has(normalized))
112
107
  return undefined;
@@ -117,9 +112,9 @@ function attachLatest(runtime, ctx) {
117
112
  ctx.ui.notify("No IDE selection is available.", "warning");
118
113
  return;
119
114
  }
120
- const mention = (0, format_1.formatRangeMention)(runtime.latestSelection, { cwd: ctx.cwd });
115
+ const mention = formatRangeMention(runtime.latestSelection, { cwd: ctx.cwd });
121
116
  ctx.ui.pasteToEditor(mention);
122
117
  runtime.attachState = "pending";
123
- (0, ui_1.updateIdeUi)(runtime, ctx);
118
+ updateIdeUi(runtime, ctx);
124
119
  ctx.ui.notify(`Attached ${mention}`, "info");
125
120
  }
@@ -1,20 +1,14 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.IdeConnection = exports.IdeConnectionTimeoutError = exports.IDE_CONNECT_TIMEOUT_MS = void 0;
7
- const ws_1 = __importDefault(require("ws"));
8
- const config_1 = require("../shared/config");
9
- const protocol_1 = require("../shared/protocol");
10
- const schema_1 = require("../shared/schema");
11
- const errors_1 = require("../shared/errors");
12
- const format_1 = require("../shared/format");
13
- const platform_1 = require("../shared/platform");
14
- const ws_2 = require("../shared/ws");
15
- const ide_host_1 = require("./ide-host");
16
- exports.IDE_CONNECT_TIMEOUT_MS = 5_000;
17
- class IdeConnectionTimeoutError extends Error {
1
+ import WebSocket from "ws";
2
+ import { EXT_CONFIG_NAME } from "../shared/config.js";
3
+ import { AUTH_HEADER, PROTOCOL_VERSION, } from "../shared/protocol.js";
4
+ import { isAtMentionedParams, isDiagnosticFixRequestedParams, isSelectionChangedParams, isSelectionClearedParams, } from "../shared/schema.js";
5
+ import { toError, logExtensionError } from "../shared/errors.js";
6
+ import { formatRangeMention } from "../shared/format.js";
7
+ import { normalizeEditorSelectionSnapshotForHost } from "../shared/platform.js";
8
+ import { decodeRawData } from "../shared/ws.js";
9
+ import { resolveIdeHost } from "./ide-host.js";
10
+ export const IDE_CONNECT_TIMEOUT_MS = 5_000;
11
+ export class IdeConnectionTimeoutError extends Error {
18
12
  candidate;
19
13
  host;
20
14
  constructor(candidate, host = candidate.lock.host) {
@@ -24,8 +18,7 @@ class IdeConnectionTimeoutError extends Error {
24
18
  this.name = "IdeConnectionTimeoutError";
25
19
  }
26
20
  }
27
- exports.IdeConnectionTimeoutError = IdeConnectionTimeoutError;
28
- class IdeConnection {
21
+ export class IdeConnection {
29
22
  candidate;
30
23
  cwd;
31
24
  callbacks;
@@ -40,20 +33,20 @@ class IdeConnection {
40
33
  this.options = options;
41
34
  }
42
35
  get isOpen() {
43
- return this.socket?.readyState === ws_1.default.OPEN;
36
+ return this.socket?.readyState === WebSocket.OPEN;
44
37
  }
45
- async connect(timeoutMs = exports.IDE_CONNECT_TIMEOUT_MS) {
38
+ async connect(timeoutMs = IDE_CONNECT_TIMEOUT_MS) {
46
39
  this.closedByUser = false;
47
40
  const { lock } = this.candidate;
48
- const resolvedHost = await (this.options.resolveHost ?? ide_host_1.resolveIdeHost)(lock);
49
- const socket = new ws_1.default(`ws://${resolvedHost}:${lock.port}`, {
41
+ const resolvedHost = await (this.options.resolveHost ?? resolveIdeHost)(lock);
42
+ const socket = new WebSocket(`ws://${resolvedHost}:${lock.port}`, {
50
43
  handshakeTimeout: timeoutMs + 1_000,
51
44
  headers: {
52
- [protocol_1.AUTH_HEADER]: lock.authToken,
45
+ [AUTH_HEADER]: lock.authToken,
53
46
  },
54
47
  });
55
48
  this.socket = socket;
56
- socket.on("message", (raw) => this.runSocketHandler("message", () => this.handleMessage((0, ws_2.decodeRawData)(raw))));
49
+ socket.on("message", (raw) => this.runSocketHandler("message", () => this.handleMessage(decodeRawData(raw))));
57
50
  socket.on("close", (_code, reason) => {
58
51
  this.runSocketHandler("close", () => {
59
52
  if (this.socket !== socket)
@@ -105,8 +98,8 @@ class IdeConnection {
105
98
  id: this.nextId++,
106
99
  method: "initialize",
107
100
  params: {
108
- protocolVersion: protocol_1.PROTOCOL_VERSION,
109
- client: { name: config_1.EXT_CONFIG_NAME, version: "0.1.0" },
101
+ protocolVersion: PROTOCOL_VERSION,
102
+ client: { name: EXT_CONFIG_NAME, version: "0.1.0" },
110
103
  cwd: this.cwd,
111
104
  },
112
105
  }));
@@ -127,20 +120,20 @@ class IdeConnection {
127
120
  }
128
121
  if (!isNotification(parsed))
129
122
  return;
130
- if (parsed.method === "selection_changed" && (0, schema_1.isSelectionChangedParams)(parsed.params)) {
131
- const params = (0, platform_1.normalizeEditorSelectionSnapshotForHost)(withReceivedAt(parsed.params), this.options.env);
123
+ if (parsed.method === "selection_changed" && isSelectionChangedParams(parsed.params)) {
124
+ const params = normalizeEditorSelectionSnapshotForHost(withReceivedAt(parsed.params), this.options.env);
132
125
  this.emitCallback("selection changed callback", () => this.callbacks.onSelectionChanged?.(params));
133
126
  }
134
- else if (parsed.method === "selection_cleared" && (0, schema_1.isSelectionClearedParams)(parsed.params)) {
127
+ else if (parsed.method === "selection_cleared" && isSelectionClearedParams(parsed.params)) {
135
128
  const params = withReceivedAt(parsed.params);
136
129
  this.emitCallback("selection cleared callback", () => this.callbacks.onSelectionCleared?.(params));
137
130
  }
138
- else if (parsed.method === "at_mentioned" && (0, schema_1.isAtMentionedParams)(parsed.params)) {
139
- const snapshot = (0, platform_1.normalizeEditorSelectionSnapshotForHost)(withReceivedAt(parsed.params), this.options.env);
140
- const params = { ...snapshot, rangeText: (0, format_1.formatRangeMention)(snapshot, { cwd: this.cwd, env: this.options.env }) };
131
+ else if (parsed.method === "at_mentioned" && isAtMentionedParams(parsed.params)) {
132
+ const snapshot = normalizeEditorSelectionSnapshotForHost(withReceivedAt(parsed.params), this.options.env);
133
+ const params = { ...snapshot, rangeText: formatRangeMention(snapshot, { cwd: this.cwd, env: this.options.env }) };
141
134
  this.emitCallback("at mentioned callback", () => this.callbacks.onAtMentioned?.(params));
142
135
  }
143
- else if (parsed.method === "diagnostic_fix_requested" && (0, schema_1.isDiagnosticFixRequestedParams)(parsed.params)) {
136
+ else if (parsed.method === "diagnostic_fix_requested" && isDiagnosticFixRequestedParams(parsed.params)) {
144
137
  const params = withReceivedAt(parsed.params);
145
138
  this.emitCallback("diagnostic fix requested callback", () => this.callbacks.onDiagnosticFixRequested?.(params));
146
139
  }
@@ -162,20 +155,19 @@ class IdeConnection {
162
155
  }
163
156
  }
164
157
  reportError(label, error) {
165
- const reported = (0, errors_1.toError)(error, label);
158
+ const reported = toError(error, label);
166
159
  if (!this.callbacks.onError) {
167
- (0, errors_1.logExtensionError)(label, reported);
160
+ logExtensionError(label, reported);
168
161
  return;
169
162
  }
170
163
  try {
171
164
  this.callbacks.onError(reported);
172
165
  }
173
166
  catch (callbackError) {
174
- (0, errors_1.logExtensionError)(`${label} onError callback`, callbackError);
167
+ logExtensionError(`${label} onError callback`, callbackError);
175
168
  }
176
169
  }
177
170
  }
178
- exports.IdeConnection = IdeConnection;
179
171
  function withReceivedAt(params) {
180
172
  return { ...params, receivedAt: params.receivedAt ?? Date.now() };
181
173
  }
@@ -1,14 +1,9 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerContextHandlers = registerContextHandlers;
4
- exports.setLatestSelection = setLatestSelection;
5
- exports.clearLatestSelection = clearLatestSelection;
6
- const format_1 = require("../shared/format");
7
- const diagnostics_1 = require("./diagnostics");
8
- const safety_1 = require("./safety");
9
- const ui_1 = require("./ui");
10
- function registerContextHandlers(pi, runtime) {
11
- pi.on("before_agent_start", (_event, ctx) => (0, safety_1.runPiBoundary)("Pi before_agent_start", runtime, () => {
1
+ import { formatEditorContext, snapshotKey, SYSTEM_REMINDER_TAG, SELECTED_CONTEXT_MARKER } from "../shared/format.js";
2
+ import { DIAGNOSTIC_CONTEXT_MARKER } from "./diagnostics.js";
3
+ import { runPiBoundary } from "./safety.js";
4
+ import { updateIdeUi } from "./ui.js";
5
+ export function registerContextHandlers(pi, runtime) {
6
+ pi.on("before_agent_start", (_event, ctx) => runPiBoundary("Pi before_agent_start", runtime, () => {
12
7
  runtime.ctx = ctx;
13
8
  if (!runtime.enabled)
14
9
  return;
@@ -18,7 +13,7 @@ function registerContextHandlers(pi, runtime) {
18
13
  return;
19
14
  runtime.turnSelection = runtime.latestSelection;
20
15
  }, ctx));
21
- pi.on("message_end", (event, ctx) => (0, safety_1.runPiBoundary)("Pi message_end", runtime, () => {
16
+ pi.on("message_end", (event, ctx) => runPiBoundary("Pi message_end", runtime, () => {
22
17
  runtime.ctx = ctx;
23
18
  if (!runtime.enabled || !runtime.turnSelection)
24
19
  return;
@@ -28,29 +23,29 @@ function registerContextHandlers(pi, runtime) {
28
23
  runtime.turnSelection = undefined;
29
24
  return;
30
25
  }
31
- const text = `${(0, format_1.formatEditorContext)(runtime.turnSelection)}\n`;
26
+ const text = `${formatEditorContext(runtime.turnSelection)}\n`;
32
27
  const message = mergeIntoUserMessage(event.message, text);
33
28
  runtime.attachState = "sent";
34
29
  runtime.turnSelection = undefined;
35
- (0, ui_1.updateIdeUi)(runtime, ctx);
30
+ updateIdeUi(runtime, ctx);
36
31
  return { message };
37
32
  }, ctx));
38
33
  }
39
- function setLatestSelection(runtime, snapshot, ctx) {
40
- const key = (0, format_1.snapshotKey)(snapshot);
34
+ export function setLatestSelection(runtime, snapshot, ctx) {
35
+ const key = snapshotKey(snapshot);
41
36
  runtime.latestSelection = snapshot;
42
37
  if (runtime.latestSelectionKey !== key) {
43
38
  runtime.latestSelectionKey = key;
44
39
  runtime.attachState = "pending";
45
40
  }
46
- (0, ui_1.updateIdeUi)(runtime, ctx);
41
+ updateIdeUi(runtime, ctx);
47
42
  }
48
- function clearLatestSelection(runtime, ctx) {
43
+ export function clearLatestSelection(runtime, ctx) {
49
44
  runtime.latestSelection = undefined;
50
45
  runtime.latestSelectionKey = undefined;
51
46
  runtime.turnSelection = undefined;
52
47
  runtime.attachState = "idle";
53
- (0, ui_1.updateIdeUi)(runtime, ctx);
48
+ updateIdeUi(runtime, ctx);
54
49
  }
55
50
  function mergeIntoUserMessage(message, text) {
56
51
  return {
@@ -63,6 +58,6 @@ function normalizeUserContent(content) {
63
58
  }
64
59
  function messageContainsMarker(message) {
65
60
  const serialized = JSON.stringify(message);
66
- return (serialized.includes(`<${format_1.SYSTEM_REMINDER_TAG}><${format_1.SELECTED_CONTEXT_MARKER}>`) ||
67
- serialized.includes(diagnostics_1.DIAGNOSTIC_CONTEXT_MARKER));
61
+ return (serialized.includes(`<${SYSTEM_REMINDER_TAG}><${SELECTED_CONTEXT_MARKER}>`) ||
62
+ serialized.includes(DIAGNOSTIC_CONTEXT_MARKER));
68
63
  }
@@ -0,0 +1,71 @@
1
+ import { Box, Text } from "@earendil-works/pi-tui";
2
+ import { toRelativeDisplayPath } from "../shared/paths.js";
3
+ import { formatDiagnosticCode, formatRange } from "./diagnostics.js";
4
+ export const DIAGNOSTIC_FIX_CUSTOM_TYPE = "pi-x-ide/diagnostic-fix";
5
+ export function registerDiagnosticRenderer(pi) {
6
+ pi.registerMessageRenderer(DIAGNOSTIC_FIX_CUSTOM_TYPE, (message, { expanded }, theme) => {
7
+ const details = message.details;
8
+ if (!details)
9
+ return undefined;
10
+ return buildComponent(details, theme, expanded);
11
+ });
12
+ }
13
+ function buildComponent(details, theme, expanded) {
14
+ const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
15
+ box.addChild(new Text(buildText(details, theme, expanded), 0, 0));
16
+ return box;
17
+ }
18
+ function buildText(details, theme, expanded) {
19
+ const severityColor = pickSeverityColor(details.diagnostics);
20
+ const label = theme.fg(severityColor, theme.bold("[DIAGNOSTIC]"));
21
+ const sourceLabel = describeSource(details.source);
22
+ const relPath = toRelativeDisplayPath(details.filePath, details.workspaceFolder, details.cwd);
23
+ const location = theme.fg("dim", `${relPath}:${formatRange(details.triggerRange)}`);
24
+ const count = details.diagnostics.length;
25
+ const noun = count === 1 ? "diagnostic" : "diagnostics";
26
+ const header = `${label} Fix ${count} ${noun} from ${sourceLabel} — ${location}`;
27
+ if (!expanded) {
28
+ return `${header}\n${theme.fg("dim", "ctrl+o to expand")}`;
29
+ }
30
+ const lines = [header];
31
+ for (const [index, diagnostic] of details.diagnostics.entries()) {
32
+ lines.push("");
33
+ lines.push(formatDiagnostic(diagnostic, index, details, theme));
34
+ }
35
+ return lines.join("\n");
36
+ }
37
+ function formatDiagnostic(diagnostic, index, details, theme) {
38
+ const color = diagnostic.severity === "error" ? "error" : "warning";
39
+ const headerText = `${diagnostic.severity.toUpperCase()} #${index + 1}`;
40
+ const lines = [`${theme.fg(color, headerText)} ${theme.fg("dim", formatRange(diagnostic.range))}`];
41
+ if (diagnostic.code !== undefined) {
42
+ lines.push(theme.fg("dim", ` code: ${formatDiagnosticCode(diagnostic.code)}`));
43
+ }
44
+ if (diagnostic.source) {
45
+ lines.push(theme.fg("dim", ` source: ${diagnostic.source}`));
46
+ }
47
+ lines.push(` ${diagnostic.message}`);
48
+ if (diagnostic.contextLines.length > 0) {
49
+ for (const contextLine of diagnostic.contextLines) {
50
+ const marker = contextLine.isPrimary ? theme.fg(color, ">") : " ";
51
+ const lineNumber = theme.fg("dim", `${contextLine.line + 1}:`.padStart(4, " "));
52
+ lines.push(` ${marker} ${lineNumber} ${contextLine.text}`);
53
+ }
54
+ }
55
+ if (diagnostic.relatedInformation && diagnostic.relatedInformation.length > 0) {
56
+ lines.push(theme.fg("dim", " related:"));
57
+ for (const related of diagnostic.relatedInformation) {
58
+ const relatedPath = toRelativeDisplayPath(related.filePath, details.workspaceFolder, details.cwd);
59
+ lines.push(theme.fg("dim", ` ${relatedPath} ${formatRange(related.range)}: ${related.message}`));
60
+ }
61
+ }
62
+ return lines.join("\n");
63
+ }
64
+ function pickSeverityColor(diagnostics) {
65
+ return diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "error" : "warning";
66
+ }
67
+ function describeSource(source) {
68
+ if (source === "vscode")
69
+ return "VS Code";
70
+ return source;
71
+ }
@@ -1,13 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DIAGNOSTIC_CONTEXT_MARKER = void 0;
4
- exports.buildDiagnosticFixPrompt = buildDiagnosticFixPrompt;
5
- exports.buildDiagnosticContextMessage = buildDiagnosticContextMessage;
6
- exports.formatDiagnosticContext = formatDiagnosticContext;
7
- exports.handleDiagnosticFixRequested = handleDiagnosticFixRequested;
8
- const config_1 = require("../shared/config");
9
- exports.DIAGNOSTIC_CONTEXT_MARKER = "pi-x-ide/diagnostic-context";
10
- function buildDiagnosticFixPrompt(params, options = {}) {
1
+ import { readPiConfigFixPrompt } from "../shared/config.js";
2
+ import { DIAGNOSTIC_FIX_CUSTOM_TYPE } from "./diagnostic-renderer.js";
3
+ export const DIAGNOSTIC_CONTEXT_MARKER = "pi-x-ide/diagnostic-context";
4
+ export function buildDiagnosticFixPrompt(params, options = {}) {
11
5
  const context = buildDiagnosticContextMessage(params);
12
6
  const template = options.fixPrompt;
13
7
  if (!template) {
@@ -20,13 +14,13 @@ ${context}`;
20
14
  return `${template}
21
15
  ${context}`;
22
16
  }
23
- function buildDiagnosticContextMessage(params) {
24
- return `<!-- ${exports.DIAGNOSTIC_CONTEXT_MARKER} -->
17
+ export function buildDiagnosticContextMessage(params) {
18
+ return `<!-- ${DIAGNOSTIC_CONTEXT_MARKER} -->
25
19
  ${formatDiagnosticContext(params)}
26
- <!-- ${exports.DIAGNOSTIC_CONTEXT_MARKER} -->
20
+ <!-- ${DIAGNOSTIC_CONTEXT_MARKER} -->
27
21
  `;
28
22
  }
29
- function formatDiagnosticContext(params) {
23
+ export function formatDiagnosticContext(params) {
30
24
  const filePath = params.filePath;
31
25
  const lines = [`File: ${filePath}`, `Source: ${params.source}`];
32
26
  if (params.documentVersion !== undefined)
@@ -61,7 +55,7 @@ function formatDiagnosticContext(params) {
61
55
  }
62
56
  return lines.join("\n");
63
57
  }
64
- function handleDiagnosticFixRequested(pi, runtime, params) {
58
+ export function handleDiagnosticFixRequested(pi, runtime, params) {
65
59
  const ctx = runtime.ctx;
66
60
  if (!ctx)
67
61
  return;
@@ -72,25 +66,34 @@ function handleDiagnosticFixRequested(pi, runtime, params) {
72
66
  ctx.ui.notify("VS Code diagnostic context added to input.", "info");
73
67
  return;
74
68
  }
75
- const fixPrompt = (0, config_1.readPiConfigFixPrompt)();
69
+ const fixPrompt = readPiConfigFixPrompt();
76
70
  const prompt = buildDiagnosticFixPrompt(params, { fixPrompt });
77
- if (ctx.isIdle()) {
78
- pi.sendUserMessage(prompt);
79
- return;
80
- }
81
- pi.sendUserMessage(prompt, { deliverAs: "followUp" });
82
- if (ctx.hasUI)
71
+ const details = {
72
+ source: params.source,
73
+ filePath: params.filePath,
74
+ workspaceFolder: params.workspaceFolder,
75
+ triggerRange: params.triggerRange,
76
+ diagnostics: params.diagnostics,
77
+ cwd: ctx.cwd,
78
+ };
79
+ pi.sendMessage({
80
+ customType: DIAGNOSTIC_FIX_CUSTOM_TYPE,
81
+ content: prompt,
82
+ display: true,
83
+ details,
84
+ }, { triggerTurn: true, deliverAs: "followUp" });
85
+ if (!ctx.isIdle() && ctx.hasUI)
83
86
  ctx.ui.notify("VS Code diagnostic fix request queued.", "info");
84
87
  }
85
- function formatRange(range) {
88
+ export function formatRange(range) {
86
89
  const start = formatPosition(range.start);
87
90
  const end = formatPosition(range.end);
88
91
  return start === end ? start : `${start}-${end}`;
89
92
  }
90
- function formatPosition(position) {
93
+ export function formatPosition(position) {
91
94
  return `L${position.line + 1}:C${position.character + 1}`;
92
95
  }
93
- function formatDiagnosticCode(code) {
96
+ export function formatDiagnosticCode(code) {
94
97
  if (typeof code === "string" || typeof code === "number")
95
98
  return String(code);
96
99
  return code.target ? `${code.value} (${code.target})` : String(code.value);
@@ -1,15 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sortCandidates = sortCandidates;
4
- exports.discoverIdeCandidates = discoverIdeCandidates;
5
- exports.resolveBestIdeCandidate = resolveBestIdeCandidate;
6
- const promises_1 = require("node:fs/promises");
7
- const node_path_1 = require("node:path");
8
- const protocol_1 = require("../shared/protocol");
9
- const paths_1 = require("../shared/paths");
10
- const schema_1 = require("../shared/schema");
11
- const platform_1 = require("../shared/platform");
12
- const ide_host_1 = require("./ide-host");
1
+ import { readdir, readFile, rm, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { LOCK_FILE_EXTENSION } from "../shared/protocol.js";
4
+ import { relationshipMatchLength, resolveLockDirs } from "../shared/paths.js";
5
+ import { parseLockFileContent } from "../shared/schema.js";
6
+ import { isWsl, normalizePathForHost } from "../shared/platform.js";
7
+ import { IDE_HOST_TCP_PROBE_TIMEOUT_MS, resolveIdeHost, tcpReachable } from "./ide-host.js";
13
8
  function isProcessAlive(pid) {
14
9
  try {
15
10
  process.kill(pid, 0);
@@ -22,21 +17,21 @@ function isProcessAlive(pid) {
22
17
  function bestWorkspaceMatch(lock, cwd, env) {
23
18
  let best;
24
19
  for (const workspaceFolder of lock.workspaceFolders) {
25
- const matchLength = (0, paths_1.relationshipMatchLength)(workspaceFolder, cwd, env);
20
+ const matchLength = relationshipMatchLength(workspaceFolder, cwd, env);
26
21
  if (matchLength <= 0)
27
22
  continue;
28
23
  if (!best || matchLength > best.matchLength) {
29
- best = { matchLength, workspaceFolder: (0, platform_1.normalizePathForHost)(workspaceFolder, env) };
24
+ best = { matchLength, workspaceFolder: normalizePathForHost(workspaceFolder, env) };
30
25
  }
31
26
  }
32
27
  return best;
33
28
  }
34
- function sortCandidates(candidates) {
29
+ export function sortCandidates(candidates) {
35
30
  return [...candidates].sort((a, b) => b.matchLength - a.matchLength || b.mtimeMs - a.mtimeMs || a.path.localeCompare(b.path));
36
31
  }
37
- async function discoverIdeCandidates(options) {
32
+ export async function discoverIdeCandidates(options) {
38
33
  const env = options.env ?? process.env;
39
- const lockDirs = (0, paths_1.resolveLockDirs)({
34
+ const lockDirs = resolveLockDirs({
40
35
  lockDir: options.lockDir,
41
36
  homeLockDir: options.homeLockDir,
42
37
  env,
@@ -54,20 +49,20 @@ async function discoverIdeCandidatesInDir(lockDir, options, env) {
54
49
  const checkPid = options.checkPid ?? true;
55
50
  let entries;
56
51
  try {
57
- entries = await (0, promises_1.readdir)(lockDir);
52
+ entries = await readdir(lockDir);
58
53
  }
59
54
  catch {
60
55
  return [];
61
56
  }
62
57
  const candidates = [];
63
58
  for (const entry of entries) {
64
- if (!entry.endsWith(protocol_1.LOCK_FILE_EXTENSION))
59
+ if (!entry.endsWith(LOCK_FILE_EXTENSION))
65
60
  continue;
66
- const path = (0, node_path_1.join)(lockDir, entry);
61
+ const path = join(lockDir, entry);
67
62
  let content;
68
63
  let mtimeMs;
69
64
  try {
70
- const [fileContent, fileStat] = await Promise.all([(0, promises_1.readFile)(path, "utf8"), (0, promises_1.stat)(path)]);
65
+ const [fileContent, fileStat] = await Promise.all([readFile(path, "utf8"), stat(path)]);
71
66
  content = fileContent;
72
67
  mtimeMs = fileStat.mtimeMs;
73
68
  }
@@ -75,31 +70,31 @@ async function discoverIdeCandidatesInDir(lockDir, options, env) {
75
70
  continue;
76
71
  }
77
72
  if (now - mtimeMs > maxAgeMs) {
78
- await (0, promises_1.rm)(path, { force: true }).catch(() => undefined);
73
+ await rm(path, { force: true }).catch(() => undefined);
79
74
  continue;
80
75
  }
81
- const lock = (0, schema_1.parseLockFileContent)(content);
76
+ const lock = parseLockFileContent(content);
82
77
  if (!lock) {
83
- await (0, promises_1.rm)(path, { force: true }).catch(() => undefined);
78
+ await rm(path, { force: true }).catch(() => undefined);
84
79
  continue;
85
80
  }
86
81
  if (checkPid && typeof lock.pid === "number" && !isProcessAlive(lock.pid)) {
87
- if (lock.runningInWindows === true && (0, platform_1.isWsl)(env)) {
82
+ if (lock.runningInWindows === true && isWsl(env)) {
88
83
  const reachable = await isDeadWindowsPidLockReachable(lock, options, env);
89
84
  if (!reachable) {
90
- await (0, promises_1.rm)(path, { force: true }).catch(() => undefined);
85
+ await rm(path, { force: true }).catch(() => undefined);
91
86
  continue;
92
87
  }
93
88
  }
94
89
  else {
95
- await (0, promises_1.rm)(path, { force: true }).catch(() => undefined);
90
+ await rm(path, { force: true }).catch(() => undefined);
96
91
  continue;
97
92
  }
98
93
  }
99
94
  const match = bestWorkspaceMatch(lock, options.cwd, env);
100
95
  const { matchLength, workspaceFolder } = match ?? {
101
96
  matchLength: 0,
102
- workspaceFolder: lock.workspaceFolders[0] ? (0, platform_1.normalizePathForHost)(lock.workspaceFolders[0], env) : "",
97
+ workspaceFolder: lock.workspaceFolders[0] ? normalizePathForHost(lock.workspaceFolders[0], env) : "",
103
98
  };
104
99
  candidates.push({ path, lock, mtimeMs, matchLength, workspaceFolder });
105
100
  }
@@ -107,13 +102,13 @@ async function discoverIdeCandidatesInDir(lockDir, options, env) {
107
102
  }
108
103
  async function isDeadWindowsPidLockReachable(lock, options, env) {
109
104
  try {
110
- const host = await (options.resolveHost ?? ((ideLock) => (0, ide_host_1.resolveIdeHost)(ideLock, { env, tcpProbe: options.tcpProbe })))(lock);
111
- return await (options.tcpProbe ?? ide_host_1.tcpReachable)(host, lock.port, ide_host_1.IDE_HOST_TCP_PROBE_TIMEOUT_MS);
105
+ const host = await (options.resolveHost ?? ((ideLock) => resolveIdeHost(ideLock, { env, tcpProbe: options.tcpProbe })))(lock);
106
+ return await (options.tcpProbe ?? tcpReachable)(host, lock.port, IDE_HOST_TCP_PROBE_TIMEOUT_MS);
112
107
  }
113
108
  catch {
114
109
  return false;
115
110
  }
116
111
  }
117
- async function resolveBestIdeCandidate(options) {
112
+ export async function resolveBestIdeCandidate(options) {
118
113
  return (await discoverIdeCandidates(options))[0];
119
114
  }