pi-cc-extensions 0.7.2 → 0.7.4
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
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
2. 从 `[Session] ...` 或 `[SubAgent] ...` 补全项中选择要引用的上下文。
|
|
86
86
|
3. 提交时以 `@session:<session-id>` 引用其当前有效上下文。
|
|
87
87
|
|
|
88
|
-
Session 模糊查询默认显示
|
|
88
|
+
Session 模糊查询默认显示 3 个候选,并与文件候选按 1:2 交错排列;路径型查询优先显示文件。若已加载 `pi-subagents`,也可直接引用现有 SubAgent。一次提示词可以引用多个 Session;扩展会自动去重,并限制注入规模以避免上下文无限膨胀。更多细节见 [`extensions/session-reference/README.md`](./extensions/session-reference/README.md)。
|
|
89
89
|
|
|
90
90
|
### SubAgent `@` 补全
|
|
91
91
|
|
|
@@ -95,7 +95,7 @@ Session 模糊查询默认显示 5 个候选;若已加载 `pi-subagents`,也
|
|
|
95
95
|
2. 从补全列表中选择 SubAgent。
|
|
96
96
|
3. 提交后自动注入委托指令;提示词中提到的每个 SubAgent 都会分别交由 Agent 工具处理。
|
|
97
97
|
|
|
98
|
-
该补全不会占用 `@session:` 的历史 Session
|
|
98
|
+
该补全不会占用 `@session:` 的历史 Session 引用语法;与 `@bacnh85/pi-fff` 同时启用时,SubAgent、Session 和 FFF 文件候选会合并显示。修改 SubAgent 定义后执行 `/reload` 即可重新加载。
|
|
99
99
|
|
|
100
100
|
## 安装
|
|
101
101
|
|
|
@@ -17,9 +17,9 @@ type AgentInfo = {
|
|
|
17
17
|
filePath: string;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
const MAX_SUGGESTIONS =
|
|
20
|
+
const MAX_SUGGESTIONS = 2;
|
|
21
21
|
// Match `@name` but NOT `@session:` (reserved by session-reference extension).
|
|
22
|
-
const AGENT_NAME_PATTERN = /(?:^|[\t ])@([^\s:@][^\s:]*)$/;
|
|
22
|
+
const AGENT_NAME_PATTERN = /(?:^|[\t ])@((?:[^\s:@][^\s:]*)?)$/;
|
|
23
23
|
|
|
24
24
|
function getAgentDir(): string {
|
|
25
25
|
const override = process.env.PI_CODING_AGENT_DIR;
|
|
@@ -58,11 +58,12 @@ function loadAgents(): AgentInfo[] {
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function createAgentAutocompleteProvider(
|
|
61
|
+
export function createAgentAutocompleteProvider(
|
|
62
62
|
current: AutocompleteProvider,
|
|
63
63
|
getAgents: () => AgentInfo[],
|
|
64
64
|
): AutocompleteProvider {
|
|
65
65
|
return {
|
|
66
|
+
triggerCharacters: ["@"],
|
|
66
67
|
async getSuggestions(
|
|
67
68
|
lines: string[],
|
|
68
69
|
cursorLine: number,
|
|
@@ -84,11 +85,15 @@ function createAgentAutocompleteProvider(
|
|
|
84
85
|
return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
const matches =
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
const [baseSuggestions, matches] = await Promise.all([
|
|
89
|
+
current.getSuggestions(lines, cursorLine, cursorCol, options),
|
|
90
|
+
Promise.resolve(query.trim()
|
|
91
|
+
? fuzzyFilter(agents, query, (a) => `${a.name} ${a.displayName} ${a.description}`)
|
|
92
|
+
: agents),
|
|
93
|
+
]);
|
|
94
|
+
if (options.signal.aborted) return null;
|
|
90
95
|
|
|
91
|
-
const
|
|
96
|
+
const agentItems: AutocompleteItem[] = matches
|
|
92
97
|
.slice(0, MAX_SUGGESTIONS)
|
|
93
98
|
.map((agent) => ({
|
|
94
99
|
value: `@${agent.name}`,
|
|
@@ -96,11 +101,18 @@ function createAgentAutocompleteProvider(
|
|
|
96
101
|
description: agent.description
|
|
97
102
|
+ (agent.model ? ` · ${agent.model}` : ""),
|
|
98
103
|
}));
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
const baseItems = baseSuggestions?.prefix === `@${query}`
|
|
105
|
+
? baseSuggestions.items
|
|
106
|
+
: [];
|
|
107
|
+
const seen = new Set<string>();
|
|
108
|
+
const items = [...agentItems, ...baseItems].filter((item) => {
|
|
109
|
+
const key = `${item.value}\0${item.label}`;
|
|
110
|
+
if (seen.has(key)) return false;
|
|
111
|
+
seen.add(key);
|
|
112
|
+
return true;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (items.length === 0) return baseSuggestions;
|
|
104
116
|
return { items, prefix: `@${query}` };
|
|
105
117
|
},
|
|
106
118
|
|
|
@@ -122,6 +134,7 @@ function createAgentAutocompleteProvider(
|
|
|
122
134
|
|
|
123
135
|
export default function agentAutocompleteExtension(pi: ExtensionAPI): void {
|
|
124
136
|
let cachedAgents: AgentInfo[] | undefined;
|
|
137
|
+
let sessionGeneration = 0;
|
|
125
138
|
|
|
126
139
|
const getAgents = (): AgentInfo[] => {
|
|
127
140
|
if (!cachedAgents) {
|
|
@@ -136,11 +149,22 @@ export default function agentAutocompleteExtension(pi: ExtensionAPI): void {
|
|
|
136
149
|
});
|
|
137
150
|
|
|
138
151
|
pi.on("session_start", (_event, ctx) => {
|
|
152
|
+
const generation = ++sessionGeneration;
|
|
139
153
|
const agents = getAgents();
|
|
140
154
|
if (agents.length === 0 || ctx.mode !== "tui") return;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
155
|
+
|
|
156
|
+
// Register after other session_start handlers have installed their wrappers.
|
|
157
|
+
// pi-fff handles every @ prefix and otherwise shadows providers loaded before it.
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
if (generation !== sessionGeneration) return;
|
|
160
|
+
ctx.ui.addAutocompleteProvider((current) =>
|
|
161
|
+
createAgentAutocompleteProvider(current, getAgents),
|
|
162
|
+
);
|
|
163
|
+
}, 0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
pi.on("session_shutdown", () => {
|
|
167
|
+
sessionGeneration++;
|
|
144
168
|
});
|
|
145
169
|
|
|
146
170
|
// Inject instruction when user types @agent-name (not @session:) in prompt.
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
## 行为
|
|
12
12
|
|
|
13
13
|
- 搜索全部 pi 历史 Session;当前 Workspace 的 Session 优先。
|
|
14
|
-
- Session 模糊查询默认最多显示
|
|
15
|
-
- 与 pi 原有 `@`
|
|
14
|
+
- Session 模糊查询默认最多显示 3 个候选。
|
|
15
|
+
- 与 pi 原有 `@` 文件补全并存:普通查询按 1:2 交错排列 Session 与文件,路径型查询优先显示文件。
|
|
16
16
|
- 若已加载 `pi-subagents`,`@` 补全会同时显示现有 SubAgent,并引用其当前有效上下文。
|
|
17
17
|
- 支持一个 prompt 引用多个 Session,并自动去重;单次最多注入前 5 个。
|
|
18
18
|
- 跳过历史 Session 中嵌套的 Session 引用,避免递归膨胀。
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
type ReferenceSource,
|
|
23
23
|
} from "./core.ts";
|
|
24
24
|
|
|
25
|
-
const MAX_SESSION_SUGGESTIONS =
|
|
26
|
-
const MAX_FILE_SUGGESTIONS =
|
|
25
|
+
const MAX_SESSION_SUGGESTIONS = 3;
|
|
26
|
+
const MAX_FILE_SUGGESTIONS = 7;
|
|
27
27
|
const MAX_REFERENCED_SESSIONS = 5;
|
|
28
28
|
const MENTION_PATTERN = /(?:^|[\t ])@([^\s@]*)$/;
|
|
29
29
|
const SUBAGENT_MANAGER_KEY = Symbol.for("pi-subagents:manager");
|
|
@@ -166,6 +166,31 @@ function filterSessions(references: SessionReference[], query: string, currentCw
|
|
|
166
166
|
return matches.slice(0, MAX_SESSION_SUGGESTIONS).map((reference) => sessionItem(reference, currentCwd));
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function isPathLikeQuery(query: string): boolean {
|
|
170
|
+
return /[\\/*?]/.test(query) || /\.[A-Za-z0-9_-]{1,12}$/.test(query);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function mergeSessionAndFileItems(
|
|
174
|
+
sessionItems: AutocompleteItem[],
|
|
175
|
+
fileItems: AutocompleteItem[],
|
|
176
|
+
query: string,
|
|
177
|
+
): AutocompleteItem[] {
|
|
178
|
+
const sessions = sessionItems.slice(0, MAX_SESSION_SUGGESTIONS);
|
|
179
|
+
const files = fileItems.slice(0, MAX_FILE_SUGGESTIONS);
|
|
180
|
+
if (isPathLikeQuery(query)) return [...files, ...sessions];
|
|
181
|
+
|
|
182
|
+
const merged: AutocompleteItem[] = [];
|
|
183
|
+
let sessionIndex = 0;
|
|
184
|
+
let fileIndex = 0;
|
|
185
|
+
while (sessionIndex < sessions.length || fileIndex < files.length) {
|
|
186
|
+
for (let count = 0; count < 2 && fileIndex < files.length; count++) {
|
|
187
|
+
merged.push(files[fileIndex++]!);
|
|
188
|
+
}
|
|
189
|
+
if (sessionIndex < sessions.length) merged.push(sessions[sessionIndex++]!);
|
|
190
|
+
}
|
|
191
|
+
return merged;
|
|
192
|
+
}
|
|
193
|
+
|
|
169
194
|
function liveSubagentReferences(agentIds: Set<string>, currentSessionId: string): SessionReference[] {
|
|
170
195
|
const manager = getSubagentManager();
|
|
171
196
|
if (!manager) return [];
|
|
@@ -215,7 +240,7 @@ function mergeReferences(sessions: SessionInfo[], subagents: SessionReference[])
|
|
|
215
240
|
return [...bySessionId.values()];
|
|
216
241
|
}
|
|
217
242
|
|
|
218
|
-
function createAutocompleteProvider(
|
|
243
|
+
export function createAutocompleteProvider(
|
|
219
244
|
current: AutocompleteProvider,
|
|
220
245
|
getReferences: () => Promise<SessionReference[]>,
|
|
221
246
|
currentCwd: string,
|
|
@@ -236,9 +261,11 @@ function createAutocompleteProvider(
|
|
|
236
261
|
if (options.signal.aborted) return null;
|
|
237
262
|
|
|
238
263
|
const sessionItems = filterSessions(references, query, currentCwd);
|
|
239
|
-
const fileItems =
|
|
240
|
-
|
|
241
|
-
|
|
264
|
+
const fileItems = baseSuggestions?.prefix === `@${query}`
|
|
265
|
+
? baseSuggestions.items
|
|
266
|
+
: [];
|
|
267
|
+
const items = mergeSessionAndFileItems(sessionItems, fileItems, query);
|
|
268
|
+
if (items.length === 0) return baseSuggestions;
|
|
242
269
|
return { prefix: `@${query}`, items };
|
|
243
270
|
},
|
|
244
271
|
|
|
@@ -263,6 +290,7 @@ function samePath(left: string | undefined, right: string): boolean {
|
|
|
263
290
|
|
|
264
291
|
export default function sessionReferenceExtension(pi: ExtensionAPI): void {
|
|
265
292
|
let getAvailableReferences: (() => Promise<SessionReference[]>) | undefined;
|
|
293
|
+
let sessionGeneration = 0;
|
|
266
294
|
const subagentIds = new Set<string>();
|
|
267
295
|
// pi-subagents emits "subagent:async-started" and "subagent:async-complete" events.
|
|
268
296
|
// We track records locally so @[SubAgent] suggestions work even without a global manager.
|
|
@@ -290,6 +318,7 @@ export default function sessionReferenceExtension(pi: ExtensionAPI): void {
|
|
|
290
318
|
});
|
|
291
319
|
|
|
292
320
|
pi.on("session_start", (_event, ctx) => {
|
|
321
|
+
const generation = ++sessionGeneration;
|
|
293
322
|
subagentIds.clear();
|
|
294
323
|
let loadErrorShown = false;
|
|
295
324
|
const currentSessionId = ctx.sessionManager.getSessionId();
|
|
@@ -320,7 +349,14 @@ export default function sessionReferenceExtension(pi: ExtensionAPI): void {
|
|
|
320
349
|
getAvailableReferences = getReferences;
|
|
321
350
|
if (ctx.mode === "tui") {
|
|
322
351
|
void getReferences();
|
|
323
|
-
|
|
352
|
+
// Register after other session_start handlers. pi-fff claims every @
|
|
353
|
+
// prefix, so a provider installed before it would never see session mentions.
|
|
354
|
+
setTimeout(() => {
|
|
355
|
+
if (generation !== sessionGeneration) return;
|
|
356
|
+
ctx.ui.addAutocompleteProvider((current) =>
|
|
357
|
+
createAutocompleteProvider(current, getReferences, ctx.cwd),
|
|
358
|
+
);
|
|
359
|
+
}, 0);
|
|
324
360
|
}
|
|
325
361
|
});
|
|
326
362
|
|
|
@@ -404,6 +440,7 @@ export default function sessionReferenceExtension(pi: ExtensionAPI): void {
|
|
|
404
440
|
});
|
|
405
441
|
|
|
406
442
|
pi.on("session_shutdown", () => {
|
|
443
|
+
sessionGeneration++;
|
|
407
444
|
for (const unsubscribe of unsubscribeSubagentEvents) unsubscribe();
|
|
408
445
|
subagentIds.clear();
|
|
409
446
|
getAvailableReferences = undefined;
|