pi-cc-extensions 0.7.0 → 0.7.3

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
@@ -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
 
@@ -19,7 +19,7 @@ type AgentInfo = {
19
19
 
20
20
  const MAX_SUGGESTIONS = 10;
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.
@@ -326,7 +326,7 @@ let scrollButtonWidget: any = null;
326
326
  let pendingScrollMessages = 0;
327
327
  let assistantMessageActive = false;
328
328
  let scrollButtonSyncScheduled = false;
329
- let resumeRenderTimer: ReturnType<typeof setTimeout> | null = null;
329
+ let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
330
330
 
331
331
  function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
332
332
  const pattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
@@ -888,9 +888,9 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
888
888
  }
889
889
 
890
890
  function teardownToolMouseInteraction(): void {
891
- if (resumeRenderTimer) {
892
- clearTimeout(resumeRenderTimer);
893
- resumeRenderTimer = null;
891
+ if (sessionRenderTimer) {
892
+ clearTimeout(sessionRenderTimer);
893
+ sessionRenderTimer = null;
894
894
  }
895
895
  toolMouseInputUnsubscribe?.();
896
896
  toolMouseInputUnsubscribe = null;
@@ -934,15 +934,15 @@ function installToolMouseInteraction(ctx: any): void {
934
934
  toolMouseInputUnsubscribe = ctx.ui.onTerminalInput(handleToolMouseInput);
935
935
  }
936
936
 
937
- function scheduleResumeRender(): void {
937
+ function scheduleSessionRender(): void {
938
938
  const tui = toolMouseTui;
939
939
  if (!tui || typeof tui.requestRender !== "function") return;
940
- if (resumeRenderTimer) clearTimeout(resumeRenderTimer);
941
- // /resume rebuilds the transcript while later session_start handlers may still
942
- // replace UI components. Repaint after that turn so the restored messages are
943
- // not left hidden until the next editor input event.
944
- resumeRenderTimer = setTimeout(() => {
945
- resumeRenderTimer = null;
940
+ if (sessionRenderTimer) clearTimeout(sessionRenderTimer);
941
+ // Restored transcripts are populated at different points for startup, reload,
942
+ // and session replacement. Repaint after session_start and the surrounding UI
943
+ // rebuild finish so messages are not left hidden until the next terminal input.
944
+ sessionRenderTimer = setTimeout(() => {
945
+ sessionRenderTimer = null;
946
946
  if (toolMouseTui === tui) tui.requestRender(true);
947
947
  }, 0);
948
948
  }
@@ -1569,13 +1569,13 @@ export default function (pi: ExtensionAPI) {
1569
1569
  },
1570
1570
  });
1571
1571
 
1572
- pi.on("session_start", async (event, ctx) => {
1572
+ pi.on("session_start", async (_event, ctx) => {
1573
1573
  diffPreviewByCall.clear();
1574
1574
  pendingScrollMessages = 0;
1575
1575
  assistantMessageActive = false;
1576
1576
  ctx.ui.setStatus("ccstyle", undefined);
1577
1577
  installToolMouseInteraction(ctx);
1578
- if (event?.reason === "resume") scheduleResumeRender();
1578
+ scheduleSessionRender();
1579
1579
  });
1580
1580
 
1581
1581
  pi.on("message_start", async (event) => {
@@ -215,7 +215,7 @@ function mergeReferences(sessions: SessionInfo[], subagents: SessionReference[])
215
215
  return [...bySessionId.values()];
216
216
  }
217
217
 
218
- function createAutocompleteProvider(
218
+ export function createAutocompleteProvider(
219
219
  current: AutocompleteProvider,
220
220
  getReferences: () => Promise<SessionReference[]>,
221
221
  currentCwd: string,
@@ -263,6 +263,7 @@ function samePath(left: string | undefined, right: string): boolean {
263
263
 
264
264
  export default function sessionReferenceExtension(pi: ExtensionAPI): void {
265
265
  let getAvailableReferences: (() => Promise<SessionReference[]>) | undefined;
266
+ let sessionGeneration = 0;
266
267
  const subagentIds = new Set<string>();
267
268
  // pi-subagents emits "subagent:async-started" and "subagent:async-complete" events.
268
269
  // We track records locally so @[SubAgent] suggestions work even without a global manager.
@@ -290,6 +291,7 @@ export default function sessionReferenceExtension(pi: ExtensionAPI): void {
290
291
  });
291
292
 
292
293
  pi.on("session_start", (_event, ctx) => {
294
+ const generation = ++sessionGeneration;
293
295
  subagentIds.clear();
294
296
  let loadErrorShown = false;
295
297
  const currentSessionId = ctx.sessionManager.getSessionId();
@@ -320,7 +322,14 @@ export default function sessionReferenceExtension(pi: ExtensionAPI): void {
320
322
  getAvailableReferences = getReferences;
321
323
  if (ctx.mode === "tui") {
322
324
  void getReferences();
323
- ctx.ui.addAutocompleteProvider((current) => createAutocompleteProvider(current, getReferences, ctx.cwd));
325
+ // Register after other session_start handlers. pi-fff claims every @
326
+ // prefix, so a provider installed before it would never see session mentions.
327
+ setTimeout(() => {
328
+ if (generation !== sessionGeneration) return;
329
+ ctx.ui.addAutocompleteProvider((current) =>
330
+ createAutocompleteProvider(current, getReferences, ctx.cwd),
331
+ );
332
+ }, 0);
324
333
  }
325
334
  });
326
335
 
@@ -404,6 +413,7 @@ export default function sessionReferenceExtension(pi: ExtensionAPI): void {
404
413
  });
405
414
 
406
415
  pi.on("session_shutdown", () => {
416
+ sessionGeneration++;
407
417
  for (const unsubscribe of unsubscribeSubagentEvents) unsubscribe();
408
418
  subagentIds.clear();
409
419
  getAvailableReferences = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.7.0",
3
+ "version": "0.7.3",
4
4
  "description": "Personal extensions for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [