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 模糊查询默认显示 5 个候选;若已加载 `pi-subagents`,也可直接引用现有 SubAgent。一次提示词可以引用多个 Session;扩展会自动去重,并限制注入规模以避免上下文无限膨胀。更多细节见 [`extensions/session-reference/README.md`](./extensions/session-reference/README.md)。
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 引用语法;修改 SubAgent 定义后执行 `/reload` 即可重新加载。
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 = 10;
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 = query.trim()
88
- ? fuzzyFilter(agents, query, (a) => `${a.name} ${a.displayName} ${a.description}`)
89
- : agents;
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 items: AutocompleteItem[] = matches
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
- if (items.length === 0) {
101
- return current.getSuggestions(lines, cursorLine, cursorCol, options);
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
- ctx.ui.addAutocompleteProvider((current) =>
142
- createAgentAutocompleteProvider(current, getAgents),
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 模糊查询默认最多显示 5 个候选。
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 = 5;
26
- const MAX_FILE_SUGGESTIONS = 10;
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 = (baseSuggestions?.items ?? []).slice(0, MAX_FILE_SUGGESTIONS);
240
- const items = [...sessionItems, ...fileItems];
241
- if (items.length === 0) return null;
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
- ctx.ui.addAutocompleteProvider((current) => createAutocompleteProvider(current, getReferences, ctx.cwd));
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Personal extensions for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [