pi-repoprompt-mcp 0.5.5 → 0.6.1
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.
|
@@ -754,7 +754,7 @@ function parseTabFromJson(raw: unknown): RpTab | null {
|
|
|
754
754
|
}
|
|
755
755
|
|
|
756
756
|
const obj = raw as Record<string, unknown>;
|
|
757
|
-
const idRaw = obj.id ?? obj.tabId ?? obj.tab_id ?? obj.uuid;
|
|
757
|
+
const idRaw = obj.contextId ?? obj.context_id ?? obj.id ?? obj.tabId ?? obj.tab_id ?? obj.uuid;
|
|
758
758
|
if (typeof idRaw !== "string" || !idRaw.trim()) {
|
|
759
759
|
return null;
|
|
760
760
|
}
|
|
@@ -786,7 +786,20 @@ function collectTabsFromJson(raw: unknown): RpTab[] {
|
|
|
786
786
|
}
|
|
787
787
|
|
|
788
788
|
const obj = raw as Record<string, unknown>;
|
|
789
|
-
const containers = [
|
|
789
|
+
const containers = [
|
|
790
|
+
obj.tabs,
|
|
791
|
+
obj.contexts,
|
|
792
|
+
obj.tab,
|
|
793
|
+
obj.context,
|
|
794
|
+
obj.createdTab,
|
|
795
|
+
obj.created_tab,
|
|
796
|
+
obj.createdContext,
|
|
797
|
+
obj.created_context,
|
|
798
|
+
obj.selectedTab,
|
|
799
|
+
obj.selected_tab,
|
|
800
|
+
obj.selectedContext,
|
|
801
|
+
obj.selected_context,
|
|
802
|
+
];
|
|
790
803
|
|
|
791
804
|
for (const candidate of containers) {
|
|
792
805
|
const parsed = collectTabsFromJson(candidate);
|
|
@@ -835,7 +848,26 @@ function dedupeTabs(tabs: RpTab[]): RpTab[] {
|
|
|
835
848
|
|
|
836
849
|
function parseTabLine(line: string): RpTab | null {
|
|
837
850
|
const trimmed = line.trim();
|
|
838
|
-
if (!trimmed
|
|
851
|
+
if (!trimmed) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const contextMatch = trimmed.match(/^[\u2022-]\s*(.+?)(?:\s+\[([^\]]+)\])?\s+—\s+context_id:\s*`([^`]+)`/i);
|
|
856
|
+
if (contextMatch) {
|
|
857
|
+
const state = contextMatch[2] ?? "";
|
|
858
|
+
return {
|
|
859
|
+
id: contextMatch[3].trim(),
|
|
860
|
+
name: stripTrailingTabStateAnnotations(contextMatch[1].trim()) || contextMatch[3].trim(),
|
|
861
|
+
isActive: /\bactive\b|\bin-focus\b/i.test(state)
|
|
862
|
+
? true
|
|
863
|
+
: /\bout-of-focus\b/i.test(state)
|
|
864
|
+
? false
|
|
865
|
+
: undefined,
|
|
866
|
+
isBound: /\bbound\b/i.test(state) ? true : undefined,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (!trimmed.includes("`")) {
|
|
839
871
|
return null;
|
|
840
872
|
}
|
|
841
873
|
|
|
@@ -869,6 +901,17 @@ function parseTabLine(line: string): RpTab | null {
|
|
|
869
901
|
};
|
|
870
902
|
}
|
|
871
903
|
|
|
904
|
+
function parseTabsFromBindContextText(text: string): RpTab[] {
|
|
905
|
+
return dedupeTabs(
|
|
906
|
+
text
|
|
907
|
+
.split("\n")
|
|
908
|
+
.map((line) => line.trim())
|
|
909
|
+
.filter((line) => /—\s+context_id:\s*`[^`]+`/i.test(line))
|
|
910
|
+
.map(parseTabLine)
|
|
911
|
+
.filter((tab): tab is RpTab => tab !== null)
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
872
915
|
export function parseTabList(text: string): RpTab[] {
|
|
873
916
|
const tabs: RpTab[] = [];
|
|
874
917
|
let lastTab: RpTab | null = null;
|
|
@@ -899,132 +942,129 @@ function parseTabsFromJson(value: unknown): RpTab[] | null {
|
|
|
899
942
|
return tabs.length > 0 ? dedupeTabs(tabs) : null;
|
|
900
943
|
}
|
|
901
944
|
|
|
902
|
-
function
|
|
903
|
-
if (!
|
|
904
|
-
return
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (Array.isArray(value)) {
|
|
908
|
-
return value.length;
|
|
945
|
+
function findLiveTab(tabs: RpTab[], reference: string | undefined): RpTab | null {
|
|
946
|
+
if (!reference) {
|
|
947
|
+
return null;
|
|
909
948
|
}
|
|
910
949
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
950
|
+
return tabs.find((tab) => tab.id === reference || tab.name === reference) ?? null;
|
|
951
|
+
}
|
|
914
952
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
);
|
|
919
|
-
if (directCount !== undefined) {
|
|
920
|
-
return directCount;
|
|
921
|
-
}
|
|
953
|
+
function isExplicitlyEmptyTab(tab: RpTab): boolean {
|
|
954
|
+
return tab.selectedFileCount === 0;
|
|
955
|
+
}
|
|
922
956
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
957
|
+
function orderReusableTabCandidates(tabs: RpTab[]): RpTab[] {
|
|
958
|
+
const ordered = [
|
|
959
|
+
...tabs.filter((tab) => tab.isBound === true),
|
|
960
|
+
...tabs.filter((tab) => tab.isBound !== true && tab.isActive === true),
|
|
961
|
+
...tabs.filter((tab) => tab.isBound !== true && tab.isActive !== true),
|
|
962
|
+
];
|
|
929
963
|
|
|
930
|
-
return
|
|
964
|
+
return ordered.filter((tab, index) => ordered.findIndex((candidate) => candidate.id === tab.id) === index);
|
|
931
965
|
}
|
|
932
966
|
|
|
933
|
-
function
|
|
934
|
-
const
|
|
935
|
-
if (countMatch?.[1]) {
|
|
936
|
-
return parseCountMaybe(countMatch[1]);
|
|
937
|
-
}
|
|
967
|
+
function parseOracleSessionTabPrefixes(text: string): Set<string> {
|
|
968
|
+
const prefixes = new Set<string>();
|
|
938
969
|
|
|
939
|
-
|
|
940
|
-
|
|
970
|
+
for (const match of text.matchAll(/\btab=([A-F0-9-]{6,})(?:…|\b)/gi)) {
|
|
971
|
+
const prefix = match[1]?.trim().toUpperCase();
|
|
972
|
+
if (prefix) {
|
|
973
|
+
prefixes.add(prefix);
|
|
974
|
+
}
|
|
941
975
|
}
|
|
942
976
|
|
|
943
|
-
|
|
944
|
-
.split("\n")
|
|
945
|
-
.map((line) => line.trim())
|
|
946
|
-
.filter((line) => /^•\s*\[[^\]]+\]/.test(line)).length;
|
|
947
|
-
|
|
948
|
-
return sessionCount > 0 ? sessionCount : undefined;
|
|
977
|
+
return prefixes;
|
|
949
978
|
}
|
|
950
979
|
|
|
951
|
-
async function
|
|
952
|
-
|
|
980
|
+
async function fetchOracleSessionTabPrefixes(
|
|
981
|
+
windowId: number,
|
|
953
982
|
client: ReturnType<typeof getRpClient> = getRpClient()
|
|
954
|
-
): Promise<
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
const chatsToolName = resolveToolName(client.tools, "chats");
|
|
960
|
-
if (!chatsToolName) {
|
|
961
|
-
return undefined;
|
|
983
|
+
): Promise<Set<string> | null> {
|
|
984
|
+
const oracleUtilsToolName = resolveToolName(client.tools, "oracle_utils");
|
|
985
|
+
if (!oracleUtilsToolName) {
|
|
986
|
+
return null;
|
|
962
987
|
}
|
|
963
988
|
|
|
964
|
-
const result = await client.callTool(
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
limit: 1,
|
|
989
|
+
const result = await client.callTool(oracleUtilsToolName, {
|
|
990
|
+
op: "sessions",
|
|
991
|
+
limit: 200,
|
|
992
|
+
_windowID: windowId,
|
|
969
993
|
});
|
|
970
994
|
|
|
971
995
|
if (result.isError) {
|
|
972
|
-
return
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
const countFromJson = parseChatCountFromJson(extractJsonContent(result.content));
|
|
976
|
-
if (countFromJson !== undefined) {
|
|
977
|
-
return countFromJson;
|
|
996
|
+
return null;
|
|
978
997
|
}
|
|
979
998
|
|
|
980
|
-
return
|
|
999
|
+
return parseOracleSessionTabPrefixes(extractTextContent(result.content));
|
|
981
1000
|
}
|
|
982
1001
|
|
|
983
|
-
function
|
|
984
|
-
if (!
|
|
985
|
-
return
|
|
1002
|
+
function tabHasOracleHistory(tabId: string, sessionTabPrefixes: Set<string> | null): boolean {
|
|
1003
|
+
if (!sessionTabPrefixes || sessionTabPrefixes.size === 0) {
|
|
1004
|
+
return false;
|
|
986
1005
|
}
|
|
987
1006
|
|
|
988
|
-
|
|
989
|
-
|
|
1007
|
+
const normalizedTabId = tabId.trim().toUpperCase();
|
|
1008
|
+
for (const prefix of sessionTabPrefixes) {
|
|
1009
|
+
if (normalizedTabId.startsWith(prefix)) {
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
990
1013
|
|
|
991
|
-
|
|
992
|
-
return tab.selectedFileCount === 0;
|
|
1014
|
+
return false;
|
|
993
1015
|
}
|
|
994
1016
|
|
|
995
|
-
async function
|
|
1017
|
+
async function hasEmptySelection(
|
|
1018
|
+
windowId: number,
|
|
996
1019
|
tab: RpTab,
|
|
997
1020
|
client: ReturnType<typeof getRpClient> = getRpClient()
|
|
998
1021
|
): Promise<boolean> {
|
|
999
|
-
if (
|
|
1022
|
+
if (isExplicitlyEmptyTab(tab)) {
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const manageSelectionToolName = resolveToolName(client.tools, "manage_selection");
|
|
1027
|
+
if (!manageSelectionToolName) {
|
|
1000
1028
|
return false;
|
|
1001
1029
|
}
|
|
1002
1030
|
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1031
|
+
const result = await client.callTool(manageSelectionToolName, {
|
|
1032
|
+
op: "get",
|
|
1033
|
+
view: "summary",
|
|
1034
|
+
_windowID: windowId,
|
|
1035
|
+
context_id: tab.id,
|
|
1036
|
+
});
|
|
1006
1037
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if (emptyTabs.length === 0) {
|
|
1010
|
-
return [];
|
|
1038
|
+
if (result.isError) {
|
|
1039
|
+
return false;
|
|
1011
1040
|
}
|
|
1012
1041
|
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
...emptyTabs.filter((tab) => tab.isBound !== true && tab.isActive !== true),
|
|
1017
|
-
];
|
|
1042
|
+
const text = extractTextContent(result.content);
|
|
1043
|
+
return /\b0 total tokens\b/i.test(text);
|
|
1044
|
+
}
|
|
1018
1045
|
|
|
1019
|
-
|
|
1046
|
+
async function isSafeReusableTab(
|
|
1047
|
+
windowId: number,
|
|
1048
|
+
tab: RpTab,
|
|
1049
|
+
sessionTabPrefixes: Set<string> | null,
|
|
1050
|
+
client: ReturnType<typeof getRpClient> = getRpClient()
|
|
1051
|
+
): Promise<boolean> {
|
|
1052
|
+
if (tabHasOracleHistory(tab.id, sessionTabPrefixes)) {
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return await hasEmptySelection(windowId, tab, client);
|
|
1020
1057
|
}
|
|
1021
1058
|
|
|
1022
1059
|
async function findReusableSafeTab(
|
|
1060
|
+
windowId: number,
|
|
1023
1061
|
tabs: RpTab[],
|
|
1024
1062
|
client: ReturnType<typeof getRpClient> = getRpClient()
|
|
1025
1063
|
): Promise<RpTab | null> {
|
|
1026
|
-
|
|
1027
|
-
|
|
1064
|
+
const sessionTabPrefixes = await fetchOracleSessionTabPrefixes(windowId, client);
|
|
1065
|
+
|
|
1066
|
+
for (const tab of orderReusableTabCandidates(tabs)) {
|
|
1067
|
+
if (await isSafeReusableTab(windowId, tab, sessionTabPrefixes, client)) {
|
|
1028
1068
|
return tab;
|
|
1029
1069
|
}
|
|
1030
1070
|
}
|
|
@@ -1066,14 +1106,14 @@ export async function fetchWindowTabs(
|
|
|
1066
1106
|
throw new Error("Not connected to RepoPrompt");
|
|
1067
1107
|
}
|
|
1068
1108
|
|
|
1069
|
-
const
|
|
1070
|
-
if (!
|
|
1109
|
+
const bindContextToolName = resolveToolName(client.tools, "bind_context");
|
|
1110
|
+
if (!bindContextToolName) {
|
|
1071
1111
|
return [];
|
|
1072
1112
|
}
|
|
1073
1113
|
|
|
1074
|
-
const result = await client.callTool(
|
|
1075
|
-
|
|
1076
|
-
|
|
1114
|
+
const result = await client.callTool(bindContextToolName, {
|
|
1115
|
+
op: "list",
|
|
1116
|
+
window_id: windowId,
|
|
1077
1117
|
});
|
|
1078
1118
|
|
|
1079
1119
|
if (result.isError) {
|
|
@@ -1086,7 +1126,7 @@ export async function fetchWindowTabs(
|
|
|
1086
1126
|
return tabsFromJson;
|
|
1087
1127
|
}
|
|
1088
1128
|
|
|
1089
|
-
return
|
|
1129
|
+
return parseTabsFromBindContextText(extractTextContent(result.content));
|
|
1090
1130
|
}
|
|
1091
1131
|
|
|
1092
1132
|
async function selectTab(
|
|
@@ -1094,16 +1134,15 @@ async function selectTab(
|
|
|
1094
1134
|
tabId: string,
|
|
1095
1135
|
client: ReturnType<typeof getRpClient> = getRpClient()
|
|
1096
1136
|
): Promise<void> {
|
|
1097
|
-
const
|
|
1098
|
-
if (!
|
|
1137
|
+
const bindContextToolName = resolveToolName(client.tools, "bind_context");
|
|
1138
|
+
if (!bindContextToolName) {
|
|
1099
1139
|
return;
|
|
1100
1140
|
}
|
|
1101
1141
|
|
|
1102
|
-
const result = await client.callTool(
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
...bindingWindowArgs(windowId),
|
|
1142
|
+
const result = await client.callTool(bindContextToolName, {
|
|
1143
|
+
op: "bind",
|
|
1144
|
+
window_id: windowId,
|
|
1145
|
+
context_id: tabId,
|
|
1107
1146
|
});
|
|
1108
1147
|
|
|
1109
1148
|
if (result.isError) {
|
|
@@ -1290,7 +1329,7 @@ export async function ensureBindingHasTab(
|
|
|
1290
1329
|
}
|
|
1291
1330
|
|
|
1292
1331
|
if (!binding.tab || reuseSoleEmptyTab || recoverIfMissing) {
|
|
1293
|
-
const reusableSafeTab = await findReusableSafeTab(liveTabs, client);
|
|
1332
|
+
const reusableSafeTab = await findReusableSafeTab(binding.windowId, liveTabs, client);
|
|
1294
1333
|
if (reusableSafeTab) {
|
|
1295
1334
|
return await adoptTab(reusableSafeTab, true);
|
|
1296
1335
|
}
|
|
@@ -1498,7 +1537,7 @@ export function getBindingArgs(): Record<string, unknown> {
|
|
|
1498
1537
|
};
|
|
1499
1538
|
|
|
1500
1539
|
if (currentBinding.tab) {
|
|
1501
|
-
args.
|
|
1540
|
+
args.context_id = currentBinding.tab;
|
|
1502
1541
|
}
|
|
1503
1542
|
|
|
1504
1543
|
return args;
|
|
@@ -813,7 +813,7 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
813
813
|
function bindingArgsForAutoSelectionState(state: AutoSelectionEntryData): Record<string, unknown> {
|
|
814
814
|
return {
|
|
815
815
|
_windowID: state.windowId,
|
|
816
|
-
...(state.tab ? {
|
|
816
|
+
...(state.tab ? { context_id: state.tab } : {}),
|
|
817
817
|
};
|
|
818
818
|
}
|
|
819
819
|
|
|
@@ -1582,9 +1582,9 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
1582
1582
|
try {
|
|
1583
1583
|
await ensureTabScopedBinding(ctx, "RepoPrompt binding has no tab. Use /rp bind or /rp tab new first.");
|
|
1584
1584
|
|
|
1585
|
-
const
|
|
1586
|
-
if (!
|
|
1587
|
-
ctx.ui.notify("RepoPrompt tool '
|
|
1585
|
+
const oracleSendToolName = resolveToolName(client.tools, "oracle_send");
|
|
1586
|
+
if (!oracleSendToolName) {
|
|
1587
|
+
ctx.ui.notify("RepoPrompt tool 'oracle_send' not available", "error");
|
|
1588
1588
|
return;
|
|
1589
1589
|
}
|
|
1590
1590
|
|
|
@@ -1598,7 +1598,7 @@ export default function repopromptMcp(pi: ExtensionAPI) {
|
|
|
1598
1598
|
if (chatName) callArgs.chat_name = chatName;
|
|
1599
1599
|
if (chatId) callArgs.chat_id = chatId;
|
|
1600
1600
|
|
|
1601
|
-
const result = await client.callTool(
|
|
1601
|
+
const result = await client.callTool(oracleSendToolName, callArgs);
|
|
1602
1602
|
|
|
1603
1603
|
const text = extractTextContent(result.content);
|
|
1604
1604
|
|
|
@@ -2541,7 +2541,15 @@ Mode priority: call > describe > search > windows > bind > status`,
|
|
|
2541
2541
|
const userArgs = (params.args ?? {}) as Record<string, unknown>;
|
|
2542
2542
|
const normalizedTool = normalizeToolName(tool.name);
|
|
2543
2543
|
|
|
2544
|
-
if (
|
|
2544
|
+
if (
|
|
2545
|
+
getBinding() &&
|
|
2546
|
+
!getBinding()?.tab &&
|
|
2547
|
+
normalizedTool !== "manage_workspaces" &&
|
|
2548
|
+
normalizedTool !== "list_windows" &&
|
|
2549
|
+
normalizedTool !== "bind_context" &&
|
|
2550
|
+
normalizedTool !== "agent_run" &&
|
|
2551
|
+
normalizedTool !== "agent_manage"
|
|
2552
|
+
) {
|
|
2545
2553
|
if (!ctx) {
|
|
2546
2554
|
return {
|
|
2547
2555
|
content: [{ type: "text" as const, text: "RepoPrompt binding has no tab. Re-bind with /rp bind before calling tab-scoped tools." }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-repoprompt-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|