pi-repoprompt-mcp 0.5.1 → 0.5.2
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
CHANGED
|
@@ -219,6 +219,8 @@ but this is best-effort
|
|
|
219
219
|
### Pi becomes unresponsive after closing/restarting RepoPrompt
|
|
220
220
|
If the RepoPrompt MCP server stops responding (for example, if the RepoPrompt app is closed while Pi stays open), tool calls may time out. When that happens, the extension will drop the connection and you can recover with `/rp reconnect`.
|
|
221
221
|
|
|
222
|
+
If RepoPrompt is not running when Pi starts, the extension auto-pauses itself after a quick connection timeout. While paused, the `rp` tool returns a short error directing the agent to use native tools. Run `/rp reconnect` once RepoPrompt is open to resume, and the agent will be notified that `rp` is available again.
|
|
223
|
+
|
|
222
224
|
### "No matching window found"
|
|
223
225
|
- Your `cwd` may not match any RepoPrompt workspace root
|
|
224
226
|
- Use `/rp windows` to list windows
|
|
@@ -15,12 +15,32 @@ const CLIENT_INFO = {
|
|
|
15
15
|
version: "1.0.0",
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 6_000;
|
|
18
19
|
const DEFAULT_LIST_TOOLS_TIMEOUT_MS = 10_000;
|
|
19
20
|
|
|
20
21
|
// Keep parity with the rp-cli integration default (15 minutes). Some RepoPrompt tools
|
|
21
22
|
// (notably context_builder and chat_send) can legitimately take longer than 10s
|
|
22
23
|
const DEFAULT_TOOL_CALL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
23
24
|
|
|
25
|
+
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
26
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
return await Promise.race([
|
|
30
|
+
promise,
|
|
31
|
+
new Promise<T>((_, reject) => {
|
|
32
|
+
timeoutId = setTimeout(() => {
|
|
33
|
+
reject(new Error(message));
|
|
34
|
+
}, timeoutMs);
|
|
35
|
+
}),
|
|
36
|
+
]);
|
|
37
|
+
} finally {
|
|
38
|
+
if (timeoutId) {
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
|
|
25
45
|
/**
|
|
26
46
|
* Manages the MCP connection to RepoPrompt server
|
|
@@ -31,23 +51,23 @@ export class RpClient {
|
|
|
31
51
|
private _status: ConnectionStatus = "disconnected";
|
|
32
52
|
private _tools: RpToolMeta[] = [];
|
|
33
53
|
private _error: string | undefined;
|
|
34
|
-
|
|
54
|
+
|
|
35
55
|
get status(): ConnectionStatus {
|
|
36
56
|
return this._status;
|
|
37
57
|
}
|
|
38
|
-
|
|
58
|
+
|
|
39
59
|
get tools(): RpToolMeta[] {
|
|
40
60
|
return this._tools;
|
|
41
61
|
}
|
|
42
|
-
|
|
62
|
+
|
|
43
63
|
get error(): string | undefined {
|
|
44
64
|
return this._error;
|
|
45
65
|
}
|
|
46
|
-
|
|
66
|
+
|
|
47
67
|
get isConnected(): boolean {
|
|
48
68
|
return this._status === "connected" && this.client !== null;
|
|
49
69
|
}
|
|
50
|
-
|
|
70
|
+
|
|
51
71
|
/**
|
|
52
72
|
* Connect to the RepoPrompt MCP server
|
|
53
73
|
*/
|
|
@@ -59,13 +79,13 @@ export class RpClient {
|
|
|
59
79
|
if (this._status === "connecting") {
|
|
60
80
|
throw new Error("Connection already in progress");
|
|
61
81
|
}
|
|
62
|
-
|
|
82
|
+
|
|
63
83
|
// Close existing connection if any
|
|
64
84
|
await this.close();
|
|
65
|
-
|
|
85
|
+
|
|
66
86
|
this._status = "connecting";
|
|
67
87
|
this._error = undefined;
|
|
68
|
-
|
|
88
|
+
|
|
69
89
|
try {
|
|
70
90
|
// Create transport
|
|
71
91
|
const mergedEnv: Record<string, string> = {};
|
|
@@ -77,36 +97,40 @@ export class RpClient {
|
|
|
77
97
|
if (env) {
|
|
78
98
|
Object.assign(mergedEnv, env);
|
|
79
99
|
}
|
|
80
|
-
|
|
100
|
+
|
|
81
101
|
this.transport = new StdioClientTransport({
|
|
82
102
|
command,
|
|
83
103
|
args,
|
|
84
104
|
env: Object.keys(mergedEnv).length > 0 ? mergedEnv : undefined,
|
|
85
105
|
});
|
|
86
|
-
|
|
106
|
+
|
|
87
107
|
// Create client
|
|
88
108
|
this.client = new Client(CLIENT_INFO, {
|
|
89
109
|
capabilities: {},
|
|
90
110
|
});
|
|
91
|
-
|
|
111
|
+
|
|
92
112
|
// Connect
|
|
93
|
-
await
|
|
94
|
-
|
|
113
|
+
await withTimeout(
|
|
114
|
+
this.client.connect(this.transport),
|
|
115
|
+
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
116
|
+
`Timed out connecting to RepoPrompt MCP server after ${DEFAULT_CONNECT_TIMEOUT_MS}ms`
|
|
117
|
+
);
|
|
118
|
+
|
|
95
119
|
// Fetch available tools
|
|
96
120
|
await this.refreshTools();
|
|
97
|
-
|
|
121
|
+
|
|
98
122
|
this._status = "connected";
|
|
99
123
|
} catch (error) {
|
|
100
124
|
this._status = "error";
|
|
101
125
|
this._error = error instanceof Error ? error.message : String(error);
|
|
102
|
-
|
|
126
|
+
|
|
103
127
|
// Clean up on error
|
|
104
128
|
await this.close();
|
|
105
|
-
|
|
129
|
+
|
|
106
130
|
throw error;
|
|
107
131
|
}
|
|
108
132
|
}
|
|
109
|
-
|
|
133
|
+
|
|
110
134
|
/**
|
|
111
135
|
* Refresh the list of available tools
|
|
112
136
|
*/
|
|
@@ -125,7 +149,7 @@ export class RpClient {
|
|
|
125
149
|
|
|
126
150
|
return this._tools;
|
|
127
151
|
}
|
|
128
|
-
|
|
152
|
+
|
|
129
153
|
/**
|
|
130
154
|
* Call a tool on the RepoPrompt MCP server
|
|
131
155
|
*/
|
|
@@ -187,33 +211,38 @@ export class RpClient {
|
|
|
187
211
|
isError: Boolean(result.isError),
|
|
188
212
|
};
|
|
189
213
|
}
|
|
190
|
-
|
|
214
|
+
|
|
191
215
|
/**
|
|
192
216
|
* Close the connection
|
|
193
217
|
*/
|
|
194
218
|
async close(): Promise<void> {
|
|
195
|
-
|
|
219
|
+
const client = this.client;
|
|
220
|
+
const transport = this.transport;
|
|
221
|
+
const wasConnecting = this._status === "connecting";
|
|
222
|
+
|
|
223
|
+
this.client = null;
|
|
224
|
+
this.transport = null;
|
|
225
|
+
this._status = "disconnected";
|
|
226
|
+
this._tools = [];
|
|
227
|
+
|
|
228
|
+
// If connect() never completed, skip the graceful MCP close and tear down the transport directly
|
|
229
|
+
if (client && !wasConnecting) {
|
|
196
230
|
try {
|
|
197
|
-
await
|
|
231
|
+
await client.close();
|
|
198
232
|
} catch {
|
|
199
233
|
// Ignore close errors
|
|
200
234
|
}
|
|
201
|
-
this.client = null;
|
|
202
235
|
}
|
|
203
236
|
|
|
204
|
-
if (
|
|
237
|
+
if (transport) {
|
|
205
238
|
try {
|
|
206
|
-
await
|
|
239
|
+
await transport.close();
|
|
207
240
|
} catch {
|
|
208
241
|
// Ignore close errors
|
|
209
242
|
}
|
|
210
|
-
this.transport = null;
|
|
211
243
|
}
|
|
212
|
-
|
|
213
|
-
this._status = "disconnected";
|
|
214
|
-
this._tools = [];
|
|
215
244
|
}
|
|
216
|
-
|
|
245
|
+
|
|
217
246
|
/**
|
|
218
247
|
* Get connection info for debugging
|
|
219
248
|
*/
|
|
@@ -221,7 +250,7 @@ export class RpClient {
|
|
|
221
250
|
if (!this.client || !this.transport) {
|
|
222
251
|
return null;
|
|
223
252
|
}
|
|
224
|
-
|
|
253
|
+
|
|
225
254
|
return {
|
|
226
255
|
client: this.client,
|
|
227
256
|
transport: this.transport,
|
|
@@ -557,6 +557,8 @@ const RpToolSchema = Type.Object({
|
|
|
557
557
|
export default function repopromptMcp(pi: ExtensionAPI) {
|
|
558
558
|
let config: RpConfig = loadConfig();
|
|
559
559
|
let initPromise: Promise<void> | null = null;
|
|
560
|
+
let shutdownRequested = false;
|
|
561
|
+
let extensionPaused = false;
|
|
560
562
|
|
|
561
563
|
pi.on("before_agent_start", async () => {
|
|
562
564
|
// Reload config so display knobs (collapsedMaxLines etc.) apply without requiring /reload
|
|
@@ -1225,6 +1227,9 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
1225
1227
|
// ───────────────────────────────────────────────────────────────────────────
|
|
1226
1228
|
|
|
1227
1229
|
pi.on("session_start", async (_event, ctx) => {
|
|
1230
|
+
shutdownRequested = false;
|
|
1231
|
+
extensionPaused = false;
|
|
1232
|
+
|
|
1228
1233
|
if (ctx.hasUI) {
|
|
1229
1234
|
// This extension used to set a status bar item; clear it to avoid persisting stale UI state
|
|
1230
1235
|
ctx.ui.setStatus("rp", undefined);
|
|
@@ -1244,16 +1249,27 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
1244
1249
|
}
|
|
1245
1250
|
|
|
1246
1251
|
// Non-blocking initialization
|
|
1247
|
-
|
|
1252
|
+
const pendingInit = initializeExtension(pi, ctx, config);
|
|
1253
|
+
initPromise = pendingInit;
|
|
1248
1254
|
|
|
1249
|
-
|
|
1250
|
-
initPromise
|
|
1255
|
+
pendingInit.then(async () => {
|
|
1256
|
+
if (initPromise === pendingInit) {
|
|
1257
|
+
initPromise = null;
|
|
1258
|
+
}
|
|
1259
|
+
if (shutdownRequested) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1251
1262
|
await syncAutoSelectionToCurrentBranch(ctx);
|
|
1252
1263
|
}).catch((err) => {
|
|
1253
|
-
|
|
1254
|
-
|
|
1264
|
+
if (initPromise === pendingInit) {
|
|
1265
|
+
initPromise = null;
|
|
1266
|
+
}
|
|
1267
|
+
if (shutdownRequested) {
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
extensionPaused = true;
|
|
1255
1271
|
if (ctx.hasUI) {
|
|
1256
|
-
ctx.ui.notify(
|
|
1272
|
+
ctx.ui.notify("RepoPrompt unavailable — extension paused. Use /rp reconnect when ready.", "warning");
|
|
1257
1273
|
}
|
|
1258
1274
|
});
|
|
1259
1275
|
});
|
|
@@ -1263,14 +1279,10 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
1263
1279
|
});
|
|
1264
1280
|
|
|
1265
1281
|
pi.on("session_shutdown", async () => {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
await initPromise;
|
|
1269
|
-
} catch {
|
|
1270
|
-
// Ignore
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1282
|
+
shutdownRequested = true;
|
|
1283
|
+
initPromise = null;
|
|
1273
1284
|
|
|
1285
|
+
// Never block Pi shutdown on an MCP startup handshake that may be stuck waiting on the app
|
|
1274
1286
|
clearReadcacheCaches();
|
|
1275
1287
|
activeAutoSelectionState = null;
|
|
1276
1288
|
await resetRpClient();
|
|
@@ -1340,15 +1352,15 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
1340
1352
|
const parts = args?.trim().split(/\s+/) ?? [];
|
|
1341
1353
|
const subcommand = parts[0]?.toLowerCase() ?? "status";
|
|
1342
1354
|
|
|
1343
|
-
// Allow status/
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
) {
|
|
1355
|
+
// Allow status/reconnect while disconnected or paused
|
|
1356
|
+
const alwaysAllowed = new Set(["reconnect", "status", "readcache-status", "readcache_status", "readcache-refresh", "readcache_refresh"]);
|
|
1357
|
+
|
|
1358
|
+
if (extensionPaused && !alwaysAllowed.has(subcommand)) {
|
|
1359
|
+
ctx.ui.notify("RepoPrompt extension is paused. Use /rp reconnect to resume.", "warning");
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if (!alwaysAllowed.has(subcommand)) {
|
|
1352
1364
|
await ensureConnected(ctx);
|
|
1353
1365
|
}
|
|
1354
1366
|
|
|
@@ -1587,16 +1599,31 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
1587
1599
|
break;
|
|
1588
1600
|
}
|
|
1589
1601
|
|
|
1590
|
-
case "reconnect":
|
|
1602
|
+
case "reconnect": {
|
|
1603
|
+
const wasPaused = extensionPaused;
|
|
1591
1604
|
try {
|
|
1592
1605
|
await resetRpClient();
|
|
1606
|
+
extensionPaused = false;
|
|
1593
1607
|
await initializeExtension(pi, ctx, config);
|
|
1594
1608
|
await syncAutoSelectionToCurrentBranch(ctx);
|
|
1595
1609
|
ctx.ui.notify("RepoPrompt reconnected", "info");
|
|
1610
|
+
|
|
1611
|
+
if (wasPaused) {
|
|
1612
|
+
pi.sendMessage(
|
|
1613
|
+
{
|
|
1614
|
+
customType: "rp-availability",
|
|
1615
|
+
content: "RepoPrompt (`rp` tool) is now available.",
|
|
1616
|
+
display: false,
|
|
1617
|
+
},
|
|
1618
|
+
{ triggerTurn: false },
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1596
1621
|
} catch (err) {
|
|
1622
|
+
extensionPaused = true;
|
|
1597
1623
|
ctx.ui.notify(`Reconnection failed: ${err instanceof Error ? err.message : err}`, "error");
|
|
1598
1624
|
}
|
|
1599
1625
|
break;
|
|
1626
|
+
}
|
|
1600
1627
|
|
|
1601
1628
|
default:
|
|
1602
1629
|
ctx.ui.notify(
|
|
@@ -1640,6 +1667,13 @@ Mode priority: call > describe > search > windows > bind > status`,
|
|
|
1640
1667
|
parameters: RpToolSchema,
|
|
1641
1668
|
|
|
1642
1669
|
async execute(_toolCallId, params: RpToolParams, _signal, onUpdate, _ctx) {
|
|
1670
|
+
if (extensionPaused) {
|
|
1671
|
+
throw new Error(
|
|
1672
|
+
"The rp tool is not currently available due to a connection issue. " +
|
|
1673
|
+
"The user can run /rp reconnect when the RepoPrompt app is running."
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1643
1677
|
// Provide a no-op if onUpdate is undefined
|
|
1644
1678
|
const safeOnUpdate = onUpdate ?? (() => {});
|
|
1645
1679
|
|
|
@@ -2176,6 +2210,9 @@ Mode priority: call > describe > search > windows > bind > status`,
|
|
|
2176
2210
|
|
|
2177
2211
|
let msg = `RepoPrompt Status\n`;
|
|
2178
2212
|
msg += `─────────────────\n`;
|
|
2213
|
+
if (extensionPaused) {
|
|
2214
|
+
msg += `Extension: ⏸ paused (use /rp reconnect to resume)\n`;
|
|
2215
|
+
}
|
|
2179
2216
|
msg += `Connection: ${client.isConnected ? "✓ connected" : "✗ disconnected"}\n`;
|
|
2180
2217
|
msg += `Tools: ${client.tools.length}\n`;
|
|
2181
2218
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-repoprompt-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "A token-efficient RepoPrompt integration for Pi with automated and branch-safe workspace management",
|
|
5
5
|
"keywords": ["pi-package", "pi", "pi-coding-agent", "repoprompt", "mcp"],
|
|
6
6
|
"license": "MIT",
|