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
|
|
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 =
|
|
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.
|
|
@@ -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
|
|
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 (
|
|
892
|
-
clearTimeout(
|
|
893
|
-
|
|
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
|
|
937
|
+
function scheduleSessionRender(): void {
|
|
938
938
|
const tui = toolMouseTui;
|
|
939
939
|
if (!tui || typeof tui.requestRender !== "function") return;
|
|
940
|
-
if (
|
|
941
|
-
//
|
|
942
|
-
//
|
|
943
|
-
// not left hidden until the next
|
|
944
|
-
|
|
945
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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;
|