pi-repoprompt-cli 0.2.7 → 0.2.8
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 +5 -1
- package/extensions/repoprompt-cli/README.md +18 -1
- package/extensions/repoprompt-cli/auto-select.ts +288 -0
- package/extensions/repoprompt-cli/config.json.example +2 -1
- package/extensions/repoprompt-cli/config.ts +1 -0
- package/extensions/repoprompt-cli/index.ts +1590 -215
- package/extensions/repoprompt-cli/types.ts +34 -2
- package/package.json +5 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import * as path from "node:path";
|
|
2
3
|
|
|
3
4
|
import type { ExtensionAPI, ExtensionContext, ToolRenderResultOptions } from "@mariozechner/pi-coding-agent";
|
|
4
5
|
import { highlightCode, Theme } from "@mariozechner/pi-coding-agent";
|
|
@@ -7,13 +8,25 @@ import { Type } from "@sinclair/typebox";
|
|
|
7
8
|
import * as Diff from "diff";
|
|
8
9
|
|
|
9
10
|
import { loadConfig } from "./config.js";
|
|
11
|
+
import {
|
|
12
|
+
computeSliceRangeFromReadArgs,
|
|
13
|
+
countFileLines,
|
|
14
|
+
inferSelectionStatus,
|
|
15
|
+
toPosixPath,
|
|
16
|
+
} from "./auto-select.js";
|
|
10
17
|
import { RP_READCACHE_CUSTOM_TYPE, SCOPE_FULL, scopeRange } from "./readcache/constants.js";
|
|
11
18
|
import { buildInvalidationV1 } from "./readcache/meta.js";
|
|
12
19
|
import { getStoreStats, pruneObjectsOlderThan } from "./readcache/object-store.js";
|
|
13
20
|
import { readFileWithCache } from "./readcache/read-file.js";
|
|
14
21
|
import { clearReplayRuntimeState, createReplayRuntimeState } from "./readcache/replay.js";
|
|
15
|
-
import { resolveReadFilePath } from "./readcache/resolve.js";
|
|
22
|
+
import { clearRootsCache, resolveReadFilePath } from "./readcache/resolve.js";
|
|
16
23
|
import type { RpReadcacheMetaV1, ScopeKey } from "./readcache/types.js";
|
|
24
|
+
import type {
|
|
25
|
+
AutoSelectionEntryData,
|
|
26
|
+
AutoSelectionEntryRangeData,
|
|
27
|
+
AutoSelectionEntrySliceData,
|
|
28
|
+
RpCliBindingEntryData,
|
|
29
|
+
} from "./types.js";
|
|
17
30
|
|
|
18
31
|
let parseBash: ((input: string) => any) | null = null;
|
|
19
32
|
let justBashLoadPromise: Promise<void> | null = null;
|
|
@@ -247,6 +260,16 @@ function hasPipeOutsideQuotes(script: string): boolean {
|
|
|
247
260
|
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
248
261
|
const DEFAULT_MAX_OUTPUT_CHARS = 12000;
|
|
249
262
|
const BINDING_CUSTOM_TYPE = "repoprompt-binding";
|
|
263
|
+
const AUTO_SELECTION_CUSTOM_TYPE = "repoprompt-cli-auto-selection";
|
|
264
|
+
const WINDOWS_CACHE_TTL_MS = 5000;
|
|
265
|
+
const BINDING_VALIDATION_TTL_MS = 5000;
|
|
266
|
+
|
|
267
|
+
interface RpCliWindow {
|
|
268
|
+
windowId: number;
|
|
269
|
+
workspaceId?: string;
|
|
270
|
+
workspaceName?: string;
|
|
271
|
+
rootFolderPaths?: string[];
|
|
272
|
+
}
|
|
250
273
|
|
|
251
274
|
const BindParams = Type.Object({
|
|
252
275
|
windowId: Type.Number({ description: "RepoPrompt window id (from `rp-cli -e windows`)" }),
|
|
@@ -1250,278 +1273,1606 @@ export default function (pi: ExtensionAPI) {
|
|
|
1250
1273
|
clearReplayRuntimeState(readcacheRuntimeState);
|
|
1251
1274
|
};
|
|
1252
1275
|
|
|
1276
|
+
let activeAutoSelectionState: AutoSelectionEntryData | null = null;
|
|
1277
|
+
|
|
1253
1278
|
let boundWindowId: number | undefined;
|
|
1254
1279
|
let boundTab: string | undefined;
|
|
1280
|
+
let boundWorkspaceId: string | undefined;
|
|
1281
|
+
let boundWorkspaceName: string | undefined;
|
|
1282
|
+
let boundWorkspaceRoots: string[] | undefined;
|
|
1255
1283
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
boundTab = tab;
|
|
1259
|
-
};
|
|
1284
|
+
let windowsCache: { windows: RpCliWindow[]; fetchedAtMs: number } | null = null;
|
|
1285
|
+
let lastBindingValidationAtMs = 0;
|
|
1260
1286
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1287
|
+
function sameOptionalText(a?: string, b?: string): boolean {
|
|
1288
|
+
return (a ?? undefined) === (b ?? undefined);
|
|
1289
|
+
}
|
|
1264
1290
|
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1291
|
+
function clearWindowsCache(): void {
|
|
1292
|
+
windowsCache = null;
|
|
1293
|
+
}
|
|
1268
1294
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
boundWindowId = undefined;
|
|
1273
|
-
boundTab = undefined;
|
|
1295
|
+
function markBindingValidationStale(): void {
|
|
1296
|
+
lastBindingValidationAtMs = 0;
|
|
1297
|
+
}
|
|
1274
1298
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1299
|
+
function shouldRevalidateBinding(): boolean {
|
|
1300
|
+
const now = Date.now();
|
|
1301
|
+
return (now - lastBindingValidationAtMs) >= BINDING_VALIDATION_TTL_MS;
|
|
1302
|
+
}
|
|
1277
1303
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1304
|
+
function markBindingValidatedNow(): void {
|
|
1305
|
+
lastBindingValidationAtMs = Date.now();
|
|
1306
|
+
}
|
|
1280
1307
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
if (windowId !== undefined && tab) {
|
|
1285
|
-
reconstructedWindowId = windowId;
|
|
1286
|
-
reconstructedTab = tab;
|
|
1287
|
-
}
|
|
1308
|
+
function normalizeWorkspaceRoots(roots: string[] | undefined): string[] {
|
|
1309
|
+
if (!Array.isArray(roots)) {
|
|
1310
|
+
return [];
|
|
1288
1311
|
}
|
|
1289
1312
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1313
|
+
return [...new Set(roots.map((root) => toPosixPath(String(root).trim())).filter(Boolean))].sort();
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function workspaceRootsEqual(left: string[] | undefined, right: string[] | undefined): boolean {
|
|
1317
|
+
const leftNormalized = normalizeWorkspaceRoots(left);
|
|
1318
|
+
const rightNormalized = normalizeWorkspaceRoots(right);
|
|
1319
|
+
return JSON.stringify(leftNormalized) === JSON.stringify(rightNormalized);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function workspaceIdentityMatches(
|
|
1323
|
+
left: { workspaceId?: string; workspaceName?: string; workspaceRoots?: string[] },
|
|
1324
|
+
right: { workspaceId?: string; workspaceName?: string; workspaceRoots?: string[] }
|
|
1325
|
+
): boolean {
|
|
1326
|
+
if (left.workspaceId && right.workspaceId) {
|
|
1327
|
+
return left.workspaceId === right.workspaceId;
|
|
1293
1328
|
}
|
|
1294
1329
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
const msg = entry.message;
|
|
1298
|
-
if (msg.role !== "toolResult" || msg.toolName !== "rp_bind") continue;
|
|
1330
|
+
const leftRoots = normalizeWorkspaceRoots(left.workspaceRoots);
|
|
1331
|
+
const rightRoots = normalizeWorkspaceRoots(right.workspaceRoots);
|
|
1299
1332
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
persistBinding(details.windowId, details.tab);
|
|
1303
|
-
}
|
|
1333
|
+
if (leftRoots.length > 0 && rightRoots.length > 0) {
|
|
1334
|
+
return JSON.stringify(leftRoots) === JSON.stringify(rightRoots);
|
|
1304
1335
|
}
|
|
1305
|
-
};
|
|
1306
1336
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
clearReadcacheCaches();
|
|
1310
|
-
if (config.readcacheReadFile === true) {
|
|
1311
|
-
void pruneObjectsOlderThan(ctx.cwd).catch(() => {
|
|
1312
|
-
// Fail-open
|
|
1313
|
-
});
|
|
1337
|
+
if (left.workspaceName && right.workspaceName) {
|
|
1338
|
+
return left.workspaceName === right.workspaceName;
|
|
1314
1339
|
}
|
|
1315
|
-
reconstructBinding(ctx);
|
|
1316
|
-
});
|
|
1317
1340
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
clearReadcacheCaches();
|
|
1321
|
-
reconstructBinding(ctx);
|
|
1322
|
-
});
|
|
1341
|
+
return false;
|
|
1342
|
+
}
|
|
1323
1343
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
reconstructBinding(ctx);
|
|
1329
|
-
});
|
|
1344
|
+
function getCurrentBinding(): RpCliBindingEntryData | null {
|
|
1345
|
+
if (boundWindowId === undefined || !boundTab) {
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1330
1348
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1349
|
+
return {
|
|
1350
|
+
windowId: boundWindowId,
|
|
1351
|
+
tab: boundTab,
|
|
1352
|
+
workspaceId: boundWorkspaceId,
|
|
1353
|
+
workspaceName: boundWorkspaceName,
|
|
1354
|
+
workspaceRoots: boundWorkspaceRoots,
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1336
1357
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1358
|
+
function normalizeBindingEntry(binding: RpCliBindingEntryData): RpCliBindingEntryData {
|
|
1359
|
+
return {
|
|
1360
|
+
windowId: binding.windowId,
|
|
1361
|
+
tab: binding.tab,
|
|
1362
|
+
workspaceId: typeof binding.workspaceId === "string" ? binding.workspaceId : undefined,
|
|
1363
|
+
workspaceName: typeof binding.workspaceName === "string" ? binding.workspaceName : undefined,
|
|
1364
|
+
workspaceRoots: normalizeWorkspaceRoots(binding.workspaceRoots),
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1342
1367
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1368
|
+
function bindingEntriesEqual(a: RpCliBindingEntryData | null, b: RpCliBindingEntryData | null): boolean {
|
|
1369
|
+
if (!a && !b) {
|
|
1370
|
+
return true;
|
|
1371
|
+
}
|
|
1346
1372
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1373
|
+
if (!a || !b) {
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
1350
1376
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
handler: async (args, ctx) => {
|
|
1354
|
-
const parsed = parseRpbindArgs(args);
|
|
1355
|
-
if ("error" in parsed) {
|
|
1356
|
-
ctx.ui.notify(parsed.error, "error");
|
|
1357
|
-
return;
|
|
1358
|
-
}
|
|
1377
|
+
const left = normalizeBindingEntry(a);
|
|
1378
|
+
const right = normalizeBindingEntry(b);
|
|
1359
1379
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1380
|
+
return (
|
|
1381
|
+
left.windowId === right.windowId &&
|
|
1382
|
+
left.tab === right.tab &&
|
|
1383
|
+
sameOptionalText(left.workspaceId, right.workspaceId) &&
|
|
1384
|
+
sameOptionalText(left.workspaceName, right.workspaceName) &&
|
|
1385
|
+
workspaceRootsEqual(left.workspaceRoots, right.workspaceRoots)
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1364
1388
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
handler: async (_args, ctx) => {
|
|
1368
|
-
config = loadConfig();
|
|
1389
|
+
function setBinding(binding: RpCliBindingEntryData | null): void {
|
|
1390
|
+
const previousWindowId = boundWindowId;
|
|
1369
1391
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1392
|
+
if (!binding) {
|
|
1393
|
+
boundWindowId = undefined;
|
|
1394
|
+
boundTab = undefined;
|
|
1395
|
+
boundWorkspaceId = undefined;
|
|
1396
|
+
boundWorkspaceName = undefined;
|
|
1397
|
+
boundWorkspaceRoots = undefined;
|
|
1398
|
+
} else {
|
|
1399
|
+
const normalized = normalizeBindingEntry(binding);
|
|
1400
|
+
boundWindowId = normalized.windowId;
|
|
1401
|
+
boundTab = normalized.tab;
|
|
1402
|
+
boundWorkspaceId = normalized.workspaceId;
|
|
1403
|
+
boundWorkspaceName = normalized.workspaceName;
|
|
1404
|
+
boundWorkspaceRoots = normalized.workspaceRoots;
|
|
1405
|
+
}
|
|
1373
1406
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
ctx.ui.notify(msg, "info");
|
|
1378
|
-
return;
|
|
1407
|
+
if (previousWindowId !== boundWindowId) {
|
|
1408
|
+
if (previousWindowId !== undefined) {
|
|
1409
|
+
clearRootsCache(previousWindowId);
|
|
1379
1410
|
}
|
|
1380
1411
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
msg += `\nObject store (under ${ctx.cwd}/.pi/readcache):\n`;
|
|
1384
|
-
msg += ` Objects: ${stats.objects}\n`;
|
|
1385
|
-
msg += ` Bytes: ${stats.bytes}\n`;
|
|
1386
|
-
} catch {
|
|
1387
|
-
msg += "\nObject store: unavailable\n";
|
|
1412
|
+
if (boundWindowId !== undefined) {
|
|
1413
|
+
clearRootsCache(boundWindowId);
|
|
1388
1414
|
}
|
|
1389
1415
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
msg += "- Use bypass_cache=true in the read command to force baseline output\n";
|
|
1416
|
+
clearWindowsCache();
|
|
1417
|
+
}
|
|
1393
1418
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
});
|
|
1419
|
+
markBindingValidationStale();
|
|
1420
|
+
}
|
|
1397
1421
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1422
|
+
function parseBindingEntryData(value: unknown): RpCliBindingEntryData | null {
|
|
1423
|
+
if (!value || typeof value !== "object") {
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1402
1426
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1427
|
+
const obj = value as Record<string, unknown>;
|
|
1428
|
+
|
|
1429
|
+
const windowId = typeof obj.windowId === "number" ? obj.windowId : undefined;
|
|
1430
|
+
const tab = typeof obj.tab === "string" ? obj.tab : undefined;
|
|
1431
|
+
|
|
1432
|
+
if (windowId === undefined || !tab) {
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
const workspaceId = typeof obj.workspaceId === "string"
|
|
1437
|
+
? obj.workspaceId
|
|
1438
|
+
: (typeof obj.workspaceID === "string" ? obj.workspaceID : undefined);
|
|
1439
|
+
|
|
1440
|
+
const workspaceName = typeof obj.workspaceName === "string"
|
|
1441
|
+
? obj.workspaceName
|
|
1442
|
+
: (typeof obj.workspace === "string" ? obj.workspace : undefined);
|
|
1443
|
+
|
|
1444
|
+
const workspaceRoots = Array.isArray(obj.workspaceRoots)
|
|
1445
|
+
? obj.workspaceRoots.filter((root): root is string => typeof root === "string")
|
|
1446
|
+
: (Array.isArray(obj.rootFolderPaths)
|
|
1447
|
+
? obj.rootFolderPaths.filter((root): root is string => typeof root === "string")
|
|
1448
|
+
: undefined);
|
|
1449
|
+
|
|
1450
|
+
return normalizeBindingEntry({
|
|
1451
|
+
windowId,
|
|
1452
|
+
tab,
|
|
1453
|
+
workspaceId,
|
|
1454
|
+
workspaceName,
|
|
1455
|
+
workspaceRoots,
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function persistBinding(binding: RpCliBindingEntryData): void {
|
|
1460
|
+
const normalized = normalizeBindingEntry(binding);
|
|
1461
|
+
const current = getCurrentBinding();
|
|
1462
|
+
|
|
1463
|
+
if (bindingEntriesEqual(current, normalized)) {
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
setBinding(normalized);
|
|
1468
|
+
pi.appendEntry(BINDING_CUSTOM_TYPE, normalized);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
function reconstructBinding(ctx: ExtensionContext): void {
|
|
1472
|
+
// Prefer persisted binding (appendEntry) from the *current branch*, then fall back to prior rp_bind tool results
|
|
1473
|
+
// Branch semantics: if the current branch has no binding state, stay unbound
|
|
1474
|
+
setBinding(null);
|
|
1475
|
+
|
|
1476
|
+
let reconstructed: RpCliBindingEntryData | null = null;
|
|
1477
|
+
|
|
1478
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
1479
|
+
if (entry.type !== "custom" || entry.customType !== BINDING_CUSTOM_TYPE) {
|
|
1480
|
+
continue;
|
|
1406
1481
|
}
|
|
1407
1482
|
|
|
1408
|
-
const
|
|
1409
|
-
if (
|
|
1410
|
-
|
|
1411
|
-
return;
|
|
1483
|
+
const parsed = parseBindingEntryData(entry.data);
|
|
1484
|
+
if (parsed) {
|
|
1485
|
+
reconstructed = parsed;
|
|
1412
1486
|
}
|
|
1487
|
+
}
|
|
1413
1488
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1489
|
+
if (reconstructed) {
|
|
1490
|
+
setBinding(reconstructed);
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1417
1493
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1494
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
1495
|
+
if (entry.type !== "message") {
|
|
1496
|
+
continue;
|
|
1421
1497
|
}
|
|
1422
1498
|
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1499
|
+
const msg = entry.message;
|
|
1500
|
+
if (msg.role !== "toolResult" || msg.toolName !== "rp_bind") {
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1425
1503
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1504
|
+
const parsed = parseBindingEntryData(msg.details);
|
|
1505
|
+
if (parsed) {
|
|
1506
|
+
persistBinding(parsed);
|
|
1429
1507
|
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1430
1510
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
return;
|
|
1437
|
-
}
|
|
1511
|
+
function parseWindowsRawJson(raw: string): RpCliWindow[] {
|
|
1512
|
+
const trimmed = raw.trim();
|
|
1513
|
+
if (!trimmed) {
|
|
1514
|
+
return [];
|
|
1515
|
+
}
|
|
1438
1516
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1517
|
+
let parsed: unknown;
|
|
1518
|
+
try {
|
|
1519
|
+
parsed = JSON.parse(trimmed);
|
|
1520
|
+
} catch {
|
|
1521
|
+
return [];
|
|
1522
|
+
}
|
|
1445
1523
|
|
|
1446
|
-
|
|
1524
|
+
const pickRows = (value: unknown): unknown[] => {
|
|
1525
|
+
if (Array.isArray(value)) {
|
|
1526
|
+
return value;
|
|
1447
1527
|
}
|
|
1448
1528
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
ctx.ui.notify(`Could not resolve path: ${pathInput}`, "error");
|
|
1452
|
-
return;
|
|
1529
|
+
if (!value || typeof value !== "object") {
|
|
1530
|
+
return [];
|
|
1453
1531
|
}
|
|
1454
1532
|
|
|
1455
|
-
|
|
1456
|
-
clearReadcacheCaches();
|
|
1533
|
+
const obj = value as Record<string, unknown>;
|
|
1457
1534
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1535
|
+
const directArrayKeys = ["windows", "items", "data", "result"];
|
|
1536
|
+
for (const key of directArrayKeys) {
|
|
1537
|
+
const candidate = obj[key];
|
|
1538
|
+
if (Array.isArray(candidate)) {
|
|
1539
|
+
return candidate;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1464
1542
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1543
|
+
const nested = obj.data;
|
|
1544
|
+
if (nested && typeof nested === "object") {
|
|
1545
|
+
const nestedObj = nested as Record<string, unknown>;
|
|
1546
|
+
if (Array.isArray(nestedObj.windows)) {
|
|
1547
|
+
return nestedObj.windows;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1470
1550
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
maybeWarnAstUnavailable(ctx);
|
|
1474
|
-
persistBinding(params.windowId, params.tab);
|
|
1551
|
+
return [];
|
|
1552
|
+
};
|
|
1475
1553
|
|
|
1476
|
-
|
|
1477
|
-
content: [{ type: "text", text: `Bound rp_exec → window ${boundWindowId}, tab "${boundTab}"` }],
|
|
1478
|
-
details: { windowId: boundWindowId, tab: boundTab },
|
|
1479
|
-
};
|
|
1480
|
-
},
|
|
1481
|
-
});
|
|
1554
|
+
const rows = pickRows(parsed);
|
|
1482
1555
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1556
|
+
return rows
|
|
1557
|
+
.map((row) => {
|
|
1558
|
+
if (!row || typeof row !== "object") {
|
|
1559
|
+
return null;
|
|
1560
|
+
}
|
|
1488
1561
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1562
|
+
const obj = row as Record<string, unknown>;
|
|
1563
|
+
const windowId = typeof obj.windowID === "number"
|
|
1564
|
+
? obj.windowID
|
|
1565
|
+
: (typeof obj.windowId === "number" ? obj.windowId : undefined);
|
|
1493
1566
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1567
|
+
if (windowId === undefined) {
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const workspaceId = typeof obj.workspaceID === "string"
|
|
1572
|
+
? obj.workspaceID
|
|
1573
|
+
: (typeof obj.workspaceId === "string" ? obj.workspaceId : undefined);
|
|
1574
|
+
|
|
1575
|
+
const workspaceName = typeof obj.workspaceName === "string"
|
|
1576
|
+
? obj.workspaceName
|
|
1577
|
+
: (typeof obj.workspace === "string" ? obj.workspace : undefined);
|
|
1578
|
+
|
|
1579
|
+
const rootFolderPaths = Array.isArray(obj.rootFolderPaths)
|
|
1580
|
+
? obj.rootFolderPaths.filter((root): root is string => typeof root === "string")
|
|
1581
|
+
: (Array.isArray(obj.roots)
|
|
1582
|
+
? obj.roots.filter((root): root is string => typeof root === "string")
|
|
1583
|
+
: undefined);
|
|
1504
1584
|
|
|
1505
|
-
if (!allowDelete && looksLikeDeleteCommand(params.cmd)) {
|
|
1506
1585
|
return {
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1586
|
+
windowId,
|
|
1587
|
+
workspaceId,
|
|
1588
|
+
workspaceName,
|
|
1589
|
+
rootFolderPaths,
|
|
1590
|
+
} as RpCliWindow;
|
|
1591
|
+
})
|
|
1592
|
+
.filter((window): window is RpCliWindow => window !== null);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
async function fetchWindowsFromCli(forceRefresh = false): Promise<RpCliWindow[]> {
|
|
1596
|
+
const now = Date.now();
|
|
1597
|
+
|
|
1598
|
+
if (!forceRefresh && windowsCache && (now - windowsCache.fetchedAtMs) < WINDOWS_CACHE_TTL_MS) {
|
|
1599
|
+
return windowsCache.windows;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
try {
|
|
1603
|
+
const result = await pi.exec("rp-cli", ["--raw-json", "-q", "-e", "windows"], { timeout: 10_000 });
|
|
1604
|
+
|
|
1605
|
+
if ((result.code ?? 0) !== 0) {
|
|
1606
|
+
return windowsCache?.windows ?? [];
|
|
1516
1607
|
}
|
|
1517
1608
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1609
|
+
const stdout = result.stdout ?? "";
|
|
1610
|
+
const stderr = result.stderr ?? "";
|
|
1611
|
+
|
|
1612
|
+
const fromStdout = parseWindowsRawJson(stdout);
|
|
1613
|
+
const parsed = fromStdout.length > 0 ? fromStdout : parseWindowsRawJson(stderr);
|
|
1614
|
+
|
|
1615
|
+
windowsCache = {
|
|
1616
|
+
windows: parsed,
|
|
1617
|
+
fetchedAtMs: now,
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
return parsed;
|
|
1621
|
+
} catch {
|
|
1622
|
+
return windowsCache?.windows ?? [];
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function windowToBinding(window: RpCliWindow, tab: string): RpCliBindingEntryData {
|
|
1627
|
+
return normalizeBindingEntry({
|
|
1628
|
+
windowId: window.windowId,
|
|
1629
|
+
tab,
|
|
1630
|
+
workspaceId: window.workspaceId,
|
|
1631
|
+
workspaceName: window.workspaceName,
|
|
1632
|
+
workspaceRoots: window.rootFolderPaths,
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
async function enrichBinding(windowId: number, tab: string): Promise<RpCliBindingEntryData> {
|
|
1637
|
+
const windows = await fetchWindowsFromCli();
|
|
1638
|
+
const match = windows.find((window) => window.windowId === windowId);
|
|
1639
|
+
|
|
1640
|
+
if (!match) {
|
|
1641
|
+
return normalizeBindingEntry({ windowId, tab });
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
return windowToBinding(match, tab);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
async function ensureBindingTargetsLiveWindow(
|
|
1648
|
+
ctx: ExtensionContext,
|
|
1649
|
+
forceRefresh = false
|
|
1650
|
+
): Promise<RpCliBindingEntryData | null> {
|
|
1651
|
+
const binding = getCurrentBinding();
|
|
1652
|
+
if (!binding) {
|
|
1653
|
+
return null;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
const windows = await fetchWindowsFromCli(forceRefresh);
|
|
1657
|
+
if (windows.length === 0) {
|
|
1658
|
+
return binding;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
const sameWindow = windows.find((window) => window.windowId === binding.windowId);
|
|
1662
|
+
if (sameWindow) {
|
|
1663
|
+
const hydrated = windowToBinding(sameWindow, binding.tab);
|
|
1664
|
+
|
|
1665
|
+
if (binding.workspaceId && hydrated.workspaceId && binding.workspaceId !== hydrated.workspaceId) {
|
|
1666
|
+
// Window IDs were recycled to a different workspace. Fall through to workspace-based remap
|
|
1667
|
+
} else {
|
|
1668
|
+
if (!bindingEntriesEqual(binding, hydrated)) {
|
|
1669
|
+
persistBinding(hydrated);
|
|
1670
|
+
return hydrated;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
setBinding(hydrated);
|
|
1674
|
+
return hydrated;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const workspaceCandidates = windows.filter((window) => workspaceIdentityMatches(
|
|
1679
|
+
{
|
|
1680
|
+
workspaceId: binding.workspaceId,
|
|
1681
|
+
workspaceName: binding.workspaceName,
|
|
1682
|
+
workspaceRoots: binding.workspaceRoots,
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
workspaceId: window.workspaceId,
|
|
1686
|
+
workspaceName: window.workspaceName,
|
|
1687
|
+
workspaceRoots: window.rootFolderPaths,
|
|
1688
|
+
}
|
|
1689
|
+
));
|
|
1690
|
+
|
|
1691
|
+
if (workspaceCandidates.length === 1) {
|
|
1692
|
+
const rebound = windowToBinding(workspaceCandidates[0], binding.tab);
|
|
1693
|
+
persistBinding(rebound);
|
|
1694
|
+
return rebound;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
setBinding(null);
|
|
1698
|
+
|
|
1699
|
+
if (ctx.hasUI) {
|
|
1700
|
+
const workspaceLabel = binding.workspaceName ?? binding.workspaceId ?? `window ${binding.windowId}`;
|
|
1701
|
+
|
|
1702
|
+
if (workspaceCandidates.length > 1) {
|
|
1703
|
+
ctx.ui.notify(
|
|
1704
|
+
`repoprompt-cli: binding for ${workspaceLabel} is ambiguous after restart. Re-bind with /rpbind`,
|
|
1705
|
+
"warning"
|
|
1706
|
+
);
|
|
1707
|
+
} else {
|
|
1708
|
+
ctx.ui.notify(
|
|
1709
|
+
`repoprompt-cli: ${workspaceLabel} not found after restart. Re-bind with /rpbind`,
|
|
1710
|
+
"warning"
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
return null;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
async function maybeEnsureBindingTargetsLiveWindow(ctx: ExtensionContext): Promise<RpCliBindingEntryData | null> {
|
|
1719
|
+
const binding = getCurrentBinding();
|
|
1720
|
+
if (!binding) {
|
|
1721
|
+
return null;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
if (!shouldRevalidateBinding()) {
|
|
1725
|
+
return binding;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const validated = await ensureBindingTargetsLiveWindow(ctx, true);
|
|
1729
|
+
|
|
1730
|
+
if (validated) {
|
|
1731
|
+
markBindingValidatedNow();
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
return validated;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function sameBindingForAutoSelection(
|
|
1738
|
+
binding: RpCliBindingEntryData | null,
|
|
1739
|
+
state: AutoSelectionEntryData | null
|
|
1740
|
+
): boolean {
|
|
1741
|
+
if (!binding || !state) {
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (!sameOptionalText(binding.tab, state.tab)) {
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (binding.windowId === state.windowId) {
|
|
1750
|
+
return true;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
return workspaceIdentityMatches(
|
|
1754
|
+
{
|
|
1755
|
+
workspaceId: binding.workspaceId,
|
|
1756
|
+
workspaceName: binding.workspaceName,
|
|
1757
|
+
workspaceRoots: binding.workspaceRoots,
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
workspaceId: state.workspaceId,
|
|
1761
|
+
workspaceName: state.workspaceName,
|
|
1762
|
+
workspaceRoots: state.workspaceRoots,
|
|
1763
|
+
}
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function makeEmptyAutoSelectionState(binding: RpCliBindingEntryData): AutoSelectionEntryData {
|
|
1768
|
+
return {
|
|
1769
|
+
windowId: binding.windowId,
|
|
1770
|
+
tab: binding.tab,
|
|
1771
|
+
workspaceId: binding.workspaceId,
|
|
1772
|
+
workspaceName: binding.workspaceName,
|
|
1773
|
+
workspaceRoots: normalizeWorkspaceRoots(binding.workspaceRoots),
|
|
1774
|
+
fullPaths: [],
|
|
1775
|
+
slicePaths: [],
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function normalizeAutoSelectionRanges(ranges: AutoSelectionEntryRangeData[]): AutoSelectionEntryRangeData[] {
|
|
1780
|
+
const normalized = ranges
|
|
1781
|
+
.map((range) => ({
|
|
1782
|
+
start_line: Number(range.start_line),
|
|
1783
|
+
end_line: Number(range.end_line),
|
|
1784
|
+
}))
|
|
1785
|
+
.filter((range) => Number.isFinite(range.start_line) && Number.isFinite(range.end_line))
|
|
1786
|
+
.filter((range) => range.start_line > 0 && range.end_line >= range.start_line)
|
|
1787
|
+
.sort((a, b) => {
|
|
1788
|
+
if (a.start_line !== b.start_line) {
|
|
1789
|
+
return a.start_line - b.start_line;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
return a.end_line - b.end_line;
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
const merged: AutoSelectionEntryRangeData[] = [];
|
|
1796
|
+
for (const range of normalized) {
|
|
1797
|
+
const last = merged[merged.length - 1];
|
|
1798
|
+
if (!last) {
|
|
1799
|
+
merged.push(range);
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
if (range.start_line <= last.end_line + 1) {
|
|
1804
|
+
last.end_line = Math.max(last.end_line, range.end_line);
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
merged.push(range);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
return merged;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function normalizeAutoSelectionState(state: AutoSelectionEntryData): AutoSelectionEntryData {
|
|
1815
|
+
const fullPaths = [...new Set(state.fullPaths.map((p) => toPosixPath(String(p).trim())).filter(Boolean))].sort();
|
|
1816
|
+
|
|
1817
|
+
const fullSet = new Set(fullPaths);
|
|
1818
|
+
|
|
1819
|
+
const sliceMap = new Map<string, AutoSelectionEntryRangeData[]>();
|
|
1820
|
+
for (const item of state.slicePaths) {
|
|
1821
|
+
const pathKey = toPosixPath(String(item.path ?? "").trim());
|
|
1822
|
+
if (!pathKey || fullSet.has(pathKey)) {
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
const existing = sliceMap.get(pathKey) ?? [];
|
|
1827
|
+
existing.push(...normalizeAutoSelectionRanges(item.ranges ?? []));
|
|
1828
|
+
sliceMap.set(pathKey, existing);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
const slicePaths: AutoSelectionEntrySliceData[] = [...sliceMap.entries()]
|
|
1832
|
+
.map(([pathKey, ranges]) => ({
|
|
1833
|
+
path: pathKey,
|
|
1834
|
+
ranges: normalizeAutoSelectionRanges(ranges),
|
|
1835
|
+
}))
|
|
1836
|
+
.filter((item) => item.ranges.length > 0)
|
|
1837
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
1838
|
+
|
|
1839
|
+
return {
|
|
1840
|
+
windowId: state.windowId,
|
|
1841
|
+
tab: state.tab,
|
|
1842
|
+
workspaceId: typeof state.workspaceId === "string" ? state.workspaceId : undefined,
|
|
1843
|
+
workspaceName: typeof state.workspaceName === "string" ? state.workspaceName : undefined,
|
|
1844
|
+
workspaceRoots: normalizeWorkspaceRoots(state.workspaceRoots),
|
|
1845
|
+
fullPaths,
|
|
1846
|
+
slicePaths,
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
function autoSelectionStatesEqual(a: AutoSelectionEntryData | null, b: AutoSelectionEntryData | null): boolean {
|
|
1851
|
+
if (!a && !b) {
|
|
1852
|
+
return true;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
if (!a || !b) {
|
|
1856
|
+
return false;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const left = normalizeAutoSelectionState(a);
|
|
1860
|
+
const right = normalizeAutoSelectionState(b);
|
|
1861
|
+
|
|
1862
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
function parseAutoSelectionEntryData(
|
|
1866
|
+
value: unknown,
|
|
1867
|
+
binding: RpCliBindingEntryData
|
|
1868
|
+
): AutoSelectionEntryData | null {
|
|
1869
|
+
if (!value || typeof value !== "object") {
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
const obj = value as Record<string, unknown>;
|
|
1874
|
+
|
|
1875
|
+
const windowId = typeof obj.windowId === "number" ? obj.windowId : undefined;
|
|
1876
|
+
const tab = typeof obj.tab === "string" ? obj.tab : undefined;
|
|
1877
|
+
|
|
1878
|
+
const workspaceId = typeof obj.workspaceId === "string"
|
|
1879
|
+
? obj.workspaceId
|
|
1880
|
+
: (typeof obj.workspaceID === "string" ? obj.workspaceID : undefined);
|
|
1881
|
+
|
|
1882
|
+
const workspaceName = typeof obj.workspaceName === "string"
|
|
1883
|
+
? obj.workspaceName
|
|
1884
|
+
: (typeof obj.workspace === "string" ? obj.workspace : undefined);
|
|
1885
|
+
|
|
1886
|
+
const workspaceRoots = Array.isArray(obj.workspaceRoots)
|
|
1887
|
+
? obj.workspaceRoots.filter((root): root is string => typeof root === "string")
|
|
1888
|
+
: (Array.isArray(obj.rootFolderPaths)
|
|
1889
|
+
? obj.rootFolderPaths.filter((root): root is string => typeof root === "string")
|
|
1890
|
+
: undefined);
|
|
1891
|
+
|
|
1892
|
+
const tabMatches = sameOptionalText(tab, binding.tab);
|
|
1893
|
+
const windowMatches = windowId === binding.windowId;
|
|
1894
|
+
const workspaceMatches = workspaceIdentityMatches(
|
|
1895
|
+
{
|
|
1896
|
+
workspaceId: binding.workspaceId,
|
|
1897
|
+
workspaceName: binding.workspaceName,
|
|
1898
|
+
workspaceRoots: binding.workspaceRoots,
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
workspaceId,
|
|
1902
|
+
workspaceName,
|
|
1903
|
+
workspaceRoots,
|
|
1904
|
+
}
|
|
1905
|
+
);
|
|
1906
|
+
|
|
1907
|
+
if (!tabMatches || (!windowMatches && !workspaceMatches)) {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const fullPaths = Array.isArray(obj.fullPaths)
|
|
1912
|
+
? obj.fullPaths.filter((item): item is string => typeof item === "string")
|
|
1913
|
+
: [];
|
|
1914
|
+
|
|
1915
|
+
const slicePathsRaw = Array.isArray(obj.slicePaths) ? obj.slicePaths : [];
|
|
1916
|
+
const slicePaths: AutoSelectionEntrySliceData[] = slicePathsRaw
|
|
1917
|
+
.map((raw) => {
|
|
1918
|
+
if (!raw || typeof raw !== "object") {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const row = raw as Record<string, unknown>;
|
|
1923
|
+
const pathValue = typeof row.path === "string" ? row.path : null;
|
|
1924
|
+
const rangesRaw = Array.isArray(row.ranges) ? row.ranges : [];
|
|
1925
|
+
|
|
1926
|
+
if (!pathValue) {
|
|
1927
|
+
return null;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
const ranges: AutoSelectionEntryRangeData[] = rangesRaw
|
|
1931
|
+
.map((rangeRaw) => {
|
|
1932
|
+
if (!rangeRaw || typeof rangeRaw !== "object") {
|
|
1933
|
+
return null;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
const rangeObj = rangeRaw as Record<string, unknown>;
|
|
1937
|
+
const start = typeof rangeObj.start_line === "number" ? rangeObj.start_line : NaN;
|
|
1938
|
+
const end = typeof rangeObj.end_line === "number" ? rangeObj.end_line : NaN;
|
|
1939
|
+
|
|
1940
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
1941
|
+
return null;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
return {
|
|
1945
|
+
start_line: start,
|
|
1946
|
+
end_line: end,
|
|
1947
|
+
};
|
|
1948
|
+
})
|
|
1949
|
+
.filter((range): range is AutoSelectionEntryRangeData => range !== null);
|
|
1950
|
+
|
|
1951
|
+
return {
|
|
1952
|
+
path: pathValue,
|
|
1953
|
+
ranges,
|
|
1954
|
+
};
|
|
1955
|
+
})
|
|
1956
|
+
.filter((item): item is AutoSelectionEntrySliceData => item !== null);
|
|
1957
|
+
|
|
1958
|
+
return normalizeAutoSelectionState({
|
|
1959
|
+
windowId: binding.windowId,
|
|
1960
|
+
tab: binding.tab,
|
|
1961
|
+
workspaceId: binding.workspaceId ?? workspaceId,
|
|
1962
|
+
workspaceName: binding.workspaceName ?? workspaceName,
|
|
1963
|
+
workspaceRoots: binding.workspaceRoots ?? workspaceRoots,
|
|
1964
|
+
fullPaths,
|
|
1965
|
+
slicePaths,
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function getAutoSelectionStateFromBranch(
|
|
1970
|
+
ctx: ExtensionContext,
|
|
1971
|
+
binding: RpCliBindingEntryData
|
|
1972
|
+
): AutoSelectionEntryData {
|
|
1973
|
+
const entries = ctx.sessionManager.getBranch();
|
|
1974
|
+
|
|
1975
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1976
|
+
const entry = entries[i];
|
|
1977
|
+
if (entry.type !== "custom" || entry.customType !== AUTO_SELECTION_CUSTOM_TYPE) {
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
const parsed = parseAutoSelectionEntryData(entry.data, binding);
|
|
1982
|
+
if (parsed) {
|
|
1983
|
+
return parsed;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
return makeEmptyAutoSelectionState(binding);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
function persistAutoSelectionState(state: AutoSelectionEntryData): void {
|
|
1991
|
+
const normalized = normalizeAutoSelectionState(state);
|
|
1992
|
+
activeAutoSelectionState = normalized;
|
|
1993
|
+
pi.appendEntry(AUTO_SELECTION_CUSTOM_TYPE, normalized);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
function autoSelectionManagedPaths(state: AutoSelectionEntryData): string[] {
|
|
1997
|
+
const fromSlices = state.slicePaths.map((item) => item.path);
|
|
1998
|
+
return [...new Set([...state.fullPaths, ...fromSlices])];
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
function autoSelectionSliceKey(item: AutoSelectionEntrySliceData): string {
|
|
2002
|
+
return JSON.stringify(normalizeAutoSelectionRanges(item.ranges));
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function bindingForAutoSelectionState(state: AutoSelectionEntryData): RpCliBindingEntryData {
|
|
2006
|
+
return {
|
|
2007
|
+
windowId: state.windowId,
|
|
2008
|
+
tab: state.tab ?? "Compose",
|
|
2009
|
+
workspaceId: state.workspaceId,
|
|
2010
|
+
workspaceName: state.workspaceName,
|
|
2011
|
+
workspaceRoots: state.workspaceRoots,
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
async function runRpCliForBinding(
|
|
2016
|
+
binding: RpCliBindingEntryData,
|
|
2017
|
+
cmd: string,
|
|
2018
|
+
rawJson = false,
|
|
2019
|
+
timeout = 10_000
|
|
2020
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number; output: string }> {
|
|
2021
|
+
const args: string[] = ["-w", String(binding.windowId)];
|
|
2022
|
+
|
|
2023
|
+
if (binding.tab) {
|
|
2024
|
+
args.push("-t", binding.tab);
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
args.push("-q", "--fail-fast");
|
|
2028
|
+
|
|
2029
|
+
if (rawJson) {
|
|
2030
|
+
args.push("--raw-json");
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
args.push("-e", cmd);
|
|
2034
|
+
|
|
2035
|
+
const result = await pi.exec("rp-cli", args, { timeout });
|
|
2036
|
+
|
|
2037
|
+
const stdout = result.stdout ?? "";
|
|
2038
|
+
const stderr = result.stderr ?? "";
|
|
2039
|
+
const exitCode = result.code ?? 0;
|
|
2040
|
+
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
2041
|
+
|
|
2042
|
+
if (exitCode !== 0) {
|
|
2043
|
+
throw new Error(output || `rp-cli exited with status ${exitCode}`);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
return { stdout, stderr, exitCode, output };
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
async function callManageSelection(binding: RpCliBindingEntryData, args: Record<string, unknown>): Promise<string> {
|
|
2050
|
+
const cmd = `call manage_selection ${JSON.stringify(args)}`;
|
|
2051
|
+
const result = await runRpCliForBinding(binding, cmd, false);
|
|
2052
|
+
return result.output;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
async function getSelectionFilesText(binding: RpCliBindingEntryData): Promise<string | null> {
|
|
2056
|
+
try {
|
|
2057
|
+
const text = await callManageSelection(binding, {
|
|
2058
|
+
op: "get",
|
|
2059
|
+
view: "files",
|
|
2060
|
+
});
|
|
2061
|
+
|
|
2062
|
+
return text.length > 0 ? text : null;
|
|
2063
|
+
} catch {
|
|
2064
|
+
return null;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
function buildSelectionPathFromResolved(
|
|
2069
|
+
inputPath: string,
|
|
2070
|
+
resolved: { absolutePath: string | null; repoRoot: string | null }
|
|
2071
|
+
): string {
|
|
2072
|
+
if (!resolved.absolutePath || !resolved.repoRoot) {
|
|
2073
|
+
return inputPath;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
const rel = path.relative(resolved.repoRoot, resolved.absolutePath);
|
|
2077
|
+
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
2078
|
+
return inputPath;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
const rootHint = path.basename(resolved.repoRoot);
|
|
2082
|
+
const relPosix = rel.split(path.sep).join("/");
|
|
2083
|
+
|
|
2084
|
+
return `${rootHint}/${relPosix}`;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
function updateAutoSelectionStateAfterFullRead(binding: RpCliBindingEntryData, selectionPath: string): void {
|
|
2088
|
+
const normalizedPath = toPosixPath(selectionPath);
|
|
2089
|
+
|
|
2090
|
+
const baseState = sameBindingForAutoSelection(binding, activeAutoSelectionState)
|
|
2091
|
+
? (activeAutoSelectionState as AutoSelectionEntryData)
|
|
2092
|
+
: makeEmptyAutoSelectionState(binding);
|
|
2093
|
+
|
|
2094
|
+
const nextState: AutoSelectionEntryData = {
|
|
2095
|
+
...baseState,
|
|
2096
|
+
fullPaths: [...baseState.fullPaths, normalizedPath],
|
|
2097
|
+
slicePaths: baseState.slicePaths.filter((entry) => entry.path !== normalizedPath),
|
|
2098
|
+
};
|
|
2099
|
+
|
|
2100
|
+
const normalizedNext = normalizeAutoSelectionState(nextState);
|
|
2101
|
+
if (autoSelectionStatesEqual(baseState, normalizedNext)) {
|
|
2102
|
+
activeAutoSelectionState = normalizedNext;
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
persistAutoSelectionState(normalizedNext);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
function existingSliceRangesForRead(
|
|
2110
|
+
binding: RpCliBindingEntryData,
|
|
2111
|
+
selectionPath: string
|
|
2112
|
+
): AutoSelectionEntryRangeData[] | null {
|
|
2113
|
+
const normalizedPath = toPosixPath(selectionPath);
|
|
2114
|
+
|
|
2115
|
+
const baseState = sameBindingForAutoSelection(binding, activeAutoSelectionState)
|
|
2116
|
+
? (activeAutoSelectionState as AutoSelectionEntryData)
|
|
2117
|
+
: makeEmptyAutoSelectionState(binding);
|
|
2118
|
+
|
|
2119
|
+
if (baseState.fullPaths.includes(normalizedPath)) {
|
|
2120
|
+
return null;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
const existing = baseState.slicePaths.find((entry) => entry.path === normalizedPath);
|
|
2124
|
+
return normalizeAutoSelectionRanges(existing?.ranges ?? []);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
function subtractCoveredRanges(
|
|
2128
|
+
incomingRange: AutoSelectionEntryRangeData,
|
|
2129
|
+
existingRanges: AutoSelectionEntryRangeData[]
|
|
2130
|
+
): AutoSelectionEntryRangeData[] {
|
|
2131
|
+
let pending: AutoSelectionEntryRangeData[] = [incomingRange];
|
|
2132
|
+
|
|
2133
|
+
for (const existing of normalizeAutoSelectionRanges(existingRanges)) {
|
|
2134
|
+
const nextPending: AutoSelectionEntryRangeData[] = [];
|
|
2135
|
+
|
|
2136
|
+
for (const candidate of pending) {
|
|
2137
|
+
const overlapStart = Math.max(candidate.start_line, existing.start_line);
|
|
2138
|
+
const overlapEnd = Math.min(candidate.end_line, existing.end_line);
|
|
2139
|
+
|
|
2140
|
+
if (overlapStart > overlapEnd) {
|
|
2141
|
+
nextPending.push(candidate);
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
if (candidate.start_line < overlapStart) {
|
|
2146
|
+
nextPending.push({
|
|
2147
|
+
start_line: candidate.start_line,
|
|
2148
|
+
end_line: overlapStart - 1,
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
if (candidate.end_line > overlapEnd) {
|
|
2153
|
+
nextPending.push({
|
|
2154
|
+
start_line: overlapEnd + 1,
|
|
2155
|
+
end_line: candidate.end_line,
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
pending = nextPending;
|
|
2161
|
+
if (pending.length === 0) {
|
|
2162
|
+
return [];
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
return normalizeAutoSelectionRanges(pending);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
function updateAutoSelectionStateAfterSliceRead(
|
|
2170
|
+
binding: RpCliBindingEntryData,
|
|
2171
|
+
selectionPath: string,
|
|
2172
|
+
range: AutoSelectionEntryRangeData
|
|
2173
|
+
): void {
|
|
2174
|
+
const normalizedPath = toPosixPath(selectionPath);
|
|
2175
|
+
|
|
2176
|
+
const baseState = sameBindingForAutoSelection(binding, activeAutoSelectionState)
|
|
2177
|
+
? (activeAutoSelectionState as AutoSelectionEntryData)
|
|
2178
|
+
: makeEmptyAutoSelectionState(binding);
|
|
2179
|
+
|
|
2180
|
+
if (baseState.fullPaths.includes(normalizedPath)) {
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
const existing = baseState.slicePaths.find((entry) => entry.path === normalizedPath);
|
|
2185
|
+
|
|
2186
|
+
const nextSlicePaths = baseState.slicePaths.filter((entry) => entry.path !== normalizedPath);
|
|
2187
|
+
nextSlicePaths.push({
|
|
2188
|
+
path: normalizedPath,
|
|
2189
|
+
ranges: [...(existing?.ranges ?? []), range],
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
const nextState: AutoSelectionEntryData = {
|
|
2193
|
+
...baseState,
|
|
2194
|
+
fullPaths: [...baseState.fullPaths],
|
|
2195
|
+
slicePaths: nextSlicePaths,
|
|
2196
|
+
};
|
|
2197
|
+
|
|
2198
|
+
const normalizedNext = normalizeAutoSelectionState(nextState);
|
|
2199
|
+
if (autoSelectionStatesEqual(baseState, normalizedNext)) {
|
|
2200
|
+
activeAutoSelectionState = normalizedNext;
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
persistAutoSelectionState(normalizedNext);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
async function removeAutoSelectionPaths(state: AutoSelectionEntryData, paths: string[]): Promise<void> {
|
|
2208
|
+
if (paths.length === 0) {
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
const binding = bindingForAutoSelectionState(state);
|
|
2213
|
+
await callManageSelection(binding, { op: "remove", paths });
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
async function addAutoSelectionFullPaths(state: AutoSelectionEntryData, paths: string[]): Promise<void> {
|
|
2217
|
+
if (paths.length === 0) {
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
const binding = bindingForAutoSelectionState(state);
|
|
2222
|
+
await callManageSelection(binding, {
|
|
2223
|
+
op: "add",
|
|
2224
|
+
mode: "full",
|
|
2225
|
+
paths,
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
async function addAutoSelectionSlices(
|
|
2230
|
+
state: AutoSelectionEntryData,
|
|
2231
|
+
slices: AutoSelectionEntrySliceData[]
|
|
2232
|
+
): Promise<void> {
|
|
2233
|
+
if (slices.length === 0) {
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
const binding = bindingForAutoSelectionState(state);
|
|
2238
|
+
await callManageSelection(binding, {
|
|
2239
|
+
op: "add",
|
|
2240
|
+
slices,
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
async function reconcileAutoSelectionWithinBinding(
|
|
2245
|
+
currentState: AutoSelectionEntryData,
|
|
2246
|
+
desiredState: AutoSelectionEntryData
|
|
2247
|
+
): Promise<void> {
|
|
2248
|
+
const currentModeByPath = new Map<string, "full" | "slices">();
|
|
2249
|
+
for (const p of currentState.fullPaths) {
|
|
2250
|
+
currentModeByPath.set(p, "full");
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
for (const s of currentState.slicePaths) {
|
|
2254
|
+
if (!currentModeByPath.has(s.path)) {
|
|
2255
|
+
currentModeByPath.set(s.path, "slices");
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
const desiredModeByPath = new Map<string, "full" | "slices">();
|
|
2260
|
+
for (const p of desiredState.fullPaths) {
|
|
2261
|
+
desiredModeByPath.set(p, "full");
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
for (const s of desiredState.slicePaths) {
|
|
2265
|
+
if (!desiredModeByPath.has(s.path)) {
|
|
2266
|
+
desiredModeByPath.set(s.path, "slices");
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
const desiredSliceByPath = new Map<string, AutoSelectionEntrySliceData>();
|
|
2271
|
+
for (const s of desiredState.slicePaths) {
|
|
2272
|
+
desiredSliceByPath.set(s.path, s);
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
const currentSliceByPath = new Map<string, AutoSelectionEntrySliceData>();
|
|
2276
|
+
for (const s of currentState.slicePaths) {
|
|
2277
|
+
currentSliceByPath.set(s.path, s);
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
const removePaths = new Set<string>();
|
|
2281
|
+
const addFullPaths: string[] = [];
|
|
2282
|
+
const addSlices: AutoSelectionEntrySliceData[] = [];
|
|
2283
|
+
|
|
2284
|
+
for (const [pathKey] of currentModeByPath) {
|
|
2285
|
+
if (!desiredModeByPath.has(pathKey)) {
|
|
2286
|
+
removePaths.add(pathKey);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
for (const [pathKey, mode] of desiredModeByPath) {
|
|
2291
|
+
const currentMode = currentModeByPath.get(pathKey);
|
|
2292
|
+
|
|
2293
|
+
if (mode === "full") {
|
|
2294
|
+
if (currentMode === "full") {
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
if (currentMode === "slices") {
|
|
2299
|
+
removePaths.add(pathKey);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
addFullPaths.push(pathKey);
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
const desiredSlice = desiredSliceByPath.get(pathKey);
|
|
2307
|
+
if (!desiredSlice) {
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
if (currentMode === "full") {
|
|
2312
|
+
removePaths.add(pathKey);
|
|
2313
|
+
addSlices.push(desiredSlice);
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
if (currentMode === "slices") {
|
|
2318
|
+
const currentSlice = currentSliceByPath.get(pathKey);
|
|
2319
|
+
if (currentSlice && autoSelectionSliceKey(currentSlice) === autoSelectionSliceKey(desiredSlice)) {
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
removePaths.add(pathKey);
|
|
2324
|
+
addSlices.push(desiredSlice);
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
addSlices.push(desiredSlice);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
await removeAutoSelectionPaths(currentState, [...removePaths]);
|
|
2332
|
+
await addAutoSelectionFullPaths(desiredState, addFullPaths);
|
|
2333
|
+
await addAutoSelectionSlices(desiredState, addSlices);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
async function reconcileAutoSelectionStates(
|
|
2337
|
+
currentState: AutoSelectionEntryData | null,
|
|
2338
|
+
desiredState: AutoSelectionEntryData | null
|
|
2339
|
+
): Promise<void> {
|
|
2340
|
+
if (autoSelectionStatesEqual(currentState, desiredState)) {
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
if (currentState && desiredState) {
|
|
2345
|
+
const sameBinding =
|
|
2346
|
+
currentState.windowId === desiredState.windowId &&
|
|
2347
|
+
sameOptionalText(currentState.tab, desiredState.tab);
|
|
2348
|
+
|
|
2349
|
+
if (sameBinding) {
|
|
2350
|
+
await reconcileAutoSelectionWithinBinding(currentState, desiredState);
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
try {
|
|
2355
|
+
await removeAutoSelectionPaths(currentState, autoSelectionManagedPaths(currentState));
|
|
2356
|
+
} catch {
|
|
2357
|
+
// Old binding/window may no longer exist after RepoPrompt app restart
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
await addAutoSelectionFullPaths(desiredState, desiredState.fullPaths);
|
|
2361
|
+
await addAutoSelectionSlices(desiredState, desiredState.slicePaths);
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
if (currentState && !desiredState) {
|
|
2366
|
+
try {
|
|
2367
|
+
await removeAutoSelectionPaths(currentState, autoSelectionManagedPaths(currentState));
|
|
2368
|
+
} catch {
|
|
2369
|
+
// Old binding/window may no longer exist after RepoPrompt app restart
|
|
2370
|
+
}
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
if (!currentState && desiredState) {
|
|
2375
|
+
await addAutoSelectionFullPaths(desiredState, desiredState.fullPaths);
|
|
2376
|
+
await addAutoSelectionSlices(desiredState, desiredState.slicePaths);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
async function syncAutoSelectionToCurrentBranch(ctx: ExtensionContext): Promise<void> {
|
|
2381
|
+
if (config.autoSelectReadSlices !== true) {
|
|
2382
|
+
activeAutoSelectionState = null;
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
const binding = await ensureBindingTargetsLiveWindow(ctx);
|
|
2387
|
+
const desiredState = binding ? getAutoSelectionStateFromBranch(ctx, binding) : null;
|
|
2388
|
+
|
|
2389
|
+
try {
|
|
2390
|
+
await reconcileAutoSelectionStates(activeAutoSelectionState, desiredState);
|
|
2391
|
+
} catch {
|
|
2392
|
+
// Fail-open
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
activeAutoSelectionState = desiredState;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
function parseReadOutputHints(readOutputText: string | undefined): {
|
|
2399
|
+
selectionPath: string | null;
|
|
2400
|
+
totalLines: number | null;
|
|
2401
|
+
} {
|
|
2402
|
+
if (!readOutputText) {
|
|
2403
|
+
return { selectionPath: null, totalLines: null };
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
const pathMatch =
|
|
2407
|
+
/\*\*Path\*\*:\s*`([^`]+)`/i.exec(readOutputText) ??
|
|
2408
|
+
/\*\*Path\*\*:\s*([^\n]+)$/im.exec(readOutputText);
|
|
2409
|
+
|
|
2410
|
+
const selectionPath = pathMatch?.[1]?.trim() ?? null;
|
|
2411
|
+
|
|
2412
|
+
const linesRegexes = [
|
|
2413
|
+
/\*\*Lines\*\*:\s*(\d+)\s*[–—-]\s*(\d+)\s+of\s+(\d+)/i,
|
|
2414
|
+
/Lines(?:\s*:)?\s*(\d+)\s*[–—-]\s*(\d+)\s+of\s+(\d+)/i,
|
|
2415
|
+
];
|
|
2416
|
+
|
|
2417
|
+
let totalLines: number | null = null;
|
|
2418
|
+
|
|
2419
|
+
for (const rx of linesRegexes) {
|
|
2420
|
+
const match = rx.exec(readOutputText);
|
|
2421
|
+
if (!match) {
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
const parsed = Number.parseInt(match[3] ?? "", 10);
|
|
2426
|
+
if (Number.isFinite(parsed)) {
|
|
2427
|
+
totalLines = parsed;
|
|
2428
|
+
break;
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
return { selectionPath, totalLines };
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
async function autoSelectReadFileInRepoPromptSelection(
|
|
2436
|
+
ctx: ExtensionContext,
|
|
2437
|
+
inputPath: string,
|
|
2438
|
+
startLine: number | undefined,
|
|
2439
|
+
limit: number | undefined,
|
|
2440
|
+
readOutputText: string | undefined
|
|
2441
|
+
): Promise<void> {
|
|
2442
|
+
if (config.autoSelectReadSlices !== true) {
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
const outputHints = parseReadOutputHints(readOutputText);
|
|
2447
|
+
|
|
2448
|
+
const binding = await maybeEnsureBindingTargetsLiveWindow(ctx);
|
|
2449
|
+
if (!binding) {
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
const selectionText = await getSelectionFilesText(binding);
|
|
2454
|
+
if (selectionText === null) {
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
const resolved = await resolveReadFilePath(pi, inputPath, ctx.cwd, binding.windowId, binding.tab);
|
|
2459
|
+
|
|
2460
|
+
const resolvedSelectionPath = buildSelectionPathFromResolved(inputPath, resolved);
|
|
2461
|
+
const selectionPath =
|
|
2462
|
+
outputHints.selectionPath && outputHints.selectionPath.trim().length > 0
|
|
2463
|
+
? outputHints.selectionPath
|
|
2464
|
+
: resolvedSelectionPath;
|
|
2465
|
+
|
|
2466
|
+
const candidatePaths = new Set<string>();
|
|
2467
|
+
candidatePaths.add(toPosixPath(selectionPath));
|
|
2468
|
+
candidatePaths.add(toPosixPath(resolvedSelectionPath));
|
|
2469
|
+
candidatePaths.add(toPosixPath(inputPath));
|
|
2470
|
+
|
|
2471
|
+
if (outputHints.selectionPath) {
|
|
2472
|
+
candidatePaths.add(toPosixPath(outputHints.selectionPath));
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
if (resolved.absolutePath) {
|
|
2476
|
+
candidatePaths.add(toPosixPath(resolved.absolutePath));
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
if (resolved.absolutePath && resolved.repoRoot) {
|
|
2480
|
+
const rel = path.relative(resolved.repoRoot, resolved.absolutePath);
|
|
2481
|
+
if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
|
|
2482
|
+
candidatePaths.add(toPosixPath(rel.split(path.sep).join("/")));
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
let selectionStatus: ReturnType<typeof inferSelectionStatus> = null;
|
|
2487
|
+
|
|
2488
|
+
for (const candidate of candidatePaths) {
|
|
2489
|
+
const status = inferSelectionStatus(selectionText, candidate);
|
|
2490
|
+
if (!status) {
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
if (status.mode === "full") {
|
|
2495
|
+
selectionStatus = status;
|
|
2496
|
+
break;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
if (status.mode === "codemap_only" && status.codemapManual === true) {
|
|
2500
|
+
selectionStatus = status;
|
|
2501
|
+
break;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
if (selectionStatus === null) {
|
|
2505
|
+
selectionStatus = status;
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
if (selectionStatus.mode === "codemap_only" && status.mode === "slices") {
|
|
2510
|
+
selectionStatus = status;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
if (selectionStatus?.mode === "full") {
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
if (selectionStatus?.mode === "codemap_only" && selectionStatus.codemapManual === true) {
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
let totalLines: number | undefined;
|
|
2523
|
+
|
|
2524
|
+
if (typeof startLine === "number" && startLine < 0) {
|
|
2525
|
+
if (resolved.absolutePath) {
|
|
2526
|
+
try {
|
|
2527
|
+
totalLines = await countFileLines(resolved.absolutePath);
|
|
2528
|
+
} catch {
|
|
2529
|
+
totalLines = undefined;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
if (totalLines === undefined && typeof outputHints.totalLines === "number") {
|
|
2534
|
+
totalLines = outputHints.totalLines;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
const sliceRange = computeSliceRangeFromReadArgs(startLine, limit, totalLines);
|
|
2539
|
+
|
|
2540
|
+
|
|
2541
|
+
if (sliceRange) {
|
|
2542
|
+
const existingRanges = existingSliceRangesForRead(binding, selectionPath);
|
|
2543
|
+
if (!existingRanges) {
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
const uncoveredRanges = subtractCoveredRanges(sliceRange, existingRanges);
|
|
2548
|
+
|
|
2549
|
+
if (uncoveredRanges.length === 0) {
|
|
2550
|
+
updateAutoSelectionStateAfterSliceRead(binding, selectionPath, sliceRange);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// Add only uncovered ranges to avoid touching unrelated selection state
|
|
2555
|
+
// (and to avoid global set semantics)
|
|
2556
|
+
const payload = {
|
|
2557
|
+
op: "add",
|
|
2558
|
+
slices: [
|
|
2559
|
+
{
|
|
2560
|
+
path: toPosixPath(selectionPath),
|
|
2561
|
+
ranges: uncoveredRanges,
|
|
2562
|
+
},
|
|
2563
|
+
],
|
|
2564
|
+
};
|
|
2565
|
+
|
|
2566
|
+
try {
|
|
2567
|
+
await callManageSelection(binding, payload);
|
|
2568
|
+
} catch {
|
|
2569
|
+
// Fail-open
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
updateAutoSelectionStateAfterSliceRead(binding, selectionPath, sliceRange);
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
const payload = {
|
|
2578
|
+
op: "add",
|
|
2579
|
+
mode: "full",
|
|
2580
|
+
paths: [toPosixPath(selectionPath)],
|
|
2581
|
+
};
|
|
2582
|
+
|
|
2583
|
+
try {
|
|
2584
|
+
await callManageSelection(binding, payload);
|
|
2585
|
+
} catch {
|
|
2586
|
+
// Fail-open
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
updateAutoSelectionStateAfterFullRead(binding, selectionPath);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
|
|
2594
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
2595
|
+
config = loadConfig();
|
|
2596
|
+
clearReadcacheCaches();
|
|
2597
|
+
clearWindowsCache();
|
|
2598
|
+
markBindingValidationStale();
|
|
2599
|
+
reconstructBinding(ctx);
|
|
2600
|
+
|
|
2601
|
+
const binding = getCurrentBinding();
|
|
2602
|
+
activeAutoSelectionState =
|
|
2603
|
+
config.autoSelectReadSlices === true && binding
|
|
2604
|
+
? getAutoSelectionStateFromBranch(ctx, binding)
|
|
2605
|
+
: null;
|
|
2606
|
+
|
|
2607
|
+
if (config.readcacheReadFile === true) {
|
|
2608
|
+
void pruneObjectsOlderThan(ctx.cwd).catch(() => {
|
|
2609
|
+
// Fail-open
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
try {
|
|
2614
|
+
await syncAutoSelectionToCurrentBranch(ctx);
|
|
2615
|
+
} catch {
|
|
2616
|
+
// Fail-open
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
2621
|
+
config = loadConfig();
|
|
2622
|
+
clearReadcacheCaches();
|
|
2623
|
+
clearWindowsCache();
|
|
2624
|
+
markBindingValidationStale();
|
|
2625
|
+
reconstructBinding(ctx);
|
|
2626
|
+
await syncAutoSelectionToCurrentBranch(ctx);
|
|
2627
|
+
});
|
|
2628
|
+
|
|
2629
|
+
// session_fork is the current event name; keep session_branch for backwards compatibility
|
|
2630
|
+
pi.on("session_fork", async (_event, ctx) => {
|
|
2631
|
+
config = loadConfig();
|
|
2632
|
+
clearReadcacheCaches();
|
|
2633
|
+
clearWindowsCache();
|
|
2634
|
+
markBindingValidationStale();
|
|
2635
|
+
reconstructBinding(ctx);
|
|
2636
|
+
await syncAutoSelectionToCurrentBranch(ctx);
|
|
2637
|
+
});
|
|
2638
|
+
|
|
2639
|
+
pi.on("session_branch", async (_event, ctx) => {
|
|
2640
|
+
config = loadConfig();
|
|
2641
|
+
clearReadcacheCaches();
|
|
2642
|
+
clearWindowsCache();
|
|
2643
|
+
markBindingValidationStale();
|
|
2644
|
+
reconstructBinding(ctx);
|
|
2645
|
+
await syncAutoSelectionToCurrentBranch(ctx);
|
|
2646
|
+
});
|
|
2647
|
+
|
|
2648
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
2649
|
+
config = loadConfig();
|
|
2650
|
+
clearReadcacheCaches();
|
|
2651
|
+
clearWindowsCache();
|
|
2652
|
+
markBindingValidationStale();
|
|
2653
|
+
reconstructBinding(ctx);
|
|
2654
|
+
await syncAutoSelectionToCurrentBranch(ctx);
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
pi.on("session_compact", async () => {
|
|
2658
|
+
clearReadcacheCaches();
|
|
2659
|
+
});
|
|
2660
|
+
|
|
2661
|
+
pi.on("session_shutdown", async () => {
|
|
2662
|
+
clearReadcacheCaches();
|
|
2663
|
+
clearWindowsCache();
|
|
2664
|
+
activeAutoSelectionState = null;
|
|
2665
|
+
setBinding(null);
|
|
2666
|
+
});
|
|
2667
|
+
|
|
2668
|
+
pi.registerCommand("rpbind", {
|
|
2669
|
+
description: "Bind rp_exec to RepoPrompt: /rpbind <window_id> <tab>",
|
|
2670
|
+
handler: async (args, ctx) => {
|
|
2671
|
+
const parsed = parseRpbindArgs(args);
|
|
2672
|
+
if ("error" in parsed) {
|
|
2673
|
+
ctx.ui.notify(parsed.error, "error");
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
const binding = await enrichBinding(parsed.windowId, parsed.tab);
|
|
2678
|
+
persistBinding(binding);
|
|
2679
|
+
|
|
2680
|
+
try {
|
|
2681
|
+
await syncAutoSelectionToCurrentBranch(ctx);
|
|
2682
|
+
} catch {
|
|
2683
|
+
// Fail-open
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
ctx.ui.notify(
|
|
2687
|
+
`Bound rp_exec → window ${boundWindowId}, tab "${boundTab}"` +
|
|
2688
|
+
(boundWorkspaceName ? ` (${boundWorkspaceName})` : ""),
|
|
2689
|
+
"success"
|
|
2690
|
+
);
|
|
2691
|
+
},
|
|
2692
|
+
});
|
|
2693
|
+
|
|
2694
|
+
|
|
2695
|
+
pi.registerCommand("rpcli-readcache-status", {
|
|
2696
|
+
description: "Show repoprompt-cli read_file cache status",
|
|
2697
|
+
handler: async (_args, ctx) => {
|
|
2698
|
+
config = loadConfig();
|
|
2699
|
+
|
|
2700
|
+
let msg = "repoprompt-cli read_file cache\n";
|
|
2701
|
+
msg += "──────────────────────────\n";
|
|
2702
|
+
msg += `Enabled: ${config.readcacheReadFile === true ? "✓" : "✗"}\n`;
|
|
2703
|
+
msg += `Auto-select reads: ${config.autoSelectReadSlices === true ? "✓" : "✗"}\n`;
|
|
2704
|
+
|
|
2705
|
+
if (config.readcacheReadFile !== true) {
|
|
2706
|
+
msg += "\nEnable by creating ~/.pi/agent/extensions/repoprompt-cli/config.json\n";
|
|
2707
|
+
msg += "\nwith:\n { \"readcacheReadFile\": true }\n";
|
|
2708
|
+
ctx.ui.notify(msg, "info");
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
try {
|
|
2713
|
+
const stats = await getStoreStats(ctx.cwd);
|
|
2714
|
+
msg += `\nObject store (under ${ctx.cwd}/.pi/readcache):\n`;
|
|
2715
|
+
msg += ` Objects: ${stats.objects}\n`;
|
|
2716
|
+
msg += ` Bytes: ${stats.bytes}\n`;
|
|
2717
|
+
} catch {
|
|
2718
|
+
msg += "\nObject store: unavailable\n";
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
msg += "\nNotes:\n";
|
|
2722
|
+
msg += "- Cache applies only to simple rp_exec reads (read/cat/read_file)\n";
|
|
2723
|
+
msg += "- Use bypass_cache=true in the read command to force baseline output\n";
|
|
2724
|
+
|
|
2725
|
+
ctx.ui.notify(msg, "info");
|
|
2726
|
+
},
|
|
2727
|
+
});
|
|
2728
|
+
|
|
2729
|
+
pi.registerCommand("rpcli-readcache-refresh", {
|
|
2730
|
+
description: "Invalidate repoprompt-cli read_file cache trust for a path and optional line range",
|
|
2731
|
+
handler: async (args, ctx) => {
|
|
2732
|
+
config = loadConfig();
|
|
2733
|
+
|
|
2734
|
+
if (config.readcacheReadFile !== true) {
|
|
2735
|
+
ctx.ui.notify("readcacheReadFile is disabled in config", "error");
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
const trimmed = args.trim();
|
|
2740
|
+
if (!trimmed) {
|
|
2741
|
+
ctx.ui.notify("Usage: /rpcli-readcache-refresh <path> [start-end]", "error");
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
const parts = trimmed.split(/\s+/);
|
|
2746
|
+
const pathInput = parts[0];
|
|
2747
|
+
const rangeInput = parts[1];
|
|
2748
|
+
|
|
2749
|
+
if (!pathInput) {
|
|
2750
|
+
ctx.ui.notify("Usage: /rpcli-readcache-refresh <path> [start-end]", "error");
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
const binding = await ensureBindingTargetsLiveWindow(ctx);
|
|
2755
|
+
if (!binding) {
|
|
2756
|
+
ctx.ui.notify("rp_exec is not bound. Bind first via /rpbind or rp_bind", "error");
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
let scopeKey: ScopeKey = SCOPE_FULL;
|
|
2761
|
+
if (rangeInput) {
|
|
2762
|
+
const match = rangeInput.match(/^(\d+)-(\d+)$/);
|
|
2763
|
+
if (!match) {
|
|
2764
|
+
ctx.ui.notify("Invalid range. Use <start-end> like 1-120", "error");
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
const start = parseInt(match[1] ?? "", 10);
|
|
2769
|
+
const end = parseInt(match[2] ?? "", 10);
|
|
2770
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end < start) {
|
|
2771
|
+
ctx.ui.notify("Invalid range. Use <start-end> like 1-120", "error");
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
scopeKey = scopeRange(start, end);
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
const resolved = await resolveReadFilePath(pi, pathInput, ctx.cwd, binding.windowId, binding.tab);
|
|
2779
|
+
if (!resolved.absolutePath) {
|
|
2780
|
+
ctx.ui.notify(`Could not resolve path: ${pathInput}`, "error");
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
pi.appendEntry(RP_READCACHE_CUSTOM_TYPE, buildInvalidationV1(resolved.absolutePath, scopeKey));
|
|
2785
|
+
clearReadcacheCaches();
|
|
2786
|
+
|
|
2787
|
+
ctx.ui.notify(
|
|
2788
|
+
`Invalidated readcache for ${resolved.absolutePath}` + (scopeKey === SCOPE_FULL ? "" : ` (${scopeKey})`),
|
|
2789
|
+
"info"
|
|
2790
|
+
);
|
|
2791
|
+
},
|
|
2792
|
+
});
|
|
2793
|
+
|
|
2794
|
+
pi.registerTool({
|
|
2795
|
+
name: "rp_bind",
|
|
2796
|
+
label: "RepoPrompt Bind",
|
|
2797
|
+
description: "Bind rp_exec to a specific RepoPrompt window and compose tab",
|
|
2798
|
+
parameters: BindParams,
|
|
2799
|
+
|
|
2800
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2801
|
+
await ensureJustBashLoaded();
|
|
2802
|
+
maybeWarnAstUnavailable(ctx);
|
|
2803
|
+
|
|
2804
|
+
const binding = await enrichBinding(params.windowId, params.tab);
|
|
2805
|
+
persistBinding(binding);
|
|
2806
|
+
|
|
2807
|
+
try {
|
|
2808
|
+
await syncAutoSelectionToCurrentBranch(ctx);
|
|
2809
|
+
} catch {
|
|
2810
|
+
// Fail-open
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
return {
|
|
2814
|
+
content: [{ type: "text", text: `Bound rp_exec → window ${boundWindowId}, tab "${boundTab}"` }],
|
|
2815
|
+
details: {
|
|
2816
|
+
windowId: boundWindowId,
|
|
2817
|
+
tab: boundTab,
|
|
2818
|
+
workspaceId: boundWorkspaceId,
|
|
2819
|
+
workspaceName: boundWorkspaceName,
|
|
2820
|
+
workspaceRoots: boundWorkspaceRoots,
|
|
2821
|
+
},
|
|
2822
|
+
};
|
|
2823
|
+
},
|
|
2824
|
+
});
|
|
2825
|
+
|
|
2826
|
+
pi.registerTool({
|
|
2827
|
+
name: "rp_exec",
|
|
2828
|
+
label: "RepoPrompt Exec",
|
|
2829
|
+
description: "Run rp-cli in the bound RepoPrompt window/tab, with quiet defaults and output truncation",
|
|
2830
|
+
parameters: ExecParams,
|
|
2831
|
+
|
|
2832
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
2833
|
+
// Routing: prefer call-time overrides, otherwise fall back to the last persisted binding
|
|
2834
|
+
await ensureJustBashLoaded();
|
|
2835
|
+
maybeWarnAstUnavailable(ctx);
|
|
2836
|
+
|
|
2837
|
+
if (params.windowId === undefined && params.tab === undefined) {
|
|
2838
|
+
try {
|
|
2839
|
+
await maybeEnsureBindingTargetsLiveWindow(ctx);
|
|
2840
|
+
} catch {
|
|
2841
|
+
// Fail-open
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
const windowId = params.windowId ?? boundWindowId;
|
|
2846
|
+
const tab = params.tab ?? boundTab;
|
|
2847
|
+
const rawJson = params.rawJson ?? false;
|
|
2848
|
+
const quiet = params.quiet ?? true;
|
|
2849
|
+
const failFast = params.failFast ?? true;
|
|
2850
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2851
|
+
const maxOutputChars = params.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
2852
|
+
const allowDelete = params.allowDelete ?? false;
|
|
2853
|
+
const allowWorkspaceSwitchInPlace = params.allowWorkspaceSwitchInPlace ?? false;
|
|
2854
|
+
const failOnNoopEdits = params.failOnNoopEdits ?? true;
|
|
2855
|
+
|
|
2856
|
+
if (!allowDelete && looksLikeDeleteCommand(params.cmd)) {
|
|
2857
|
+
return {
|
|
2858
|
+
isError: true,
|
|
2859
|
+
content: [
|
|
2860
|
+
{
|
|
2861
|
+
type: "text",
|
|
2862
|
+
text: "Blocked potential delete command. If deletion is explicitly requested, rerun with allowDelete=true",
|
|
2863
|
+
},
|
|
2864
|
+
],
|
|
2865
|
+
details: { blocked: true, reason: "delete", cmd: params.cmd, windowId, tab },
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
if (!allowWorkspaceSwitchInPlace && looksLikeWorkspaceSwitchInPlace(params.cmd)) {
|
|
2870
|
+
return {
|
|
2871
|
+
isError: true,
|
|
2872
|
+
content: [
|
|
2873
|
+
{
|
|
2874
|
+
type: "text",
|
|
2875
|
+
text:
|
|
1525
2876
|
"Blocked in-place workspace change (it can clobber selection/prompt/context and disrupt other sessions). " +
|
|
1526
2877
|
"Add `--new-window`, or rerun with allowWorkspaceSwitchInPlace=true if explicitly safe",
|
|
1527
2878
|
},
|
|
@@ -1593,10 +2944,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1593
2944
|
let rpReadcache: RpReadcacheMetaV1 | null = null;
|
|
1594
2945
|
|
|
1595
2946
|
if (
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
2947
|
+
config.readcacheReadFile === true &&
|
|
2948
|
+
readRequest !== null &&
|
|
2949
|
+
readRequest.cacheable === true &&
|
|
2950
|
+
!execError &&
|
|
1600
2951
|
exitCode === 0 &&
|
|
1601
2952
|
windowId !== undefined &&
|
|
1602
2953
|
tab !== undefined
|
|
@@ -1627,6 +2978,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
1627
2978
|
}
|
|
1628
2979
|
}
|
|
1629
2980
|
|
|
2981
|
+
const shouldAutoSelectRead =
|
|
2982
|
+
config.autoSelectReadSlices === true &&
|
|
2983
|
+
readRequest !== null &&
|
|
2984
|
+
!execError &&
|
|
2985
|
+
exitCode === 0 &&
|
|
2986
|
+
windowId !== undefined &&
|
|
2987
|
+
tab !== undefined &&
|
|
2988
|
+
params.windowId === undefined &&
|
|
2989
|
+
params.tab === undefined;
|
|
2990
|
+
|
|
2991
|
+
if (shouldAutoSelectRead) {
|
|
2992
|
+
try {
|
|
2993
|
+
await autoSelectReadFileInRepoPromptSelection(
|
|
2994
|
+
ctx,
|
|
2995
|
+
readRequest.path,
|
|
2996
|
+
readRequest.startLine,
|
|
2997
|
+
readRequest.limit,
|
|
2998
|
+
combinedOutput,
|
|
2999
|
+
);
|
|
3000
|
+
} catch {
|
|
3001
|
+
// Fail-open
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
|
|
1630
3005
|
const editNoop =
|
|
1631
3006
|
!execError &&
|
|
1632
3007
|
exitCode === 0 &&
|